diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ada61da --- /dev/null +++ b/.clang-format @@ -0,0 +1,8 @@ +--- +BasedOnStyle: Google +IndentWidth: 2 +--- +Language: Cpp +ColumnLimit: 100 +ReflowComments: true +--- diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6dfa6b9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +/build* +generators +misc +tmtc +doc + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb6a49f --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +/build* +/cmake-build* + +# Eclipse +.settings +.metadata +.project +.cproject +!misc/eclipse/**/.cproject +!misc/eclipse/**/.project + +#vscode +/.vscode + +# IntelliJ +/.idea/* + +# Python +__pycache__ + +# CLion +!/.idea/cmake.xml + +generators/*.db + +# Clangd LSP +/compile_commands.json +/.cache diff --git a/.gitmodules b/.gitmodules index 4b96622..e1ea03a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,24 @@ +[submodule "arduino"] + path = arduino + url = https://egit.irs.uni-stuttgart.de/eive/eive_arduino_interface.git [submodule "fsfw"] path = fsfw - url = https://egit.irs.uni-stuttgart.de/fsfw/fsfw.git + url = https://egit.irs.uni-stuttgart.de/eive/fsfw.git +[submodule "tmtc"] + path = tmtc + url = https://egit.irs.uni-stuttgart.de/eive/eive-tmtc.git +[submodule "thirdparty/lwgps"] + path = thirdparty/lwgps + url = https://github.com/rmspacefish/lwgps.git +[submodule "thirdparty/json"] + path = thirdparty/json + url = https://github.com/nlohmann/json.git +[submodule "thirdparty/rapidcsv"] + path = thirdparty/rapidcsv + url = https://github.com/d99kris/rapidcsv.git +[submodule "thirdparty/gomspace-sw"] + path = thirdparty/gomspace-sw + url = https://egit.irs.uni-stuttgart.de/eive/gomspace-sw.git +[submodule "thirdparty/sagittactl"] + path = thirdparty/sagittactl + url = https://egit.irs.uni-stuttgart.de/eive/sagittactl.git diff --git a/.idea/cmake.xml b/.idea/cmake.xml new file mode 100644 index 0000000..eff02c3 --- /dev/null +++ b/.idea/cmake.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/Q7S FM.run.xml b/.run/Q7S FM.run.xml new file mode 100644 index 0000000..ea4ae08 --- /dev/null +++ b/.run/Q7S FM.run.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f7a863b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2362 @@ +Change Log +======= + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/). + +The [milestone](https://egit.irs.uni-stuttgart.de/eive/eive-obsw/milestones) +list yields a list of all related PRs for each release. + +Starting at v2.0.0, this project will adhere to semantic versioning and the the following changes +will consitute of a breaking change warranting a new major release: + +- The TMTC interface changes in any shape of form. +- The behaviour of the OBSW changes in a major shape or form relevant for operations + +# [unreleased] + +# [v8.2.1] 2025-02-07 + +Patch release with EM changes only. + +## Changed + +- BPX battery is not added anymore for EM build, dummy is used by default now. + +## Fixed + +- Small fix for `q7s-cp.py` script: Add `-O` argument. + +# [v8.2.0] 2024-06-26 + +## Changed + +- STR quaternions are now used by the `MEKF` by default +- Changed nominal `SUS Assembly` side to `B Side`. +- Changed source for state machine of detumbling to SUS and MGM only. +- Changed `FusedRotRateData` dataset to always display rotation rate from SUS and MGM. +- Solution from `QUEST` will be set to invalid if sun vector and magnetic field vector are too close + to each other. +- Changed collection intervals of dataset collection + - `GPS Controller`: `GPS Set` to 60s + - `MTQ Handler`: `HK with Torque`, `HK without Torque` to 60s + - `RW Handler`: `Status Set` to 30s + - `STR Handler`: `Solution Set` to 30s + - `ACS Controller`: `MGM Sensor`, `MGM Processed`, `SUS Sensor`, `SUS Processed`, `GYR Sensor`, + `GPS Processed` to 60s + - `ACS Controller` at or below `IDLE`: `CTRL Values`, `ACT Commands`, `Attitude Estimation`, + `Fused Rotation Rate` to 30s + - `ACS Controller` above `IDLE`: `CTRL Values`, `ACT Commands`, `Attitude Estimation`, + `Fused Rotation Rate` to 10s + +## Fixed + +- Added null termination for PLOC MPSoC image taking command which could possibly lead to + default target filenames. + +# [v8.1.1] 2024-06-05 + +## Added + +- PLOC SUPV MPSoC update re-try logic for the `WRITE_MEMORY` command. These packets form > 98% + of all packets required for a software update, but the update mechanism is not tolerant against + occasional glitches on the RS485 communication to the PLOC SUPV. A simple re-try mechanism which + tries to re-attempt packet handling up to three times for those packets is introduced. + +# [v8.1.0] 2024-05-29 + +## Fixed + +- Small fix for transition failure handling of the MPSoC when the `START_MPSOC` action command + to the supervisor fails. +- Fixed inits of arrays within the `MEKF` not being zeros. +- Important bugfix for PLOC SUPV: The SUPV previously was able to steal packets from the special + communication helper, for example during software updates. +- Corrected sigma of STR for `MEKF`. + +## Added + +- Added new command to cancel the PLOC SUPV special communication helper. + +# [v8.0.0] 2024-05-13 + +- `eive-tmtc` v7.0.0 + +## Fixed + +- Fixed calculation for target rotation rate during pointing modes. +- Possible fix for MPSoC file read algorithm. + +## Changed + +- Reworked MPSoC handler to be compatible to new MPSoC software image and use + new device handler base class. This should increase the reliability of the communication + significantly. +- MPSoC device modes IDLE, SNAPSHOT and REPLAY are now modelled using submodes. +- Commanding a submode the device is already in will not result in a completion failure + anymore. + +## Added + +- Added `VERIFY_BOOT` command for MPSoC. +- New command for MPSoC to store camera image in chunks. + +# [v7.8.1] 2024-04-11 + +## Fixed + +- Reverted fix for wrong order in quaternion multiplication for computation of the error quaternion. + +# [v7.8.0] 2024-04-10 + +## Changed + +- Reverted lower OP limit of `PLOC` to -10°C. +- All pointing laws are now allowed to use the `MEKF` per default. +- Changed limits in `PWR Controller`. +- PUS time service: Now dumps the time before and after relative timeshift or setting absolute time +- The `GPS Controller` does not set itself to `OFF` anymore, if it has not detected a valid fix for + some time. Instead it attempts to reset both GNSS devices once. +- The maximum time to reach a fix is shortened from 30min to 15min. +- The time the reset pin of the GNSS devices is pulled is prolonged from 5ms to 10s. +- A `GPS FIX HAS CHANGED` is now only triggered if no fix change has been detected within the past + 2min. This means, this event might be thrown with a 2min delay. It is instantly thrown, if the mode + of the controller is changed. As arguments it now displays the new fix and the numer of fix changes + missed. +- The number of satellites seen and used is reset to 0, in case they are set to invalid. +- Altitude, latitude and longitude messages are not checked anymore, in case the mode message was + already invalid. + +## Added + +- PUS timeservice relative timeshift. + +## Fixed + +- Fixed wrong order in quaternion multiplication for computation of the error quaternion. +- Re-worked some FDIR logic in the FSFW. The former logic prevented events with a severity + higher than INFO if the device was in EXTERNAL CONTROL. The new logic will allow to trigger + events but still inhibit FDIR reactions if the device is in EXTERNAL CONTROL. + +# [v7.7.4] 2024-03-21 + +## Changed + +- Rotational rate limit for the GS target pointing is now seperated from controller limit. It + is also reduced to 0.75°/s now. + +## Fixed + +- Fixed wrong sign in calculation of total current within the `PWR Controller`. + +# [v7.7.3] 2024-03-18 + +- Bumped `eive-fsfw` + +## Added + +- Added parameter to disable STR input for MEKF. + +## Changed + +- If a primary heater is set to `EXTERNAL CONTROL` and `ON`, the `TCS Controller` will no + try to control the temperature of that object. +- Set lower OP limit of `PLOC` to -5°C. + +## Fixed + +- Added prevention of sign jump for target quaternion of GS pointing, which would reduce the + performance of the controller. +- Heaters set to `EXTERNAL CONTROL` no longer can be switched off by the `TCS Controller`, if + they violate the maximum burn duration of the controller. + +# [v7.7.2] 2024-03-06 + +## Fixed + +- Camera and E-band antenna now point towards the target instead of away from the target for the + pointing target mode. + +# [v7.7.1] 2024-03-06 + +- Bumped `eive-tmtc` to v6.1.1 +- Bumped `eive-fsfw` + +## Added + +- The `CoreController` now sets the leap seconds on initalization. They are stored in a persistent + file. If the file does yet not exist, it will be created. The leap seconds can be updated using an + action command. This will also update the file. + +## Fixed + +- Fixed wrong dimension of a matrix within the `MEKF`, which would lead to a seg fault, if the + star tracker was available. +- Fixed case in which control values within the `AcsController` could become NaN. + +# [v7.7.0] 2024-02-29 + +- Bumped `eive-tmtc` to v6.1.0 +- Bumped `eive-fsfw` + +## Fixed + +- PLOC SUPV sets: Added missing `PoolReadGuard` instantiations when reading boot status report + and latchup status report. +- PLOC SUPV latchup report could not be handled previously. +- Bugfix in PLOC SUPV latchup report parsing. +- Bugfix in PLOC MPSoC HK set: Set and variables were not set valid. +- The `PTG_CTRL_NO_ATTITUDE_INFORMATION` will now actually trigger a fallback into safe mode + and is triggered by the `AcsController` now. +- Fixed a corner case, in which an invalid speed command could be sent to the `RwHandler`. +- Fixed calculation of desaturation torque for faulty RWs. +- Fixed bugs within the `MEKF` and simplified the code. + +## Changed + +- `FusedRotationRate` now only uses rotation rate from QUEST and STR in higher modes +- QUEST and STR rates are now allowed per default +- Changed PTG Strat priorities to favor STR before MEKF. +- Increased message queue depth and maximum number of handled messages per cycle for + `PusServiceBase` based classes (especially PUS scheduler). +- `MathOperations` functions were moved to their appropriate classes within the `eive-fsfw` +- Changed pointing strategy for target groundstation mode to prevent blinding of the STR. This + also limits the rotation for the reference target quaternion to prevent spikes in required + rotation rates. +- Updated QUEST and Sun Vector Params to new values. +- Removed the satellites's angular momentum from desaturation calculation. +- Bumped internal `sagittactl` library to v11.11. + +## Added + +- Updated STR handler to unlock and allow using the secondary firmware slot. +- STR handling for new BlobStats TM set. +- Added new action command to update the standard deviations within the `MEKF` from the + `AcsParameters`. + +# [v7.6.1] 2024-02-05 + +## Changed + +- Guidance now uses the coordinate functions from the FSFW. +- Idle should now point the STR away from the earth + +## Fixed + +- Fixed bugs in `Guidance::comparePtg` and corrected overloading +- Detumbling State Machine is now robust to commanded mode changes. + +# [v7.6.0] 2024-01-30 + +- Bumped `eive-tmtc` to v5.13.0 +- Bumped `eive-fsfw` + +## Added + +- Added new parameter for MPSoC which allows to skip SUPV commanding. + +## Changed + +- Increased allowed mode transition time for PLOC SUPV. +- Detumbling can now be triggered from all modes of the `AcsController`. In case the + current mode is a higher pointing mode, the STR will be set to faulty, to trigger a + transition to safe first. Then, in a second step, a transition to detumble is triggered. + +## Fixed + +- If the PCDU handler fails reading data from the IPC store, it will + not try to do a deserialization anymore. +- All action commands sent by the PLOC SUPV to itself will have no sender now. +- RW speed commands get reset to 0 RPM, if the `AcsController` has changed its mode + to Safe +- Antistiction for RWs will set commanded speed to 0 RPM, if a wheel is detected as not + working +- Removed parameter to disable antistiction, as deactivating it would result in the + `AcsController` being allowed sending invalid speed commands to the RW Handler, which + would then trigger FDIR and turning off the functioning device +- `RwHandler` returnvalues would use the `INTERFACE_ID` of the `DeviceHandlerBase` +- The `AcsController` will reset its stored guidance values on mode change and lost + orientation. +- The nullspace controller will only be used if all RWs are available. +- Calculation of required rotation rate in pointing modes has been fixed to actual + calculation of rotation rate from two quaternions. +- Fixed alignment matrix and pseudo inverses of RWs, to match the wrong definition of + positive rotation. + +# [v7.5.5] 2024-01-22 + +## Fixed + +- Calculation of error quaternion was done with inverse of the required target quaternion. + +# [v7.5.4] 2024-01-16 + +## Fixed + +- Pointing strategy now actually uses fused rotation rate source instead of its valid flag. +- All datasets now get updated during pointing mode, even if the strategy is a fault one. + +# [v7.5.3] 2023-12-19 + +## Fixed + +- Set STR quaternions to invalid in device handler if the solution is not trustworthy. + +# [v7.5.2] 2023-12-14 + +## Fixed + +- Fixed faulty scaling within the QUEST algorithm. + +# [v7.5.1] 2023-12-13 + +- `eive-tmtc` v5.12.1 + +## Changed + +- Increased the maximum number of scheduled telecommands from 500 to 4000. Merry Christmas! + +## Fixed + +- Faulty mapping of input values for QUEST algorithm. +- Fixed validity check for QUEST algorithm. + +# [v7.5.0] 2023-12-06 + +- `eive-tmtc` v5.12.0 + +## Changed + +- ACS-Board default side changed to B-Side +- The TLE uploaded now gets stored in a file on the filesystem. It will always be stored on + the current active SD Card. After a reboot, the TLE will be read from the filesystem. + A filesystem change via `prefSD` on bootup, can lead to the TLE not being read, even + though it is there. +- Added action cmd to read the currently stored TLE. +- Both the `AcsController` and the `PwrController` now use the monotonic clock to calculate + the time difference. +- `ACS Controller` now has the function `performAttitudeControl` which is called prior to passing + on to the relevant mode functions. It handles all telemetry relevant functions, which were + always called, regardless of the mode. + +## Added + +- Higher ACS modes can now be entered without a running `MEKF`. Higher modes will collect their + quaternion and rotational rate depending on the available sources. +- `QUEST` attitude estimation was added to the `AcsController`. +- The fused rotational rate can now be estimated from `QUEST` and the `STR`. + +# [v7.4.1] 2023-12-06 + +## Fixed + +- Schedule SCEX again. Scheduling was removed accidentaly when Payload Task was converted to a PST. +- SCEX transition was previously 0 seconds.. which did not lead to bugs? In any case it is 5 + seconds now. + +# [v7.4.0] 2023-11-30 + +- `eive-tmtc` v5.11.0 + +## Changed + +- Rewrote the PLOC Supervisor Handler, which is now based on a new device handler base class. + Added ADC and Logging Counters telemetry set support. + +## Fixed + +- Increase allowed time for PTME writers to finish partial transfers. A duration of 200 ms was + not sufficient for cases where 3 writers write concurrently. +- Fixed state issue for PTME writer object where the writer was not reset properly after a timeout + of a partial transfer. This was a major bug blocking the whole VC if it occured. +- STR config path was previously hardcoded to `/mnt/sd0/startracker/flight-config.json`. + A new abstraction was introduces which now uses the active SD card to build the correct + config path when initializing the star tracker. + +## Added + +- PL PCDU: Add command to enable and disable channel order checks. +- Added new PUS 15 subservice `DELETE_BY_TIME_RANGE` which allows to also specify a deletion + start time when deleting packets from the persistent TM store. +- Introduced a new `RELOAD_JSON_CFG_FILE` command for the STR to reload the JSON configuration + data based on the current output of the config file path getter function. A reboot of the + device is still necessary to load the configuration to the STR. + +# [v7.3.0] 2023-11-07 + +## Changed + +- Changed PDEC addresses depending on which firmware version is used. It is suspected that + the previous addresses were invalid and not properly covered by the Linux memory protection. + The OBSW will use the old addresses for older FW versions. +- Reverted some STR ComIF behaviour back to an older software version. + +## Added + +- Always add PLOC MPSoC and PLOC SUPV components for the EM as well. + +# [v7.2.0] 2023-10-27 + +- `eive-tmtc` v5.10.1 + +## Added + +- STR: Added new TM sets: Blob, Blobs, MatchedCentroids, Contrast, AutoBlob, Centroid, Centroids +- STR: Added new mechanism where the secondary TM which is polled can now be a set instead of + being temperature only. An API is exposed which allows to add a data set to that set of secondary + telemetry, reset it back to temperature only, and read the whole set. This allows more debugging + capability. +- CFDP source handler, which allows file downlink using the standardized + CFDP interface. +- Proper back pressure handling for the CFDP handler, where the `LiveTmTask` is able to throttle + the CFDP handler. +- Added CFDP fault handler events. +- The EIVE system will command the payload OFF explicitely again when receiving the + `power::POWER_LEVEL_CRITICAL` event. + +## Fixed + +- If the PTME is driven in a way where it fills faster than it can be emptied, the interface + can become full during the process of a regular packet write. The interface of the PAPB VC + was adapted to be stateful now. Packet generation is started with a `write` call while + write transfers are advanced and completed with the `advanceWrite` call if they can not be + completed immediately. +- CFDP Space Packets SSC is now generated properly, was always 0 before. +- Host build fixes +- PL Enable set of the power controller is now set to invalid properly if the power controller + is not in normal mode. +- MPSoC debug mode. +- Possible bugfix for PL PCDU parameter JSON handling which might not have been initialized + properly from the JSON file. + +## Changed + +- Swapped RTD 9 (PLOC HPA Sensor) and RTD 11 (PLOC MPA Sensor) chip select definitions. It is + strongly suspected the cables for those devices were swapped during integration. This is probably + the easiest way to fix the issue without the need to tweak ground or other OBSW or controller + code. +- Added a 3 second delay in the EIVE system between commanding all PL components except the SUPV, + and the SUPV itself OFF when the power level becomes low or critical. +- SUS FDIR should now trigger less events. The finish event is now only triggered once the + SUS has been working properly for a minute again. It will then display the number of periods + during which the SUS was not working as well as the maximum amount of invalid messages. +- Updated battery internal resistance to new value + +# [v7.1.0] 2023-10-11 + +- Bumped `eive-tmtc` to v5.8.0. +- Activate Xiphos WDT with a timeout period of 80 seconds using the `libxiphos` API. The WDT + calls are done by the new `XiphosWdtHandler` object. + +# [v7.0.0] 2023-10-11 + +- Bumped `eive-tmtc` to v5.7.1. +- Bumped `eive-fsfw` + +## Added + +- EPS Subsystem has been added to EIVE System Tree +- Power Controller for calculating the State of Charge and FDIR regarding low SoC has been + introduced. + +## Changed + +- Changed internals for MPSoC boot process to make the code more understandable and some + parameters better configurable. This should not affect the behaviour of the OBSW, but might + make it more reliable and fix some corner cases. + +## Fixed + +- Missing `nullptr` checks for PLOC Supervisor handler, which could lead to crashes. +- SCEX bugfix for normal and transition commanding. + +# [v6.6.0] 2023-09-18 + +## Changed + +- Changed the memory initialized for the PDEC Config Memory and the PDEC RAM by using `mmap` + directly and ignoring UIO. This makes the OBSW compatible to a device tree update, where those + memory segments are marked reserved and are thus not properly accessible through the UIO API + anymore. This change should be downwards compatible to older device trees. + +# [v6.5.1] 2023-09-12 + +- Bumped `eive-tmtc` to v5.5.0. + +# [v6.5.0] 2023-09-12 + +## Changed + +- Relaxed SUS FDIR. The devices have shown to be glitchy in orbit, but still seem to deliver + sensible raw values most of the time. Some further testing is necessary, but some changes in the + code should cause the SUS devices to remain healthy for now. +- The primary and the secondary temperature sensors for the PLOC mission boards are exchanged. +- ACS parameters for the SUSMGM (FLP) safe mode have been adjusted. This safe mode is now the + default one. +- MGM3100 Startup Configuration: Ignore bit 1 of the CMM reply, which is sometimes set to + 1 in the reply for some reason. + +# [v6.4.1] 2023-08-21 + +## Fixed + +- `PDEC_CONFIG_CORRUPTED` event now actually contains the readback instead of the expected + config +- Magnetic field vector was not calculated if only MGM4 was available, but still written to + the dataset. This would result in a NaN vector. Allowance for usage of MGM4 is now checked + before entering calculation. + +# [v6.4.0] 2023-08-16 + +- `eive-tmtc`: v5.4.3 + +## Fixed + +- The handling function of the GPS data is only called once per GPS read. This should remove + the fake fix-has-changed events. +- Fix for PLOC SUPV HK set parsing. +- The timestamp for the `POSSIBLE_FILE_CORRUPTION` event will be generated properly now. + +## Changed + +- PDEC FDIR rework: A full PDEC reboot will now only be performed after a regular PDEC reset has + failed 10 times. The mechanism will reset after no PDEC reset has happended for 2 minutes. + The PDEC reset will be performed when counting 4 dirty frame events 10 seconds after the count + was incremented initially. +- GPS Fix has changed event is no longer triggered for the EM +- MGM and SUS rates now will only be calculated, if 2 valid consecutive datapoints are available. + The stored value of the last timestep will now be reset, if no actual value is available. + +## Added + +- The PLOC SUPV HK set is requested and downlinked periodically if the SUPV is on now. +- SGP4 Propagator is now used for propagating the position of EIVE. It will only work once + a TLE has been uploaded with the newly added action command for the ACS Controller. In + return the actual GPS data will be ignored once SPG4 is running. However, by setting the + according parameter, the ACS Controller can be directed to ignore the SGP4 solution. +- Skyview dataset for more GPS TM has been added +- `PDEC_CONFIG_CORRUPTED` event which is triggered when the PDEC configuration does not match the + expected configuration. P1 will contain the readback of the first word and P2 will contain the + readback of the second word. +- The MGM and SUS vectors being too close together does not prevent the usage of the safe + mode controller anymore. +- Parameter to disable usage of MGM4, which is part of the MTQ and therefore cannot be + disabled without disabling the MTQ itself. + +# [v6.3.0] 2023-08-03 + +## Fixed + +- Small SCEX fix: The temperatur check option was not passed + on for commands with a user data size larger than 1. +- SCEX: Properly check whether filesystem is usable for filesystem checks. +- ACS Controller strategy is now actually written to the dataset for detumbling. +- During detumble the fused rotation rate is now calculated. +- Detumbling is now exited when its exit value is undercut and not its entry value. +- Rotation rate of last cycle is now stored in all cases for the fused rotational rate + calculation. +- Fused rotation rate estimation during eclipse can be disabled. This will also prevent + detumbling during eclipse, as no relevant rotational rate is available for now. +- `EiveSystem`: Add a small delay between triggering an event for FDIR reboots and sending the + command to the core controller. +- PL PDU: Fixed bounds checking logic. Bound checks will only be performed for modules which are + enabled. + +## Changed + +- SCEX: Only perform filesystem checks when not in OFF mode. +- The `EiveSystem` now only sends reboot commands targetting the same image. +- Added 200 ms delay between switching HPA/MPA/TX/X8 and DRO GPIO pin OFF. +- PL PCDU ADC set is now automatically enabled for `NORMAL` mode transitions. It is automatically + disabled for `OFF` mode transitions. + +## Added + +- PL PCDU for EM build. +- SCEX: Add warning event if filesystem is unusable. + +# [v6.2.0] 2023-07-26 + +- `eive-tmtc`: v5.3.1 + +## Changed + +- STR missed reply handling is now moved to DHB rather than the COM interface. The COM IF will + still trigger an event if a reply is taking too long, and FDIR should still work via reply + timeouts. +- Re-ordered some functions of the core controller in the initialize function. +- Rad sensor is now only polled every 30 minutes instead of every device cycle to reduce wear of + the RADFET electronics. +- The SD cards will still be switched OFF on a reboot, but this is done in a non-blocking manner + now with a timeout of 10 seconds where the reboot will be performed in any case. +- ACS Controller now includes the safe mode from FLP, which will calculate its rotational rate + from SUS and MGM measurements. To accommodate these changes, low-pass filters for SUS + measurements and rates as well as MGM measurements and rates are included. Usage of the new + controller as well as settings of the low-pass filters can be handled via parameter commands. +- Simplify and fix the chip and copy protection functions in the core controller. This mechanism + now is always performed for the target chip and target copy in the reboot handlers. +- Improvement in FSFW: HK generation is now countdown based. + +## Added + +- 5 ms delay after pulling RADFET enable pin high before starting + the ADC conversion. +- Set STR time in configuration sequence to firmware mode. +- The STR `AutoThreshold` parameters are now set from the configuration JSON file at STR + startup. +- The STR handler can now handle the COM error reply and triggers an low severity event accordingly. +- Add SCEX handler for EM. +- Radiation sensor handler dummy for the EM. +- Added event for SD card information in core controller initialize function. This event will also + be triggered after the SD state machine has run, so the event will generally be triggered twice + at system boot, and once after commanding SD card switches. + +## Fixed + +- General bugs in the SD card state machine. This might fix some other known bugs for certain + combinations of switching ON and OFF SD cards and also makes the whole state machine a lot more + robust against hanging up. +- SUS dummy handler went to `MODE_NORMAL` for ON commands. +- PL PCDU dummy went to `MODE_NORMAL` for ON commands. + +# [v6.1.0] 2023-07-13 + +- `eive-tmtc`: v5.2.0 + +## Changed + +- TCS: Remove OBC IF board thermal module, which is exactly identical to OBC module and therefore + obsolete. +- Swapped PL and PS I2C, BPX battery and MGT are connected to PS I2C now for firmware versions + equal or above v4. However, this software version is compatible to both v3 and v4 of the firmware. +- The firmware version variables are global statics inititalized early during the program + runtime now. This makes it possible to check the firmware version earlier. +- The TCS controller will now always command heaters OFF when being blind for thermal + components (no sensors available), irrespective of current switch state. +- Make OBSW compatible to prospective FW version v5.0.0, where the Q7 I2C devices were + moved to a PL I2C block and the TMP sensor devices were moved to the PS I2C0. +- Made `Xadc` code a little bit more robust against errors. + +## Fixed + +- TMP1075: Set dataset invalid on shutdown explicitely +- Small fixes for TMP1075 FDIR: Use strange and missed reply counters. +- TCS controller: Last heater (S-band heater) was skipped for transition completion + checks. +- TCS controller: A helper member to track the elapsed heater control cycles was not reset + properly, which could lead to switch transitions being completed immediately. This can + lead to weird bugs like heaters being commanded ON twice and can potentially lead to + other bugs. +- TMP1075: Devices did not go to OFF mode when being set faulty. +- Update PL PCDU 1 in TCS mode tree on the EM. +- TMP1075: Possibly ignored health commands. +- Bugfix in FSFW where certain packet types could only be sent with source data fields with a + maximum size of 255 bytes. +- TCS CTRL: Limit number of heater handler messages sent in case there are not sensors available + anymore. +- Fix to allow adding real STR device for EM. + +# Added + +- Two events for heaters being commanded ON and OFF by the TCS controller +- Upper limit for burn time of TCS heaters. Currently set to 1 hour for each heater. + This mechanism will only track the burn time for heaters which were commanded by the + TCS controller. +- TCS controller is now observable by introducing a new HK dataset which exposes some internal + fields related to TCS control. + +# [v6.0.0] 2023-07-02 + +- `q7s-package` version v3.2.0 +- Important bugfixes for PTME. See `q7s-package` CHANGELOG. + +## Changed + +- Added back PTME busy bit polling. This is necessary due to changes to the AXI APB interface + to the PTME core. + +## Fixed + +- For the live channel (VC0), telemetry was still only dumped if the transmitter is active. + Please note that this fix will lead to crashes for FW versions below v3.2. + However, it might not be an issue for the oldest firmware on the satellite (v2.5.1). + +# [v5.2.0] 2023-07-02 + +## Fixed + +- The firmware information event was not triggered even when possible because of an ordering + bug in the initializer function. +- Empty dumps (no TM in time range) will now correctly be completed immediately + +## Changed + +- PTME was always reset on submode changes. The reset will now only be performed if the actual data + rate changes. +- Add back ACS board code for the EM. Now that the radiation sensor was removed, the image switching + issue has disappeared and adding back the ACS board is worth it for the GPS timekeeping. + +# [v5.1.0] 2023-06-28 + +- `eive-tmtc` version v5.1.0 + +## Changed + +- Persistent TM store dumps are now performed in chronological order. +- Increase Syrlinks RX HK rate to 5.0 seconds during a pass. +- Various robustness improvements for the heater handler. The heater handler will now only + process the command queue if it is not busy with switch commanding which reduces the amount + of possible bugs. +- The heater handler is only able to process messages stricly sequentially now but is scheduled + twice in a 0.5 second slot so something like a consecutive heater ON or OFF command can still + be handled relatively quickly. + +## Added + +- Sequence counters for PUS and CFDP packets are now stored persistently across graceful reboots. +- The PUS packet message type counter will now be incremented properly for each PUS service. +- Internal error reporter set is now enabled by default and generated every 120 seconds. + +# [v5.0.0] 2023-06-26 + +v3.3.1 and all following version will now be moved to v5.0.0 with the additional changes listed +here. This was done because the firmware update (v4.0.0) is not working right now and it is not +known when and how it will be fixed. Because of that, all updates to make the SW work with the new +firmware, which are limited to a few files will be moved to a dev branch so regular development +compatible to the old firmware can continue. + +TLDR: This version is compatible to the old firmware and some changes which only work with the new +firmware have been reverted. + +## Changed + +- Added `sync` syscall in graceful shutdown handler +- Graceful shutdown is now performed by the reboot watchdog +- There is now a separate file for the total reboot counter. The reboot watchdog has its own local + counters to determine whether a reboot is necessary. + +# [v4.0.1] 2023-06-24 + +## Fixed + +- `PusLiveDemux` packet demultiplexing bugfix where the demultiplexing did not work when there was + only one destination available. + +# [v4.0.0] 2023-06-22 + +- `eive-tmtc` version v5.0.0 +- `q7s-package` version v3.1.1 + +## Fixed + +- Important bugfixes for PTME. See `q7s-package` CHANGELOG. +- TCS fixes: Set temperature values to invalid value for MAX31865 RTD handler, SUS handler + and STR handler. Also set dataset to invakid for RTD handler. +- Fixed H parameter in SUS converter from 1 mm to 2.5 mm. + +## Changed + +- Removed PTME busy/ready signals. Those were not used anyway because register reads are used now. +- APB bus access busy checking is not done anymore as this is performed by the bus itself now. +- Core controller will now announce version and image information event in addition to reboot + event in the `inititalize` function. +- Core controller will now try to request and announce the firmware version in addition to the + OBSW version as well. +- Added core controller action to read reboot mechansm information +- GNSS reset pin will now only be asserted for 5 ms instead of 100 ms. + +## Added + +- Added PL I2C reset pin. It is not used/connected for now but could be used for FDIR procedures to + restore the PL I2C. +- Core controller now announces firmware version as well when requesting a version info event + +# [v3.3.1] 2023-06-22 + +## Fixed + +- `PusLiveDemux` packet demultiplexing bugfix where the demultiplexing did not work when there was + only one destination available. + +## Fixed + +- Fixed H parameter in SUS converter from 1 mm to 2.5 mm. + +# [v3.3.0] 2023-06-21 + +Like v3.2.0 but without the custom FM changes related to VC0. + +# [v3.2.0] 2023-06-21 + +## Fixed + +- Fix sun vector calculation +- SUS total vector was not reset to being a zero vector during eclipse due to a wrong memcpy + length. + +## Changed + +- Reverted all changes related to VC0 handling to avoid FM bug possibly related to FPGA bug. + +# [v3.1.1] 2023-06-14 + +## Fixed + +- TMP1075 bugfix where negative temperatures could not be measured because of a two's-complement + conversion bug. + +# [v3.1.0] 2023-06-14 + +- `eive-tmtc` version v4.1.0 + +## Fixed + +- TCS heater switch enumeration naming was old/wrong and was not updated in sync with the object ID + update. This lead to the TCS controller commanding the wrong heaters. + +## Changed + +- Increase number of allowed parallel HK commands to 16 + +## Added + +- Added `CONFIG_SET`, `MAN_HEATER_ON` and `MAN_HEATER_OFF` support for the BPX battery handler + +# [v3.0.0] 2023-06-11 + +- `eive-tmtc` version v4.0.0 + +## Changed + +- Adapt EM configuration to include all GomSpace PCDU devices except the ACU. For the ACU + (which broke), a dummy will still be used. +- Event Manager queue depth is configurable now. +- Do not construct and schedule broken TMP1075 device anymore. +- Do not track payload modes in system mode tables. +- ACS modes derived from system modes. +- The CMake build generator will now search for the cross-compiler binaries in the environmental + variable named `CROSS_COMPILE_BIN_PATH` first when setting up the build system. This prevents + CMake from selecting wrong cross-compilers if multiple cross-compilers with the same name are used + on the same system. +- Add ACS board for EM by default now. +- Add support for MPSoC HK packet. +- Add support for MPSoC Flash Directory Content Report. +- Dynamically enable and disable HK packets for MPSoC on `ON` and `OFF` commands. +- Add support for MPSoC Flash Directory Content Report. +- Larger allowed path and file sizes for STR and PLOC MPSoC modules. +- More robust MPSoC flash read and write command data handling. +- Increase frequency of payload handlers from 0.8 seconds to 0.5 seconds. +- Disable missed deadlines per default. Not useful in orbit, and triggers all the time on the EM + build after a number of subsequent runs, without any apparent reason (deadlines are not actually + missed, thread usage displayed is nominal) +- TM store dumpes will not be cancelled anymore if the transmitter is off. The dump can be cancelled + with an OFF command, and the PTME is perfectly capable of dumping without the transmitter being + on. +- Transmitter state is not taken into account anymore for writing into the PTME. The PTME should + be perfectly capable of generating a valid CADU, even when the transmitter is not ON for any + reason. +- OFF mode is ignores in TM store for determining whether a store will be written. The modes will + only be used to cancel a transfer. +- Handling of multiple RWs in the ACS Controller is improved and can be changed by parameter + commands. +- The Directory Listing direct dumper now has a state machine to stagger the directory listing dump. + This is required because very large dumps will overload the queue capacities in the framework. +- The PUS Service 8 now has larger queue sizes to handle more action replies. The PUS Service 1 + also has a larger queue size to handle a lot of step replies now. +- Moved PDU `vcc` and `vbat` variable from auxiliary dataset to core dataset. +- Tweak TCP/IP configuration: Only the TCP server will be included on the EM. For the FM, no + TCP/IP servers will be included by default. + +## Added + +- Add the remaining system modes. +- PLOC MPSoC flash read command working. +- BPX battery handler is added for EM by default. +- ACU dummy HK sets +- IMTQ HK sets +- IMTQ dummy now handles power switch +- Added some new ACS parameters +- Enabled decimation filter for the ADIS GYRs +- Enabled second low-pass filter for L3GD20H GYRs + +## Fixed + +- CFDP low level protocol bugfix. Requires `fsfw` update and `tmtc` update. +- Compile fix if SCEX is compiled for the EM. +- Set up Rad Sensor chip select even for EM to avoid SPI bus issues. +- Correct ADIS Gyroscope type configuration for the EM, where the 16507 type is used instead of the + 16505 type. +- Host build is working again. Added reduced live TM helper which schedules the PUS and CFDP + funnel. +- PLOC Supervisor handler now has a power switcher assigned to make PLOC power switching work + without an additional PCDU power switch command. +- The PLOC Supervisor handler now waits for the replies to the `SET_TIME` command to verify working + communication. +- The PLOC MPSoC now waits 10 cycles before going to on. These wait cycles are necessary because + the MPSoC is not ready to process commands without this additional boot time. +- Fixed correction for `GPS Altitude` in case the sensor data is out of the expected bonds. +- PLOC MPSoC special communication is now scheduled, which allows flash read and flash write + commands to work. +- Fixed the MPSoC flash write command. +- Added missing ACS parameter. +- HK TM store: The HK store dump success event was triggered for cancelled HK dumps. +- When a PUS parsing error occured while parsing a TM store file, the dump completion procedure + was always executed. +- Some smaller logic fixes in the TM store base class +- Fixed usage of C `abs` instead of C++ `std::abs`, which results in MTQ commands not being + scaled correctly between 1Am² and 0.2Am². +- TCS Heater Handler: Always trigger mode event if a heater goes `OFF` or `ON`. This event might + soon replace the `HEATER_WENT_ON` and `HEATER_WENT_OFF` events. +- Prevent spam of TCS controller heater unavailability event if all heaters are in external control. +- TCS heater switch info set contained invalid values because of a faulty `memcpy` in the TCS + controller. There is not crash risk but the heater states were invalid. +- STR datasets were not set to invalid on shutdown. +- Fixed usage of quaternion valid flag, which does not actually represent the validity of the + quaternion. +- Various fixes for the pointing modes of the `ACS Controller`. All modes should work now as + intended. +- The variance for the ADIS GYRs now represents the used `-3` version and not the `-1` version +- CFDP funnel did not route packets to live channel VC0 + +# [v2.0.5] 2023-05-11 + +- The dual lane assembly transition failed handler started new transitions towards the current mode + instead of the target mode. This means that if the dual lane assembly never reached the initial + submode (e.g. mode normal and submode dual side), it will transition back to the current mode, + which miht be `MODE_OFF`. Furthermore, this can lead to invalid internal states, so the subsequent + recovery handling becomes stuck in the custom recovery sequence when swichting power back on. +- The dual lane custom recovery handling was adapted to always perform proper power switch handling + irrespective of current or target modes. + +# [v2.0.4] 2023-04-19 + +## Fixed + +- The dual lane assembly datasets were not marked invalid properly on OFF transitions. + +# [v2.0.3] 2023-04-17 + +- eive-tmtc: v3.1.1 + +## Fixed + +- Fixed shadowing within the `SafeCtrl`, which prevented actuator commands to be calculated during + eclipse phase. +- EM build idle mode fixes for RW dummy. + +## Added + +- Add `MGT_OVERHEATING` event and fallback to system SAFE mode if the MGT is overheating for + whatever reason. + +## Changed + +- Low-pass filters can no longer be executed if no actual data is available. + +# [v2.0.2] 2023-04-16 + +- Bump patch version to 2. + +# [v2.0.1] 2023-04-16 + +- eive-tmtc: v3.1.0 + +# [v2.0.0] 2023-04-16 + +This is the version which will fly on the satellite for the initial launch phase. + +- q7s-package: v2.5.0 +- eive-tmtc: v3.0.0 +- `wire` library is now on version v10.7 as well. + +## Added + +- Added `mv`, `cp` and `rm` action helpers for the core controller for common filesystem operations. +- Extended directory listing helpers. There is now a directory listing helper which dumps the + directory listing as an action data reply immediately. For smaller directory listings, this + allows a listing without requiring a separate file downlink (which also has not been implemented + yet) + +## Changed + +- The directory listing action commands now allow compressing of either the target output file + for the directory listing into file action command, or compression in the helper which dumps + the directory listing directly. + +# [v1.45.0] 2023-04-14 + +- q7s-package: v2.5.0 +- eive-tmtc: v3.0.0 +- STR firmware was updated to v10.7. `wire` library still needs to be updated. + +## Fixed + +- Small fix for `install-obsw-yocto.sh` script +- Bugfix for STR firmware update procedure where the last remaining + bytes were not written properly. +- Bugfix for STR where an invalid reply was received for special requests + like firmware updates. +- Bugfix for shell command executors in command controller which lead to a crash. +- Important bugfix for STR solution set. Missing STR mode u8 parameter. +- Fix for STR image download. +- Possible fix for STR image upload. +- Fixed regression where the reply result for ACS board and SUS board devices was set to FAILED + even when going to OFF mode. In that case, it has to be set to OK so the device handler can + complete the OFF transition. + +## Changed + +- STR `wire` library updated to v10.3. Submodule renamed to `sagittactl`. +- Custom FDIR for TMP1075 sensors. The device handlers reject `NEEDS_RECOVERY` health commands + anyway, so it does not really make sense to use the default FDIR. +- Reject `NEEDS_RECOVERY` health commands for the heater health devices. +- Adapted some queue sizes so that EM startup works without queue errors + - Event Manager: 120 -> 160 + - UDP TMTC Bridge: 50 -> 120 + - TCP TMTC Bridge: 50 -> 120 + - Service 5: 120 -> 160, number of events handled in one cycle increased to 80 +- EM: PCDU dummy is not a device handler anymore, but a custom power switcher object. This avoids + some issues where the event manager could not send an event message to the PCDU dummy because + the FDIR event queue was too small. + +## Added + +- Add a way for the MAX31865 RTD handlers to recognize faulty/broken/off sensor devices. +- Add parameter interface for core controller +- Allow setting the preferred SD card via the new parameter interface of the core controller + with domain ID 0 and unque ID 0. +- Added action commands to reset the PDEC. Also added autonomous reset handling for the PDEC, + because there is no way so send TCs with a faulty PDEC. +- Added `I2C_REBOOT` and `PDEC_REBOOT` events for EIVE system component to ensure ground gets + informed. + +## ACS + +- Commanding from ACS Controller is now enabled. +- Safe Controller was reverted to FLP Design. This also introduces safe mode strategies. + They contain what the controller does and which data it uses. The controller will + automatically based on the available data decide on which strategy to use. If a strategy + is undesirable (e.g. the MEKF should not be used) this can be handeld via setting parameters. + +# [v1.44.1] 2023-04-12 + +- eive-tmtc: v2.22.1 + +## Fixed + +- Bugfixes and improvements for SDC state machine. Internal state was not updated correctly due + to a regression, so commanding the SDC state machine externally lead to confusing results. +- Heater states array in TCS controller was too small. +- Fixed a bug in persistent TM store, where the active file was not reset of SD card switches. + SD card switch from 0 to 1 and vice-versa works without errors from persistent TM stores now. +- Add a way for the SUS polling to detect broken or off devices by checking the retrieved + temperature for the all-ones value (0x0fff). +- Better reply result handling for the ACS board devices. +- ADIS1650X initial timeout handling now performed in device handler. +- The RW assembly and TCS board assembly now perform proper power switch handling for their + recovery handling. + +## Changed + +- Added additional logic for SDC state machine so that the SD cards are marked unusable when + the active SD card is switched or there is a transition from hot redundant to cold redundant mode. + This gives other tasks some time to register the SD cards being unusable, and therefore provides + a way for them to perform any re-initialization tasks necessary after SD card switches. +- TCS controller now only has an OFF mode and an ON mode +- The TCS controller pauses operations related to the TCS board assembly (reading sensors and + the primary control loop) while a TCS board recovery is on-going. +- Allow specifying custom OBSW update filename. This allowed keeping a cleaner file structure + where each update has a name including the version +- The files extracted during an update process are deleted after the update was performed to keep + the update directory cleaner. + +## Added + +- TCS controller: SUBMODE_NO_HEATER_CTRL (1) added for ON mode. If this submode is + commanded, all heaters will be switched off and then no further heater + commanding will be done. +- Fixed a bug in persistent TM store, where the active file was not reset of SD card switches. + SD card switch from 0 to 1 and vice-versa works without errors from persistent TM stores now. + +# [v1.44.0] 2023-04-07 + +- eive-tmtc: v2.22.0 + +## Added + +- Special I2C recovery handling. If the I2C bus is unavailable for whatever reason, the EIVE + system component will power-cycle all I2C devices by first going to the OFF/BOOT mode, then + power-cycling the 3V3 stack and rebooting the battery, and finally going back to safe mode. + If this does not restore the bus, a full reboot will be performed. This special sequence can + be commanded as well. + +## Fixed + +- RW Assembly: Correctly transition back to off when more than 1 devices is OFF. Also do this + when this was due to two devices being marked faulty. +- RW dummy and STR dummy components: Set/Update modes correctly. +- RW handlers: Bugfix for TM set retrieval and special request handling in general where the CRC + check always failed for special request. Also removed an unnecessary delay for special requests. +- RW handlers: Polling is now disabled for RWs which are off. + +## Changed + +- RW shutdown now waits for the speed to be near 0 or for a OFF transition countdown to be expired + before going to off. + +# [v1.43.2] 2023-04-05 + +## Changed + +- Adapted HK data rates to new table for LEOP SAFE mode. +- GPS controller HK is now generated periodically as well. +- Better mode combination checks for assembly components. This includes: + - IMTQ assembly + - Syrlinks assembly + - Dual Lane Assembly +- RWs are no longer commanded by the ACS Controller during safe mode. Instead the RW speed command + is set to 0 as part or the `doShutDown` of the RW handler. + +## Fixed + +- Dual lane assemblies: Fix handling when health states are overwritten. Also add better handling + when some devices are permanent faulty and some are only faulty. In that case, only the faulty + devices will be restored. +- ACS dual lane assembly: Gyro 3 helper mode was assigned to the Gyro 2 mode. + +# [v1.43.1] 2023-04-04 + +## Fixed + +- Generic HK handling: Bug where HKs were generated a lot more often than required. This is the case + if a device handler `PERFORM_OPERATION` step is performed more than once per PST cycle. +- Syrlinks now goes to `_MODE_TO_ON` when finishing the `doStartUp` transition. + +## Changed + +- Doubled GS PST interval instead of scheduling everything twice. +- Syrlinks now only has one `PERFORM_OPERATION` step, but still has two communication steps. +- PCDU components only allow setting `NEEDS_RECOVERY`, `HEALTHY` and `EXTERNAL_CONTROL` health + states now. TMP sensor components only allow `HEALTHY` , `EXTERNAL_CONTROL`, `FAULTY` and + `PERMANENT_FAULTY`. +- TCS controller now does a sanity check on the temperature values: Values below -80 C or above + 160 C are ignored. + +# [v1.43.0] 2023-04-04 + +- q7s-package: v2.4.0 +- eive-tmtc: v2.21.0 + +## Added + +- Version of thermal controller which performs basic control tasks. +- PCDU handler can now command switch of the 3V3 stack (switch ID 19) +- Set STR dev to OFF in assembly when it is faulty. +- STR: Reset data link layer and flush RX for regular commands and before performing special + commands to ensure consistent start state + +## Fixed + +- PTME was not reset after configuration changes. +- GPS health devices: ACS board assembly not reacts to health changes. +- STR COM helper: Reset reply size after returning a reply + +## Changed + +- Poll threshold configuration of the PTME IP core is now configurable via a parameter command + and is set to 0b010 (4 polls) instead of 0b001 (1 poll) per default. +- EIVE system fallback and COM system fallback: Perform general subsystem handling first, then + event reception, and finally any new transition handling. +- IMTQ MGM integration time lowered to 6 ms. This relaxes scheduling requirements a bit. +- PCDU handler switcher HK set now has additional 3V3 switcher state HK. + +# [v1.42.0] 2023-04-01 + +- eive-tmtc: v2.20.1 +- q7s-package: v2.3.0 + +## Changed + +- SCEX filename updates. Also use T as the file ID / date separator between date and time. +- COM TM store and dump handling: Introduce modes for all 4 TM VC/store tasks. The OFF mode can be + used to disable ongoing dumps or to prevent writes to the PTME VC. This allows cleaner reset + handling of the PTME. All 4 VC/store tasks were attached to the COM mode tree and are commanded + as part of the COM sequence as well to ensure consistent state with the CCSDS IP core handler. +- Added `PTME_LOCKED` boolean lock which is used to lock the PTME so it is not used by the VC tasks + anymore. This lock will be controlled by the CCSDS IP core handler and is locked when the PTME + needs to be reset. Examples for this are datarate changes. +- Simulate real PCDU in PCDU dummy by remembering commandes switch change and triggering appropriate + events. Switch feedback is still immediate. +- GomSpace devices are polled with a doubled frequency. This speeds up power switch commanding. + +## Fixed + +- Bugfix for side lane transitions of the dual lane assemblies, which only worked when the + assembly was directly commanded. +- Syrlinks Handler: Bugfix so transition command is only sent once. +- SCEX file name bug: Create file name time stamp with `strftime` similarly to how it's done + for the persistent TM store. + +## Added + +- Added GPS0 and GPS1 health device which are used by the ACS board assembly when deciding whether + to change to the other side or to go to dual side directly. Setting the health devices to faulty + should also trigger a side switch or a switch to dual mode. + +# [v1.41.0] 2023-03-28 + +- eive-tmtc: v2.20.0 +- q7s-package: v2.2.0 + +## Fixed + +- Proper Faulty/External Control handling for the dual lane assemblies. +- ACS board devices: Go to ON mode instead of going to NORMAL mode directly. +- SUS device handlers: Go to ON mode on startup instead of NORMAL mode. +- Tweaks for the delay handling for the persistent TM stores. This allows pushing the full + high datarate when dumping telemetry. The most important and interesting fix is that + there needs to be a small delay between the polling of the GPIO. Polling the GPIO + without any delay consecutively can lead to scheduling issues. +- Bump FSFW for fix of `ControllerBase` class `startTransition` implementation. +- Bump FSFW for possible fix of `PowerSwitcherComponent`: Initial mode `MODE_UNDEFINED`. + +## Changed + +- Enabled periodic hosuekeeping generation for release images. +- Project structure (linux and mission folder) is subsystem centric now. + +# [v1.40.0] 2023-03-24 + +- eive-tmtc: v2.19.4 +- q7s-packasge: v2.1.0 +- Bumped fsfwgen for bugfix: Event translation can deal with duplicate event names now. + +## Fixed + +- PAPB busy polling now implemented properly with an upper bound of how often the PAPB is allowed + to be busy before returning the BUSY returnvalue. Also propagate and check for that case properly. + Ideally, this will never be an issue and the PAPB VC interface should never block for a longer + period. +- The `mekfInvalidCounter` now resets on setting the STR to faulty. +- Improve the SD lock handling. The file handling does not need to be locked as it + is only handled by one thread. + +## Added + +- The event `MEKF_RECOVERY` will be triggered in case the `MEKF` does manage to recover itself. +- The persistent TM stores now have low priorities and behave like background threads now. This + should prevent them from blocking or slowing down the system even during dumps + (at least in theory). +- STR: Fix weird issues on datalink layer data reception which sometimes occur. +- Syrlinks FDIR: Fully allow FDIR to do more recoveries. Assembly should take care of preventing + the switch to go off. +- Allow dual lane assembly side switches. + +## Changed + +- Rework FSFW OSALs to properly support regular scheduling (NICE priorities) and real-time + scheduling. +- STR: Move datalink layer to `StrComHandler` completely. DLL is now completely hidden from + device handler. +- STR: Is now scheduled twice in ACS PST. +- `StrHelper` renamed to `StrComHandler`, is now a `DeviceHandlerIF` directly and does not wrap + a separate UART COM interface anymore. +- TCS: Local pool variables are members now. +- Syrlinks: Create dedicated COM helper which uses a ring buffer to parse the Syrlinks datalinklayer + and should make communication more reliable even on high CPU loads. +- Syrlinks: Two communication cycles per PST. +- Fine-tuning of various task priorities. +- The CSP router now is scheduled with the `SCHED_RR` policy and the same priority as the PCDU + handlers as well. +- Change project structure to be more subsystem centric for ACS and COM. + +# [v1.39.1] 2023-03-22 + +## Fixed + +- Bugfix for STR: Some action commands wrongfully declined. +- STR: No normal command handling while a special request like an image upload is active. +- RS485 data line was not enabled when the transmitter was switched on. + +# [v1.39.0] 2023-03-21 + +Requires firmware update for new FPGA design where reset line is routed into the software. +2 relevant PRs: + - https://egit.irs.uni-stuttgart.de/eive/q7s-vivado/pulls/53 + - https://egit.irs.uni-stuttgart.de/eive/q7s-vivado/pulls/54 + +eive-tmtc: v2.19.3 + +## Added + +- Added NaN and Inf check for the `MEKF`. If these are detected, the `AcsController` will reset + the `MEKF` on its own once. This way, there will not be an event spam and operators will have + to look into the reason of wrong outputs. To restore the reset ability, an action command has + been added. +- Contingency handling for non-working I2C bus bug. Reboot the system if the I2C is not working. + +## Fixed + +- Fixed transition for dual power lane assemblies: When going from dual side submode to single side + submode, perform logical commanding first, similarly to when going to OFF mode. +- GPS time is only set to valid if at least one satellite is in view. + +## Changed + +- Bugfixes for STR mode transitions: Booting to mode ON with submode FIRMWARE now works properly. + Furthermore, the submode in the NORMAL mode now should be 0 instead of some ON mode submode. +- Updated GYR bias values to newest measurements. This also corrects the ADIS values to always + consit of just one digit. +- The CCSDS IP core handler now exposes a parameter to enable the priority select mode + for the PTME core. This mode prioritizes virtual channels with a lower index, so for example + the virtual channel (VC0) will have the highest priority, while VC3 will have the lowestg + priority. This mode will be enabled by default for now, but can be set via the parameter IF with + the unique parameter ID 0. The update of this mode requires a PTME reset. Therefore, it will only + be performed when the transmitter is off to avoid weird bugs. +- Connect and handle reset line for the PTME core in the software now. +- Safe mode controller failure event now only triggers once per minute. + +# [v1.38.0] 2023-03-17 + +eive-tmtc: v2.19.2 + +## Fixed + +- SA deployment file handling: Use exceptionless API. +- Fix deadlock in SD card manager constructor: Double lock of preferred SD card lock. + +## Added + +- Failure of Safe Mode Ctrl will now trigger an event. As this can only be caused by sensors not + being in the correct mode, the assemblies should take care that this will never happen and no + additional FDIR is needed. + +## Changed + +- Telemetry relevant datasets for the RWs are now set invalid and partially reset on shotdown. +- I2C PST now has a polling frequency of 0.4 seconds instead of 0.2 seconds. +- GS PST now has a polling frequency of 0.5 seconds instead of 1 second. +- Bump FSFW: merged upstream. +- Move BPX battery scheduling to ACS PST to avoid clashes with IMTQ scheduling / polling + +# [v1.37.2] 2023-03-14 + +- Changed `PoolManager` bugfix implementation in the FSFW. +- Some tweaks for IPC and TM store configuration + +# [v1.37.1] 2023-03-14 + +This version works on the EM as well. + +eive-tmtc: v2.19.1 + +## Added + +- Added `EXECUTE_SHELL_CMD` action command for `CoreController` to execute arbitrary Linux commands. +- Added some missing PLOC commands. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/462 +- Add `PcduHandlerDummy` component. +- Added parameter for timeout until `MEKF_INVALID_MODE_VIOLATION` event is triggered. + +## Fixed + +- Pointing control of the `AcsController` was still expecting submodes instead of modes. +- Limitation of RW speeds was done before converting them to the correct unit scale. +- The Syrlinks task now has a proper name instead of `MAIN_SPI`. +- Make whole EIVE system initial transition work for the EM. This was also made possible by + always scheduling most EIVE components instead of tying the scheduling to preprocessor defines. +- Store more TCP und UDP packets. +- Bump fsfw for small tweak in local datapool manager: Re-enable or re-disabling dataset + generation will not fail anymore. +- Bump FSFW to simplify HK helpers: Was previously buggy and did not allow to use + minmum sampling frequency. Now both diagnostics and HK can use minimum + sampling frequency for requesting HK +- Bump FSFW to allow the TC/TM/IPC pools to spill to higher pools/pool pages. + +## Changed + +- Set `OBSW_ADD_TCS_CTRL` to 1. Always add TCS controller now for both EM and FM. +- generators module: Bump `fsfwgen` dependency to v0.3.1. The returnvalue CSV files are now sorted. +- generators module: Always generate subsystem ID CSV files now. +- The COM subsystem is now not commanded by the EIVE system anymore. Instead, + a separate RX_ONLY mode command will be sent at OBSW boot. After that, + commanding is done autonomously by the COM subsystem internally or by the operator. This prevents + the transmitter from going off during fallbacks to the SAFE mode, which might not always be + desired. +- Initialize switch states to a special `SWITCH_STATE_UNKNOWN` (2) variable. Return + `PowerSwitchIF::SWITCH_UNKNOWN` in switch state getter if this is the state. +- Wait 1 second before commanding SAFE mode. This ensures or at least increases the chance that + the switch states were initialized. +- Dual Lane Assemblies: The returnvalues of the dual lane power state machine FSM are not ignored + anymore. + +# [v1.37.0] 2023-03-11 + +eive-tmtc: v2.18.1 + +## Added + +- `SensorProcessing` now includes an FDIR for GPS altitude. If the measured GPS altitude is out + of bounds of the range defined in the `AcsParameters`, the altitude defaults to an altitude + set in the `AcsParameters`. +- `AcsController` will now never command a RW speed larger than the maximum allowed speed. + +## Fixed + +- `PAPB_EMPTY_SIGNAL_VC1` GPIO was not set up properly. +- Fix for heater names: HPA heater (index 7) is now the Syrlinks heater. +- `AcsParameters` setter were previously all for scalar parameters. Now vector and matrix + parameters use their respective setters. +- Several `AcsController` components had their own implementation of `AcsParameters`. This resulted + in those parameters not being updated, while the actual ones were updated. All instances of + `AcsParameters` not belonging to `AcsController` are eiter removed or replaced by pointer + instances. +- Instead of updating the `gsTargetModeControllerParameters`, the `targetModeControllerParameters` + were updated. +- Instead of updating the `idleModeControllerParameters`, the `targetModeControllerParameters` + were updated. +- Fixed Idle Mode Controller never calling `ptgLaw` and therefore never calculating control + values. +- Fixed wrong check on wether file used for persistant boolean flag on successful still existed. +- Scaling of MTQ Cmds now scales the current values to command with the current values and not + the values of the last step, which would result in undefined behaviour. +- Solved naming collision between file used for solar array deployment and confirmation for + ACS for solar array deployment. +- Fixed that scaling of RW torque would result in a zero vector unless the maximum value was exceeded. +- Bias for the GYR data was substracted within the wrong rf (sensor rf vs body rf). + +## Changed + +- Refactored TM pipeline to optimize usage of the PTME and communication downlink bandwidth. + This was done by moving the dumping of TMs to the VCs into separate threads with permanent loops. + These threads are then able to process high TM loads on demand. The PUS TM funnel will route + PUS packets to the approrpiate persisten TM stores and then demultiplex the TM to all registered + TM destinations as before. +- Service 5 now handles 40 events per cycle instead of 15 +- Remove periodic SD card check. The file system is not mounted read-only anymore when using an + ext4 filesystem +- The `detumbleCounter` now does not get hard reset anymore, if the critical rate does not get + violated anymore. Instead it is incrementally reset. +- The RW antistiction now only takes the RW speeds in account. +- ACS CTRL transition to DETUBMLE is now done in CTRL internally. No + system level handling necessary anymore. +- More fixes and improvements for SD card handling. Extend SD card setup in core controller to + create full initial state for SD card manager are core controller as early as possible, turn + execution of setup file update blocking. This might solve the issue with the SD card manager + sometimes blocking for a long time. +- Request raw MTM measurement twice for IMTQ, might reduce number of times measurement could not + be retrieved. +- Event manager and event service have larger queues now: 45 -> 120 for Service 5, 80 -> 120 for + event manager +- ACS mode changes: The ACS CTRL submodes are now modes. DETUBMLE is now submode of SAFE mode. +- EIVE system now tracks the mode of the ACS subsyste in SAFE mode. + +# [v1.36.0] 2023-03-08 + +eive-tmtc: v2.17.2 + +## Added + +- Star Tracker Assembly +- New `REBOOT_COUNTER` and `INDIVIDUAL_BOOT_COUNTS` events. The first contains the total boot count + as a u64, the second one contains the individual boot counts as 4 u16. Add new core controller + action command `ANNOUNCE_BOOT_COUNTS` with action ID 3 which triggers both events. These events + will also be triggered on each reboot. + +## Changed + +- Persistent TM stores will now create new files on each reboot. +- Fast ACS subsystem commanding: Command SUS board consecutively with other devices now +- Star Tracker: Use ground confguration for EM and flight config for FM by default. + +## Fixed + +- Command TCS controller off first for TCS subsystem transition to off. +- Health handling for TCS board assembly +- Mode fallback from IDLE mode to SAFE mode due to ACS errors/events now works properly for + the ACS subsystem +- Bugfix in IDLE transition for system. +- `std::filesystem` API usages: Avoid exceptions by using variants which return an error code + instead of throwing exceptions. +- GPS fix loss was not reported if mode is unset. +- Star Tracker: OFF to NORMAL transition now posssible. Requires FSFW bump which sets + transition source modes properly for those transitions. + FSFW PR: https://egit.irs.uni-stuttgart.de/eive/fsfw/pulls/131 +- Star Tracker JSON initialization is now done during object initization instead of redoing it + when building a command. This avoids missed deadlines issues in the ACS PST. +- Allow arbitrary submodes for dual lane boards to avoid FDIR reactions of subsystem components. + Bump FSFW to allow this. +- PUS 15 was not scheduled +- Transmitter timeout set to 2 minutes instead of 15 minutes. This will prevent to discharge the + battery in case the syrlinks starts transmitting due to detection of unintentional bitlock. This + happened e.g. on ground when the uplink to the flying latop was established. +- ACS system components are now always scheduled (EM specific) + +# [v1.35.1] 2023-03-04 + +## Fixed + +- ACS Board Assembly FDIR: Prevent permanent SAFE mode fallbacks by introducing special health + handling. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/418/files +- Watchdog fixes +- IMTQ timing fixes + +## Added + +- Add IMTQ assembly + +# [v1.35.0] 2023-03-04 + +eive-tmtc: v2.16.4 + +## Added + +- Improved the OBSW watchdog by adding a watch functionality. The watch functionality is optional + and has to be enabled specifically by the application being watched by the watchdog when + starting the watchdog. If the watch functionality is enabled and the OBSW has not pinged + the watchdog via the FIFO for 2 minutes, the watchdog will restart the OBSW service via systemd. + The primary OBSW will only activate the watch functionality if it is the OBSW inside the + `/usr/bin` directory. This allows debugging the system by leaving flashed or manually copied + debugging images 2 minutes to start the watchdog without the watch functionality. + +## Fixed + +- Bumped FSFW: `Countdown` and `Stopwatch` use new monotonic clock API now. +- IMTQ: Various fixes, most notably missing buffer time after starting MGM measurement + and corrections for actuator commanding. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/430 + +# [v1.34.0] 2023-03-03 + +eive-tmtc: v2.16.3 + +This might include the fix for the race condition where CPU usage jumped to 200 %. The race +condition was traced to the `Countdown` class, more specifically to the `getUptime` function where +the `/proc/uptime` file is read. + +## Changed + +- The SD card prefix is now set earlier inside the `CoreController` constructor +- The watchdog handling was moved outside the `CoreController` into the main loop. +- Moved polling of all SPI parts to the same PST. +- Allow quicker transition for the EIVE system component by allowing consecutive TCS and ACS + component commanding again. +- Changed a lot of lock guards to use timeouts +- Queue sizes of TCP/UDP servers increased from 20 to 50 +- Significantly simplified and improved lock guard handling in TCS and ACS board polling + tasks. + +## Fixed + +- IMTQ: Sets were filled with wrong data, e.g. Raw MTM was filled with calibrated MTM measurements. +- Set RM3100 dataset to valid. +- Fixed units in calculation of ACS control laws safe and detumble. +- Bump FSFW for change in Countdown: Use system clock instead of reading uptime from file + to prevent possible race condition. +- GPS: No fix considered a fault now after 30 minutes instead of 5 hours. +- SUS Assembly FDIR: Prevent permanent SAFE mode fallbacks by introducing special health + handling. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/418/files + +## Added + +- Added Syrlinks Assembly object to allow recovery handling and to fix faulty FDIR behaviour. + +# [v1.33.0] 2023-03-01 + +eive-tmtc: v2.16.2 + +## Changed + +- Move ACS board polling to separate worker thread. +- Move SUS board polling to separate worker thread. + +## Fixed + +- Linux GPS handler now checks the individual `*_SET` flags when analysing the `gpsd` struct. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/400 + +# [v1.32.0] 2023-02-24 + +eive-tmtc: v2.16.1 + +## Fixed + +- ADIS1650X: Added missing MDL_RANG pool entry for configuration set +- Bumped FSFW for bugfix in health service: No execution complete for targeted health announce + command. +- Removed matrix determinant calculation as part of the `MEKF`, which would take about + 300ms of runtime +- Resetting the `MEKF` now also actually resets its stored state +- Bumped FSFW for bugfix in destination handler: Better error handling and able to process + destination folder path. + +## Changed + +- Added basic persistent TM store for PUS telemetry and basic interface to dump and delete + telemetry. Implementation is based on a timed rotating files, with the addition that files + might be generated more often if the maximum file size of 8192 bytes is exceeded. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/320/files +- Commented out commanding of actuators as part of the `AcsController` +- Collection sets of the `AcsController` now get updated before running the actual ACS + algorithm +- `GpsController` now always gets scheduled +- The `CoreController` now initializes the initial clock from the time file as early as possible + (in the constructor) if possible, which should usually be the case. + +## Added + +- Added basic persistent TM store for PUS telemetry and basic interface to dump and delete + telemetry. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/320/files +- `ExecutableComIfDummy` class to have a dummy for classes like the RTD polling class. +- Added `AcsController` action command to confirm solar array deployment, which then deletes + two files +- Added `AcsController` action command to reset `MEKF` +- `GpsCtrlDummy` now initializes the `gpsSet` +- `RwDummy` now initializes with a non faulty state + + +# [v1.31.1] 2023-02-23 + +## Fixed + +- ADIS1650X configuration set was empty because the local pool variables were not registered. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/402 +- ACS Controller: Correction for size of MEKF dataset and some optimization and fixes + for actuator control which lead to a crash. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/403 + +# [v1.31.0] 2023-02-23 + +eive-tmtc: v2.16.0 + +## Fixed + +- Usage of floats as iterators and using them to calculate a uint8_t index in `SusConverter` +- Removed unused variables in the `AcsController` +- Remove shadowing variables inside ACS assembly classes. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/385 + +## Changed + +COM PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/364 + +* Moved transmitter timer and handling of carrier and bitlock event from CCSDS handler to COM + subsystem +* Added parameter command to be able to change the transmitter timeout +* Solves [#362](https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/362) +* Solves [#360](https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/360) +* Solves [#361](https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/361) +* Solves [#386](https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/386) +- All `targetQuat` functions in `Guidance` now return the target quaternion (target + in ECI frame), which is passed on to `CtrlValData`. +- Moved polling sequence table definitions and source code to `mission/core` folder. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/395 + +## Added + +- `MEKF` now returns an unique returnvalue depending on why the function terminates. These + returnvalues are used in the `AcsController` to determine on how to procede with its + perform functions. In case the `MEKF` did terminate before estimating the quaternion + and rotational rate, an info event will be triggered. Another info event can only be + triggered after the `MEKF` has run successfully again. If the `AcsController` tries to + perform any pointing mode and the `MEKF` fails, the `performPointingCtrl` function will + set the RWs to the last RW speeds and set a zero dipole vector. If the `MEKF` does not + recover within 5 cycles (2 mins) the `AcsController` triggers another event, resulting in + the `AcsSubsystem` being commanded to `SAFE`. +- `MekfData` now includes `mekfStatus` +- `CtrlValData` now includes `tgtRotRate` + +# [v1.30.0] 2023-02-22 + +eive-tmtc: v2.14.0 + +Event IDs for PDEC handler have changed in a breaking manner. + +## Added and Fixed + +- PDEC: Added basic FDIR to limit the number of allowed TC interrupts and to allow complete task + lockups in the case an IRQ is immediately re-raised by the PDEC module. This is done by only + allowing a certain number of handled IRQs (whether they yield a valid TC or not) during + time windows of one second. Right now, 800 IRQs/TCs are allowed per time window. + This time window is reset if a TC reception timeout after 500ms occurs. TBD whether the maximum + allowed number will be a configurable parameter. If the number of occured IRQs is exceeded, + an event is triggered and the task is delayed for 400 ms. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/393 + +# [v1.29.1] 2023-02-21 + +## Fixed + +- Limit number of handled messages for core TM handlers: + - https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/391 + - https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/390 + - https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/389 +- HeaterHandler better handling for faulty message reception + Issue: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/issues/388 +- Disable stopwatch in MAX31865 polling task + +# [v1.29.0] 2023-02-21 + +eive-tmtc: v2.13.0 + +## Changed + +- Refactored IMTQ handlers to also perform low level I2C communication tasks in separate thread. + This avoids the various delays needed for I2C communication with that device inside the ACS PST. + (e.g. 1 ms delay between each transfer, or 10 ms integration delay for MGM measurements). + +## Added + +- Added new heater info set for the TCS controller. This set contains the heater switch states + and the current draw. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/351 +- The HeaterHandler now exposes a mode which reflects whether the heater power + is on or off. It also triggers mode events for its heater children objects + which show whether the specific heaters are on or off. The heater handler + will be part of the TCS tree. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/351 + +# [v1.28.1] 2023-02-21 + +## Fixed + +- Patch version which compiles for EM +- CFDP Funnel bugfix: CCSDS wrapping was buggy and works properly now. +- PDEC: Some adaptions to prevent task lockups on invalid FAR states. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/393 +- CMakeLists.txt fix which broke CI/CD builds when server could not retrieve full git SHA. +- Possible regression in the MAX31865 polling task: Using a `ManualCsLockGuard` for reconfiguring + and then polling the sensor is problematic, invalid sensor values will be read. + CS probably needs to be de-asserted or some other HW/SPI specific issue. Letting the SPI ComIF + do the locking does the job. + +## Changed + +- Add `-Wshadow=local` shadowing warnings and fixed all of them +- Updated generated CSV files: Support for skip directive and explicit + "No description" info string +- The polling threads for actuator polling now have a slightly higher priority than the ACS PST + to ensure timing requirements are met. + +## Added + +- git post checkout hook which initializes and updates the submodules + automatically. + +# [v1.28.0] 2023-02-17 + +eive-tmtc: v2.12.7 + +## Added + +- In case the ACS Controller does recognize more than one RW to be invalid and therefore not + available, it does not perform pointing control but aborts shortly after `sensorProcessing`. If the + problem persits for 5 ACS cycles, the `MULTIPLE_RW_INVALID` event is triggered, which invokes the + transition of the `AcsSubsystem` to safe mode. + +## Changed + +- Igrf13 model vector now outputs as uT instead of nT +- Changed timings for `AcsPst`, more time for sun sensors. +- Added values for MGM sensor fusion +- Refactored RW Software: Polling runs in separate thread, all RWs are now polled in under 60 ms. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/381 +- Bumped FSFW to allow initializing child modes in `SubsystemBase` derived objects. + +## Fixed + +- Fixed values for GYR sensor fusion +- Fixed speed types for `rwHandlingParameter` +- Pseudo inverse used for allocating torque to RWs and RW antistiction now actually consider the + state of the RWs + +# [v1.27.2] 2023-02-14 + +Reaction Wheel handling was determined to be (quasi) broken and needs to be fixed in future release +to be usable by ACS controller. + +eive-tmtc: v2.12.6 + +## Added + +- Function for the ACS controller to command MTQ and RWs called by all subroutines +- RwHandler now handles commanding of RW speeds via RwSpeedActuationSet +- Tracing supports which allows checking whether threads are running as usual. + +## Changed + +- Remove 2 TCS threads. +- Move low level polling into ACS PST, move high level device handlers into TCS system task. +- ActCmds now returns command vectors as integers as required by the actuators + and scales them to the appropriate range +- All RwHandler are now polled five times per ACS cycle +- Remove 2 TCS threads. Move low level polling into ACS PST, move high level device handlers into + TCS system task. +- Further reduce number of threads: + 1. Remove PUS low priority task, move assigned threads to the generic system task + 2. Group events and verification tasks into PUS high priority task + 3. Group all other components into PUS medium priority task + 4. Add SCEX device handler to PL task, remove dedicated thread + +## Removed + +- lwgps dependency not compiled anymore, is not used + +# [v1.27.1] 2023-02-13 + +## Fixed + +- Fix for SPI ComIF: Set transfer size to 0 for failed transfers +- Fix shadowing issue with locks in MAX31865 low level handler + +# [v1.27.0] 2023-02-13 + +eive-tmtc: v2.12.5 + +Added EIVE system top mode component. Currently, only SAFE and IDLE mode are +implemented, and the system does not do more than commanding TCS and ACS +into the correct modes. It does not have a lot of mode tracking capabilities +yet because the ACS controller might alternate between SAFE and DETUMBLE. +It takes around 5-10 seconds for the EIVE system to reach the SAFE mode. + +The new system is used at software boot to command the satellite into safe mode +on each reboot. This behaviour can be disabled with the +`OBSW_SWITCH_TO_NORMAL_MODE_AFTER_STARTUP` flag. + +## Added + +- New EIVE system component like explained above. + +## Changed + +- The satellite now commands itself into SAFE mode on each reboot, which + triggers a lot of events on each SW reboot. The TCS subsystem will commanded + to NORMAL mode immediately while the ACS subsystem will be commanded to + SAFE mode. The payload subsystem will be commanded OFF. +- `RELEASE_BUILD` flag moved to `commonConfig.h` +- The ACS subsystem transitions are now staggered: The SUS board assembly + is commanded as a separate transition. This reduces the risk of long bus lockups. +- No INFO mode event translations for release builds to reduce number of + printouts. +- More granular locking inside the MAX31865 low level read handler. + +## Fixed + +- More DHB thermal module fixes. +- ACS PST frequency extended to 0.8 seconds in debug builds to avoid SPI + bus lockups. +- Local datapool fixes for the `PlocSupervisorHandler` + +# [v1.26.4] 2023-02-10 + +eive-tmtc: v2.12.3 + +## Fixed + +- `SdCardManager.cpp` `isSdCardUsable`: Use `ext4` instead of `vfat` to check read-only state. + +# [v1.26.3] 2023-02-09 + +eive-tmtc: v2.12.2 + +## Added + +- First version of a TCS controller heater control loop, but + the loop is disabled for now. + +## Changed + +- Reworked dummy handling for the TCS controller. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/325 +- Generator scripts now generate files for hosted and for Q7S build. + +## Fixed + +- GPS Controller: Set fix value to 0 when switching off to allow + `GPS_FIX_CHANGE` to work when switching the GPS back on. + +# [v1.26.2] 2023-02-08 + +## Changed + +- ACS Controller scheduling is now configurable via the `eive/definitions.h` file. Also ensured + that scheduling is done in big blocks to reduce risk of missed deadlines. +- Replaced chained locks for polling new sensor data to the `AcsController`. +- Made TM store even larger. + +## Fixed + +- Bugfix for PDEC handler which causes the PIR register of the PDEC to never + be cleared on release builds. The dummy variable used to read the register + needs to be declared volatile to avoid compiler optimizations. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/374 +- Bumped FSFW for fix of possible memory leaks in TCP/IP TMTC bridge + inside the FSFW. + +## Added + +- Create TCS controller for EM build. + +# [v1.26.1] 2023-02-08 + +- Initialize parameter helper in ACS controller. + +# [v1.26.0] 2023-02-08 + +eive-tmtc v2.12.1 + +## Changed + +### ACS + +- Readded calibration matrices for MGM calibration. +- Added calculation of satellite velocity vector from GPS position data +- Added detumble mode using GYR values +- Added inertial pointing mode +- Added nadir pointing mode +- Added ground station target mode +- Added antistiction for RWs +- Added `sunTargetSafe` differentiation for LEOP +- Added check for existance of `SD_0_SKEWED_PTG_FILE` and `SD_1_SKEWED_PTG_FILE` to determine + which `sunTargetSafe` to use +- Added `gpsVelocity` and `gpsPosition` to `gpsProcessed` +- Removed deprecated `OutputValues` +- Added `HasParametersIF` to `AcsParameters` +- Added `ReceivesParameterMessagesIF` and `ParameterHelper` to `AcsController` +- Updated `AcsParameters` with actual values and changed structure +- Sun vector model and magnetic field vector model calculations are always executed now +- `domainId` is now used as identifier for parameter structs +- Changed onboard GYR value handling from deg/s to rad/s + +## Fixed + +- Single sourcing the version information into `CMakeLists.txt`. The `git describe` functionality + is only used to retrieve the git SHA hash now. Also removed `OBSWVersion.h` accordingly. +- Build system: Fixed small bug, where the version itself was + stored as the git SHA hash in `commonConfig.h`. This will be + an empty string now for regular versions. +- Bump FSFW for important fix in PUS mode service. + +### ACS + +- Bugfixes in 'SensorProcessing' where previously MGM values would be calibrated before being + transformed in body RF. However, the calibration values are in the body RF. Also fixed the + validity flag of 'mgmVecTotDerivative'. +- Fixed calculation of model sun vector +- Fixed calculation of model magnetic field vector +- Fixed MEKF algorithm +- Fixed several variable initializations +- Fixed several variable types +- Fixed use of `sunMagAngleMin` for safe mode +- Fixed MEKF not using correct `sampleTime` +- Fixed assignment of `SUS0` and `SUS6` calibration matrices due to wiring being mixed up +- Various smaller bugfixes + +# [v1.25.0] 2023-02-06 + +eive-tmtc version: v2.12.0 + +## Changed + +- Updated Subsystem mode IDs to avoid clashes with regular device handler modes. + +## Fixed + +- `GpsHyperionLinuxController`: Fix `gpsd` polling by continuously calling `gps_read` in one cycle + until it does not have any data left anymore. Also, the data is now polled in a permanent loop, + where controller handling is done on 0.2 second timeouts. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/368 + +# [v1.24.0] 2023-02-03 + +- eive-tmtc v2.10.0 +- `AcsSubsystem`: OFF, SAFE and DETUMBLE mode were tested. Auto-transitions SAFE <-> DETUMBLE tested + as well. Other modes still need to be tested. + +## Fixed + +- `AcsController`: Parameter fix in `DetumbleParameter`. +- Set GPS set entries to invalid on MODE_OFF command. +- Bump FSFW for bugfix in `setNormalDatapoolEntriesInvalid` where the validity was not set to false + properly +- Fixed usage of uint instead of int for commanding MTQ. Also fixed the range in which the ACS Ctrl + commands the MTQ to match the actual commanding range. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/363 +- Regression: Revert swap of SUS0 and SUS6. Those devices are on separate power lines. In a + future fix, the calibration matrices of SUS0 and SUS6 will be swapped. + +## Changed + +- Update ACS scheduling to represent the actual ACS design. There is one ACS PST now for all + timing sensitive ACS operations. In the debug builds, the new ACS polling sequence table + will have a period of 0.6 seconds, but will remain 0.4 seconds for the release build. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/365 +- `ACS::SensorValues` is now an ACS controller member to reduce the risk of stack overflow. +- ACS Subsystem Sequence Mode IDs updated. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/365 + TMTC PR: https://egit.irs.uni-stuttgart.de/eive/eive-tmtc/pulls/130 +- Update and tweak ACS subsystem to represent the actual ACS design +- Event handling in the ACS subsystem for events triggered by the ACS controller. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/365 + +# [v1.23.1] 2023-02-02 + +TMTC rev: 15adb9bf2ec68304a4f87b8dd418c1a8353283a3 + +## Fixed + +- Bugfix in FSFW where the sequence flags of the PUS packets were set to continuation segment (0b00) + instead of unsegmented (0b11). +- Bugfix in FSFW where the MGM RM3100 value Z axis data was parse incorrectly. + PR: https://egit.irs.uni-stuttgart.de/eive/fsfw/pulls/123 + +# [v1.23.0] 2023-02-01 + +TMTC version: v2.9.0 + +## Changed + +- Bumped FSFW to include improvements and bugfix for Health Service. The health service now + supports the announce all health info command. + PR: https://egit.irs.uni-stuttgart.de/fsfw/fsfw/pulls/725 + +## Fixed + +- Bumped FSFW to include fixes in the time service. + PR: https://egit.irs.uni-stuttgart.de/fsfw/fsfw/pulls/726 +- The CCSDS handler starts the transmitter timer each time it is commanded to MODE_ON and times + out the timer when the handler is commanded to MODE_OFF +- If the timer is timed out the CCSDS handler will disable the TX clock which will cause the + syrlinks to got to standby mode +- PDEC handler now parses the FAR register also in interrupt mode + + +# [v1.22.1] 2023-01-30 + +## Changed + +- Updated FSFW to include addition where the `SO_REUSEADDR` option is set + on the TCP server, which should improve its ergonomics. + +# [v1.22.0] 2023-01-28 + +TMTC version: v2.6.1 + +## Added + +- First COM subsystem implementation. It mirrors the Syrlinks mode/submodes but also takes + care of commanding the CCSDS handler. It expects the Syrlinks submodes as mode commands. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/358 +- The CCSDS handler has has a new submode (3) to configure the default datarate. +- Default datarate parameter commanding moved to COM subsystem. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/358 + +# [v1.21.0] 2023-01-26 + +TMTC version: v2.5.0 +Syrlinks PR: PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/353 + +## Fixed + +- The `OBSW_SYRLINKS_SIMULATED` flag is set to 0 for for both EM and FM. +- MGM4 handling in ACS sensor processing: Bugfix in `mulScalar` operation + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/354 +- Subsystem ID clash: CORE subsystem ID was the same as Syrlinks subsystem ID. + +## Changed + +- Startracker temperature set and PCDU switcher set are diagnostic now +- `SyrlinksHkHandler` renamed to `SyrlinksHandler` to better reflect that it does more than + just HK and is also responsible for setting the TX mode of the device. +- `SyrlinksHandler`: Go to startup immediately because the Syrlinks device should always be on + by default. +- `SyrlinksHandler`: Go to normal mode at startup. + +## Added + +- The Syrlinks handler has submodes for the TX mode now: RX Only (0), RX and TX default + datarate (1), RX and TX Low Rate (2), RX and TX High Rate (3) and TX Carrier Wave (4). + The submodes apply for both ON and NORMAL mode. The default datarate can be updated using + a parameter command (domain ID 0 and unique ID 0) with value 0 for low rate and 1 for high rate. +- The Syrlinks handler always sets TX to standby when switching off +- The Syrlinks handler triggers a new TX_ON event when the transmitter was switched on successfully + and a TX_OFF event when it was switched off successfully. +- Startracker temperature set and PCDU switcher set are diagnostic now +- The CCSDS handler can accept mode commands now. It accepts ON and OFF commands. Furthermore + it has a submode for low datarate (1) and high datarate (2) for the ON command. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/352 + +# [v1.20.0] 2023-01-24 + +## Added + +- The Q7S SW now checks for a file named `boot_delay_secs.txt` in the home directory. + If it exists and the file is empty, it will delay for 6 seconds before continuing + with the regular boot. It can also try to read delay seconds from the file. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/340. +- Basic TCS Subsystem component. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/319 +- Expose base set of STR periodic housekeeping packets + +## Changed + +- Moved some PDEC/PTME configuration to `common/config/eive/definitions.h` + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/319 +- The ACS Controller Gyro Sets (raw and processed) and the MEKF dataset are diagnostics now. +- Bumped FSFW for Service 11 improvement which includes size and CRC check for contained TC +- Syrlinks module now always included for both EM and FM +- SA Deployment: Allow specifying the switch interval and the initial channel. This allows testing + the new deployment procedure where each channel is burned for half of the whole burn duration. + It also allows burning only one channel for the whole burn duration. The autonomous mechanism + was adapted to burn each channel for half of the burn time by default. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/347 + TMTC PR: https://egit.irs.uni-stuttgart.de/eive/eive-tmtc/pulls/127 +- `Max31865RtdLowlevelHandler.cpp`: For each RTD device, the config is now re-written before + every read. This seems to fix some issue with invalid temperature sensor readings. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/345 + +## Fixed + +- `GyroADIS1650XHandler`: Updated handler to determine correct dynamic range from `RANG_MDL` + register readout. This is because ADIS16505-3BMLZ devices are used on the ACS board and the + previous range setting was wrong. Also fixed a small error properly set internal state + on shut-down. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/342 +- Syrlinks Handler: Read RX frequency shift as 24 bit signed number now. Also include + validity handling for datasets. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/350 +- `GyroADIS1650XHandler`: Changed calculation of angular rate to be sensitivity based instead of + max. range based, as previous fix still left an margin of error between ADIS16505 sensors + and L3GD20 sensors. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/346 + +# [v1.19.0] 2023-01-10 + +## Changed + +- 5V stack is now off by default + +## Fixed + +- PLOC SUPV: Minor adaptions and important bugfix for UART manager +- Allow cloning and building the hosted OBSW version without proprietary libraries, + which also avoids the need to have a Gitea account. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/337 + +## Added + +- First version of ACS controller + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/329 +- Allow commanding the 5V stack internally in software + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/334 +- Add automatic 5V stack commanding for all connected devices + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/335 + +# [v1.18.0] 2022-12-01 + +## Changed + +- PLOC Supervisor: Changes baudrate to 921600 +- Renamed `/dev/ul-plsv` to `/dev/ploc_supv`, is not a UART lite anymore +- Renamed `/dev/i2c_eive` to `/dev/i2c_pl` and `/dev/i2c-2` to `/dev/i2c_ps`. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/328 + +# [v1.17.0] 2022-11-28 + +## Added + +- PLOC Supervisor Update: Update SW to use newest PLOC SUPV version by TAS + PR 1: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/316 + PR 2: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/324 + PR 3: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/326 + +# [v1.16.0] 2022-11-18 + +- It is now possible to compile Linux components for the hosted build conditionally + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/322 +- ACS Subsystem. PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/231 +- Payload Subsystem. PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/231 +- Add IRQ mode for PDEC handler. PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/310 +- Extended TM funnels to allow multiple TM recipients. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/312 +- DHB: Transitions to normal mode now possible directly, which simplifies subsystem implementations + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/313 +- MAX3185 Low Level Handler and Device Handler: Simplifications and bugfixes to allow switching + off without triggering unrequested replies + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/313 +- Add remaining missing TMP1075 device handlers. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/318 + +# [v1.15.0] 2022-10-27 + +- Consistent device file naming +- Remove rad sensor from EM build, lead to weird bugs on EM which + prevented `xsc_boot_copy` from working properly +- CFDP closure handling is now working + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/307 +- Safety mechanism for SD card handling on graceful reboots + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/308 +- Solar Array Deployment handler update + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/305 +- IMTQ updates as preparation for ACS controller expansion + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/306 +- P60 Module: Reduce number of set IDs, use same set IDs for core, auxiliary + and config HK set across the three PCDU modules + +# [v1.14.1] 11.10.2022 + +- Various bugfixes and regression fixes +- General file handling at program initialization now works properly again +- Scratch buffer preferred SD card handling works again +- Use scoped locks in TCS controller to avoid deadlocks + +# [v1.14.0] 10.10.2022 + +- Provide full SW update capability for the OBSW. + This includes very basic CFDP integration, a software update + procedure specified in detail in the README and some high level + commands to make this easier for operators. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/302 +- Update for FSFW: `HasReturnvaluesIF` class replaced by namespace `returnvalue` +- Add some GomSpace clients as a submodule dependency. Use this dependency to deserialize the + GomSpace TM tables +- Add API to retrieve GomSpace device parameter tables + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/287 +- Add API to save and load GomSpace config tables + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/293 +- Increase number of allowed consescutive action commands from 3 to 16 + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/294 +- Fix for EM SW: Always create ACS Task +- Added Scex device handler and Scex uart reader + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/303 +- ACS Subsystem. PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/228 + +# [v1.13.0] 24.08.2022 + +- Added first version of ACS Controller with gathers MGM data in a set +- Some tweaks for IMTQ handler + +# [v1.12.1] 05.07.2022 + +- Disable periodic TCS controller HK generation by default + +# [v1.12.0] 04.07.2022 + +## Added + +- Dummy components to run OBSW without relying on external hardware + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/266 +- Basic Thermal Controller + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/266 +- PUS11 TC scheduler + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/259 +- Regular reboot command + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/242 +- Commands for individual RTD devices + PR: https://egit.irs.uni-stuttgart.de/eive/eive-tmtc/pulls/84 +- `RwAssembly` added to system components. Assembly works in principle, + issues making 4 consecutives RWs communicate at once.. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/224 +- Adds a yocto helper script which is able to install the release build binaries + (OBSW and Watchdog) into the `q7s-yocto` repository as long as the `q7s-package` + or `q7s-yocto` repo was cloned in the same directory the EIVE OBSW repo. + This makes updating the root filesystem a lot easier. It also creates and installs a + version file. + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/248 +- Create the generic image by default for the Q7S build. The unique binary with the + username appended at the end is created as a side-product now + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/248 + +## Fixed + +- `q7s-cp.py` bugfix + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/256 +- Generator scripts output now produce platform-independent artifacts + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/267 + +### Heater + +- Adds `HealthIF` to heaters. Heaters are own system object with queues now which allows to set them faulty. +- SW will attempt to shut down heaters which are on but marked faulty +- Some simplifications for `HeaterHandler`, use `std::vector` instead of `std::unordered_map` for primary container. Using the heater indexes 0 to 7 allows to use natural array indexing +- Some additional input sanity checks in `executeAction` + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/236 + +## Changed + +- CCSDS handler improvements + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/268 +- Build unittest as default side product of hosted builds + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/244 +- Let CI/CD build host build and run unittest side product in same step +- Catch2 pre-installed in CI/CD docker container, Xiphos SDK installed in CI/CD docker + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/247 +- Sun Sensors have names denoting their location and poiting in the satellite now + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/245 +- Better RTD names denoting their purpose (and location consequently) + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/246 + +# [v1.11.0] + +## Fixed + +- Host build working again + +## Added + +- Custom Syrlinks FDIR which disabled most of the default FDIR functionality + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/232 +- Custom Gomspace FDIR which disabled most of the default FDIR functionality +- Custom Syrlinks FDIR which disabled most of the default FDIR functionality + +## Changed + +- PCDU handler only called once in PST, but can handle multiple messages now + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/221 + Bugfix: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/235 +- Update rootfs base of Linux, all related OBSW changes +- Add `/usr/local/bin` to PATH. All shell scripts are there now +- Add Syrlinks and TMP devices to Software by default +- Update GPS Linux Hyperion Handler to use socket interface. Still allows switching + back to SHM interface, but the SHM interface is a possible cause of SW crashes +- Updated code for changed FSFW HAL GPIO API: `readGpio` prototype has changed + PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/240 and + https://egit.irs.uni-stuttgart.de/eive/fsfw/pulls/76 + +### GPS + +PRs: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/239 + +- Rename GPS device to `/dev/gps0` +- Use gpsd version 3.17 now. Includes API changes + +### EM and FM splitup & Build Workflow improvements + +PR: https://egit.irs.uni-stuttgart.de/eive/eive-obsw/pulls/238 + +- Split up `bsp_q7s` in separate EM and FM build with module loading set to different + default values. The EM object factory is unique which allows building a parallel setup + with dummy components +- All major BSPs have an own `OBSWConfig.h.in` file which simplifies the file significantly +- Renamed Q7S primary build folders: + - `cmake-build-debug-q7s` for primary development build + - `cmake-build-release-q7s` for primary release build + - `cmake-build-debug-q7s-em` for primary development build of the EM software + - `cmake-build-release-q7s-em` for primary release build of the EM software +- Refactored Q7S helper script handling. It is now intended and preferred to copy the environment + script to the same folder level as the `eive-obsw` and source it. This will also + add the path containing the shell helper scripts to `PATH` +- The actual helper shell scripts were renamed as well to `q7s--.sh` + +# [v1.10.1] + +Version bump + +# [v1.10.0] + +For all releases equal or prior to v1.10.0, +see [milestones](https://egit.irs.uni-stuttgart.de/eive/eive-obsw/milestones) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2914b88 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,581 @@ +# ############################################################################## +# CMake support for the EIVE OBSW +# +# Author: R. Mueller +# ############################################################################## + +# ############################################################################## +# Pre-Project preparation +# ############################################################################## +cmake_minimum_required(VERSION 3.13) + +set(OBSW_VERSION_MAJOR 8) +set(OBSW_VERSION_MINOR 2) +set(OBSW_VERSION_REVISION 1) + +# set(CMAKE_VERBOSE TRUE) + +option( + EIVE_HARDCODED_TOOLCHAIN_FILE + "\ +For Linux Board Target BSPs, a default toolchain file will be set. Should be set to OFF \ +if a different toolchain file is set externally" + ON) + +if(NOT FSFW_OSAL) + set(FSFW_OSAL + linux + CACHE STRING "OS for the FSFW.") +endif() + +if(TGT_BSP) + if(TGT_BSP MATCHES "arm/q7s" + OR TGT_BSP MATCHES "arm/raspberrypi" + OR TGT_BSP MATCHES "arm/beagleboneblack") + option(LINUX_CROSS_COMPILE ON) + endif() + if(TGT_BSP MATCHES "arm/raspberrypi" OR TGT_BSP MATCHES "arm/beagleboneblack") + option(EIVE_BUILD_GPSD_GPS_HANDLER "Build GPSD dependent GPS Handler" OFF) + elseif(TGT_BSP MATCHES "arm/q7s") + option(EIVE_Q7S_EM "Build configuration for the EM" OFF) + option(EIVE_BUILD_GPSD_GPS_HANDLER "Build GPSD dependent GPS Handler" ON) + endif() + option(EIVE_CREATE_UNIQUE_OBSW_BIN "Append username to generated binary name" + ON) +else() + option(EIVE_CREATE_UNIQUE_OBSW_BIN "Append username to generated binary name" + OFF) +endif() + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +# Perform steps like loading toolchain files where applicable. +include(PreProjectConfig) +pre_project_config() + +# Project Name +project(eive-obsw) + +# Specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +include(EiveHelpers) + +option(EIVE_ADD_ETL_LIB "Add ETL library" ON) +option(EIVE_ADD_JSON_LIB "Add JSON library" ON) + +set(OBSW_MAX_SCHEDULED_TCS 4000) + +if(EIVE_Q7S_EM) + set(OBSW_Q7S_EM + 1 + CACHE STRING "Q7S EM configuration") + set(OBSW_Q7S_FM 0) + set(OBSW_STAR_TRACKER_GROUND_CONFIG 1) +else() + set(OBSW_Q7S_EM + 0 + CACHE STRING "Q7S EM configuration") + set(OBSW_Q7S_FM 1) + set(OBSW_STAR_TRACKER_GROUND_CONFIG 0) +endif() + +set(OBSW_ADD_TMTC_TCP_SERVER + ${OBSW_Q7S_EM} + CACHE STRING "Add TCP TMTC Server") +set(OBSW_ADD_TMTC_UDP_SERVER + 0 + CACHE STRING "Add UDP TMTC Server") +set(OBSW_ADD_MGT + ${OBSW_Q7S_FM} + CACHE STRING "Add MGT module") +set(OBSW_ADD_BPX_BATTERY_HANDLER + ${OBSW_Q7S_FM} + CACHE STRING "Add BPX battery module") +set(OBSW_ADD_STAR_TRACKER + 1 + CACHE STRING "Add Startracker module") +set(OBSW_ADD_SUN_SENSORS + ${OBSW_Q7S_FM} + CACHE STRING "Add sun sensor module") +set(OBSW_ADD_SUS_BOARD_ASS + ${OBSW_Q7S_FM} + CACHE STRING "Add sun sensor board assembly") +set(OBSW_ADD_THERMAL_TEMP_INSERTER + ${OBSW_Q7S_EM} + CACHE STRING "Add thermal sensor temperature inserter") +set(OBSW_ADD_ACS_BOARD + 1 + CACHE STRING "Add ACS board module") +set(OBSW_ADD_GPS_CTRL + ${OBSW_Q7S_FM} + CACHE STRING "Add GPS controllers") +set(OBSW_ADD_CCSDS_IP_CORES + 1 + CACHE STRING "Add CCSDS IP cores") +set(OBSW_TM_TO_PTME + 1 + CACHE STRING "Send telemetry to PTME IP core") +set(OBSW_TC_FROM_PDEC + 1 + CACHE STRING "Poll telecommand from PDEC IP core") +set(OBSW_ADD_TCS_CTRL + 1 + CACHE STRING "Add TCS controllers") +set(OBSW_ADD_HEATERS + 1 + CACHE STRING "Add TCS heaters") +set(OBSW_ADD_PLOC_SUPERVISOR + 1 + CACHE STRING "Add PLOC supervisor handler") +set(OBSW_ADD_SA_DEPL + ${OBSW_Q7S_FM} + CACHE STRING "Add SA deployment handler") +set(OBSW_ADD_PLOC_MPSOC + 1 + CACHE STRING "Add MPSoC handler") +set(OBSW_ADD_ACS_CTRL + ${OBSW_Q7S_FM} + CACHE STRING "Add ACS controller") +set(OBSW_ADD_RTD_DEVICES + ${OBSW_Q7S_FM} + CACHE STRING "Add RTD devices") +set(OBSW_ADD_RAD_SENSORS + ${OBSW_Q7S_FM} + CACHE STRING "Add Rad Sensor module") +set(OBSW_ADD_PL_PCDU + 1 + CACHE STRING "Add Payload PCDU modukle") +set(OBSW_ADD_SYRLINKS + 1 + CACHE STRING "Add Syrlinks module") +set(OBSW_ADD_TMP_DEVICES + 1 + CACHE STRING "Add TMP devices") +set(OBSW_ADD_GOMSPACE_PCDU + 1 + CACHE STRING "Add GomSpace PCDU modules") +set(OBSW_ADD_GOMSPACE_ACU + ${OBSW_Q7S_FM} + CACHE STRING "Add GomSpace ACU submodule") +set(OBSW_ADD_RW + ${OBSW_Q7S_FM} + CACHE STRING "Add RW modules") +set(OBSW_ADD_SCEX_DEVICE + 1 + CACHE STRING "Add Solar Cell Experiment module") +set(OBSW_SYRLINKS_SIMULATED + 0 + CACHE STRING "Syrlinks is simulated") + +# ############################################################################## +# Pre-Sources preparation +# ############################################################################## + +# Version handling +set(GIT_VER_HANDLING_OK FALSE) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git) + determine_version_with_git("--exclude" "docker_*") + set(GIT_INFO + ${GIT_INFO} + CACHE STRING "Version information retrieved with git describe") + if(GIT_INFO) + set(GIT_INFO + ${GIT_INFO} + CACHE STRING "Version information retrieved with git describe") + # CMakeLists.txt is now single source of information. list(GET GIT_INFO 1 + # OBSW_VERSION_MAJOR) list(GET GIT_INFO 2 OBSW_VERSION_MINOR) list(GET + # GIT_INFO 3 OBSW_VERSION_REVISION) + list(LENGTH GIT_INFO LIST_LEN) + if(LIST_LEN GREATER 4) + list(GET GIT_INFO 4 OBSW_VERSION_CST_GIT_SHA1) + endif() + set(GIT_VER_HANDLING_OK TRUE) + else() + set(GIT_VER_HANDLING_OK FALSE) + endif() +endif() + +# Set names and variables +set(OBSW_NAME ${CMAKE_PROJECT_NAME}) +set(WATCHDOG_NAME eive-watchdog) +set(SIMPLE_OBSW_NAME eive-simple) +set(UNITTEST_NAME eive-unittest) +set(LIB_FSFW_NAME fsfw) +set(LIB_EIVE_MISSION eive-mission) +set(LIB_ETL_TARGET etl::etl) +set(LIB_CSP_NAME libcsp) +set(LIB_LWGPS_NAME lwgps) +set(LIB_ARCSEC wire) +set(LIB_GOMSPACE_CLIENTS gs_clients) +set(LIB_GOMSPACE_CSP gs_csp) + +set(THIRD_PARTY_FOLDER thirdparty) +set(LIB_CXX_FS -lstdc++fs) +set(LIB_CATCH2 Catch2) +set(LIB_GPS gps) +set(LIB_JSON_NAME nlohmann_json::nlohmann_json) +set(LIB_DUMMIES dummies) + +# Set path names +set(FSFW_PATH fsfw) +set(TEST_PATH test) +set(UNITTEST_PATH unittest) +set(LINUX_PATH linux) +set(LIB_GOMSPACE_PATH ${THIRD_PARTY_FOLDER}/gomspace-sw) +set(COMMON_PATH common) +set(DUMMY_PATH dummies) +set(WATCHDOG_PATH watchdog) +set(COMMON_CONFIG_PATH ${COMMON_PATH}/config) +set(UNITTEST_CFG_PATH ${UNITTEST_PATH}/testcfg) + +set(LIB_EIVE_MISSION_PATH mission) +set(LIB_ETL_PATH ${THIRD_PARTY_FOLDER}/etl) +set(LIB_CATCH2_PATH ${THIRD_PARTY_FOLDER}/Catch2) +set(LIB_LWGPS_PATH ${THIRD_PARTY_FOLDER}/lwgps) +set(LIB_ARCSEC_PATH ${THIRD_PARTY_FOLDER}/sagittactl) +set(LIB_JSON_PATH ${THIRD_PARTY_FOLDER}/json) + +set(FSFW_WARNING_SHADOW_LOCAL_GCC OFF) +set(EIVE_ADD_LINUX_FILES OFF) +set(FSFW_ADD_TMSTORAGE ON) + +set(FSFW_ADD_COORDINATES ON) +set(FSFW_ADD_SGP4_PROPAGATOR ON) + +# Analyse different OS and architecture/target options, determine BSP_PATH, +# display information about compiler etc. +pre_source_hw_os_config() + +if(TGT_BSP) + set(LIBGPS_VERSION_MAJOR 3) + # I assume a newer version than 3.17 will be installed on other Linux board + # than the Q7S + set(LIBGPS_VERSION_MINOR 20) + if(TGT_BSP MATCHES "arm/q7s" + OR TGT_BSP MATCHES "arm/raspberrypi" + OR TGT_BSP MATCHES "arm/beagleboneblack" + OR TGT_BSP MATCHES "arm/egse" + OR TGT_BSP MATCHES "arm/te0720-1cfa") + find_library(${LIB_GPS} gps) + set(FSFW_CONFIG_PATH "linux/fsfwconfig") + if(NOT BUILD_Q7S_SIMPLE_MODE) + set(EIVE_ADD_LINUX_FILES TRUE) + set(EIVE_ADD_LINUX_FSFWCONFIG TRUE) + set(ADD_GOMSPACE_CSP TRUE) + set(ADD_GOMSPACE_CLIENTS TRUE) + set(FSFW_HAL_ADD_LINUX ON) + set(FSFW_HAL_LINUX_ADD_LIBGPIOD ON) + set(FSFW_HAL_LINUX_ADD_PERIPHERAL_DRIVERS ON) + endif() + elseif(UNIX) + set(EIVE_ADD_LINUX_FILES ON) + endif() + + if(TGT_BSP MATCHES "arm/raspberrypi") + # Used by configure file + set(RASPBERRY_PI ON) + set(FSFW_HAL_ADD_RASPBERRY_PI ON) + endif() + + if(TGT_BSP MATCHES "arm/egse") + # Used by configure file + set(EGSE ON) + set(FSFW_HAL_LINUX_ADD_LIBGPIOD OFF) + set(OBSW_ADD_STAR_TRACKER 1) + set(OBSW_DEBUG_STARTRACKER 1) + endif() + + if(TGT_BSP MATCHES "arm/beagleboneblack") + # Used by configure file + set(BEAGLEBONEBLACK ON) + endif() + + if(TGT_BSP MATCHES "arm/q7s") + # Used by configure file + set(XIPHOS_Q7S ON) + set(LIBGPS_VERSION_MAJOR 3) + set(LIBGPS_VERSION_MINOR 17) + endif() + + if(TGT_BSP MATCHES "arm/te0720-1cfa") + set(TE0720_1CFA ON) + endif() +else() + # Required by FSFW library + set(FSFW_CONFIG_PATH "${BSP_PATH}/fsfwconfig") + if(UNIX) + set(EIVE_ADD_LINUX_FILES ON) + endif() +endif() + +include(BuildType) +set_build_type() + +set(FSFW_DEBUG_INFO 0) +set(OBSW_ENABLE_PERIODIC_HK 1) +set(Q7S_CHECK_FOR_ALREADY_RUNNING_IMG 0) +if(RELEASE_BUILD MATCHES 0) + set(FSFW_DEBUG_INFO 1) + set(OBSW_ENABLE_PERIODIC_HK 0) + set(Q7S_CHECK_FOR_ALREADY_RUNNING_IMG 1) +endif() + +# Configuration files +configure_file(${COMMON_CONFIG_PATH}/commonConfig.h.in commonConfig.h) +configure_file(${FSFW_CONFIG_PATH}/FSFWConfig.h.in FSFWConfig.h) +configure_file(${BSP_PATH}/OBSWConfig.h.in OBSWConfig.h) +if(TGT_BSP MATCHES "arm/q7s") + configure_file(${BSP_PATH}/boardconfig/q7sConfig.h.in q7sConfig.h) +elseif(TGT_BSP MATCHES "arm/raspberrypi" OR TGT_BSP MATCHES "arm/egse") + configure_file(${BSP_PATH}/boardconfig/rpiConfig.h.in rpiConfig.h) +endif() + +configure_file(${WATCHDOG_PATH}/watchdogConf.h.in watchdogConf.h) + +# Set common config path for FSFW +set(FSFW_ADDITIONAL_INC_PATHS "${COMMON_PATH}/config" + ${CMAKE_CURRENT_BINARY_DIR}) + +# ############################################################################## +# Executable and Sources +# ############################################################################## + +# global compiler options need to be set before adding executables +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # Remove unused sections. + add_compile_options("-ffunction-sections" "-fdata-sections") + + # Removed unused sections. + add_link_options("-Wl,--gc-sections") + +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(COMPILER_FLAGS "/permissive-") +endif() + +add_library(${LIB_EIVE_MISSION}) +add_library(${LIB_DUMMIES}) + +# Add main executable +add_executable(${OBSW_NAME}) +set(OBSW_BIN_NAME ${CMAKE_PROJECT_NAME}) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(WARNING_FLAGS + "-Wall" + "-Wextra" + "-Wimplicit-fallthrough=1" + "-Wno-unused-parameter" + "-Wno-psabi" + "-Wshadow=local" + "-Wduplicated-cond" # check for duplicate conditions + "-Wduplicated-branches" # check for duplicate branches + "-Wlogical-op" # Search for bitwise operations instead of logical + "-Wnull-dereference" # Search for NULL dereference + "-Wundef" # Warn if undefind marcos are used + "-Wformat=2" # Format string problem detection + "-Wformat-overflow=2" # Formatting issues in printf + "-Wformat-truncation=2" # Formatting issues in printf + "-Wformat-security" # Search for dangerous printf operations + "-Wstrict-overflow=3" # Warn if integer overflows might happen + "-Warray-bounds=2" # Some array bounds violations will be found + "-Wshift-overflow=2" # Search for bit left shift overflows ( + + EIVE On-Board Software +====== + +# Index + +1. [General](#general) +2. [Prerequisites](#prereq) +3. [Building the Software](#build) +4. [Useful and Common Host Commands](#host-commands) +5. [Setting up Prerequisites](#set-up-prereq) +6. [Remote Debugging](#remote-debugging) +6. [Remote Reset](#remote-reset) +8. [TMTC testing](#tmtc-testing) +9. [Direct Debugging](#direct-debugging) +10. [Transfering Files to the Q7S](#file-transfer) +11. [Q7S OBC](#q7s) +12. [Static Code Analysis](#static-code-analysis) +13. [Eclipse](#eclipse) +14. [CLion](#clion) +14. [Running the OBSW on a Raspberry Pi](#rpi) +15. [Running OBSW on EGSE](#egse) +16. [Manually preparing sysroots to compile gpsd](#gpsd) +17. [FSFW](#fsfw) +18. [Coding Style](#coding-style) + +# General information + +Target systems: + +* OBC with Linux OS + * Xiphos Q7S + * Based on Zynq-7020 SoC (xc7z020clg484-2) + * Dual-core ARM Cortex-A9 + * 766 MHz + * Artix-7 FPGA (85K pogrammable logic cells) + * Datasheet at https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Arbeitsdaten/08_Used%20Components/Q7S&fileid=340648 + * Also a lot of information about the Q7S can be found on + the [Xiphos Traq Platform](https://trac2.xiphos.ca/eive-q7). Press on index to find all + relevant pages. The most recent datasheet can be found + [here](https://trac2.xiphos.ca/manual/wiki/Q7RevB/UserManual). + * Linux OS built with Yocto 2.5. SDK and root filesystem can be rebuilt with + [yocto](https://egit.irs.uni-stuttgart.de/eive/q7s-yocto) + * [Linux Kernel](https://github.com/XiphosSystemsCorp/linux-xlnx.git) . EIVE version can be found + [here](https://github.com/spacefisch/linux-xlnx) . Pre-compiled files can be + found [here](https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Software/q7s-linux-components&fileid=777299). + * Q7S base project can be found [here](https://egit.irs.uni-stuttgart.de/eive/q7s-base) + * Minimal base project files and Xiphos SDK can be found + [here](https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Software/xiphos-q7s-sdk&fileid=510908) +* Host System + * Generic software components which are not dependant on hardware can also + be run on a host system. All host code is contained in the `bsp_hosted` folder + * Tested for Linux (Ubuntu 20.04) and Windows 10 +* Raspberry Pi + * EIVE OBC can be built for Raspberry Pi as well (either directly on Raspberry Pi or by installing a cross compiler) + +The steps in the primary README are related to the main OBC target Q7S. +The CMake build system can be used to generate build systems as well (see helper scripts in `cmake/scripts`: + +- Linux Raspberry Pi: See special section below. Uses the `bsp_linux_board` folder +- Linux Trenz TE7020_1CFA: Uses the `bsp_te0720_1cfa` folder +- Linux Host: Uses the `bsp_hosted` BSP folder and the CMake Unix Makefiles generator. +- Windows Host: Uses the `bsp_hosted` BSP folder, the CMake MinGW Makefiles generator and MSYS2. + +# Prerequisites + +There is a separate [prerequisites](#set-up-prereq) which specifies how to set up all +prerequisites. + +## Building the OBSW and flashing it on the Q7S + +1. ARM cross-compiler installed, either as part of [Vivado 2018.2 installation](#vivado) or + as a [separate download](#arm-toolchain). The Xiphos SDK also installs a cross-compiler, + but its version is currently too old to compile the OBSW (7.3.0). +2. [Q7S sysroot](#sysroot) on local development machine. It is installed by the Xiphos SDK +3. Recommended: Eclipse or [Vivado 2018.2 SDK](#vivado) for OBSW development +3. [TCF agent](https://wiki.eclipse.org/TCF) running on Q7S + +## Hardware Design + +1. [Vivado 2018.2](#vivado) for programmable logic design + +# Building the software + +## CMake + +When using Windows, run theses steps in MSYS2. + +1. Clone the repository with + + ```sh + git clone https://egit.irs.uni-stuttgart.de/eive/eive-obsw.git + ``` + +2. Update all the submodules + + ```sh + git submodule update --init + ``` + +3. Create two system variables to pass the system root path and the cross-compiler path to the + build system. You only need to do this once when setting up the build system. + Example for Unix: + + ```sh + export CROSS_COMPILE_BIN_PATH= + export ZYNQ_7020_SYSROOT= + ``` + +4. Ensure that the cross-compiler is working with + `${CROSS_COMPILE_BIN_PATH}/arm-linux-gnueabihf-gcc --version` and that + the sysroot environmental variables have been set like specified in the + [root filesystem chapter](#sysroot). + +5. Run the CMake configuration to create the build system in a `build-Debug-Q7S` folder. + Add `-G "MinGW Makefiles` in MinGW64 on Windows. + + ```sh + mkdir cmake-build-debug-q7s && cd cmake-build-debug-q7s + cmake -DTGT_BSP="arm/q7s" -DCMAKE_BUILD_TYPE=Debug .. + cmake --build . -j + ``` + + Please note that you can also use provided shell scripts to perform these commands. + ```sh + cp scripts/q7s-env.sh .. + cp scripts/q7s-env-em.sh .. + ``` + + Adapt these scripts for your needs by editing the `CROSS_COMPILE_BIN_PATH` + and `ZYNQ_7020_SYSROOT`. After that, you can run the following commands to set up + the FM build + + ```sh + cd .. + ./q7s-env.sh + q7s-make-debug.sh + ``` + + You can build the EM setup by running + + ```sh + export EIVE_Q7S_EM=1 + ``` + + or by running the `q7s-env-em.sh` script instead before setting up the build + configuration. + + The shell scripts will invoke a Python script which in turn invokes CMake with the correct + arguments to configure CMake for Q7S cross-compilation. You can look into the command + output to see which commands were run exactly. + + There are also different values for `-DTGT_BSP` to build for the Raspberry Pi + or the Beagle Bone Black: `arm/raspberrypi` and `arm/beagleboneblack`. + +6. Build the software with + + ```sh + cd cmake-build-debug-q7s + cmake --build . -j + ``` + +## Preparing and executing an OBSW update + +A OBSW update consists of a `xz` compressed file `eive-sw-update.tar.xz` +which contains the following two files: + +1. Stripped OBSW binary `eive-obsw-stripped` +2. OBSW version text file with the name `obsw_version.txt` + +These files can be created manually: + +1. Build the release image inside `cmake-build-release-q7s` +2. Switch into the build directory +3. Run the following command to create the version file + + ```sh + git describe --tags --always --exclude docker_* > obsw_version.txt + ``` + + You can also use the `create-version-file.sh` helper shell script + located in the `scripts` folder to do this. + +4. Set the Q7S user as the file owner for both files + + ```sh + sudo chown root:root eive-obsw-stripped + sudo chown root:root obsw_version.txt + ``` + +5. Run the following command to create the compressed archive + + ```sh + tar -cJvf eive-sw-update.tar.xz eive-obsw-stripped obsw_version.txt + ``` + +You can also use the helper script `create-sw-update.sh` inside the build folder +after sourcing the `q7s-env.sh` helper script to perform all steps including +a rebuild. + +After creating these files, they need to be transferred onto the Q7S +to either the `/mnt/sd0/bin` or `/mnt/sd1/bin` folder if the OBSW update +is performed from the SD card. It can also be transferred to the `/tmp` folder +to perform the update from a temporary directory, which does not rely on any +of the SD cards being on and mounted. However, all files in the temporary +directory will be deleted if the Linux OS is rebooted for any reason. + +After both files are in place (this is checked by the OBSW), the example command +sequence is used by the OBSW to write the OBSW update to the QSPI chip 0 and +slot 0 using SD card 0: + +```sh +tar -xJvf eive-update.tar.xz +xsc_mount_copy 0 0 +cp eive-obsw-stripped /tmp/mntupdate-xdi-qspi0-nom-rootfs/usr/bin/eive-obsw +cp obsw_update.txt /tmp/mntupdate-xdi-qspi0-nom-rootfs/usr/share/obsw_update.txt +writeprotect 0 0 1 +``` + +Some context information about the used commands: + +1. It mounts the target chip and copy combination into the `/tmp` folder + using the `xsc_mount_copy ` utility. This also unlocks the + writeprotection for the chip. The mount point name inside `/tmp` depends + on which chip and copy is used + + - Chip 0 Copy 0: `/tmp/mntupdate-xdi-qspi0-nom-rootfs` + - Chip 0 Copy 1: `/tmp/mntupdate-xdi-qspi0-gold-rootfs` + - Slot 1 Copy 0: `/tmp/mntupdate-xdi-qspi1-nom-rootfs` + - Slot 1 Copy 1: `/tmp/mntupdate-xdi-qspi1-gold-rootfs` + +2. Writing the file with a regular `cp ` command +3. Enabling the writeprotection using the `writeprotect 1` utility. + +## Build for the Q7S target root filesystem with `yocto` + +The EIVE root filesystem will contain the EIVE OBSW and the Watchdog component. +It is currently generated with `yocto`, but the tool can not compile the primary +OBSW due to toolchain version incompatibility. Therefore, the OBSW components +are currently compiled using the toolchain specified in this README (e.g. installed by Vivado). + +However, it is still possible to install the two components using yocto. A few helper files were +provided to make this process easier. The following steps can be used to install the OBSW +components and a version file to the yocto sources for the generation of the complete EIVE root +file system image. The steps here are shown for Ubuntu, you can use the according Windows +helper scripts as well. + +1. Copy the `q7s-env.sh` script to the same layer as the `eive-obsw`. + + ```sh + cp scripts/q7s-env.sh .. + cd .. + ./q7s-env.sh + q7s-make-release.sh + ``` + +2. Compile the OBSW components in release mode + + ```sh + cd cmake-build-release-q7s + cmake --build . -j + ``` + +3. Make sure the [`q7s-yocto`](https://egit.irs.uni-stuttgart.de/eive/q7s-yocto) + repository or the [`q7s-package`](https://egit.irs.uni-stuttgart.de/eive/q7s-package.git) + repository and its `q7s-yocto` submodule were cloned in the same directory layer as + the `eive-obsw`. + +4. Run the install script to install the files into `q7s-yocto`. + + ```sh + install-obsw-yocto.sh + ``` + +5. Navigate into the `q7s-yocto` repo and review the changes. You can then add and push those + changes. + +6. You can now rebuild the root filesystem with the updated OBSW using `yocto`. This probably needs + to be done on another machine or in a VM. The [`q7s-yocto`](https://egit.irs.uni-stuttgart.de/eive/q7s-yocto) + repository contains details on how to best do this. + +## Building in Xilinx SDK 2018.2 + +1. Open Xilinx SDK 2018.2 +2. Import project + * File → Import → C/C++ → Existing Code as Makefile Project +3. Set build command. Replace \ with either debug or release. + * When on Linux right click project → Properties → C/C++ Build → Set build command to `make -j` + * -j causes the compiler to use all available cores + * The target is used to either compile the debug or the optimized release build. + * On windows create a make target additionally (Windows → Show View → Make Target) + * Right click eive_obsw → New + * Target name: all + * Uncheck "Same as the target name" + * Uncheck "Use builder settings" + * As build command type: `cmake --build .` + * In the Behaviour tab, you can enable build acceleration +4. Run build command by double clicking the created target or by right clicking + the project folder and selecting Build Project. + +# Useful and Common Commands + +## Build generation + +Replace `Debug` with `Release` for release build. Add `-G "MinGW Makefiles` or `-G "Ninja"` +on Windows or when `ninja` should be used. You can build with `cmake --build . -j` after +build generation. You can finds scripts in `cmake/scripts` to perform the build commands +automatically. + +### Q7S OBSW + +The EIVE OBSW is the default target if no target is specified. + +**Debug** + +```sh +mkdir cmake-build-debug-q7s && cd cmake-build-debug-q7s +cmake -DTGT_BSP=arm/q7s -DCMAKE_BUILD_TYPE=Debug .. +cmake --build . -j +``` + +**Release** + +```sh +mkdir cmake-build-release-q7s && cd cmake-build-release-q7s +cmake -DTGT_BSP=arm/q7s -DCMAKE_BUILD_TYPE=Release .. +cmake --build . -j +``` + +### Hosted OBSW + +You can also use the FSFW OSAL `host` to build on Windows or for generic OSes. +You can use the `clone-submodules-no-privlibs.sh` script to only clone the required (non-private) +submodules required to build the hosted OBSW. + +```sh +mkdir cmake-build-debug && cd cmake-build-debug +cmake -DFSFW_OSAL=host -DCMAKE_BUILD_TYPE=Debug .. +cmake --build . -j +``` + +You can also use the `linux` OSAL: + +```sh +mkdir cmake-build-debug && cd cmake-build-debug +cmake -DFSFW_OSAL=linux -DCMAKE_BUILD_TYPE=Debug .. +cmake --build . -j +``` + +Please note that some additional Linux setup might be necessary. +You can find more information in the [Linux section of the FSFW example](https://egit.irs.uni-stuttgart.de/fsfw/fsfw-example-linux-mcu/src/branch/mueller/master/doc/README-linux.md#raising-message-queue-size-limit) + +### Q7S Watchdog + +The watchdog will be built along side the primary OBSW binary. + +### Unittests + +To build the unittests, the corresponding target must be specified in the build command. +The configure steps do not need to be repeated if the folder has already been configured. + +```sh +mkdir cmake-build-debug && cd cmake-build-debug +cmake .. +cmake --build . --target eive-unittests -j +``` + +## Connect to EIVE flatsat + +### DNS + +```sh +ssh eive@flatsat.eive.absatvirt.lw +``` + +### IPv6 +```sh +ssh eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 +``` + +### IPv4 + +```sh +ssh eive@192.168.199.227 +``` + +## Connecting to the serial console or ssh console + +A serial console session is up permanently in a `tmux` session + +### Serial console + +You can check whether the sessions exist with `tmux ls`. +This is the command to connect to the serial interface of the FM using the +RS422 interface of the flight preparation panel: + +```sh +tmux a -t q7s-fm-fpp +``` + +If the session does not exist, you can create it like this + +```sh +tmux new -s q7s-fm-fpp -t /bin/bash +launch-q7s-fpp +``` + +Other useful tmux commands: +- Enable scroll mode: You can press `ctrl + b` and then `[` (`AltGr + 8`) to enable scroll mode. + You can quit scroll mode with `q`. +- Kill a tmux session: run `ctrl + b` and then `k`. +- Detach from a tmux session: run `ctrl + b` and then `d` +- [Pipe last 3000 lines](https://unix.stackexchange.com/questions/26548/write-all-tmux-scrollback-to-a-file) + into file for copying or analysis: + 1. `ctrl + b` and `:` + 2. `capture-pane -S -3000` + `enter` + 3. `save-buffer /tmp/tmux-output.txt` + `enter` + + +### SSH console + +You can use the following command to connect to the Q7S with `ssh`: + +```sh +q7s-fm-ssh +``` + +## Set up all port forwarding at once + +Port forwarding is necessary for remote-debugging using the `tcf-agent`, copying files +with `scp` & `q7s-cp.py` and sending TMTC commands. +You can specify the `-L` option multiple times to set up all port forwarding at once. + +```sh +ssh -L 1534:192.168.155.55:1534 \ + -L 1535:192.168.155.55:22 \ + -L 1536:192.168.155.55:7301 \ + -L 1537:127.0.0.1:7100 \ + -L 1538:192.168.133.10:1534 \ + -L 1539:192.168.133.10:22 \ + -L 1540:192.168.133.10:7301 \ + eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 \ + -t 'CONSOLE_PREFIX="[Q7S Tunnel]" /bin/bash' +``` + +There is also a shell script called `q7s-port.sh` which can be used to achieve the same. + +# Setting up prerequisites + +## Getting system root for Linux cross-compilation + +Cross-compiling any program for an embedded Linux board generally required parts of the target root +file system on the development/host computer. For the Q7S, you can install the cross-compilation +root file system by simply installing the SDK. You can find the most recent SDK +[here](https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Software/xiphos-q7s-sdk). + +If you are compiling for the Q7S or the TE7020, the `ZYNQ_7020_SYSROOT` environment variable +must be set to the location of the SDK compile sysroot. Here is an example on how to do this +in Ubuntu, assuming the SDK was installed in the default location + +```sh +export ZYNQ_7020_SYSROOT="/opt/xiphos/sdk/ark/sysroots/cortexa9hf-neon-xiphos-linux-gnueabi" +``` + +If you are comiling for the Raspberry Pi, you have to set the `LINUX_ROOTFS` environmental +variable instead. You can find a base root filesystem for the Raspberry Pi +[here](https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Software/rootfs). + +## Installing Vivado and the Xilinx development tools + +It's also possible to perform debugging with a normal Eclipse installation by installing +the TCF plugin and downloading the cross-compiler as specified in the section below. However, +if you want to generate the `*.xdi` files necessary to update the firmware, you need to +installed Vivado with the SDK core tools. + +* Install Vivado 2018.2 and + [Xilinx SDK](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vivado-design-tools/archive.html). + Install the Vivado Design Suite - HLx Editions - 2018.2 Full Product Installation instead of + the updates. It is recommended to use the installer. + +* Install settings. In the Devices selection, it is sufficient to pick SoC → Zynq-7000:
+ +
+ +
+ +
+ +* For supported OS refer to https://www.xilinx.com/support/documentation/sw_manuals/xilinx2018_2/ug973-vivado-release-notes-install-license.pdf . + Installation was tested on Windows and Ubuntu 21.04. +* Add path of linux cross-compiler to permanent environment variables (`.bashrc` file in Linux): + `\SDK\2018.2\gnu\aarch32\nt\gcc-arm-linux-gnueabi\bin` + or set up path each time before debugging. + +### Installing on Linux - Device List Issue + +When installing on Ubuntu, the installer might get stuck at the `Generating installed device list` +step. When this happens, you can kill the installation process (might be necessara to kill a process +twice) and generate this list manually with the following commands, according to +[this forum entry](https://forums.xilinx.com/t5/Installation-and-Licensing/Vivado-2018-3-Final-Processing-hangs-at-Generating-installed/m-p/972114#M25861). + +1. Install the following library + ```sh + sudo apt install libncurses5 + ``` + +2. Execute the following command + + ```sh + sudo /Vivado/2018.2/bin/vivado -nolog -nojournal -mode batch -source + /.xinstall/Vivado_2018.2/scripts/xlpartinfo.tcl -tclargs + /Vivado/2018.2/data/parts/installed_devices.txt + ``` + +For Linux, you can also download a more recent version of the +[Linaro 8.3.0 cross-compiler](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads) +from [here](https://developer.arm.com/-/media/Files/downloads/gnu-a/8.3-2019.03/binrel/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.xz?revision=e09a1c45-0ed3-4a8e-b06b-db3978fd8d56&la=en&hash=93ED4444B8B3A812B893373B490B90BBB28FD2E3) + +### Compatibility issues with wayland on more recent Linux distributions + +If Vivado crashes and you find following lines in the `hs_err_pid*` files: + +```sh +# +# An unexpected error has occurred (11) +# +Stack: +/opt/Xilinx/Vivado/2017.4/tps/lnx64/jre/lib/amd64/server/libjvm.so(+0x923da9) [0x7f666cf5eda9] +/opt/Xilinx/Vivado/2017.4/tps/lnx64/jre/lib/amd64/server/libjvm.so(JVM_handle_linux_signal+0xb6) [0x7f666cf653f6] +/opt/Xilinx/Vivado/2017.4/tps/lnx64/jre/lib/amd64/server/libjvm.so(+0x9209d3) [0x7f666cf5b9d3] +/lib/x86_64-linux-gnu/libc.so.6(+0x35fc0) [0x7f66a993efc0] +/opt/Xilinx/Vivado/2017.4/tps/lnx64/jre/lib/amd64/libawt_xawt.so(+0x42028) [0x7f664e24d028] +... +``` + +You can [solve this](https://forums.xilinx.com/t5/Design-Entry/Bug-Vivado-2017-4-crashing-on-rightclick-in-console-log/td-p/881811) +by logging in with `xorg` like specified [here](https://www.maketecheasier.com/switch-xorg-wayland-ubuntu1710/). + +### Using `docnav` on more recent Linux versions + +If you want to use `docnav` for navigating Xilinx documentation, it is recommended to install +it as a standalone version from [here](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/documentation-nav.html). +This is because the `docnav` installed as part of version 2018.2 requires `libpng12`, which is not part of +more recent disitributions anymore. + +## Installing toolchain without Vivado + +You can download the toolchains for Windows and Linux +[from the EIVE cloud](https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Software/tools). + +## Installing CMake and MSYS2 on Windows + +1. Install [MSYS2](https://www.msys2.org/) and [CMake](https://cmake.org/download/) first. + +2. Open the MinGW64 console. It is recommended to set up aliases in `.bashrc` to navigate to the + software repository quickly + +3. Run the following commands in MinGW64 + + ```sh + pacman -Syu + ``` + + It is recommended to install the full base development toolchain + + ```sh + pacman -S base-devel + ``` + + It is also possible to only install required packages + + ```sh + pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-make mingw-w64-x86_64-gcc mingw-w64-x86_64-gdb python3 + ``` + +## Installing CMake on Linux + +1. Run the following command + + ```sh + sudo apt-get install cmake + ```` + +### Updating system root for CI + +If the system root is updated, it needs to be manually updated on the buggy file server. +If access on `buggy.irs.uni-stuttgart.de` is possible with `ssh` and the rootfs in the cloud +[was updated](https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Software/rootfs&fileid=831849) +as well, you can update the rootfs like this: + +```sh +cd /var/www/eive/tools +wget https://eive-cloud.irs.uni-stuttgart.de/index.php/s/SyXpdBBQX32xPgE/download/cortexa9hf-neon-xiphos-linux-gnueabi.tar.gz +``` + +## Setting up UNIX environment for real-time functionalities + +Please note that on most UNIX environments (e.g. Ubuntu), the real time functionalities +used by the UNIX pthread module are restricted, which will lead to permission errors when creating +these tasks and configuring real-time properites like scheduling priorities. + +To solve this issues, try following steps: + +1. Edit the /etc/security/limits.conf +file and add following lines at the end: +```sh + hard rtprio 99 + soft rtprio 99 +``` +The soft limit can also be set in the console with `ulimit -Sr` if the hard +limit has been increased, but it is recommended to add it to the file as well for convenience. +If adding the second line is not desired for security reasons, +the soft limit needs to be set for each session. If using an IDE like eclipse +in that case, the IDE needs to be started from the console after setting +the soft limit higher there. After adding the two lines to the file, +the computer needs to be restarted. + +It is also recommended to perform the following change so that the unlockRealtime +script does not need to be run anymore each time. The following steps +raise the maximum allowed message queue length to a higher number permanently, which is +required for some framework components. The recommended values for the new message +length is 130. + +2. Edit the /etc/sysctl.conf file + + ```sh + sudo nano /etc/sysctl.conf + ``` + + Append at end: + ```sh + fs/mqueue/msg_max = + ``` + + Apply changes with: + ```sh + sudo sysctl -p + ``` + + A possible solution which only persists for the current session is + ```sh + echo | sudo tee /proc/sys/fs/mqueue/msg_max + ``` + +## TCF-Agent + +Most of the steps specified here were already automated + +1. On reboot, some steps have to be taken on the Q7S. Set static IP address and netmask + + ```sh + ifconfig eth0 192.168.133.10 + ifconfig eth0 netmask 255.255.255.0 + ``` + +2. `tcfagent` application should run automatically but this can be checked with + ```sh + systemctl status tcfagent + ``` + +3. If the agent is not running, check whether `agent` is located inside `usr/bin`. + You can run it manually there. To perform auto-start on boot, have a look at the start-up + application section. + +# Remote Debugging + +Open SSH connection to flatsat PC: + +```sh +ssh eive@flatsat.eive.absatvirt.lw +``` + +or + +```sh +ssh eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 +``` + +or + +```sh +ssh eive@192.168.199.227 +``` + +If the static IP address of the Q7S has already been set, +you can access it with ssh + +```sh +ssh root@192.168.133.10 +``` + +If this has not been done yet, you can access the serial +console of the Q7S like this + +```sh +picocom -b 115200 /dev/q7sSerial +``` + +The flatsat has the aliases and shell scripts `q7s_ssh` and `q7s_serial` for this task as well. +If the serial port is blocked for some reason, you can kill +the process using it with `q7s_kill`. + +You can use `AltGr` + `X` to exit the picocom session. + +To debug an application, first make sure a static IP address is assigned to the Q7S. Run ifconfig +on the Q7S serial console. + +```sh +ifconfig +``` + +Set IP address and netmask with + +```sh +ifconfig eth0 192.168.133.10 +ifconfig eth0 netmask 255.255.255.0 +``` + +To launch application from Xilinx SDK setup port fowarding on the development machine +(not on the flatsat!) + +```sh +ssh -L 1534:192.168.133.10:1534 eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 -t bash +``` + +This forwards any requests to localhost:1534 to the port 1534 of the Q7S with the IP address +192.168.133.10. This needs to be done every time, so it is recommended to create an +alias or shell script to do this quickly. + +Note: When now setting up a debug session in the Xilinx SDK or Eclipse, the host must be set +to localhost instead of the IP address of the Q7S. + +# Remote Reset +1. Launch xilinx hardware server on flatsat with alias +```` +launch-hwserver-xilinx +```` +2. On host PC start xsc +3. In xsct console type the follwing command to connect to the hardware server (replace with the IP address of the flatsat PC. Can be found out with ifconfig) +```` +connect -url tcp::3121 +```` +4. The following command will list all available devices +```` +targets +```` +5. Connect to the APU of the Q7S +```` +target +```` +6. Perform reset +```` +rst +```` + +# TMTC testing + +The OBSW supports sending PUS TM packets via TCP or the PDEC IP Core which transmits the data as +CADU frames. To make the CADU frames receivabel by the +[TMTC porgram](https://egit.irs.uni-stuttgart.de/eive/eive-tmtc), a python script is running as +`systemd` service named `tmtc_bridge` on the flatsat PC which forwards TCP commands to the TCP +server of the OBC and reads CADU frames from a serial interface. + +You can check whether the service is running the following command on the flatsat PC + +```sh +systemctl status tmtc_bridge +``` + +The PUS packets transported with the CADU frames are extracted +and forwared to the TMTC program's TCP client. The code of the TMTC bridge can be found +[here](https://egit.irs.uni-stuttgart.de/eive/tmtc-bridge). To connect the TMTC program to the +TMTC-bridge a port forwarding from a host must be set up with the following command: + +```sh +ssh -L 1537:127.0.0.1:7100 eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 -t bash +``` + +You can print the output of the `systemd` service with + +```sh +journalctl -u tmtc_bridge +``` + +This can be helpful to determine whether any TCs arrive or TMs are coming back. + +Note: The encoding of the TM packets and conversion of CADU frames takes some time. +Thus the replies are received with a larger delay compared to a direct TCP connection. + +# Direct Debugging + +1. Assign static IP address to Q7S + * Open serial console of Q7S (Accessible via the micro-USB of the PIM, see also Q7S user + manual chapter 10.3) + * Baudrate 115200 + * Login to Q7S: + * user: root + * pw: root + +2. Connect Q7S to workstation via ethernet +3. Make sure the netmask of the ehternet interface of the workstation matches the netmask of the Q7S + * When IP address is set to 192.168.133.10 and the netmask is 255.255.255.0, an example IP address for the workstation + is 192.168.133.2 +4. Make sure th `tcf-agent` is running by checking `systemctl status tcf-agent` +5. In Xilinx SDK 2018.2 right click on project → Debug As → Debug Configurations +6. Right click Xilinx C/C++ applicaton (System Debugger) → New → +7. Set Debug Type to Linux Application Debug and Connectin to Linux Agent +8. Click New +9. Give connection a name +10. Set Host to static IP address of Q7S. e.g. 192.168.133.10 +11. Test connection (This ensures the TCF Agent is running on the Q7S) +12. Select Application tab + * Project Name: eive_obsw + * Local File Path: Path to OBSW application image with debug symbols (non-stripped) + * Remote File Path: `/tmp/` + +# Transfering Files to the Q7S + +To transfer files from the local machine to the Q7S, use port forwarding + +```sh +ssh -L 1535:192.168.133.10:22 eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 +``` + +An `example` file can be copied like this + +```sh +scp -P 1535 example root@localhost:/tmp +``` + +Copying a file from Q7S to flatsat PC +```` +scp -P 22 root@192.168.133.10:/tmp/kernel-config /tmp +```` + +From a windows machine files can be copied with putty tools (note: use IPv4 address) +```` +pscp -scp -P 22 eive@192.168.199.227:/example-file +```` + +A helper script named `q7s-cp.py` can be used together with the `q7s-port.sh` +script to make this process easier. + +# Q7S OBC + +## Launching an application at start-up - deprecated + +This way to enable auto-startup is deprecated. It is instead recommended to tweak the yocto +recipes file for the related `systemd` service to enable auto-startup with `SYSTEMD_AUTO_ENABLE`. + +You can also do the steps performed here on a host computer inside the `q7s-rootfs` directory +of the [Q7S base repository](https://egit.irs.uni-stuttgart.de/eive/q7s-base). This might +be more convenient while also allowing to update all images at once with the finished `rootfs.xdi`. + +Load the root partiton from the flash memory (there are to nor-flash memories and each flash holds +two xdi images). Note: It is not possible to modify the currently loaded root partition, e.g. +creating directories. To do this, the parition needs to be mounted. + +1. Disable write protection of the desired root partition + + ```sh + writeprotect 0 0 0 # unlocks nominal image on nor-flash 0 + ``` + +2. Mount the root partition + + ```sh + xsc_mount_copy 0 0 # Mounts the nominal image from nor-flash 0 + ``` + The mounted partition will be located inside the `/tmp` folder + +3. Copy the executable to `/usr/bin` + +4. Make sure the permissions to execute the application are set + + ```sh + chmod +x application + ``` + +5. Create systemd service in `/etc/systemd/system`. The following shows an example service. + + ```sh + cat > example.service + [Unit] + Description=Example Service + StartLimitIntervalSec=0 + + [Service] + Type=simple + Restart=always + RestartSec=1 + User=root + ExecStart=/usr/bin/application + + [Install] + WantedBy=multi-user.target + ``` + +6. Enable the service. This is normally done with `systemctl enable ` which would create + a symlink in the `multi-user.target.wants` directory. However, this is not possible + when the service is created for a mounted root partition. It is also not possible during run + time because symlinks can't be created in a read-only filesystem. Therefore, relative symlinks + are used like this: + + ```sh + cd etc/systemd/system/multi-user.target.wants/ + ln -s ../example.service example.service + ``` + + You can check the symlinnks with `ls -l` + +7. The modified root partition is written back when the partion is locked again. + ```sh + writeprotect 0 0 1 + ``` +8. Now verify the application start by booting from the modified image + ```sh + xsc_boot_copy 0 0 + ```` + +9. After booting verify if the service is running + ```sh + systemctl status example + ``` + +## Current user systemd services + +The following custom `systemd` services are currently running on the Q7S and can be found in +the `/etc/systemd/system` folder. +You can query that status of a service by running `systemctl status `. + +### `eive-watchdog` + +The watchdog will create a pipe at `/tmp/watchdog-pipe` which can be used both by the watchdog and +the EIVE OBSW. The watchdog will only read from this pipe while the OBSW will only write +to this pipe. The watchdog checks for basic ASCII commands as a first basic feature set. +The most important functionality is that the watchdog cant detect if a timeout +has happened. This can happen beause the OBSW is hanging (or at least the CoreController thread) or +there is simply now OBSW running on the system. It does to by checking whether the FIFO is +regulary written to, which means the EIVE OBSW is alive. + +If the EIVE OBSW is alive, a special file called `/tmp/obsw-running` will be created. +This file can be used by any other software component to query whether the EIVE OBSW is running. +The EIVE OBSW itself can be configured to check whether this file exists, which prevents two +EIVE OBSW instances running on the Q7S at once. + +If a timeout occurs, this special file will be deleted as well. +The watchdog and its configuration will be directly integrated into this repostory, which +makes adaptions easy. + +### `tcf-agent` + +This starts the `/usr/bin/tcf-agent` program to allows remote debugging + +### `eive-early-config` + +This is a configuration script which runs early after `local-fs.target` and `sysinit.target` +Currently only pipes the output of `xsc_boot_copy` into the file `/tmp/curr_copy.txt` which can be +used by other software components to read the current chip and copy. + +### `eive-post-ntpd-config` + +This is a configuration scripts which runs after the Network Time Protocol has run. This script +currently sets the static IP address `192.168.133.10` and starts the `can` interface. + +## Initial boot delay + +You can create a file named `boot_delays_secs.txt` inside the home folder to delay the OBSW boot +for 6 seconds if the file is empty of for the number of seconds specified inside the file. This +can be helpful if something inside the OBSW leads to an immediate crash of the OBC. + +## PCDU + +Connect to serial console of P60 Dock +```` +picocom -b 500000 /dev/ttyUSBx +```` +General information +```` +cmp ident +```` +List parameter table: +x values: 1,2 or 4 +```` +param list x +```` +Table 4 lists HK parameters +Changing parameters +First switch to table where parameter shall be changed (here table id is 1) +```` +p60-dock # param mem 1 +p60-dock # param set out_en[0] 1 +p60-dock # param get out_en[0] +GET out_en[0] = 1 +```` + +## Core commands + +Display currently running image: + +```sh +xsc_boot_copy +``` + +Rebooting currently running image: + +```sh +xsc_boot_copy -r +``` + +### Setting time on Q7S +Setting date and time (only timezone UTC available) +```` +timedatectl set-time 'YYYY-MM-DD HH:MM:SS' +```` +Setting UNIX time +```` +date +%s -s @1626337522 +```` +This only sets the system time and does not updating the time of the real time clock. To harmonize +the system time with the real time clock run +```` +hwclock -w +```` +Reading the real time clock +```` +hwclock --show +```` + +## pa3tool Host Tool + +The `pa3tool` is a host tool to interface with the ProASIC3 on the Q7S board. It was +installed on the clean room PC but it can also be found +[on the Traq platform](https://trac2.xiphos.ca/manual/attachment/wiki/WikiStart/libpa3-1.3.4.tar.gz). + +For more information, see Q7S datasheet. + +## Creating files with cat and echo + +The only folder which can be written in the root filesystem is the `tmp` folder. + +You can create a simple file with initial content with `echo` + +```sh +echo "Hallo Welt" > /tmp/test.txt +cat /tmp/test.txt +``` + +For more useful combinations, see this [link](https://www.freecodecamp.org/news/the-cat-command-in-linux-how-to-create-a-text-file-with-cat-or-touch/). + +## Using the scratch buffer of the ProASIC3 + +The ProASIC3 has a 1024 byte scratch buffer. The values in this scratch buffer will survive +a reboot, so this buffer can be used as an alternative to the SD cards to exchange information +between images or to store mission critical information. + +You can use `xsc_scratch --help` for more information. + +Write to scratch buffer: + +```sh +xsc_scratch write TEST "1" +``` + +Read from scratch buffer: + +```sh +xsc_scratch read TEST +``` + +Read all keys: + +```sh +xsc_scratch read + +``` + +Get fill count: + +```sh +xsc_scratch read | wc -c +``` + +## Custom device names in Linux with the `udev` module + +You can assign custom device names using the Linux `udev` system. +This works by specifying a rules file inside the `/etc/udev/rules.d` folder +which creates a SYMLINK if certain device properties are true. + +Each rule is a new line inside a rules file. +For example, the rule + +```txt +SUBSYSTEM=="tty", ATTRS{interface}=="Dual RS232-HS", ATTRS{bInterfaceNumber}=="01", SYMLINK+="ploc_supv +``` + +Will create a symlink `/dev/ploc_supv` if a connected USB device has the +same `interface` and `bInterfaceNumber` properties as shown above. + +You can list the properties for a given connected device using `udevadm`. +For example, you can do this for a connected example device `/dev/ttyUSB0` +by using + +```txt +udevadm info -a /dev/ttyUSB0 +``` + +## Using `system` when debugging + +Please note that when using a `system` call in C++/C code and debugging, a new thread will be +spawned which will appear on the left in Eclipse or Xilinx SDK as a `sh` program. +The debugger might attach to this child process automatically, depending on debugger configuration, +and the process needs to be selected and continued/started manually. You can enable or disable +this behaviour by selecting or deselecting the `Attach Process Children` option in the Remote +Application Configuration for the TCF plugin like shown in the following picture + +
+ +## Libgpiod + +Detect all gpio device files: +```` +gpiodetect +```` +Get info about a specific gpio group: +```` +gpioinfo +```` +The following sets the gpio 18 from gpio group gpiochip7 to high level. +```` +gpioset gpiochip7 18=1 +```` +Setting the gpio to low. +```` +gpioset gpiochip7 18=0 +```` +Show options for setting gpios. +```` +gpioset -h +```` +To get the state of a gpio: +```` +gpioget +```` +Example to get state: +gpioget gpiochip7 14 + +Both the MIOs and EMIOs can be accessed via the zynq_gpio instance which +comprises 118 pins (54 MIOs and 64 EMIOs). + +## Xilinx UARTLIE + +Get info about ttyUL* devices +```` +cat /proc/tty/driver +```` + +## I2C + +Getting information about some I2C device + +```sh +ls /sys/class/i2c-dev/i2c-0/device/device/driver +``` +This shows the memory mapping of `/dev/i2c-0`. + +You can use the `i2cdetect` utility to scan for I2C devices. +For example, to do this for bus 0 (`/dev/i2c-0`), you can use + +```sh +i2cdetect -r -y 0 +``` + +## CAN + +```sh +ip link set can0 down +ip link set can0 type can loopback off +ip link set can0 up type can bitrate 1000000 +``` + +Following command sends 8 bytes to device with id 99 (for petalinux) +```` +cansend can0 -i99 99 88 77 11 33 11 22 99 +```` +For Q7S use this: +```` +cansend can0 5A1#11.22.33.44.55.66.77.88 +```` +Turn loopback mode on: +```` +ip link set can0 type can bitrate 1000000 loopback on +```` +Reading data from CAN: +```` +candump can0 +```` + +## Dump content of file in hex +```` +cat file.bin | hexdump -C +```` +All content will be printed with +```` +cat file.bin | hexdump -v +```` +To print only the first X bytes of a file +```` +cat file.bin | hexdump -v -n X +```` + +## Preparation of a fresh rootfs and SD card + +See [q7s-package repository README](https://egit.irs.uni-stuttgart.de/eive/q7s-package) + +# Running cppcheck on the Software + +Static code analysis can be useful to find bugs. +`cppcheck` can be used for this purpose. On Windows you can use MinGW64 to do this. + +```sh +pacman -S mingw-w64-x86_64-cppcheck +``` + +On Ubuntu, install with + +```sh +sudo apt-get install cppcheck +``` + +You can use the Eclipse integration or you can perform the scanning manually from the command line. +CMake will be used for this. + +Run the CMake build generation commands specified above but supply +`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to the build generation. Invoking the build command will +generate a `compile_commands.json` file which can be used by cppcheck. + +```sh +cppcheck --project=compile_commands.json --xml 2> report.xml +``` + +Finally, you can convert the generated `.xml` file to HTML with the following command + +```sh +cppcheck-htmlreport --file=report.xml --report-dir=cppcheck --source-dir=.. +``` + +# CLion + +CLion is the recommended IDE for the development of the hosted version of EIVE. +You can also set up CLion for cross-compilation of the primary OBSW. + +There is a shared `.idea/cmake.xml` file to get started with this. +To make cross-compilation work, two special environment variables +need to be set: + +- `ZYNQ_7020_ROOTFS` pointing to the root filesystem +- `CROSS_COMPILE` pointing to the the full path of the cross-compiler + without the specific tool suffix. For example, if the the cross-compiler + tools are located at `/opt/q7s-gcc/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin`, + this variable would be set + to `/opt/q7s-gcc/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf` + +# Eclipse + +When using Eclipse, there are two special build variables in the project properties +→ C/C++ Build → Build Variables called `Q7S_SYSROOT` or `RPI_SYSROOT`. You can set +the sysroot path in those variables to get any additional includes like `gpiod.h` in the +Eclipse indexer. + +## Setting up default Eclipse for Q7S projects - TCF agent + +The [TCF agent](https://wiki.eclipse.org/TCF) can be used to perform remote debugging on the Q7S. + +1. Copy the `.cproject` file and the `.project` file inside the `misc/eclipse` folder into the + repo root + + ```sh + cd eive-obsw + cp misc/eclipse/.cproject . + cp misc/eclipse/.project . + ``` + +2. Open the repo in Eclipse as a folder. + +3. Install the TCF agent plugin in Eclipse from + the [releases](https://www.eclipse.org/tcf/downloads.php). Go to + Help → Install New Software and use the download page, for + example https://download.eclipse.org/tools/tcf/releases/1.7/1.7.0/ to search for the plugin and + install it. You can find the newest version [here](https://www.eclipse.org/tcf/downloads.php) + +4. Go to Window → Perspective → Open Perspective and open the **Target Explorer Perspective**. + Here, the Q7S should show up if the local port forwarding was set up as explained previously. + Please note that you have to connect to `localhost` and port `1534` with port forwaring set up. + +5. A launch configuration was provided, but it might be necessary to adapt it for your own needs. + Alternatively: + + - Create a new **TCF Remote Application** by pressing the cogs button at the top or going to + Run → Debug Configurations → Remote Application and creating a new one there. + + - Set up the correct image in the main tab (it might be necessary to send the image to the + Q7S manually once) and file transfer properties + + - It is also recommended to link the correct Eclipse project. + +After that, comfortable remote debugging should be possible with the Debug button. + +A build configuration and a shell helper script has been provided to set up the path variables and +build the Q7S binary on Windows, but a launch configuration needs to be newly created because the +IP address and path settings differ from machine to machine. + +# Running the EIVE OBSW on a Raspberry Pi + +Special section for running the EIVE OBSW on the Raspberry Pi. +The Raspberry Pi build uses the `bsp_rpi` BSP folder, and a very similar cross-compiler. + +For running the software on a Raspberry Pi, it is recommended to follow the steps specified in +[the fsfw example](https://egit.irs.uni-stuttgart.de/fsfw/fsfw_example/src/branch/mueller/master/doc/README-rpi.md#top) +and using the TCF agent to have a similar set-up process also required for the Q7S. +You should run the following command first on your Raspberry Pi + +```sh +sudo apt-get install gpiod libgpiod-dev +``` + +to install the required GPIO libraries before cloning the system root folder. + +# Running OBSW on EGSE +The EGSE is a test system from arcsec build arround a raspberry pi 4 to test the star tracker. The IP address of the EGSE (raspberry pi) is 192.168.18.31. An ssh session can be opened with +```` +ssh pi@192.168.18.31 +```` +Password: raspberry + +To run the obsw perform the following steps: +1. Build the cmake EGSE Configuration + * the sysroots for the EGSE can be found [here](https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Software/egse&fileid=1190471) + * toolchain for linux host can be downloaded from [here](https://github.com/Pro/raspi-toolchain) + * toolchain for windows host from [here](https://gnutoolchains.com/raspberry/) (the raspios-buster-armhf toolchain is the right one for the EGSE) +2. Disable the ser2net systemd service on the EGSE +````sh +$ sudo systemctl stop ser2net.service +```` +3. Power on the star tracker by running +````sh +$ ~/powerctrl/enable0.sh` +```` +4. Run portforwarding script for tmtc tcp connection and tcf agent on host PC +````sh +$ ./scripts/egse-port.sh +```` +5. The star tracker can be powered off by running +````sh +$ ~/powerctrl/disable0.sh +```` + +# Manually preparing sysroots to compile gpsd +Copy all header files from [here](https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/Software/gpsd&fileid=1189985) to the /usr/include directory and all static libraries to /usr/lib. + +# Flight Software Framework (FSFW) + +An EIVE fork of the FSFW is submodules into this repository. +To add the master upstream branch and merge changes and updates from it +into the fork, run the following command in the fsfw folder first: + +```sh +git remote add upstream https://egit.irs.uni-stuttgart.de/fsfw/fsfw.git +git remote update --prune +``` + +After that, an update can be merged by running + +```sh +git merge upstream/master +``` + +Alternatively, changes from other upstreams (forks) and branches can be merged like that +in the same way. + +# Coding Style + +* the formatting is based on the clang-format tools + +## Setting up auto-formatter with clang-format in Xilinx SDK + +1. Help → Install New Software → Add +2. In location insert the link http://www.cppstyle.com/luna +3. The software package CppStyle should now be available for installation +4. On windows download the clang-formatting tools from https://llvm.org/builds/. On linux clang-format can be installed with the package manager. +5. Navigate to Preferences → C/C++ → CppStyle +6. Insert the path to the clang-format executable +7. Under C/C++ → Code Style → Formatter, change the formatter to CppStyle (clang-format) +8. Code can now be formatted with the clang tool by using the key combination Ctrl + Shift + f + +## Setting up auto-fromatter with clang-format in eclipse +1. Help → Eclipse market place → Search for "Cppstyle" and install +2. On windows download the clang-formatting tools from https://llvm.org/builds/. On linux clang-format can be installed with the package manager. +3. Navigate to Preferences → C/C++ → CppStyle +4. Insert the path to the clang-format executable +5. Under C/C++ → Code Style → Formatter, change the formatter to CppStyle (clang-format) +6. Code can now be formatted with the clang tool by using the key combination Ctrl + Shift + f diff --git a/archive/PlocMpsocHandler.cpp b/archive/PlocMpsocHandler.cpp new file mode 100644 index 0000000..ecb0bed --- /dev/null +++ b/archive/PlocMpsocHandler.cpp @@ -0,0 +1,1559 @@ +#include +#include +#include + +#include "OBSWConfig.h" +#include "fsfw/datapool/PoolReadGuard.h" +#include "fsfw/globalfunctions/CRC.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/parameters/HasParametersIF.h" + +PlocMpsocHandler::PlocMpsocHandler(object_id_t objectId, object_id_t uartComIFid, + CookieIF* comCookie, + PlocMpsocSpecialComHelperLegacy* plocMPSoCHelper, + Gpio uartIsolatorSwitch, object_id_t supervisorHandler) + : DeviceHandlerBase(objectId, uartComIFid, comCookie), + hkReport(this), + specialComHelper(plocMPSoCHelper), + uartIsolatorSwitch(uartIsolatorSwitch), + supervisorHandler(supervisorHandler), + commandActionHelper(this) { + if (comCookie == nullptr) { + sif::error << "PlocMPSoCHandler: Invalid communication cookie" << std::endl; + } + eventQueue = QueueFactory::instance()->createMessageQueue(10); + commandActionHelperQueue = QueueFactory::instance()->createMessageQueue(10); + spParams.maxSize = sizeof(commandBuffer); + spParams.buf = commandBuffer; +} + +PlocMpsocHandler::~PlocMpsocHandler() {} + +ReturnValue_t PlocMpsocHandler::initialize() { + ReturnValue_t result = returnvalue::OK; + result = DeviceHandlerBase::initialize(); + if (result != returnvalue::OK) { + return result; + } + uartComIf = dynamic_cast(communicationInterface); + if (uartComIf == nullptr) { + sif::warning << "PlocMPSoCHandler::initialize: Invalid uart com if" << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + EventManagerIF* manager = ObjectManager::instance()->get(objects::EVENT_MANAGER); + if (manager == nullptr) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "PlocMPSoCHandler::initialize: Invalid event manager" << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + ; + } + result = manager->registerListener(eventQueue->getId()); + if (result != returnvalue::OK) { + return result; + } + result = manager->subscribeToEvent( + eventQueue->getId(), + event::getEventId(PlocMpsocSpecialComHelperLegacy::MPSOC_FLASH_WRITE_FAILED)); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + result = manager->subscribeToEvent( + eventQueue->getId(), + event::getEventId(PlocMpsocSpecialComHelperLegacy::MPSOC_FLASH_WRITE_SUCCESSFUL)); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + result = manager->subscribeToEvent( + eventQueue->getId(), + event::getEventId(PlocMpsocSpecialComHelperLegacy::MPSOC_FLASH_READ_SUCCESSFUL)); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + result = manager->subscribeToEvent( + eventQueue->getId(), + event::getEventId(PlocMpsocSpecialComHelperLegacy::MPSOC_FLASH_READ_FAILED)); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + result = specialComHelper->setComIF(communicationInterface); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + specialComHelper->setComCookie(comCookie); + specialComHelper->setSequenceCount(&sequenceCount); + result = commandActionHelper.initialize(); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + return result; +} + +void PlocMpsocHandler::performOperationHook() { + if (commandIsPending and cmdCountdown.hasTimedOut()) { + sif::warning << "PlocMpsocHandler: Command " << getPendingCommand() << " has timed out" + << std::endl; + commandIsPending = false; + // TODO: Better returnvalue? + cmdDoneHandler(false, returnvalue::FAILED); + } + EventMessage event; + for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::OK; + result = eventQueue->receiveMessage(&event)) { + switch (event.getMessageId()) { + case EventMessage::EVENT_MESSAGE: + handleEvent(&event); + break; + default: + sif::debug << "PlocMPSoCHandler::performOperationHook: Did not subscribe to this event" + << " message" << std::endl; + break; + } + } + CommandMessage message; + for (ReturnValue_t result = commandActionHelperQueue->receiveMessage(&message); + result == returnvalue::OK; result = commandActionHelperQueue->receiveMessage(&message)) { + result = commandActionHelper.handleReply(&message); + if (result == returnvalue::OK) { + continue; + } + } +} + +ReturnValue_t PlocMpsocHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + ReturnValue_t result = returnvalue::OK; + switch (actionId) { + case mpsoc::SET_UART_TX_TRISTATE: { + uartIsolatorSwitch.pullLow(); + return EXECUTION_FINISHED; + break; + } + case mpsoc::RELEASE_UART_TX: { + uartIsolatorSwitch.pullHigh(); + return EXECUTION_FINISHED; + break; + default: + break; + } + } + + if (specialComHelperExecuting) { + return mpsoc::MPSOC_HELPER_EXECUTING; + } + + switch (actionId) { + case mpsoc::TC_FLASH_WRITE_FULL_FILE: { + mpsoc::FlashBasePusCmd flashWritePusCmd; + result = flashWritePusCmd.extractFields(data, size); + if (result != returnvalue::OK) { + return result; + } + result = specialComHelper->startFlashWrite(flashWritePusCmd.getObcFile(), + flashWritePusCmd.getMPSoCFile()); + if (result != returnvalue::OK) { + return result; + } + specialComHelperExecuting = true; + return EXECUTION_FINISHED; + } + case mpsoc::TC_FLASH_READ_FULL_FILE: { + mpsoc::FlashReadPusCmd flashReadPusCmd; + result = flashReadPusCmd.extractFields(data, size); + if (result != returnvalue::OK) { + return result; + } + result = specialComHelper->startFlashRead(flashReadPusCmd.getObcFile(), + flashReadPusCmd.getMPSoCFile(), + flashReadPusCmd.getReadSize()); + if (result != returnvalue::OK) { + return result; + } + specialComHelperExecuting = true; + return EXECUTION_FINISHED; + } + case (mpsoc::OBSW_RESET_SEQ_COUNT): { + sequenceCount = 0; + return EXECUTION_FINISHED; + } + default: + break; + } + // For longer commands, do not set these. + commandIsPending = true; + cmdCountdown.resetTimer(); + return DeviceHandlerBase::executeAction(actionId, commandedBy, data, size); +} + +void PlocMpsocHandler::doStartUp() { + if (startupState == StartupState::IDLE) { + startupState = StartupState::HW_INIT; + } + if (startupState == StartupState::HW_INIT) { + if (handleHwStartup()) { + startupState = StartupState::DONE; + } + } + if (startupState == StartupState::DONE) { + setMode(_MODE_TO_ON); + hkReport.setReportingEnabled(true); + powerState = PowerState::IDLE; + startupState = StartupState::IDLE; + } +} + +void PlocMpsocHandler::doShutDown() { + if (handleHwShutdown()) { + hkReport.setReportingEnabled(false); + setMode(_MODE_POWER_DOWN); + commandIsPending = false; + sequenceCount = 0; + powerState = PowerState::IDLE; + startupState = StartupState::IDLE; + } +} + +ReturnValue_t PlocMpsocHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + if (not commandIsPending and not specialComHelperExecuting) { + *id = mpsoc::TC_GET_HK_REPORT; + commandIsPending = true; + return buildCommandFromCommand(*id, nullptr, 0); + } + return NOTHING_TO_SEND; +} + +ReturnValue_t PlocMpsocHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t PlocMpsocHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + spParams.buf = commandBuffer; + ReturnValue_t result = returnvalue::OK; + switch (deviceCommand) { + case (mpsoc::TC_MEM_WRITE): { + result = prepareTcMemWrite(commandData, commandDataLen); + break; + } + case (mpsoc::TC_MEM_READ): { + result = prepareTcMemRead(commandData, commandDataLen); + break; + } + case (mpsoc::TC_FLASHDELETE): { + result = prepareTcFlashDelete(commandData, commandDataLen); + break; + } + case (mpsoc::TC_REPLAY_START): { + result = prepareTcReplayStart(commandData, commandDataLen); + break; + } + case (mpsoc::TC_REPLAY_STOP): { + result = prepareTcReplayStop(); + break; + } + case (mpsoc::TC_DOWNLINK_PWR_ON): { + result = prepareTcDownlinkPwrOn(commandData, commandDataLen); + break; + } + case (mpsoc::TC_DOWNLINK_PWR_OFF): { + result = prepareTcDownlinkPwrOff(); + break; + } + case (mpsoc::TC_REPLAY_WRITE_SEQUENCE): { + result = prepareTcReplayWriteSequence(commandData, commandDataLen); + break; + } + case (mpsoc::TC_GET_HK_REPORT): { + result = prepareTcGetHkReport(); + break; + } + case (mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT): { + result = prepareTcGetDirContent(commandData, commandDataLen); + break; + } + case (mpsoc::TC_MODE_REPLAY): { + result = prepareTcModeReplay(); + break; + } + case (mpsoc::TC_MODE_IDLE): { + result = prepareTcModeIdle(); + break; + } + case (mpsoc::TC_CAM_CMD_SEND): { + result = prepareTcCamCmdSend(commandData, commandDataLen); + break; + } + case (mpsoc::TC_CAM_TAKE_PIC): { + result = prepareTcCamTakePic(commandData, commandDataLen); + break; + } + case (mpsoc::TC_SIMPLEX_SEND_FILE): { + result = prepareTcSimplexSendFile(commandData, commandDataLen); + break; + } + case (mpsoc::TC_DOWNLINK_DATA_MODULATE): { + result = prepareTcDownlinkDataModulate(commandData, commandDataLen); + break; + } + case (mpsoc::TC_MODE_SNAPSHOT): { + result = prepareTcModeSnapshot(); + break; + } + default: + sif::debug << "PlocMPSoCHandler::buildCommandFromCommand: Command not implemented" + << std::endl; + result = DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + break; + } + + if (result == returnvalue::OK) { + /** + * Flushing the receive buffer to make sure there are no data left from a faulty reply. + */ + uartComIf->flushUartRxBuffer(comCookie); + } + + return result; +} + +void PlocMpsocHandler::fillCommandAndReplyMap() { + this->insertInCommandMap(mpsoc::TC_MEM_WRITE); + this->insertInCommandMap(mpsoc::TC_MEM_READ); + this->insertInCommandMap(mpsoc::TC_FLASHDELETE); + insertInCommandMap(mpsoc::TC_FLASH_WRITE_FULL_FILE); + insertInCommandMap(mpsoc::TC_FLASH_READ_FULL_FILE); + this->insertInCommandMap(mpsoc::TC_REPLAY_START); + this->insertInCommandMap(mpsoc::TC_REPLAY_STOP); + this->insertInCommandMap(mpsoc::TC_DOWNLINK_PWR_ON); + this->insertInCommandMap(mpsoc::TC_DOWNLINK_PWR_OFF); + this->insertInCommandMap(mpsoc::TC_REPLAY_WRITE_SEQUENCE); + this->insertInCommandMap(mpsoc::TC_MODE_REPLAY); + this->insertInCommandMap(mpsoc::TC_MODE_IDLE); + this->insertInCommandMap(mpsoc::TC_CAM_CMD_SEND); + this->insertInCommandMap(mpsoc::TC_GET_HK_REPORT); + this->insertInCommandMap(mpsoc::RELEASE_UART_TX); + this->insertInCommandMap(mpsoc::SET_UART_TX_TRISTATE); + this->insertInCommandMap(mpsoc::TC_CAM_TAKE_PIC); + this->insertInCommandMap(mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT); + this->insertInCommandMap(mpsoc::TC_SIMPLEX_SEND_FILE); + this->insertInCommandMap(mpsoc::TC_DOWNLINK_DATA_MODULATE); + this->insertInCommandMap(mpsoc::TC_MODE_SNAPSHOT); + this->insertInReplyMap(mpsoc::ACK_REPORT, 3, nullptr, mpsoc::SIZE_ACK_REPORT); + this->insertInReplyMap(mpsoc::EXE_REPORT, 3, nullptr, mpsoc::SIZE_EXE_REPORT); + this->insertInReplyMap(mpsoc::TM_MEMORY_READ_REPORT, 2, nullptr, mpsoc::SIZE_TM_MEM_READ_REPORT); + this->insertInReplyMap(mpsoc::TM_GET_HK_REPORT, 5, nullptr, mpsoc::SIZE_TM_HK_REPORT); + this->insertInReplyMap(mpsoc::TM_CAM_CMD_RPT, 2, nullptr, mpsoc::SP_MAX_SIZE); + this->insertInReplyMap(mpsoc::TM_FLASH_DIRECTORY_CONTENT, 2, nullptr, mpsoc::SP_MAX_SIZE); +} + +ReturnValue_t PlocMpsocHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + ReturnValue_t result = returnvalue::OK; + + SpacePacketReader spacePacket; + spacePacket.setReadOnlyData(start, remainingSize); + if (DEBUG_MPSOC_COMMUNICATION) { + sif::debug << "RECV MPSOC packet. APID 0x" << std::hex << std::setw(3) << spacePacket.getApid() + << std::dec << " Size " << spacePacket.getFullPacketLen() << " SSC " + << spacePacket.getSequenceCount() << std::endl; + } + if (spacePacket.isNull()) { + return returnvalue::FAILED; + } + auto res = spacePacket.checkSize(); + if (res != returnvalue::OK) { + return res; + } + uint16_t apid = spacePacket.getApid(); + + auto handleDedicatedReply = [&](DeviceCommandId_t replyId) { + *foundLen = spacePacket.getFullPacketLen(); + foundPacketLen = *foundLen; + *foundId = replyId; + }; + switch (apid) { + case (mpsoc::apid::ACK_SUCCESS): + *foundLen = mpsoc::SIZE_ACK_REPORT; + *foundId = mpsoc::ACK_REPORT; + break; + case (mpsoc::apid::ACK_FAILURE): + *foundLen = mpsoc::SIZE_ACK_REPORT; + *foundId = mpsoc::ACK_REPORT; + break; + case (mpsoc::apid::TM_MEMORY_READ_REPORT): + *foundLen = tmMemReadReport.rememberRequestedSize; + *foundId = mpsoc::TM_MEMORY_READ_REPORT; + break; + case (mpsoc::apid::TM_CAM_CMD_RPT): + handleDedicatedReply(mpsoc::TM_CAM_CMD_RPT); + break; + case (mpsoc::apid::TM_HK_GET_REPORT): { + handleDedicatedReply(mpsoc::TM_GET_HK_REPORT); + break; + } + case (mpsoc::apid::TM_FLASH_DIRECTORY_CONTENT): { + handleDedicatedReply(mpsoc::TM_FLASH_DIRECTORY_CONTENT); + break; + } + case (mpsoc::apid::EXE_SUCCESS): + *foundLen = mpsoc::SIZE_EXE_REPORT; + *foundId = mpsoc::EXE_REPORT; + break; + case (mpsoc::apid::EXE_FAILURE): + *foundLen = mpsoc::SIZE_EXE_REPORT; + *foundId = mpsoc::EXE_REPORT; + break; + default: { + sif::debug << "PlocMPSoCHandler::scanForReply: Reply has invalid APID 0x" << std::hex + << std::setfill('0') << std::setw(2) << apid << std::dec << std::endl; + *foundLen = remainingSize; + return mpsoc::INVALID_APID; + } + } + + uint16_t recvSeqCnt = ((*(start + 2) << 8) | *(start + 3)) & PACKET_SEQUENCE_COUNT_MASK; + if (recvSeqCnt != sequenceCount) { + triggerEvent(MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH, sequenceCount, recvSeqCnt); + sequenceCount = recvSeqCnt; + } + // This sequence count ping pong does not make any sense but it is how the MPSoC expects it. + sequenceCount++; + + return result; +} + +ReturnValue_t PlocMpsocHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) { + ReturnValue_t result = returnvalue::OK; + + switch (id) { + case mpsoc::ACK_REPORT: { + result = handleAckReport(packet); + break; + } + case (mpsoc::TM_MEMORY_READ_REPORT): { + result = handleMemoryReadReport(packet); + break; + } + case (mpsoc::TM_GET_HK_REPORT): { + result = handleGetHkReport(packet); + break; + } + case (mpsoc::TM_CAM_CMD_RPT): { + result = handleCamCmdRpt(packet); + break; + } + case (mpsoc::TM_FLASH_DIRECTORY_CONTENT): { + result = verifyPacket(packet, foundPacketLen); + if (result == mpsoc::CRC_FAILURE) { + sif::warning << "PLOC MPSoC: Flash directory content reply invalid CRC" << std::endl; + } + /** Send data to commanding queue */ + handleDeviceTm(packet + mpsoc::DATA_FIELD_OFFSET, + foundPacketLen - mpsoc::DATA_FIELD_OFFSET - mpsoc::CRC_SIZE, + mpsoc::TM_FLASH_DIRECTORY_CONTENT); + nextReplyId = mpsoc::EXE_REPORT; + return result; + } + case (mpsoc::EXE_REPORT): { + result = handleExecutionReport(packet); + break; + } + default: { + sif::debug << "PlocMPSoCHandler::interpretDeviceReply: Unknown device reply id" << std::endl; + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + + return result; +} + +void PlocMpsocHandler::setNormalDatapoolEntriesInvalid() { + PoolReadGuard pg(&hkReport); + hkReport.setValidity(false, true); +} + +uint32_t PlocMpsocHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 15000; } + +ReturnValue_t PlocMpsocHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(mpsoc::poolid::STATUS, &peStatus); + localDataPoolMap.emplace(mpsoc::poolid::MODE, &peMode); + localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_PWR_ON, &peDownlinkPwrOn); + localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_REPLY_ACTIIVE, &peDownlinkReplyActive); + localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_JESD_SYNC_STATUS, &peDownlinkJesdSyncStatus); + localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_DAC_STATUS, &peDownlinkDacStatus); + localDataPoolMap.emplace(mpsoc::poolid::CAM_STATUS, &peCameraStatus); + localDataPoolMap.emplace(mpsoc::poolid::CAM_SDI_STATUS, &peCameraSdiStatus); + localDataPoolMap.emplace(mpsoc::poolid::CAM_FPGA_TEMP, &peCameraFpgaTemp); + localDataPoolMap.emplace(mpsoc::poolid::CAM_SOC_TEMP, &peCameraSocTemp); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_TEMP, &peSysmonTemp); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCINT, &peSysmonVccInt); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCAUX, &peSysmonVccAux); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCBRAM, &peSysmonVccBram); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPAUX, &peSysmonVccPaux); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPINT, &peSysmonVccPint); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPDRO, &peSysmonVccPdro); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB12V, &peSysmonMb12V); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB3V3, &peSysmonMb3V3); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB1V8, &peSysmonMb1V8); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC12V, &peSysmonVcc12V); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC5V, &peSysmonVcc5V); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC3V3, &peSysmonVcc3V3); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC3V3VA, &peSysmonVcc3V3VA); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC2V5DDR, &peSysmonVcc2V5DDR); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC1V2DDR, &peSysmonVcc1V2DDR); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC0V9, &peSysmonVcc0V9); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC0V6VTT, &peSysmonVcc0V6VTT); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_SAFE_COTS_CUR, &peSysmonSafeCotsCur); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_NVM4_XO_CUR, &peSysmonNvm4XoCur); + localDataPoolMap.emplace(mpsoc::poolid::SEM_UNCORRECTABLE_ERRS, &peSemUncorrectableErrs); + localDataPoolMap.emplace(mpsoc::poolid::SEM_CORRECTABLE_ERRS, &peSemCorrectableErrs); + localDataPoolMap.emplace(mpsoc::poolid::SEM_STATUS, &peSemStatus); + localDataPoolMap.emplace(mpsoc::poolid::REBOOT_MPSOC_REQUIRED, &peRebootMpsocRequired); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(hkReport.getSid(), false, 10.0)); + return returnvalue::OK; +} + +void PlocMpsocHandler::handleEvent(EventMessage* eventMessage) { + object_id_t objectId = eventMessage->getReporter(); + switch (objectId) { + case objects::PLOC_MPSOC_HELPER: { + specialComHelperExecuting = false; + break; + } + default: + sif::debug << "PlocMPSoCHandler::handleEvent: Did not subscribe to this event" << std::endl; + break; + } +} + +ReturnValue_t PlocMpsocHandler::prepareTcMemWrite(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + mpsoc::TcMemWrite tcMemWrite(spParams, sequenceCount); + result = tcMemWrite.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcMemWrite); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcMemRead(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + mpsoc::TcMemRead tcMemRead(spParams, sequenceCount); + result = tcMemRead.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcMemRead); + tmMemReadReport.rememberRequestedSize = tcMemRead.getMemLen() * 4 + TmMemReadReport::FIX_SIZE; + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcFlashDelete(const uint8_t* commandData, + size_t commandDataLen) { + if (commandDataLen > config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE) { + return mpsoc::NAME_TOO_LONG; + } + ReturnValue_t result = returnvalue::OK; + mpsoc::TcFlashDelete tcFlashDelete(spParams, sequenceCount); + std::string filename = std::string(reinterpret_cast(commandData), commandDataLen); + result = tcFlashDelete.setPayload(filename); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcFlashDelete); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcReplayStart(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + mpsoc::TcReplayStart tcReplayStart(spParams, sequenceCount); + result = tcReplayStart.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcReplayStart); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcReplayStop() { + mpsoc::TcReplayStop tcReplayStop(spParams, sequenceCount); + finishTcPrep(tcReplayStop); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcDownlinkPwrOn(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + mpsoc::TcDownlinkPwrOn tcDownlinkPwrOn(spParams, sequenceCount); + result = tcDownlinkPwrOn.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcDownlinkPwrOn); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcDownlinkPwrOff() { + mpsoc::TcDownlinkPwrOff tcDownlinkPwrOff(spParams, sequenceCount); + finishTcPrep(tcDownlinkPwrOff); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcGetHkReport() { + mpsoc::TcGetHkReport tcGetHkReport(spParams, sequenceCount); + finishTcPrep(tcGetHkReport); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcReplayWriteSequence(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcReplayWriteSeq tcReplayWriteSeq(spParams, sequenceCount); + ReturnValue_t result = tcReplayWriteSeq.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcReplayWriteSeq); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcModeReplay() { + mpsoc::TcModeReplay tcModeReplay(spParams, sequenceCount); + finishTcPrep(tcModeReplay); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcModeIdle() { + mpsoc::TcModeIdle tcModeIdle(spParams, sequenceCount); + finishTcPrep(tcModeIdle); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcCamCmdSend(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcCamcmdSend tcCamCmdSend(spParams, sequenceCount); + ReturnValue_t result = tcCamCmdSend.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcCamCmdSend); + nextReplyId = mpsoc::TM_CAM_CMD_RPT; + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcCamTakePic(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcCamTakePic tcCamTakePic(spParams, sequenceCount); + ReturnValue_t result = tcCamTakePic.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcCamTakePic); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcSimplexSendFile(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcSimplexSendFile tcSimplexSendFile(spParams, sequenceCount); + ReturnValue_t result = tcSimplexSendFile.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcSimplexSendFile); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcGetDirContent(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcGetDirContent tcGetDirContent(spParams, sequenceCount); + ReturnValue_t result = tcGetDirContent.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcGetDirContent); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcDownlinkDataModulate(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcDownlinkDataModulate tcDownlinkDataModulate(spParams, sequenceCount); + ReturnValue_t result = tcDownlinkDataModulate.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(tcDownlinkDataModulate); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::prepareTcModeSnapshot() { + mpsoc::TcModeSnapshot tcModeSnapshot(spParams, sequenceCount); + finishTcPrep(tcModeSnapshot); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::finishTcPrep(mpsoc::TcBase& tcBase) { + nextReplyId = mpsoc::ACK_REPORT; + ReturnValue_t result = tcBase.finishPacket(); + if (result != returnvalue::OK) { + return result; + } + rawPacket = commandBuffer; + rawPacketLen = tcBase.getFullPacketLen(); + sequenceCount++; + + if (DEBUG_MPSOC_COMMUNICATION) { + sif::debug << "SEND MPSOC packet. APID 0x" << std::hex << std::setw(3) << tcBase.getApid() + << " Size " << std::dec << tcBase.getFullPacketLen() << " SSC " + << tcBase.getSeqCount() << std::endl; + } + cmdCountdown.resetTimer(); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::verifyPacket(const uint8_t* start, size_t foundLen) { + if (CRC::crc16ccitt(start, foundLen) != 0) { + return mpsoc::CRC_FAILURE; + } + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::handleAckReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, mpsoc::SIZE_ACK_REPORT); + if (result == mpsoc::CRC_FAILURE) { + sif::warning << "PlocMPSoCHandler::handleAckReport: CRC failure" << std::endl; + nextReplyId = mpsoc::NONE; + replyRawReplyIfnotWiretapped(data, mpsoc::SIZE_ACK_REPORT); + triggerEvent(MPSOC_HANDLER_CRC_FAILURE); + sendFailureReport(mpsoc::ACK_REPORT, mpsoc::CRC_FAILURE); + disableAllReplies(); + return IGNORE_REPLY_DATA; + } + + uint16_t apid = (*(data) << 8 | *(data + 1)) & APID_MASK; + + switch (apid) { + case mpsoc::apid::ACK_FAILURE: { + DeviceCommandId_t commandId = getPendingCommand(); + uint16_t status = mpsoc::getStatusFromRawData(data); + sif::warning << "MPSoC ACK Failure: " << mpsoc::getStatusString(status) << std::endl; + if (commandId != DeviceHandlerIF::NO_COMMAND_ID) { + triggerEvent(ACK_FAILURE, commandId, status); + } + sendFailureReport(mpsoc::ACK_REPORT, status); + disableAllReplies(); + nextReplyId = mpsoc::NONE; + result = IGNORE_REPLY_DATA; + break; + } + case mpsoc::apid::ACK_SUCCESS: { + setNextReplyId(); + break; + } + default: { + sif::debug << "PlocMPSoCHandler::handleAckReport: Invalid APID in Ack report" << std::endl; + result = returnvalue::FAILED; + break; + } + } + + return result; +} + +ReturnValue_t PlocMpsocHandler::handleExecutionReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, mpsoc::SIZE_EXE_REPORT); + if (result == mpsoc::CRC_FAILURE) { + sif::warning << "PlocMPSoCHandler::handleExecutionReport: CRC failure" << std::endl; + nextReplyId = mpsoc::NONE; + return result; + } + + uint16_t apid = (*(data) << 8 | *(data + 1)) & APID_MASK; + + switch (apid) { + case (mpsoc::apid::EXE_SUCCESS): { + cmdDoneHandler(true, result); + break; + } + case (mpsoc::apid::EXE_FAILURE): { + DeviceCommandId_t commandId = getPendingCommand(); + if (commandId == DeviceHandlerIF::NO_COMMAND_ID) { + sif::debug << "PlocMPSoCHandler::handleExecutionReport: Unknown command id" << std::endl; + } + uint16_t status = mpsoc::getStatusFromRawData(data); + sif::warning << "MPSoC EXE Failure: " << mpsoc::getStatusString(status) << std::endl; + triggerEvent(EXE_FAILURE, commandId, status); + sendFailureReport(mpsoc::EXE_REPORT, mpsoc::RECEIVED_EXE_FAILURE); + result = IGNORE_REPLY_DATA; + cmdDoneHandler(false, mpsoc::RECEIVED_EXE_FAILURE); + break; + } + default: { + sif::warning << "PlocMPSoCHandler::handleExecutionReport: Unknown APID" << std::endl; + result = returnvalue::FAILED; + break; + } + } + nextReplyId = mpsoc::NONE; + return result; +} + +ReturnValue_t PlocMpsocHandler::handleMemoryReadReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + result = verifyPacket(data, tmMemReadReport.rememberRequestedSize); + if (result == mpsoc::CRC_FAILURE) { + sif::warning << "PlocMPSoCHandler::handleMemoryReadReport: Memory read report has invalid crc" + << std::endl; + } + uint16_t memLen = + *(data + mpsoc::MEM_READ_RPT_LEN_OFFSET) << 8 | *(data + mpsoc::MEM_READ_RPT_LEN_OFFSET + 1); + /** Send data to commanding queue */ + handleDeviceTm(data + mpsoc::DATA_FIELD_OFFSET, mpsoc::SIZE_MEM_READ_RPT_FIX + memLen * 4, + mpsoc::TM_MEMORY_READ_REPORT); + nextReplyId = mpsoc::EXE_REPORT; + return result; +} + +ReturnValue_t PlocMpsocHandler::handleGetHkReport(const uint8_t* data) { + ReturnValue_t result = verifyPacket(data, foundPacketLen); + if (result != returnvalue::OK) { + return result; + } + SpacePacketReader packetReader(data, foundPacketLen); + const uint8_t* dataStart = data + 6; + PoolReadGuard pg(&hkReport); + size_t deserLen = mpsoc::SIZE_TM_HK_REPORT; + SerializeIF::Endianness endianness = SerializeIF::Endianness::NETWORK; + result = SerializeAdapter::deSerialize(&hkReport.status.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.mode.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.downlinkPwrOn.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.downlinkReplyActive.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.downlinkJesdSyncStatus.value, &dataStart, + &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.downlinkDacStatus.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.camStatus.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.camSdiStatus.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.camFpgaTemp.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonTemp.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccInt.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccAux.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccBram.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPaux.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPint.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPdro.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonMb12V.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonMb3V3.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonMb1V8.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc12V.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonVcc5V.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc3V3.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc3V3VA.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc2V5DDR.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc1V2DDR.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc0V9.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc0V6VTT.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonSafeCotsCur.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonNvm4XoCur.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.semUncorrectableErrs.value, &dataStart, + &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.semCorrectableErrs.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.semStatus.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + // Skip the weird filename + dataStart += 256; + result = SerializeAdapter::deSerialize(&hkReport.rebootMpsocRequired, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + hkReport.setValidity(true, true); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocHandler::handleCamCmdRpt(const uint8_t* data) { + ReturnValue_t result = verifyPacket(data, foundPacketLen); + if (result == mpsoc::CRC_FAILURE) { + sif::warning << "PlocMPSoCHandler::handleCamCmdRpt: CRC failure" << std::endl; + } + SpacePacketReader packetReader(data, foundPacketLen); + const uint8_t* dataFieldPtr = data + mpsoc::SPACE_PACKET_HEADER_SIZE + sizeof(uint16_t); + std::string camCmdRptMsg(reinterpret_cast(dataFieldPtr), + foundPacketLen - mpsoc::SPACE_PACKET_HEADER_SIZE - sizeof(uint16_t) - 3); +#if OBSW_DEBUG_PLOC_MPSOC == 1 + uint8_t ackValue = *(packetReader.getFullData() + packetReader.getFullPacketLen() - 2); + sif::info << "PlocMPSoCHandler: CamCmdRpt message: " << camCmdRptMsg << std::endl; + sif::info << "PlocMPSoCHandler: CamCmdRpt Ack value: 0x" << std::hex + << static_cast(ackValue) << std::endl; +#endif /* OBSW_DEBUG_PLOC_MPSOC == 1 */ + handleDeviceTm(packetReader.getPacketData() + sizeof(uint16_t), + packetReader.getPacketDataLen() - 1, mpsoc::TM_CAM_CMD_RPT); + return result; +} + +ReturnValue_t PlocMpsocHandler::enableReplyInReplyMap(DeviceCommandMap::iterator command, + uint8_t expectedReplies, bool useAlternateId, + DeviceCommandId_t alternateReplyID) { + ReturnValue_t result = returnvalue::OK; + + uint8_t enabledReplies = 0; + + auto enableThreeReplies = [&](DeviceCommandId_t replyId) { + enabledReplies = 3; + result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, replyId); + if (result != returnvalue::OK) { + sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Reply with id " + << mpsoc::TM_MEMORY_READ_REPORT << " not in replyMap" << std::endl; + return result; + } + return returnvalue::OK; + }; + switch (command->first) { + case mpsoc::TC_MEM_WRITE: + case mpsoc::TC_FLASHDELETE: + case mpsoc::TC_REPLAY_START: + case mpsoc::TC_REPLAY_STOP: + case mpsoc::TC_DOWNLINK_PWR_ON: + case mpsoc::TC_DOWNLINK_PWR_OFF: + case mpsoc::TC_REPLAY_WRITE_SEQUENCE: + case mpsoc::TC_MODE_REPLAY: + case mpsoc::TC_MODE_IDLE: + case mpsoc::TC_CAM_TAKE_PIC: + case mpsoc::TC_SIMPLEX_SEND_FILE: + case mpsoc::TC_DOWNLINK_DATA_MODULATE: + case mpsoc::TC_MODE_SNAPSHOT: + enabledReplies = 2; + break; + case mpsoc::TC_GET_HK_REPORT: { + result = enableThreeReplies(mpsoc::TM_GET_HK_REPORT); + if (result != returnvalue::OK) { + return result; + } + break; + } + case mpsoc::TC_MEM_READ: { + result = enableThreeReplies(mpsoc::TM_MEMORY_READ_REPORT); + if (result != returnvalue::OK) { + return result; + } + break; + } + case mpsoc::TC_CAM_CMD_SEND: { + result = enableThreeReplies(mpsoc::TM_CAM_CMD_RPT); + if (result != returnvalue::OK) { + return result; + } + break; + } + case mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT: { + result = enableThreeReplies(mpsoc::TM_FLASH_DIRECTORY_CONTENT); + if (result != returnvalue::OK) { + return result; + } + break; + } + case mpsoc::OBSW_RESET_SEQ_COUNT: + break; + default: + sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Unknown command id" << std::endl; + break; + } + + /** + * Every command causes at least one acknowledgment and one execution report. Therefore both + * replies will be enabled here. + */ + result = + DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, mpsoc::ACK_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Reply with id " << mpsoc::ACK_REPORT + << " not in replyMap" << std::endl; + } + + result = + DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, mpsoc::EXE_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocMPSoCHandler::enableReplyInReplyMap: Reply with id " << mpsoc::EXE_REPORT + << " not in replyMap" << std::endl; + } + + switch (command->first) { + case mpsoc::TC_REPLAY_WRITE_SEQUENCE: { + DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT); + // Overwrite delay cycles because replay write sequence command can required up to + // 30 seconds for execution + iter->second.delayCycles = mpsoc::TC_WRITE_SEQ_EXECUTION_DELAY; + break; + } + case mpsoc::TC_DOWNLINK_PWR_ON: { + DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT); + iter->second.delayCycles = mpsoc::TC_DOWNLINK_PWR_ON_EXECUTION_DELAY; + break; + } + case mpsoc::TC_CAM_TAKE_PIC: { + DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT); + iter->second.delayCycles = mpsoc::TC_CAM_TAKE_PIC_EXECUTION_DELAY; + break; + } + case mpsoc::TC_SIMPLEX_SEND_FILE: { + DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT); + iter->second.delayCycles = mpsoc::TC_SIMPLEX_SEND_FILE_DELAY; + break; + } + default: + break; + } + + return returnvalue::OK; +} + +void PlocMpsocHandler::setNextReplyId() { + switch (getPendingCommand()) { + case mpsoc::TC_MEM_READ: + nextReplyId = mpsoc::TM_MEMORY_READ_REPORT; + break; + case mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT: { + nextReplyId = mpsoc::TM_FLASH_DIRECTORY_CONTENT; + break; + } + case mpsoc::TC_GET_HK_REPORT: { + nextReplyId = mpsoc::TM_GET_HK_REPORT; + break; + } + default: + /* If no telemetry is expected the next reply is always the execution report */ + nextReplyId = mpsoc::EXE_REPORT; + break; + } +} + +size_t PlocMpsocHandler::getNextReplyLength(DeviceCommandId_t commandId) { + size_t replyLen = 0; + + if (nextReplyId == mpsoc::NONE) { + return replyLen; + } + + DeviceReplyIter iter = deviceReplyMap.find(nextReplyId); + + if (iter != deviceReplyMap.end()) { + if (iter->second.delayCycles == 0) { + /* Reply inactive */ + return replyLen; + } + switch (nextReplyId) { + case mpsoc::TM_MEMORY_READ_REPORT: { + replyLen = tmMemReadReport.rememberRequestedSize; + break; + } + case mpsoc::TM_CAM_CMD_RPT: + // Read acknowledgment, camera and execution report in one go because length of camera + // report is not fixed + replyLen = mpsoc::SP_MAX_SIZE; + break; + case mpsoc::TM_FLASH_DIRECTORY_CONTENT: + // I think the reply size is not fixed either. + replyLen = mpsoc::SP_MAX_SIZE; + break; + default: { + replyLen = iter->second.replyLen; + break; + } + } + } else { + sif::debug << "PlocMPSoCHandler::getNextReplyLength: No entry for reply with reply id " + << std::hex << nextReplyId << " in deviceReplyMap" << std::endl; + } + + return replyLen; +} + +ReturnValue_t PlocMpsocHandler::doSendReadHook() { + // Prevent DHB from polling UART during commands executed by the mpsoc helper task + if (specialComHelperExecuting) { + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +MessageQueueIF* PlocMpsocHandler::getCommandQueuePtr() { return commandActionHelperQueue; } + +void PlocMpsocHandler::stepSuccessfulReceived(ActionId_t actionId, uint8_t step) { return; } + +void PlocMpsocHandler::stepFailedReceived(ActionId_t actionId, uint8_t step, + ReturnValue_t returnCode) { + switch (actionId) { + case supv::START_MPSOC: { + sif::warning << "PlocMPSoCHandler::stepFailedReceived: Failed to start MPSoC" << std::endl; + break; + } + case supv::SHUTDOWN_MPSOC: { + triggerEvent(MPSOC_SHUTDOWN_FAILED); + sif::warning << "PlocMPSoCHandler::stepFailedReceived: Failed to shutdown MPSoC" << std::endl; + break; + } + default: + sif::debug << "PlocMPSoCHandler::stepFailedReceived: Received unexpected action reply" + << std::endl; + break; + } + powerState = PowerState::SUPV_FAILED; +} + +void PlocMpsocHandler::dataReceived(ActionId_t actionId, const uint8_t* data, uint32_t size) { + return; +} + +void PlocMpsocHandler::completionSuccessfulReceived(ActionId_t actionId) { + if (actionId == supv::ACK_REPORT) { + // I seriously don't know why this happens.. + // sif::warning + // << "PlocMpsocHandler::completionSuccessfulReceived: Only received ACK report. Consider + // " + // "increasing the MPSoC boot timer." + // << std::endl; + } else if (actionId != supv::EXE_REPORT) { + sif::warning << "PlocMpsocHandler::completionSuccessfulReceived: Did not expect the action " + << "ID " << actionId << std::endl; + return; + } + switch (powerState) { + case PowerState::PENDING_STARTUP: { + mpsocBootTransitionCd.resetTimer(); + powerState = PowerState::DONE; + break; + } + case PowerState::PENDING_SHUTDOWN: { + powerState = PowerState::DONE; + break; + } + default: { + break; + } + } +} + +void PlocMpsocHandler::completionFailedReceived(ActionId_t actionId, ReturnValue_t returnCode) { + handleActionCommandFailure(actionId); +} + +void PlocMpsocHandler::handleDeviceTm(const uint8_t* data, size_t dataSize, + DeviceCommandId_t replyId) { + ReturnValue_t result = returnvalue::OK; + + if (wiretappingMode == RAW) { + /* Data already sent in doGetRead() */ + return; + } + + DeviceReplyMap::iterator iter = deviceReplyMap.find(replyId); + if (iter == deviceReplyMap.end()) { + sif::debug << "PlocMPSoCHandler::handleDeviceTM: Unknown reply id" << std::endl; + return; + } + MessageQueueId_t queueId = iter->second.command->second.sendReplyTo; + + if (queueId == NO_COMMANDER) { + return; + } + + result = actionHelper.reportData(queueId, replyId, data, dataSize); + if (result != returnvalue::OK) { + sif::debug << "PlocMPSoCHandler::handleDeviceTM: Failed to report data" << std::endl; + } +} + +void PlocMpsocHandler::disableAllReplies() { + using namespace mpsoc; + DeviceReplyMap::iterator iter; + + /* Disable ack reply */ + iter = deviceReplyMap.find(ACK_REPORT); + DeviceReplyInfo* info = &(iter->second); + info->delayCycles = 0; + info->command = deviceCommandMap.end(); + + DeviceCommandId_t commandId = getPendingCommand(); + + auto disableCommandWithReply = [&](DeviceCommandId_t replyId) { + iter = deviceReplyMap.find(replyId); + info = &(iter->second); + info->delayCycles = 0; + info->active = false; + info->command = deviceCommandMap.end(); + }; + /* If the command expects a telemetry packet the appropriate tm reply will be disabled here */ + switch (commandId) { + case TC_MEM_WRITE: + case TC_FLASHDELETE: + case TC_REPLAY_START: + case TC_REPLAY_STOP: + case TC_DOWNLINK_PWR_ON: + case TC_DOWNLINK_PWR_OFF: + case TC_REPLAY_WRITE_SEQUENCE: + case TC_MODE_REPLAY: + case TC_MODE_IDLE: + case TC_CAM_TAKE_PIC: + case TC_SIMPLEX_SEND_FILE: + case TC_DOWNLINK_DATA_MODULATE: + case TC_MODE_SNAPSHOT: + break; + case TC_MEM_READ: { + disableCommandWithReply(TM_MEMORY_READ_REPORT); + break; + } + case TC_GET_HK_REPORT: { + disableCommandWithReply(TM_GET_HK_REPORT); + break; + } + case TC_FLASH_GET_DIRECTORY_CONTENT: { + disableCommandWithReply(TM_FLASH_DIRECTORY_CONTENT); + break; + } + case TC_CAM_CMD_SEND: { + disableCommandWithReply(TM_CAM_CMD_RPT); + break; + } + default: { + sif::debug << "PlocMPSoCHandler::disableAllReplies: Unknown command id: " << commandId + << std::endl; + break; + } + } + + /* We always need to disable the execution report reply here */ + disableExeReportReply(); + nextReplyId = mpsoc::NONE; +} + +void PlocMpsocHandler::sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status) { + DeviceReplyIter iter = deviceReplyMap.find(replyId); + if (iter == deviceReplyMap.end()) { + sif::debug << "PlocMPSoCHandler::sendFailureReport: Reply not in reply map" << std::endl; + return; + } + DeviceCommandInfo* info = &(iter->second.command->second); + if (info == nullptr) { + sif::debug << "PlocMPSoCHandler::sendFailureReport: Reply has no active command" << std::endl; + return; + } + if (info->sendReplyTo != NO_COMMANDER) { + actionHelper.finish(false, info->sendReplyTo, iter->first, status); + } + info->isExecuting = false; +} + +void PlocMpsocHandler::disableExeReportReply() { + DeviceReplyIter iter = deviceReplyMap.find(mpsoc::EXE_REPORT); + DeviceReplyInfo* info = &(iter->second); + info->delayCycles = 0; + info->command = deviceCommandMap.end(); + /* Expected replies is set to one here. The value will be set to 0 in replyToReply() */ + info->command->second.expectedReplies = 0; +} + +void PlocMpsocHandler::stopSpecialComHelper() { + if (specialComHelper != nullptr) { + specialComHelper->stopProcess(); + } + specialComHelperExecuting = false; +} + +bool PlocMpsocHandler::handleHwStartup() { +#if OBSW_MPSOC_JTAG_BOOT == 1 + uartIsolatorSwitch.pullHigh(); + startupState = StartupState::WAIT_CYCLES; + return true; +#endif + if (powerState == PowerState::IDLE) { + if (skipSupvCommandingToOn) { + powerState = PowerState::DONE; + } else { + if (supv::SUPV_ON) { + commandActionHelper.commandAction(supervisorHandler, supv::START_MPSOC); + supvTransitionCd.resetTimer(); + powerState = PowerState::PENDING_STARTUP; + } else { + triggerEvent(SUPV_NOT_ON, 1); + // Set back to OFF for now, failing the transition. + setMode(MODE_OFF); + } + } + } + if (powerState == PowerState::SUPV_FAILED) { + setMode(MODE_OFF); + powerState = PowerState::IDLE; + return false; + } + if (powerState == PowerState::PENDING_STARTUP) { + if (supvTransitionCd.hasTimedOut()) { + // Process with transition nonetheless.. + triggerEvent(SUPV_REPLY_TIMEOUT); + powerState = PowerState::DONE; + } else { + return false; + } + } + if (powerState == PowerState::DONE) { + if (mpsocBootTransitionCd.hasTimedOut()) { + // Wait a bit for the MPSoC to fully boot. + uartIsolatorSwitch.pullHigh(); + powerState = PowerState::IDLE; + } else { + return false; + } + } + return true; +} + +bool PlocMpsocHandler::handleHwShutdown() { + stopSpecialComHelper(); + uartIsolatorSwitch.pullLow(); +#if OBSW_MPSOC_JTAG_BOOT == 1 + powerState = PowerState::DONE; + return true; +#endif + + if (powerState == PowerState::IDLE) { + if (supv::SUPV_ON) { + commandActionHelper.commandAction(supervisorHandler, supv::SHUTDOWN_MPSOC); + supvTransitionCd.resetTimer(); + powerState = PowerState::PENDING_SHUTDOWN; + } else { + triggerEvent(SUPV_NOT_ON, 0); + powerState = PowerState::DONE; + } + } + if (powerState == PowerState::PENDING_SHUTDOWN) { + if (supvTransitionCd.hasTimedOut()) { + powerState = PowerState::DONE; + // Process with transition nonetheless.. + triggerEvent(SUPV_REPLY_TIMEOUT); + return true; + } else { + // Wait till power state is OFF. + return false; + } + } + return true; +} + +void PlocMpsocHandler::handleActionCommandFailure(ActionId_t actionId) { + switch (actionId) { + case supv::ACK_REPORT: + case supv::EXE_REPORT: + break; + default: + sif::warning << "PlocMPSoCHandler::handleActionCommandFailure: Did not expect the action ID " + << actionId << std::endl; + return; + } + switch (powerState) { + case PowerState::PENDING_STARTUP: { + sif::info << "PlocMPSoCHandler::handleActionCommandFailure: MPSoC boot command failed" + << std::endl; + // This is commonly the case when the MPSoC is already operational. Thus the power state is + // set to on here + break; + } + case PowerState::PENDING_SHUTDOWN: { + // FDIR will intercept event and switch PLOC power off + triggerEvent(MPSOC_SHUTDOWN_FAILED); + sif::warning << "PlocMPSoCHandler::handleActionCommandFailure: Failed to shutdown MPSoC" + << std::endl; + break; + } + default: + break; + } + powerState = PowerState::SUPV_FAILED; + return; +} + +LocalPoolDataSetBase* PlocMpsocHandler::getDataSetHandle(sid_t sid) { + if (sid == hkReport.getSid()) { + return &hkReport; + } + return nullptr; +} + +bool PlocMpsocHandler::dontCheckQueue() { + // The TC and TMs need to be handled strictly sequentially, so while a command is pending, + // more specifically while replies are still expected, do not check the queue. + return commandIsPending; +} + +void PlocMpsocHandler::cmdDoneHandler(bool success, ReturnValue_t result) { + commandIsPending = false; + auto commandIter = deviceCommandMap.find(getPendingCommand()); + if (commandIter != deviceCommandMap.end()) { + commandIter->second.isExecuting = false; + if (commandIter->second.sendReplyTo != MessageQueueIF::NO_QUEUE) { + actionHelper.finish(success, commandIter->second.sendReplyTo, getPendingCommand(), result); + } + } + disableAllReplies(); +} + +ReturnValue_t PlocMpsocHandler::checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t* msToReachTheMode) { + if (commandedMode != MODE_OFF) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) { + return NON_OP_STATE_OF_CHARGE; + } + } + } + return DeviceHandlerBase::checkModeCommand(commandedMode, commandedSubmode, msToReachTheMode); +} + +ReturnValue_t PlocMpsocHandler::getParameter(uint8_t domainId, uint8_t uniqueId, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, + uint16_t startAtIndex) { + if (uniqueId == mpsoc::ParamId::SKIP_SUPV_ON_COMMANDING) { + uint8_t value = 0; + newValues->getElement(&value); + if (value > 1) { + return HasParametersIF::INVALID_VALUE; + } + parameterWrapper->set(skipSupvCommandingToOn); + return returnvalue::OK; + } + return DeviceHandlerBase::getParameter(domainId, uniqueId, parameterWrapper, newValues, + startAtIndex); +} diff --git a/archive/PlocMpsocHandler.h b/archive/PlocMpsocHandler.h new file mode 100644 index 0000000..f3f2485 --- /dev/null +++ b/archive/PlocMpsocHandler.h @@ -0,0 +1,322 @@ +#ifndef BSP_Q7S_DEVICES_PLOC_PLOCMPSOCHANDLER_H_ +#define BSP_Q7S_DEVICES_PLOC_PLOCMPSOCHANDLER_H_ + +#include +#include +#include +#include + +#include "fsfw/action/CommandActionHelper.h" +#include "fsfw/action/CommandsActionsIF.h" +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "fsfw/tmtcservices/SourceSequenceCounter.h" +#include "fsfw_hal/linux/gpio/Gpio.h" +#include "fsfw_hal/linux/serial/SerialComIF.h" + +static constexpr bool DEBUG_MPSOC_COMMUNICATION = true; + +/** + * @brief This is the device handler for the MPSoC of the payload computer. + * + * @details The PLOC uses the space packet protocol for communication. Each command will be + * answered with at least one acknowledgment and one execution report. + * Flight manual: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/PLOC_MPSoC ICD: + * https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_TAS-ILH-IRS/ICD-PLOC/ILH&fileid=1030263 + * + * @note The sequence count in the space packets must be incremented with each received and sent + * packet otherwise the MPSoC will reply with an acknowledgment failure report. + * + * NOTE: This is not an example for a good device handler, DO NOT USE THIS AS A REFERENCE HANDLER. + * @author J. Meier, R. Mueller + */ +class PlocMpsocHandler : public DeviceHandlerBase, public CommandsActionsIF { + public: + /** + * @brief Constructor + * + * @param ojectId Object ID of the MPSoC handler + * @param uartcomIFid Object ID of the UART communication interface + * @param comCookie UART communication cookie + * @param plocMPSoCHelper Pointer to MPSoC helper object + * @param uartIsolatorSwitch Gpio object representing the GPIO connected to the UART isolator + * module in the programmable logic + * @param supervisorHandler Object ID of the supervisor handler + */ + PlocMpsocHandler(object_id_t objectId, object_id_t uartComIFid, CookieIF* comCookie, + PlocMpsocSpecialComHelperLegacy* plocMPSoCHelper, Gpio uartIsolatorSwitch, + object_id_t supervisorHandler); + virtual ~PlocMpsocHandler(); + virtual ReturnValue_t initialize() override; + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + void performOperationHook() override; + MessageQueueIF* getCommandQueuePtr() override; + void stepSuccessfulReceived(ActionId_t actionId, uint8_t step) override; + void stepFailedReceived(ActionId_t actionId, uint8_t step, ReturnValue_t returnCode) override; + void dataReceived(ActionId_t actionId, const uint8_t* data, uint32_t size) override; + void completionSuccessfulReceived(ActionId_t actionId) override; + void completionFailedReceived(ActionId_t actionId, ReturnValue_t returnCode) override; + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + void setNormalDatapoolEntriesInvalid() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + ReturnValue_t enableReplyInReplyMap(DeviceCommandMap::iterator command, + uint8_t expectedReplies = 1, bool useAlternateId = false, + DeviceCommandId_t alternateReplyID = 0) override; + size_t getNextReplyLength(DeviceCommandId_t deviceCommand) override; + ReturnValue_t doSendReadHook() override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + bool dontCheckQueue() override; + + private: + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_MPSOC_HANDLER; + + //! [EXPORT] : [COMMENT] PLOC crc failure in telemetry packet + static const Event MEMORY_READ_RPT_CRC_FAILURE = MAKE_EVENT(1, severity::LOW); + //! [EXPORT] : [COMMENT] PLOC receive acknowledgment failure report + //! P1: Command Id which leads the acknowledgment failure report + //! P2: The status field inserted by the MPSoC into the data field + static const Event ACK_FAILURE = MAKE_EVENT(2, severity::LOW); + //! [EXPORT] : [COMMENT] PLOC receive execution failure report + //! P1: Command Id which leads the execution failure report + //! P2: The status field inserted by the MPSoC into the data field + static const Event EXE_FAILURE = MAKE_EVENT(3, severity::LOW); + //! [EXPORT] : [COMMENT] PLOC reply has invalid crc + static const Event MPSOC_HANDLER_CRC_FAILURE = MAKE_EVENT(4, severity::LOW); + //! [EXPORT] : [COMMENT] Packet sequence count in received space packet does not match expected + //! count P1: Expected sequence count P2: Received sequence count + static const Event MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH = MAKE_EVENT(5, severity::LOW); + //! [EXPORT] : [COMMENT] Supervisor fails to shutdown MPSoC. Requires to power off the PLOC and + //! thus also to shutdown the supervisor. + static const Event MPSOC_SHUTDOWN_FAILED = MAKE_EVENT(6, severity::HIGH); + //! [EXPORT] : [COMMENT] SUPV not on for boot or shutdown process. P1: 0 for OFF transition, 1 for + //! ON transition. + static constexpr Event SUPV_NOT_ON = event::makeEvent(SUBSYSTEM_ID, 7, severity::LOW); + static constexpr Event SUPV_REPLY_TIMEOUT = event::makeEvent(SUBSYSTEM_ID, 8, severity::LOW); + + static const uint16_t APID_MASK = 0x7FF; + static const uint16_t PACKET_SEQUENCE_COUNT_MASK = 0x3FFF; + + mpsoc::HkReport hkReport; + Countdown mpsocBootTransitionCd = Countdown(6500); + Countdown supvTransitionCd = Countdown(3000); + + MessageQueueIF* eventQueue = nullptr; + MessageQueueIF* commandActionHelperQueue = nullptr; + + SourceSequenceCounter sequenceCount = SourceSequenceCounter(0); + + uint8_t commandBuffer[mpsoc::MAX_COMMAND_SIZE]; + SpacePacketCreator creator; + ploc::SpTcParams spParams = ploc::SpTcParams(creator); + + PoolEntry peStatus = PoolEntry(); + PoolEntry peMode = PoolEntry(); + PoolEntry peDownlinkPwrOn = PoolEntry(); + PoolEntry peDownlinkReplyActive = PoolEntry(); + PoolEntry peDownlinkJesdSyncStatus = PoolEntry(); + PoolEntry peDownlinkDacStatus = PoolEntry(); + PoolEntry peCameraStatus = PoolEntry(); + PoolEntry peCameraSdiStatus = PoolEntry(); + PoolEntry peCameraFpgaTemp = PoolEntry(); + PoolEntry peCameraSocTemp = PoolEntry(); + PoolEntry peSysmonTemp = PoolEntry(); + PoolEntry peSysmonVccInt = PoolEntry(); + PoolEntry peSysmonVccAux = PoolEntry(); + PoolEntry peSysmonVccBram = PoolEntry(); + PoolEntry peSysmonVccPaux = PoolEntry(); + PoolEntry peSysmonVccPint = PoolEntry(); + PoolEntry peSysmonVccPdro = PoolEntry(); + PoolEntry peSysmonMb12V = PoolEntry(); + PoolEntry peSysmonMb3V3 = PoolEntry(); + PoolEntry peSysmonMb1V8 = PoolEntry(); + PoolEntry peSysmonVcc12V = PoolEntry(); + PoolEntry peSysmonVcc5V = PoolEntry(); + PoolEntry peSysmonVcc3V3 = PoolEntry(); + PoolEntry peSysmonVcc3V3VA = PoolEntry(); + PoolEntry peSysmonVcc2V5DDR = PoolEntry(); + PoolEntry peSysmonVcc1V2DDR = PoolEntry(); + PoolEntry peSysmonVcc0V9 = PoolEntry(); + PoolEntry peSysmonVcc0V6VTT = PoolEntry(); + PoolEntry peSysmonSafeCotsCur = PoolEntry(); + PoolEntry peSysmonNvm4XoCur = PoolEntry(); + PoolEntry peSemUncorrectableErrs = PoolEntry(); + PoolEntry peSemCorrectableErrs = PoolEntry(); + PoolEntry peSemStatus = PoolEntry(); + PoolEntry peRebootMpsocRequired = PoolEntry(); + + /** + * This variable is used to store the id of the next reply to receive. This is necessary + * because the PLOC sends as reply to each command at least one acknowledgment and execution + * report. + */ + DeviceCommandId_t nextReplyId = mpsoc::NONE; + + SerialComIF* uartComIf = nullptr; + + PlocMpsocSpecialComHelperLegacy* specialComHelper = nullptr; + Gpio uartIsolatorSwitch; + object_id_t supervisorHandler = 0; + CommandActionHelper commandActionHelper; + + // Used to block incoming commands when MPSoC helper class is currently executing a command + bool specialComHelperExecuting = false; + bool commandIsPending = false; + + struct TmMemReadReport { + static const uint8_t FIX_SIZE = 14; + size_t rememberRequestedSize = 0; + }; + + TmMemReadReport tmMemReadReport; + Countdown cmdCountdown = Countdown(15000); + + struct TelemetryBuffer { + uint16_t length = 0; + uint8_t buffer[mpsoc::SP_MAX_SIZE]; + }; + + size_t foundPacketLen = 0; + TelemetryBuffer tmBuffer; + + enum class StartupState { IDLE, HW_INIT, DONE } startupState = StartupState::IDLE; + enum class PowerState { IDLE, PENDING_STARTUP, PENDING_SHUTDOWN, SUPV_FAILED, DONE }; + + PowerState powerState = PowerState::IDLE; + + uint8_t skipSupvCommandingToOn = false; + + /** + * @brief Handles events received from the PLOC MPSoC helper + */ + void handleEvent(EventMessage* eventMessage); + + ReturnValue_t prepareTcMemWrite(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcMemRead(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcFlashDelete(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcReplayStart(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcReplayStop(); + ReturnValue_t prepareTcDownlinkPwrOn(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcDownlinkPwrOff(); + ReturnValue_t prepareTcGetHkReport(); + ReturnValue_t prepareTcGetDirContent(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcReplayWriteSequence(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcCamCmdSend(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcModeIdle(); + ReturnValue_t prepareTcCamTakePic(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcSimplexSendFile(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcDownlinkDataModulate(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareTcModeSnapshot(); + ReturnValue_t finishTcPrep(mpsoc::TcBase& tcBase); + + /** + * @brief This function checks the crc of the received PLOC reply. + * + * @param start Pointer to the first byte of the reply. + * @param foundLen Pointer to the length of the whole packet. + * + * @return returnvalue::OK if CRC is ok, otherwise CRC_FAILURE. + */ + ReturnValue_t verifyPacket(const uint8_t* start, size_t foundLen); + + /** + * @brief This function handles the acknowledgment report. + * + * @param data Pointer to the data holding the acknowledgment report. + * + * @return returnvalue::OK if successful, otherwise an error code. + */ + ReturnValue_t handleAckReport(const uint8_t* data); + + /** + * @brief This function handles the data of a execution report. + * + * @param data Pointer to the received data packet. + * + * @return returnvalue::OK if successful, otherwise an error code. + */ + ReturnValue_t handleExecutionReport(const uint8_t* data); + + /** + * @brief This function handles the memory read report. + * + * @param data Pointer to the data buffer holding the memory read report. + * + * @return returnvalue::OK if successful, otherwise an error code. + */ + ReturnValue_t handleMemoryReadReport(const uint8_t* data); + + ReturnValue_t handleGetHkReport(const uint8_t* data); + ReturnValue_t handleCamCmdRpt(const uint8_t* data); + + /** + * @brief Depending on the current active command, this function sets the reply id of the + * next reply after a successful acknowledgment report has been received. This is + * required by the function getNextReplyLength() to identify the length of the next + * reply to read. + */ + void setNextReplyId(); + + /** + * @brief This function handles action message replies in case the telemetry has been + * requested by another object. + * + * @param data Pointer to the telemetry data. + * @param dataSize Size of telemetry in bytes. + * @param replyId Id of the reply. This will be added to the ActionMessage. + */ + void handleDeviceTm(const uint8_t* data, size_t dataSize, DeviceCommandId_t replyId); + + /** + * @brief In case an acknowledgment failure reply has been received this function disables + * all previously enabled commands and resets the exepected replies variable of an + * active command. + */ + void disableAllReplies(); + + /** + * @brief This function sends a failure report if the active action was commanded by an other + * object. + * + * @param replyId The id of the reply which signals a failure. + * @param status A status byte which gives information about the failure type. + */ + void sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status); + + /** + * @brief This function disables the execution report reply. Within this function also the + * the variable expectedReplies of an active command will be set to 0. + */ + void disableExeReportReply(); + + ReturnValue_t prepareTcModeReplay(); + + void cmdDoneHandler(bool success, ReturnValue_t result); + bool handleHwStartup(); + bool handleHwShutdown(); + void stopSpecialComHelper(); + + void handleActionCommandFailure(ActionId_t actionId); + + pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER); + ReturnValue_t checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t* msToReachTheMode) override; + + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueId, ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, uint16_t startAtIndex) override; +}; + +#endif /* BSP_Q7S_DEVICES_PLOC_PLOCMPSOCHANDLER_H_ */ diff --git a/archive/PlocMpsocSpecialComHelperLegacy.cpp b/archive/PlocMpsocSpecialComHelperLegacy.cpp new file mode 100644 index 0000000..5dcaeb1 --- /dev/null +++ b/archive/PlocMpsocSpecialComHelperLegacy.cpp @@ -0,0 +1,545 @@ +#include +#include +#include +#include + +#include +#include + +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/FilesystemHelper.h" +#endif + +#include "mission/utility/Timestamp.h" + +using namespace ploc; + +PlocMpsocSpecialComHelperLegacy::PlocMpsocSpecialComHelperLegacy(object_id_t objectId) + : SystemObject(objectId) { + spParams.buf = commandBuffer; + spParams.maxSize = sizeof(commandBuffer); +} + +PlocMpsocSpecialComHelperLegacy::~PlocMpsocSpecialComHelperLegacy() {} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::initialize() { +#ifdef XIPHOS_Q7S + sdcMan = SdCardManager::instance(); + if (sdcMan == nullptr) { + sif::warning << "PlocMPSoCHelper::initialize: Invalid SD Card Manager" << std::endl; + return returnvalue::FAILED; + } +#endif + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::performOperation(uint8_t operationCode) { + ReturnValue_t result = returnvalue::OK; + semaphore.acquire(); + while (true) { +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "PLOC MPSOC Helper"); +#endif + switch (internalState) { + case InternalState::IDLE: { + semaphore.acquire(); + break; + } + case InternalState::FLASH_WRITE: { + result = performFlashWrite(); + if (result == returnvalue::OK) { + triggerEvent(MPSOC_FLASH_WRITE_SUCCESSFUL, sequenceCount->get()); + } else { + triggerEvent(MPSOC_FLASH_WRITE_FAILED, sequenceCount->get()); + } + internalState = InternalState::IDLE; + break; + } + case InternalState::FLASH_READ: { + result = performFlashRead(); + if (result == returnvalue::OK) { + triggerEvent(MPSOC_FLASH_READ_SUCCESSFUL, sequenceCount->get()); + } else { + triggerEvent(MPSOC_FLASH_READ_FAILED, sequenceCount->get()); + } + internalState = InternalState::IDLE; + break; + } + default: + sif::debug << "PlocMPSoCHelper::performOperation: Invalid state" << std::endl; + break; + } + } +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::setComIF( + DeviceCommunicationIF* communicationInterface_) { + uartComIF = dynamic_cast(communicationInterface_); + if (uartComIF == nullptr) { + sif::warning << "PlocMPSoCHelper::initialize: Invalid uart com if" << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void PlocMpsocSpecialComHelperLegacy::setComCookie(CookieIF* comCookie_) { comCookie = comCookie_; } + +void PlocMpsocSpecialComHelperLegacy::setSequenceCount(SourceSequenceCounter* sequenceCount_) { + sequenceCount = sequenceCount_; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::startFlashWrite(std::string obcFile, + std::string mpsocFile) { + if (internalState != InternalState::IDLE) { + return returnvalue::FAILED; + } + ReturnValue_t result = startFlashReadOrWriteBase(std::move(obcFile), std::move(mpsocFile)); + if (result != returnvalue::OK) { + return result; + } + internalState = InternalState::FLASH_WRITE; + return semaphore.release(); +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::startFlashRead(std::string obcFile, + std::string mpsocFile, + size_t readFileSize) { + if (internalState != InternalState::IDLE) { + return returnvalue::FAILED; + } + ReturnValue_t result = startFlashReadOrWriteBase(std::move(obcFile), std::move(mpsocFile)); + if (result != returnvalue::OK) { + return result; + } + flashReadAndWrite.totalReadSize = readFileSize; + internalState = InternalState::FLASH_READ; + return semaphore.release(); +} + +void PlocMpsocSpecialComHelperLegacy::resetHelper() { + spParams.buf = commandBuffer; + terminate = false; + uartComIF->flushUartRxBuffer(comCookie); +} + +void PlocMpsocSpecialComHelperLegacy::stopProcess() { terminate = true; } + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::performFlashWrite() { + ReturnValue_t result = returnvalue::OK; + std::ifstream file(flashReadAndWrite.obcFile, std::ifstream::binary); + if (file.bad()) { + return returnvalue::FAILED; + } + result = flashfopen(mpsoc::FileAccessModes::WRITE | mpsoc::FileAccessModes::OPEN_ALWAYS); + if (result != returnvalue::OK) { + return result; + } + // Set position of next character to end of file input stream + file.seekg(0, file.end); + // tellg returns position of character in input stream + size_t remainingSize = file.tellg(); + size_t dataLength = 0; + size_t bytesRead = 0; + while (remainingSize > 0) { + if (terminate) { + return returnvalue::OK; + } + // The minus 4 is necessary for unknown reasons. Maybe some bug in the ILH software? + if (remainingSize > mpsoc::MAX_FLASH_WRITE_DATA_SIZE - 4) { + dataLength = mpsoc::MAX_FLASH_WRITE_DATA_SIZE - 4; + } else { + dataLength = remainingSize; + } + if (file.bad() or not file.is_open()) { + return FILE_WRITE_ERROR; + } + file.seekg(bytesRead, file.beg); + file.read(reinterpret_cast(fileBuf.data()), dataLength); + bytesRead += dataLength; + remainingSize -= dataLength; + mpsoc::TcFlashWrite tc(spParams, *sequenceCount); + result = tc.setPayload(fileBuf.data(), dataLength); + if (result != returnvalue::OK) { + return result; + } + result = tc.finishPacket(); + if (result != returnvalue::OK) { + return result; + } + (*sequenceCount)++; + result = handlePacketTransmissionNoReply(tc); + if (result != returnvalue::OK) { + return result; + } + } + result = flashfclose(); + if (result != returnvalue::OK) { + return result; + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::performFlashRead() { + std::error_code e; + std::ofstream ofile(flashReadAndWrite.obcFile, std::ios::trunc | std::ios::binary); + if (ofile.bad()) { + return returnvalue::FAILED; + } + ReturnValue_t result = flashfopen(mpsoc::FileAccessModes::READ); + if (result != returnvalue::OK) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return result; + } + size_t readSoFar = 0; + size_t nextReadSize = mpsoc::MAX_FLASH_READ_DATA_SIZE; + while (readSoFar < flashReadAndWrite.totalReadSize) { + if (terminate) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return returnvalue::OK; + } + nextReadSize = mpsoc::MAX_FLASH_READ_DATA_SIZE; + if (flashReadAndWrite.totalReadSize - readSoFar < mpsoc::MAX_FLASH_READ_DATA_SIZE) { + nextReadSize = flashReadAndWrite.totalReadSize - readSoFar; + } + if (ofile.bad() or not ofile.is_open()) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return FILE_READ_ERROR; + } + mpsoc::TcFlashRead flashReadRequest(spParams, *sequenceCount); + result = flashReadRequest.setPayload(nextReadSize); + if (result != returnvalue::OK) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return result; + } + result = flashReadRequest.finishPacket(); + if (result != returnvalue::OK) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return result; + } + (*sequenceCount)++; + result = handlePacketTransmissionFlashRead(flashReadRequest, ofile, nextReadSize); + if (result != returnvalue::OK) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return result; + } + readSoFar += nextReadSize; + } + result = flashfclose(); + if (result != returnvalue::OK) { + return result; + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::flashfopen(uint8_t mode) { + spParams.buf = commandBuffer; + mpsoc::FlashFopen flashFopen(spParams, *sequenceCount); + ReturnValue_t result = flashFopen.setPayload(flashReadAndWrite.mpsocFile, mode); + if (result != returnvalue::OK) { + return result; + } + result = flashFopen.finishPacket(); + if (result != returnvalue::OK) { + return result; + } + (*sequenceCount)++; + result = handlePacketTransmissionNoReply(flashFopen); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::flashfclose() { + spParams.buf = commandBuffer; + mpsoc::FlashFclose flashFclose(spParams, *sequenceCount); + ReturnValue_t result = flashFclose.finishPacket(); + if (result != returnvalue::OK) { + return result; + } + (*sequenceCount)++; + result = handlePacketTransmissionNoReply(flashFclose); + if (result != returnvalue::OK) { + return result; + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::handlePacketTransmissionFlashRead( + mpsoc::TcFlashRead& tc, std::ofstream& ofile, size_t expectedReadLen) { + ReturnValue_t result = sendCommand(tc); + if (result != returnvalue::OK) { + return result; + } + result = handleAck(); + if (result != returnvalue::OK) { + return result; + } + result = handleTmReception(); + if (result != returnvalue::OK) { + return result; + } + + // We have the nominal case where the flash read report appears first, or the case where we + // get an EXE failure immediately. + if (spReader.getApid() == mpsoc::apid::TM_FLASH_READ_REPORT) { + result = handleFlashReadReply(ofile, expectedReadLen); + if (result != returnvalue::OK) { + return result; + } + return handleExe(); + } else if (spReader.getApid() == mpsoc::apid::EXE_FAILURE) { + handleExeFailure(); + } else { + triggerEvent(MPSOC_EXE_INVALID_APID, spReader.getApid(), static_cast(internalState)); + sif::warning << "PLOC MPSoC: Expected execution report " + << "but received space packet with apid " << std::hex << spReader.getApid() + << std::endl; + } + return returnvalue::FAILED; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::handlePacketTransmissionNoReply(ploc::SpTcBase& tc) { + ReturnValue_t result = sendCommand(tc); + if (result != returnvalue::OK) { + return result; + } + result = handleAck(); + if (result != returnvalue::OK) { + return result; + } + return handleExe(); +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::sendCommand(ploc::SpTcBase& tc) { + ReturnValue_t result = returnvalue::OK; + result = uartComIF->sendMessage(comCookie, tc.getFullPacket(), tc.getFullPacketLen()); + if (result != returnvalue::OK) { + sif::warning << "PlocMPSoCHelper::sendCommand: Failed to send command" << std::endl; + triggerEvent(MPSOC_SENDING_COMMAND_FAILED, result, static_cast(internalState)); + return result; + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::handleAck() { + ReturnValue_t result = returnvalue::OK; + result = handleTmReception(); + if (result != returnvalue::OK) { + return result; + } + result = checkReceivedTm(); + if (result != returnvalue::OK) { + return result; + } + uint16_t apid = spReader.getApid(); + if (apid != mpsoc::apid::ACK_SUCCESS) { + handleAckApidFailure(spReader); + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void PlocMpsocSpecialComHelperLegacy::handleAckApidFailure(const ploc::SpTmReader& reader) { + uint16_t apid = reader.getApid(); + if (apid == mpsoc::apid::ACK_FAILURE) { + uint16_t status = mpsoc::getStatusFromRawData(reader.getFullData()); + sif::warning << "PLOC MPSoC ACK Failure: " << mpsoc::getStatusString(status) << std::endl; + triggerEvent(MPSOC_ACK_FAILURE_REPORT, static_cast(internalState), status); + } else { + triggerEvent(MPSOC_ACK_INVALID_APID, apid, static_cast(internalState)); + sif::warning << "PlocMPSoCHelper::handleAckApidFailure: Expected acknowledgement report " + << "but received space packet with apid " << std::hex << apid << std::endl; + } +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::handleExe() { + ReturnValue_t result = returnvalue::OK; + + result = handleTmReception(); + if (result != returnvalue::OK) { + return result; + } + result = checkReceivedTm(); + if (result != returnvalue::OK) { + return result; + } + uint16_t apid = spReader.getApid(); + if (apid == mpsoc::apid::EXE_FAILURE) { + handleExeFailure(); + return returnvalue::FAILED; + } else if (apid != mpsoc::apid::EXE_SUCCESS) { + triggerEvent(MPSOC_EXE_INVALID_APID, apid, static_cast(internalState)); + sif::warning << "PLOC MPSoC: Expected execution report " + << "but received space packet with apid " << std::hex << apid << std::endl; + } + return returnvalue::OK; +} + +void PlocMpsocSpecialComHelperLegacy::handleExeFailure() { + uint16_t status = mpsoc::getStatusFromRawData(spReader.getFullData()); + sif::warning << "PLOC MPSoC EXE Failure: " << mpsoc::getStatusString(status) << std::endl; + triggerEvent(MPSOC_EXE_FAILURE_REPORT, static_cast(internalState)); +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::handleTmReception() { + ReturnValue_t result = returnvalue::OK; + tmCountdown.resetTimer(); + size_t readBytes = 0; + size_t currentBytes = 0; + uint32_t usleepDelay = 5; + size_t fullPacketLen = 0; + while (true) { + if (tmCountdown.hasTimedOut()) { + triggerEvent(MPSOC_READ_TIMEOUT, tmCountdown.getTimeoutMs()); + return returnvalue::FAILED; + } + result = receive(tmBuf.data() + readBytes, 6, ¤tBytes); + if (result != returnvalue::OK) { + return result; + } + spReader.setReadOnlyData(tmBuf.data(), tmBuf.size()); + fullPacketLen = spReader.getFullPacketLen(); + readBytes += currentBytes; + if (readBytes == 6) { + break; + } + usleep(usleepDelay); + if (usleepDelay < 200000) { + usleepDelay *= 4; + } + } + while (true) { + if (tmCountdown.hasTimedOut()) { + triggerEvent(MPSOC_READ_TIMEOUT, tmCountdown.getTimeoutMs()); + return returnvalue::FAILED; + } + result = receive(tmBuf.data() + readBytes, fullPacketLen - readBytes, ¤tBytes); + readBytes += currentBytes; + if (fullPacketLen == readBytes) { + break; + } + usleep(usleepDelay); + if (usleepDelay < 200000) { + usleepDelay *= 4; + } + } + // arrayprinter::print(tmBuf.data(), readBytes); + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::handleFlashReadReply(std::ofstream& ofile, + size_t expectedReadLen) { + ReturnValue_t result = checkReceivedTm(); + if (result != returnvalue::OK) { + return result; + } + uint16_t apid = spReader.getApid(); + if (apid != mpsoc::apid::TM_FLASH_READ_REPORT) { + triggerEvent(MPSOC_FLASH_READ_PACKET_ERROR, FlashReadErrorType::FLASH_READ_APID_ERROR); + sif::warning << "PLOC MPSoC Flash Read: Unexpected APID" << std::endl; + return result; + } + const uint8_t* packetData = spReader.getPacketData(); + size_t deserDummy = spReader.getPacketDataLen() - mpsoc::CRC_SIZE; + uint32_t receivedReadLen = 0; + // I think this is buggy, weird stuff in the short name field. + // std::string receivedShortName = std::string(reinterpret_cast(packetData), 12); + // if (receivedShortName != flashReadAndWrite.mpsocFile.substr(0, 11)) { + // sif::warning << "PLOC MPSoC Flash Read: Missmatch between request file name and " + // "received file name" + // << std::endl; + // triggerEvent(MPSOC_FLASH_READ_PACKET_ERROR, FlashReadErrorType::FLASH_READ_FILENAME_ERROR); + // return returnvalue::FAILED; + // } + packetData += 12; + result = SerializeAdapter::deSerialize(&receivedReadLen, &packetData, &deserDummy, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + if (receivedReadLen != expectedReadLen) { + sif::warning << "PLOC MPSoC Flash Read: Missmatch between request read length and " + "received read length" + << std::endl; + triggerEvent(MPSOC_FLASH_READ_PACKET_ERROR, FlashReadErrorType::FLASH_READ_READLEN_ERROR); + return returnvalue::FAILED; + } + ofile.write(reinterpret_cast(packetData), receivedReadLen); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::fileCheck(std::string obcFile) { +#ifdef XIPHOS_Q7S + ReturnValue_t result = FilesystemHelper::checkPath(obcFile); + if (result != returnvalue::OK) { + return result; + } +#elif defined(TE0720_1CFA) + if (not std::filesystem::exists(obcFile)) { + sif::warning << "PlocMPSoCHelper::startFlashWrite: File " << obcFile << "does not exist" + << std::endl; + return returnvalue::FAILED; + } +#endif + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::startFlashReadOrWriteBase(std::string obcFile, + std::string mpsocFile) { + ReturnValue_t result = fileCheck(obcFile); + if (result != returnvalue::OK) { + return result; + } + + flashReadAndWrite.obcFile = std::move(obcFile); + flashReadAndWrite.mpsocFile = std::move(mpsocFile); + resetHelper(); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::checkReceivedTm() { + ReturnValue_t result = spReader.checkSize(); + if (result != returnvalue::OK) { + sif::error << "PLOC MPSoC: Size check on received TM failed" << std::endl; + triggerEvent(MPSOC_TM_SIZE_ERROR); + return result; + } + result = spReader.checkCrc(); + if (result != returnvalue::OK) { + sif::warning << "PLOC MPSoC: CRC check failed" << std::endl; + triggerEvent(MPSOC_TM_CRC_MISSMATCH, *sequenceCount); + return result; + } + uint16_t recvSeqCnt = spReader.getSequenceCount(); + if (recvSeqCnt != *sequenceCount) { + triggerEvent(MPSOC_HELPER_SEQ_CNT_MISMATCH, *sequenceCount, recvSeqCnt); + *sequenceCount = recvSeqCnt; + } + // This sequence count ping pong does not make any sense but it is how the MPSoC expects it. + (*sequenceCount)++; + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelperLegacy::receive(uint8_t* data, size_t requestBytes, + size_t* readBytes) { + ReturnValue_t result = returnvalue::OK; + uint8_t* buffer = nullptr; + result = uartComIF->requestReceiveMessage(comCookie, requestBytes); + if (result != returnvalue::OK) { + sif::warning << "PlocMPSoCHelper::receive: Failed to request reply" << std::endl; + triggerEvent(MPSOC_HELPER_REQUESTING_REPLY_FAILED, result, + static_cast(static_cast(internalState))); + return returnvalue::FAILED; + } + result = uartComIF->readReceivedMessage(comCookie, &buffer, readBytes); + if (result != returnvalue::OK) { + sif::warning << "PlocMPSoCHelper::receive: Failed to read received message" << std::endl; + triggerEvent(MPSOC_HELPER_READING_REPLY_FAILED, result, static_cast(internalState)); + return returnvalue::FAILED; + } + if (*readBytes > 0) { + std::memcpy(data, buffer, *readBytes); + } + return result; +} diff --git a/archive/PlocMpsocSpecialComHelperLegacy.h b/archive/PlocMpsocSpecialComHelperLegacy.h new file mode 100644 index 0000000..e3d5370 --- /dev/null +++ b/archive/PlocMpsocSpecialComHelperLegacy.h @@ -0,0 +1,200 @@ +#ifndef BSP_Q7S_DEVICES_PLOCMPSOCHELPER_H_ +#define BSP_Q7S_DEVICES_PLOCMPSOCHELPER_H_ + +#include +#include + +#include + +#include "OBSWConfig.h" +#include "fsfw/devicehandlers/CookieIF.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/osal/linux/BinarySemaphore.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw/tmtcservices/SourceSequenceCounter.h" +#include "fsfw_hal/linux/serial/SerialComIF.h" +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/SdCardManager.h" +#endif + +/** + * @brief Helper class for MPSoC of PLOC intended to accelerate large data transfers between + * MPSoC and OBC. + * @author J. Meier + */ +class PlocMpsocSpecialComHelperLegacy : public SystemObject, public ExecutableObjectIF { + public: + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_MPSOC_HELPER; + + //! [EXPORT] : [COMMENT] Flash write fails + static const Event MPSOC_FLASH_WRITE_FAILED = MAKE_EVENT(0, severity::LOW); + //! [EXPORT] : [COMMENT] Flash write successful + static const Event MPSOC_FLASH_WRITE_SUCCESSFUL = MAKE_EVENT(1, severity::INFO); + //! [EXPORT] : [COMMENT] Communication interface returned failure when trying to send the command + //! to the MPSoC + //! P1: Return value returned by the communication interface sendMessage function + //! P2: Internal state of MPSoC helper + static const Event MPSOC_SENDING_COMMAND_FAILED = MAKE_EVENT(2, severity::LOW); + //! [EXPORT] : [COMMENT] Request receive message of communication interface failed + //! P1: Return value returned by the communication interface requestReceiveMessage function + //! P2: Internal state of MPSoC helper + static const Event MPSOC_HELPER_REQUESTING_REPLY_FAILED = MAKE_EVENT(3, severity::LOW); + //! [EXPORT] : [COMMENT] Reading receive message of communication interface failed + //! P1: Return value returned by the communication interface readingReceivedMessage function + //! P2: Internal state of MPSoC helper + static const Event MPSOC_HELPER_READING_REPLY_FAILED = MAKE_EVENT(4, severity::LOW); + //! [EXPORT] : [COMMENT] Did not receive acknowledgment report + //! P1: Number of bytes missing + //! P2: Internal state of MPSoC helper + static const Event MPSOC_MISSING_ACK = MAKE_EVENT(5, severity::LOW); + //! [EXPORT] : [COMMENT] Did not receive execution report + //! P1: Number of bytes missing + //! P2: Internal state of MPSoC helper + static const Event MPSOC_MISSING_EXE = MAKE_EVENT(6, severity::LOW); + //! [EXPORT] : [COMMENT] Received acknowledgment failure report + //! P1: Internal state of MPSoC + static const Event MPSOC_ACK_FAILURE_REPORT = MAKE_EVENT(7, severity::LOW); + //! [EXPORT] : [COMMENT] Received execution failure report + //! P1: Internal state of MPSoC + static const Event MPSOC_EXE_FAILURE_REPORT = MAKE_EVENT(8, severity::LOW); + //! [EXPORT] : [COMMENT] Expected acknowledgment report but received space packet with other apid + //! P1: Apid of received space packet + //! P2: Internal state of MPSoC + static const Event MPSOC_ACK_INVALID_APID = MAKE_EVENT(9, severity::LOW); + //! [EXPORT] : [COMMENT] Expected execution report but received space packet with other apid + //! P1: Apid of received space packet + //! P2: Internal state of MPSoC + static const Event MPSOC_EXE_INVALID_APID = MAKE_EVENT(10, severity::LOW); + //! [EXPORT] : [COMMENT] Received sequence count does not match expected sequence count + //! P1: Expected sequence count + //! P2: Received sequence count + static const Event MPSOC_HELPER_SEQ_CNT_MISMATCH = MAKE_EVENT(11, severity::LOW); + static const Event MPSOC_TM_SIZE_ERROR = MAKE_EVENT(12, severity::LOW); + static const Event MPSOC_TM_CRC_MISSMATCH = MAKE_EVENT(13, severity::LOW); + static const Event MPSOC_FLASH_READ_PACKET_ERROR = MAKE_EVENT(14, severity::LOW); + static const Event MPSOC_FLASH_READ_FAILED = MAKE_EVENT(15, severity::LOW); + static const Event MPSOC_FLASH_READ_SUCCESSFUL = MAKE_EVENT(16, severity::INFO); + static const Event MPSOC_READ_TIMEOUT = MAKE_EVENT(17, severity::LOW); + + enum FlashReadErrorType : uint32_t { + FLASH_READ_APID_ERROR = 0, + FLASH_READ_FILENAME_ERROR = 1, + FLASH_READ_READLEN_ERROR = 2 + }; + + PlocMpsocSpecialComHelperLegacy(object_id_t objectId); + virtual ~PlocMpsocSpecialComHelperLegacy(); + + ReturnValue_t initialize() override; + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + + ReturnValue_t setComIF(DeviceCommunicationIF* communicationInterface_); + void setComCookie(CookieIF* comCookie_); + + /** + * @brief Starts flash write sequence + * + * @param obcFile File where to read from the data + * @param mpsocFile The file of the MPSoC where should be written to + * + * @return returnvalue::OK if successful, otherwise error return value + */ + ReturnValue_t startFlashWrite(std::string obcFile, std::string mpsocFile); + /** + * + * @param obcFile Full target file name on OBC + * @param mpsocFile The file on the MPSoC which should be copied ot the OBC + * @param readFileSize The size of the file on the MPSoC. + * @return + */ + ReturnValue_t startFlashRead(std::string obcFile, std::string mpsocFile, size_t readFileSize); + + /** + * @brief Can be used to interrupt a running data transfer. + */ + void stopProcess(); + + /** + * @brief Sets the sequence count object responsible for the sequence count handling + */ + void setSequenceCount(SourceSequenceCounter* sequenceCount_); + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::PLOC_MPSOC_HELPER; + + //! [EXPORT] : [COMMENT] File error occured for file transfers from OBC to the MPSoC. + static const ReturnValue_t FILE_WRITE_ERROR = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] File error occured for file transfers from MPSoC to OBC. + static const ReturnValue_t FILE_READ_ERROR = MAKE_RETURN_CODE(0xA1); + + // Maximum number of times the communication interface retries polling data from the reply + // buffer + static const int RETRIES = 10000; + + struct FlashInfo { + std::string obcFile; + std::string mpsocFile; + }; + + struct FlashRead : public FlashInfo { + size_t totalReadSize = 0; + }; + + struct FlashRead flashReadAndWrite; +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter = 0; +#endif + + enum class InternalState { IDLE, FLASH_WRITE, FLASH_READ }; + + InternalState internalState = InternalState::IDLE; + + BinarySemaphore semaphore; +#ifdef XIPHOS_Q7S + SdCardManager* sdcMan = nullptr; +#endif + uint8_t commandBuffer[mpsoc::MAX_COMMAND_SIZE]; + SpacePacketCreator creator; + ploc::SpTcParams spParams = ploc::SpTcParams(creator); + + Countdown tmCountdown = Countdown(5000); + + std::array fileBuf{}; + std::array tmBuf{}; + + bool terminate = false; + + /** + * Communication interface of MPSoC responsible for low level access. Must be set by the + * MPSoC Handler. + */ + SerialComIF* uartComIF = nullptr; + // Communication cookie. Must be set by the MPSoC Handler + CookieIF* comCookie = nullptr; + // Sequence count, must be set by Ploc MPSoC Handler + SourceSequenceCounter* sequenceCount = nullptr; + ploc::SpTmReader spReader; + + void resetHelper(); + ReturnValue_t performFlashWrite(); + ReturnValue_t performFlashRead(); + ReturnValue_t flashfopen(uint8_t accessMode); + ReturnValue_t flashfclose(); + ReturnValue_t handlePacketTransmissionNoReply(ploc::SpTcBase& tc); + ReturnValue_t handlePacketTransmissionFlashRead(mpsoc::TcFlashRead& tc, std::ofstream& ofile, + size_t expectedReadLen); + ReturnValue_t handleFlashReadReply(std::ofstream& ofile, size_t expectedReadLen); + ReturnValue_t sendCommand(ploc::SpTcBase& tc); + ReturnValue_t receive(uint8_t* data, size_t requestBytes, size_t* readBytes); + ReturnValue_t handleAck(); + ReturnValue_t handleExe(); + ReturnValue_t startFlashReadOrWriteBase(std::string obcFile, std::string mpsocFile); + ReturnValue_t fileCheck(std::string obcFile); + void handleAckApidFailure(const ploc::SpTmReader& reader); + void handleExeFailure(); + ReturnValue_t handleTmReception(); + ReturnValue_t checkReceivedTm(); +}; + +#endif /* BSP_Q7S_DEVICES_PLOCMPSOCHELPER_H_ */ diff --git a/archive/PlocSupervisorHandler.cpp b/archive/PlocSupervisorHandler.cpp new file mode 100644 index 0000000..67ec707 --- /dev/null +++ b/archive/PlocSupervisorHandler.cpp @@ -0,0 +1,2027 @@ +#include "PlocSupervisorHandler.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "OBSWConfig.h" +#include "eive/definitions.h" +#include "fsfw/datapool/PoolReadGuard.h" +#include "fsfw/globalfunctions/CRC.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/timemanager/Clock.h" + +using namespace supv; +using namespace returnvalue; + +PlocSupervisorHandler::PlocSupervisorHandler(object_id_t objectId, CookieIF* comCookie, + Gpio uartIsolatorSwitch, power::Switch_t powerSwitch, + PlocSupvUartManager& supvHelper) + : DeviceHandlerBase(objectId, supvHelper.getObjectId(), comCookie), + uartIsolatorSwitch(uartIsolatorSwitch), + hkset(this), + bootStatusReport(this), + latchupStatusReport(this), + countersReport(this), + adcReport(this), + powerSwitch(powerSwitch), + uartManager(supvHelper) { + if (comCookie == nullptr) { + sif::error << "PlocSupervisorHandler: Invalid com cookie" << std::endl; + } + spParams.buf = commandBuffer; + spParams.maxSize = sizeof(commandBuffer); + eventQueue = QueueFactory::instance()->createMessageQueue(EventMessage::EVENT_MESSAGE_SIZE * 5); +} + +PlocSupervisorHandler::~PlocSupervisorHandler() {} + +ReturnValue_t PlocSupervisorHandler::initialize() { + ReturnValue_t result = returnvalue::OK; + result = DeviceHandlerBase::initialize(); + if (result != returnvalue::OK) { + return result; + } +#ifdef XIPHOS_Q7S + sdcMan = SdCardManager::instance(); +#endif /* TE0720_1CFA */ + + result = eventSubscription(); + if (result != returnvalue::OK) { + return result; + } + return result; +} + +void PlocSupervisorHandler::performOperationHook() { + if (normalCommandIsPending and normalCmdCd.hasTimedOut()) { + // Event, FDIR, printout? Leads to spam though and normally should not happen.. + normalCommandIsPending = false; + } + if (commandIsPending and cmdCd.hasTimedOut()) { + // Event, FDIR, printout? Leads to spam though and normally should not happen.. + commandIsPending = false; + + // if(iter->second.sendReplyTo != NO_COMMANDER) { + // actionHelper.finish(true, iter->second.sendReplyTo, iter->first, returnvalue::OK); + // } + disableAllReplies(); + } + EventMessage event; + for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::OK; + result = eventQueue->receiveMessage(&event)) { + switch (event.getMessageId()) { + case EventMessage::EVENT_MESSAGE: + handleEvent(&event); + break; + default: + sif::debug << "PlocSupervisorHandler::performOperationHook: Did not subscribe to this event" + << " message" << std::endl; + break; + } + } +} + +ReturnValue_t PlocSupervisorHandler::executeAction(ActionId_t actionId, + MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + using namespace supv; + ReturnValue_t result = returnvalue::OK; + + switch (actionId) { + default: + break; + } + + if (uartManager.longerRequestActive()) { + return result::SUPV_HELPER_EXECUTING; + } + + result = acceptExternalDeviceCommands(); + if (result != returnvalue::OK) { + return result; + } + + switch (actionId) { + case PERFORM_UPDATE: { + if (size > config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE) { + return result::FILENAME_TOO_LONG; + } + shutdownCmdSent = false; + UpdateParams params; + result = extractUpdateCommand(data, size, params); + if (result != returnvalue::OK) { + return result; + } + result = uartManager.performUpdate(params); + if (result != returnvalue::OK) { + return result; + } + return EXECUTION_FINISHED; + } + case CONTINUE_UPDATE: { + shutdownCmdSent = false; + uartManager.initiateUpdateContinuation(); + return EXECUTION_FINISHED; + } + case MEMORY_CHECK_WITH_FILE: { + shutdownCmdSent = false; + UpdateParams params; + result = extractBaseParams(&data, size, params); + if (result != returnvalue::OK) { + return result; + } + if (not std::filesystem::exists(params.file)) { + return HasFileSystemIF::FILE_DOES_NOT_EXIST; + } + uartManager.performMemCheck(params.file, params.memId, params.startAddr); + return EXECUTION_FINISHED; + } + default: + break; + } + return DeviceHandlerBase::executeAction(actionId, commandedBy, data, size); +} + +void PlocSupervisorHandler::doStartUp() { + if (startupState == StartupState::OFF) { + bootTimeout.resetTimer(); + startupState = StartupState::BOOTING; + } + if (startupState == StartupState::BOOTING) { + if (bootTimeout.hasTimedOut()) { + uartIsolatorSwitch.pullHigh(); + uartManager.start(); + if (SET_TIME_DURING_BOOT) { + startupState = StartupState::SET_TIME; + } else { + startupState = StartupState::ON; + } + } + } + if (startupState == StartupState::TIME_WAS_SET) { + startupState = StartupState::ON; + } + if (startupState == StartupState::ON) { + hkset.setReportingEnabled(true); + supv::SUPV_ON = true; + setMode(_MODE_TO_ON); + } +} + +void PlocSupervisorHandler::doShutDown() { + setMode(_MODE_POWER_DOWN); + hkset.setReportingEnabled(false); + hkset.setValidity(false, true); + shutdownCmdSent = false; + packetInBuffer = false; + nextReplyId = supv::NONE; + uartManager.stop(); + uartIsolatorSwitch.pullLow(); + disableAllReplies(); + supv::SUPV_ON = false; + startupState = StartupState::OFF; +} + +ReturnValue_t PlocSupervisorHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + if (not normalCommandIsPending) { + *id = GET_HK_REPORT; + normalCommandIsPending = true; + normalCmdCd.resetTimer(); + return buildCommandFromCommand(*id, nullptr, 0); + } + return NOTHING_TO_SEND; +} + +ReturnValue_t PlocSupervisorHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + if (startupState == StartupState::SET_TIME) { + *id = supv::SET_TIME_REF; + startupState = StartupState::WAIT_FOR_TIME_REPLY; + return buildCommandFromCommand(*id, nullptr, 0); + } + return NOTHING_TO_SEND; +} + +ReturnValue_t PlocSupervisorHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + using namespace supv; + ReturnValue_t result = returnvalue::FAILED; + spParams.buf = commandBuffer; + switch (deviceCommand) { + case GET_HK_REPORT: { + prepareEmptyCmd(Apid::HK, static_cast(tc::HkId::GET_REPORT)); + result = returnvalue::OK; + break; + } + case START_MPSOC: { + sif::info << "PLOC SUPV: Starting MPSoC" << std::endl; + prepareEmptyCmd(Apid::BOOT_MAN, static_cast(tc::BootManId::START_MPSOC)); + result = returnvalue::OK; + break; + } + case SHUTDOWN_MPSOC: { + sif::info << "PLOC SUPV: Shutting down MPSoC" << std::endl; + prepareEmptyCmd(Apid::BOOT_MAN, static_cast(tc::BootManId::SHUTDOWN_MPSOC)); + result = returnvalue::OK; + break; + } + case SEL_MPSOC_BOOT_IMAGE: { + prepareSelBootImageCmd(commandData); + result = returnvalue::OK; + break; + } + case RESET_MPSOC: { + sif::info << "PLOC SUPV: Resetting MPSoC" << std::endl; + prepareEmptyCmd(Apid::BOOT_MAN, static_cast(tc::BootManId::RESET_MPSOC)); + result = returnvalue::OK; + break; + } + case SET_TIME_REF: { + result = prepareSetTimeRefCmd(); + break; + } + case SET_BOOT_TIMEOUT: { + prepareSetBootTimeoutCmd(commandData); + result = returnvalue::OK; + break; + } + case SET_MAX_RESTART_TRIES: { + prepareRestartTriesCmd(commandData); + result = returnvalue::OK; + break; + } + case DISABLE_PERIOIC_HK_TRANSMISSION: { + prepareDisableHk(); + result = returnvalue::OK; + break; + } + case GET_BOOT_STATUS_REPORT: { + prepareEmptyCmd(Apid::BOOT_MAN, static_cast(tc::BootManId::GET_BOOT_STATUS_REPORT)); + result = returnvalue::OK; + break; + } + case ENABLE_LATCHUP_ALERT: { + result = prepareLatchupConfigCmd(commandData, deviceCommand); + break; + } + case DISABLE_LATCHUP_ALERT: { + result = prepareLatchupConfigCmd(commandData, deviceCommand); + break; + } + case SET_ALERT_LIMIT: { + result = prepareSetAlertLimitCmd(commandData); + break; + } + case GET_LATCHUP_STATUS_REPORT: { + prepareEmptyCmd(Apid::LATCHUP_MON, static_cast(tc::LatchupMonId::GET_STATUS_REPORT)); + result = returnvalue::OK; + break; + } + case RUN_AUTO_EM_TESTS: { + result = prepareRunAutoEmTest(commandData); + break; + } + case SET_GPIO: { + result = prepareSetGpioCmd(commandData, commandDataLen); + break; + } + case FACTORY_RESET: { + result = prepareFactoryResetCmd(commandData, commandDataLen); + break; + } + case READ_GPIO: { + result = prepareReadGpioCmd(commandData, commandDataLen); + break; + } + case SET_SHUTDOWN_TIMEOUT: { + prepareSetShutdownTimeoutCmd(commandData); + result = returnvalue::OK; + break; + } + case FACTORY_FLASH: { + prepareEmptyCmd(Apid::BOOT_MAN, static_cast(tc::BootManId::FACTORY_FLASH)); + result = returnvalue::OK; + break; + } + case RESET_PL: { + prepareEmptyCmd(Apid::BOOT_MAN, static_cast(tc::BootManId::RESET_PL)); + result = returnvalue::OK; + break; + } + case SET_ADC_ENABLED_CHANNELS: { + prepareSetAdcEnabledChannelsCmd(commandData); + result = returnvalue::OK; + break; + } + case SET_ADC_WINDOW_AND_STRIDE: { + prepareSetAdcWindowAndStrideCmd(commandData); + result = returnvalue::OK; + break; + } + case SET_ADC_THRESHOLD: { + prepareSetAdcThresholdCmd(commandData); + result = returnvalue::OK; + break; + } + case WIPE_MRAM: { + result = prepareWipeMramCmd(commandData); + break; + } + case REQUEST_ADC_REPORT: { + prepareEmptyCmd(Apid::ADC_MON, static_cast(tc::AdcMonId::REQUEST_ADC_SAMPLE)); + result = returnvalue::OK; + break; + } + case REQUEST_LOGGING_COUNTERS: { + prepareEmptyCmd(Apid::DATA_LOGGER, + static_cast(tc::DataLoggerServiceId::REQUEST_COUNTERS)); + result = returnvalue::OK; + break; + } + default: + sif::debug << "PlocSupervisorHandler::buildCommandFromCommand: Command not implemented" + << std::endl; + result = DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + break; + } + commandIsPending = true; + cmdCd.resetTimer(); + return result; +} + +void PlocSupervisorHandler::fillCommandAndReplyMap() { + // Command only + insertInCommandMap(GET_HK_REPORT); + insertInCommandMap(START_MPSOC); + insertInCommandMap(SHUTDOWN_MPSOC); + insertInCommandMap(SEL_MPSOC_BOOT_IMAGE); + insertInCommandMap(SET_BOOT_TIMEOUT); + insertInCommandMap(SET_MAX_RESTART_TRIES); + insertInCommandMap(RESET_MPSOC); + insertInCommandMap(WIPE_MRAM); + insertInCommandMap(SET_TIME_REF); + insertInCommandMap(DISABLE_PERIOIC_HK_TRANSMISSION); + insertInCommandMap(GET_BOOT_STATUS_REPORT); + insertInCommandMap(ENABLE_LATCHUP_ALERT); + insertInCommandMap(DISABLE_LATCHUP_ALERT); + insertInCommandMap(SET_ALERT_LIMIT); + insertInCommandMap(GET_LATCHUP_STATUS_REPORT); + insertInCommandMap(RUN_AUTO_EM_TESTS); + insertInCommandMap(SET_GPIO); + insertInCommandMap(READ_GPIO); + insertInCommandMap(FACTORY_RESET); + insertInCommandMap(MEMORY_CHECK); + insertInCommandMap(SET_SHUTDOWN_TIMEOUT); + insertInCommandMap(FACTORY_FLASH); + insertInCommandMap(SET_ADC_ENABLED_CHANNELS); + insertInCommandMap(SET_ADC_THRESHOLD); + insertInCommandMap(SET_ADC_WINDOW_AND_STRIDE); + insertInCommandMap(RESET_PL); + insertInCommandMap(REQUEST_ADC_REPORT); + insertInCommandMap(REQUEST_LOGGING_COUNTERS); + + // ACK replies, use countdown for them + insertInReplyMap(ACK_REPORT, 0, nullptr, SIZE_ACK_REPORT, false, &acknowledgementReportTimeout); + insertInReplyMap(EXE_REPORT, 0, nullptr, SIZE_EXE_REPORT, false, &executionReportTimeout); + insertInReplyMap(MEMORY_CHECK, 5, nullptr, 0, false); + + // TM replies + insertInReplyMap(HK_REPORT, 3, &hkset); + insertInReplyMap(BOOT_STATUS_REPORT, 3, &bootStatusReport, SIZE_BOOT_STATUS_REPORT); + insertInReplyMap(LATCHUP_REPORT, 3, &latchupStatusReport, SIZE_LATCHUP_STATUS_REPORT); + insertInReplyMap(COUNTERS_REPORT, 3, &countersReport, SIZE_COUNTERS_REPORT); + insertInReplyMap(ADC_REPORT, 3, &adcReport, SIZE_ADC_REPORT); +} + +ReturnValue_t PlocSupervisorHandler::enableReplyInReplyMap(DeviceCommandMap::iterator command, + uint8_t expectedReplies, + bool useAlternateId, + DeviceCommandId_t alternateReplyID) { + ReturnValue_t result = OK; + + uint8_t enabledReplies = 0; + + switch (command->first) { + case GET_HK_REPORT: { + enabledReplies = 3; + result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, HK_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " << HK_REPORT + << " not in replyMap" << std::endl; + } + break; + } + case GET_BOOT_STATUS_REPORT: { + enabledReplies = 3; + result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, + BOOT_STATUS_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " + << BOOT_STATUS_REPORT << " not in replyMap" << std::endl; + } + break; + } + case GET_LATCHUP_STATUS_REPORT: { + enabledReplies = 3; + result = + DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, LATCHUP_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " + << LATCHUP_REPORT << " not in replyMap" << std::endl; + } + break; + } + case REQUEST_LOGGING_COUNTERS: { + enabledReplies = 3; + result = + DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, COUNTERS_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " + << COUNTERS_REPORT << " not in replyMap" << std::endl; + } + break; + } + case REQUEST_ADC_REPORT: { + enabledReplies = 3; + result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, ADC_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " << ADC_REPORT + << " not in replyMap" << std::endl; + } + break; + } + case FIRST_MRAM_DUMP: { + enabledReplies = 2; // expected replies will be increased in handleMramDumpPacket + result = + DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, FIRST_MRAM_DUMP); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " + << FIRST_MRAM_DUMP << " not in replyMap" << std::endl; + } + break; + } + case CONSECUTIVE_MRAM_DUMP: { + enabledReplies = 2; // expected replies will be increased in handleMramDumpPacket + result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, + CONSECUTIVE_MRAM_DUMP); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " + << CONSECUTIVE_MRAM_DUMP << " not in replyMap" << std::endl; + } + break; + } + case MEMORY_CHECK: { + enabledReplies = 3; + result = + DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, MEMORY_CHECK); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " << MEMORY_CHECK + << " not in replyMap" << std::endl; + } + break; + } + case START_MPSOC: + case SHUTDOWN_MPSOC: + case SEL_MPSOC_BOOT_IMAGE: + case SET_BOOT_TIMEOUT: + case SET_MAX_RESTART_TRIES: + case RESET_MPSOC: + case SET_TIME_REF: + case ENABLE_LATCHUP_ALERT: + case DISABLE_LATCHUP_ALERT: + case SET_ALERT_LIMIT: + case SET_ADC_ENABLED_CHANNELS: + case SET_ADC_WINDOW_AND_STRIDE: + case SET_ADC_THRESHOLD: + case RUN_AUTO_EM_TESTS: + case WIPE_MRAM: + case SET_GPIO: + case FACTORY_RESET: + case READ_GPIO: + case DISABLE_PERIOIC_HK_TRANSMISSION: + case SET_SHUTDOWN_TIMEOUT: + case FACTORY_FLASH: + case ENABLE_AUTO_TM: + case DISABLE_AUTO_TM: + case RESET_PL: + enabledReplies = 2; + break; + default: + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Unknown command id" << std::endl; + break; + } + + /** + * Every command causes at least one acknowledgment and one execution report. Therefore both + * replies will be enabled here. + */ + result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, ACK_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " << ACK_REPORT + << " not in replyMap" << std::endl; + } + + setExecutionTimeout(command->first); + + result = DeviceHandlerBase::enableReplyInReplyMap(command, enabledReplies, true, EXE_REPORT); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::enableReplyInReplyMap: Reply with id " << EXE_REPORT + << " not in replyMap" << std::endl; + } + + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + using namespace supv; + + tmReader.setData(start, remainingSize); + uint16_t apid = tmReader.getModuleApid(); + if (DEBUG_PLOC_SUPV) { + handlePacketPrint(); + } + + switch (apid) { + case (Apid::TMTC_MAN): { + switch (tmReader.getServiceId()) { + case (static_cast(supv::tm::TmtcId::ACK)): + case (static_cast(supv::tm::TmtcId::NAK)): { + *foundLen = tmReader.getFullPacketLen(); + *foundId = ReplyId::ACK_REPORT; + return OK; + } + case (static_cast(supv::tm::TmtcId::EXEC_ACK)): + case (static_cast(supv::tm::TmtcId::EXEC_NAK)): { + *foundLen = tmReader.getFullPacketLen(); + *foundId = EXE_REPORT; + return OK; + } + } + break; + } + case (Apid::HK): { + if (tmReader.getServiceId() == static_cast(supv::tm::HkId::REPORT)) { + normalCommandIsPending = false; + // Yeah apparently this is needed?? + disableCommand(GET_HK_REPORT); + *foundLen = tmReader.getFullPacketLen(); + *foundId = ReplyId::HK_REPORT; + return OK; + } else if (tmReader.getServiceId() == static_cast(supv::tm::HkId::HARDFAULTS)) { + handleBadApidServiceCombination(SUPV_UNINIMPLEMENTED_TM, apid, tmReader.getServiceId()); + return INVALID_DATA; + } + break; + } + case (Apid::BOOT_MAN): { + if (tmReader.getServiceId() == + static_cast(supv::tm::BootManId::BOOT_STATUS_REPORT)) { + *foundLen = tmReader.getFullPacketLen(); + *foundId = ReplyId::BOOT_STATUS_REPORT; + return OK; + } + break; + } + case (Apid::ADC_MON): { + if (tmReader.getServiceId() == static_cast(supv::tm::AdcMonId::ADC_REPORT)) { + *foundLen = tmReader.getFullPacketLen(); + *foundId = ReplyId::ADC_REPORT; + return OK; + } + break; + } + case (Apid::MEM_MAN): { + if (tmReader.getServiceId() == + static_cast(supv::tm::MemManId::UPDATE_STATUS_REPORT)) { + *foundLen = tmReader.getFullPacketLen(); + *foundId = ReplyId::UPDATE_STATUS_REPORT; + return OK; + } + break; + } + case (Apid::DATA_LOGGER): { + if (tmReader.getServiceId() == + static_cast(supv::tm::DataLoggerId::COUNTERS_REPORT)) { + *foundLen = tmReader.getFullPacketLen(); + *foundId = ReplyId::COUNTERS_REPORT; + return OK; + } + } + } + handleBadApidServiceCombination(SUPV_UNKNOWN_TM, apid, tmReader.getServiceId()); + *foundLen = remainingSize; + return INVALID_DATA; +} + +void PlocSupervisorHandler::handlePacketPrint() { + if (tmReader.getModuleApid() == Apid::TMTC_MAN) { + if ((tmReader.getServiceId() == static_cast(supv::tm::TmtcId::ACK)) or + (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::NAK))) { + AcknowledgmentReport ack(tmReader); + ReturnValue_t result = ack.parse(); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler: Parsing ACK failed" << std::endl; + } + if (REDUCE_NORMAL_MODE_PRINTOUT and ack.getRefModuleApid() == (uint8_t)supv::Apid::HK and + ack.getRefServiceId() == (uint8_t)supv::tc::HkId::GET_REPORT) { + return; + } + const char* printStr = "???"; + if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::ACK)) { + printStr = "ACK"; + + } else if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::NAK)) { + printStr = "NAK"; + } + sif::debug << "PlocSupervisorHandler: RECV " << printStr << " for APID Module ID " + << (int)ack.getRefModuleApid() << " Service ID " << (int)ack.getRefServiceId() + << " Seq Count " << ack.getRefSequenceCount() << std::endl; + return; + } else if ((tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_ACK)) or + (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_NAK))) { + ExecutionReport exe(tmReader); + ReturnValue_t result = exe.parse(); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler: Parsing EXE failed" << std::endl; + } + const char* printStr = "???"; + if (REDUCE_NORMAL_MODE_PRINTOUT and exe.getRefModuleApid() == (uint8_t)supv::Apid::HK and + exe.getRefServiceId() == (uint8_t)supv::tc::HkId::GET_REPORT) { + return; + } + if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_ACK)) { + printStr = "ACK EXE"; + + } else if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_NAK)) { + printStr = "NAK EXE"; + } + sif::debug << "PlocSupervisorHandler: RECV " << printStr << " for APID Module ID " + << (int)exe.getRefModuleApid() << " Service ID " << (int)exe.getRefServiceId() + << " Seq Count " << exe.getRefSequenceCount() << std::endl; + return; + } + } + sif::debug << "PlocSupervisorHandler: RECV PACKET Size " << tmReader.getFullPacketLen() + << " Module APID " << (int)tmReader.getModuleApid() << " Service ID " + << (int)tmReader.getServiceId() << std::endl; +} +ReturnValue_t PlocSupervisorHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t* packet) { + using namespace supv; + ReturnValue_t result = returnvalue::OK; + + switch (id) { + case ACK_REPORT: { + result = handleAckReport(packet); + break; + } + case (HK_REPORT): { + result = handleHkReport(packet); + break; + } + case (BOOT_STATUS_REPORT): { + result = handleBootStatusReport(packet); + break; + } + case (COUNTERS_REPORT): { + result = genericHandleTm("COUNTERS", packet, countersReport); +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_PLOC_SUPERVISOR == 1 + countersReport.printSet(); +#endif + break; + } + case (LATCHUP_REPORT): { + result = handleLatchupStatusReport(packet); + break; + } + case (ADC_REPORT): { + result = genericHandleTm("ADC", packet, adcReport); +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_PLOC_SUPERVISOR == 1 + adcReport.printSet(); +#endif + break; + } + case (EXE_REPORT): { + result = handleExecutionReport(packet); + break; + } + case (UPDATE_STATUS_REPORT): { + // TODO: handle status report here + break; + } + default: { + sif::debug << "PlocSupervisorHandler::interpretDeviceReply: Unknown device reply id" + << std::endl; + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + + return result; +} + +ReturnValue_t PlocSupervisorHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(supv::NUM_TMS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TEMP_PS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TEMP_PL, new PoolEntry({0})); + localDataPoolMap.emplace(supv::HK_SOC_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::NVM0_1_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::NVM3_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MISSION_IO_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::FMC_STATE, &fmcStateEntry); + localDataPoolMap.emplace(supv::NUM_TCS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TEMP_SUP, &tempSupEntry); + localDataPoolMap.emplace(supv::UPTIME, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CPULOAD, new PoolEntry({0})); + localDataPoolMap.emplace(supv::AVAILABLEHEAP, new PoolEntry({0})); + + localDataPoolMap.emplace(supv::BR_SOC_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::POWER_CYCLES, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BOOT_AFTER_MS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BOOT_TIMEOUT_MS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::ACTIVE_NVM, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BP0_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BP1_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BP2_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BOOT_STATE, &bootStateEntry); + localDataPoolMap.emplace(supv::BOOT_CYCLES, &bootCyclesEntry); + + localDataPoolMap.emplace(supv::LATCHUP_ID, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT0, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT1, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT2, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT3, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT4, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT5, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT6, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_MSEC, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_SEC, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_MIN, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_HOUR, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_DAY, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_MON, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_YEAR, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_IS_SET, new PoolEntry({0})); + + localDataPoolMap.emplace(supv::SIGNATURE, new PoolEntry()); + localDataPoolMap.emplace(supv::LATCHUP_HAPPENED_CNTS, &latchupCounters); + localDataPoolMap.emplace(supv::ADC_DEVIATION_TRIGGERS_CNT, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TC_RECEIVED_CNT, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TM_AVAILABLE_CNT, new PoolEntry({0})); + localDataPoolMap.emplace(supv::SUPERVISOR_BOOTS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_BOOTS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_BOOT_FAILED_ATTEMPTS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_POWER_UP, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_UPDATES, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_HEARTBEAT_RESETS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CPU_WDT_RESETS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::PS_HEARTBEATS_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::PL_HEARTBEATS_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::EB_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::AM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TCTMM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::HK_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::DL_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::RWS_TASKS_LOST, new PoolEntry(3)); + + localDataPoolMap.emplace(supv::ADC_RAW, &adcRawEntry); + localDataPoolMap.emplace(supv::ADC_ENG, &adcEngEntry); + + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(hkset.getSid(), false, 10.0)); + return returnvalue::OK; +} + +void PlocSupervisorHandler::handleEvent(EventMessage* eventMessage) { + ReturnValue_t result = returnvalue::OK; + object_id_t objectId = eventMessage->getReporter(); + Event event = eventMessage->getEvent(); + switch (objectId) { + case objects::PLOC_SUPERVISOR_HELPER: { + // After execution of update procedure, PLOC is in a state where it draws approx. 700 mA of + // current. To leave this state the shutdown MPSoC command must be sent here. + if (event == PlocSupvUartManager::SUPV_UPDATE_FAILED || + event == PlocSupvUartManager::SUPV_UPDATE_SUCCESSFUL || + event == PlocSupvUartManager::SUPV_CONTINUE_UPDATE_FAILED || + event == PlocSupvUartManager::SUPV_CONTINUE_UPDATE_SUCCESSFUL || + event == PlocSupvUartManager::SUPV_MEM_CHECK_FAIL || + event == PlocSupvUartManager::SUPV_MEM_CHECK_OK) { + // Wait for a short period for the uart state machine to adjust + // TaskFactory::delayTask(5); + if (not shutdownCmdSent) { + shutdownCmdSent = true; + result = this->executeAction(supv::SHUTDOWN_MPSOC, NO_COMMANDER, nullptr, 0); + if (result != returnvalue::OK) { + triggerEvent(SUPV_MPSOC_SHUTDOWN_BUILD_FAILED); + sif::warning << "PlocSupervisorHandler::handleEvent: Failed to build MPSoC shutdown " + "command" + << std::endl; + return; + } + } + } + break; + } + default: + sif::debug << "PlocMPSoCHandler::handleEvent: Did not subscribe to this event" << std::endl; + break; + } +} + +void PlocSupervisorHandler::setExecutionTimeout(DeviceCommandId_t command) { + using namespace supv; + switch (command) { + case FIRST_MRAM_DUMP: + case CONSECUTIVE_MRAM_DUMP: + executionReportTimeout.setTimeout(MRAM_DUMP_EXECUTION_TIMEOUT); + break; + case COPY_ADC_DATA_TO_MRAM: + executionReportTimeout.setTimeout(COPY_ADC_TO_MRAM_TIMEOUT); + break; + default: + executionReportTimeout.setTimeout(EXECUTION_DEFAULT_TIMEOUT); + break; + } +} + +ReturnValue_t PlocSupervisorHandler::verifyPacket(const uint8_t* start, size_t foundLen) { + if (CRC::crc16ccitt(start, foundLen) != 0) { + return result::CRC_FAILURE; + } + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::handleAckReport(const uint8_t* data) { + using namespace supv; + ReturnValue_t result = returnvalue::OK; + + if (not tmReader.verifyCrc()) { + sif::error << "PlocSupervisorHandler::handleAckReport: CRC failure" << std::endl; + nextReplyId = supv::NONE; + replyRawReplyIfnotWiretapped(data, supv::SIZE_ACK_REPORT); + triggerEvent(SUPV_CRC_FAILURE_EVENT); + sendFailureReport(supv::ACK_REPORT, result::CRC_FAILURE); + disableAllReplies(); + return returnvalue::OK; + } + AcknowledgmentReport ack(tmReader); + result = ack.parse(); + if (result != returnvalue::OK) { + nextReplyId = supv::NONE; + replyRawReplyIfnotWiretapped(data, supv::SIZE_ACK_REPORT); + triggerEvent(SUPV_CRC_FAILURE_EVENT); + sendFailureReport(supv::ACK_REPORT, result); + disableAllReplies(); + return result; + } + if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::NAK)) { + DeviceCommandId_t commandId = getPendingCommand(); + if (commandId != DeviceHandlerIF::NO_COMMAND_ID) { + triggerEvent(SUPV_ACK_FAILURE, commandId, static_cast(ack.getStatusCode())); + } + ack.printStatusInformation(); + printAckFailureInfo(ack.getStatusCode(), commandId); + sendFailureReport(supv::ACK_REPORT, result::RECEIVED_ACK_FAILURE); + disableAllReplies(); + nextReplyId = supv::NONE; + result = IGNORE_REPLY_DATA; + } else if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::ACK)) { + setNextReplyId(); + } + return result; +} + +ReturnValue_t PlocSupervisorHandler::handleExecutionReport(const uint8_t* data) { + using namespace supv; + ReturnValue_t result = returnvalue::OK; + + if (not tmReader.verifyCrc()) { + nextReplyId = supv::NONE; + return result::CRC_FAILURE; + } + ExecutionReport report(tmReader); + result = report.parse(); + if (result != OK) { + nextReplyId = supv::NONE; + return result; + } + if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_ACK)) { + result = handleExecutionSuccessReport(report); + } else if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_NAK)) { + handleExecutionFailureReport(report); + } + commandIsPending = false; + nextReplyId = supv::NONE; + return result; +} + +ReturnValue_t PlocSupervisorHandler::handleHkReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, tmReader.getFullPacketLen()); + + if (result == result::CRC_FAILURE) { + sif::error << "PlocSupervisorHandler::handleHkReport: Hk report has invalid crc" << std::endl; + return result; + } + + uint16_t offset = supv::PAYLOAD_OFFSET; + hkset.tempPs = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkset.tempPl = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkset.tempSup = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + size_t size = sizeof(hkset.uptime.value); + result = SerializeAdapter::deSerialize(&hkset.uptime, data + offset, &size, + SerializeIF::Endianness::BIG); + offset += 8; + hkset.cpuLoad = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkset.availableHeap = *(data + offset) << 24 | *(data + offset + 1) << 16 | + *(data + offset + 2) << 8 | *(data + offset + 3); + offset += 4; + hkset.numTcs = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkset.numTms = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkset.socState = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkset.nvm0_1_state = *(data + offset); + offset += 1; + hkset.nvm3_state = *(data + offset); + offset += 1; + hkset.missionIoState = *(data + offset); + offset += 1; + hkset.fmcState = *(data + offset); + offset += 1; + + nextReplyId = supv::EXE_REPORT; + hkset.setValidity(true, true); + +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_PLOC_SUPERVISOR == 1 + sif::info << "PlocSupervisorHandler::handleHkReport: temp_ps: " << hkset.tempPs << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: temp_pl: " << hkset.tempPl << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: temp_sup: " << hkset.tempSup << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: uptime: " << hkset.uptime << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: cpu_load: " << hkset.cpuLoad << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: available_heap: " << hkset.availableHeap + << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: num_tcs: " << hkset.numTcs << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: num_tms: " << hkset.numTms << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: soc_state: " << hkset.socState << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: nvm0_1_state: " + << static_cast(hkset.nvm0_1_state.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: nvm3_state: " + << static_cast(hkset.nvm3_state.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: mission_io_state: " + << static_cast(hkset.missionIoState.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: fmc_state: " + << static_cast(hkset.fmcState.value) << std::endl; + +#endif + + return result; +} + +ReturnValue_t PlocSupervisorHandler::handleBootStatusReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, tmReader.getFullPacketLen()); + + if (result == result::CRC_FAILURE) { + sif::error << "PlocSupervisorHandler::handleBootStatusReport: Boot status report has invalid" + " crc" + << std::endl; + return result; + } + + const uint8_t* payloadStart = tmReader.getPayloadStart(); + uint16_t offset = 0; + bootStatusReport.socState = payloadStart[0]; + offset += 1; + bootStatusReport.powerCycles = payloadStart[1]; + offset += 1; + bootStatusReport.bootAfterMs = *(payloadStart + offset) << 24 | + *(payloadStart + offset + 1) << 16 | + *(payloadStart + offset + 2) << 8 | *(payloadStart + offset + 3); + offset += 4; + bootStatusReport.bootTimeoutMs = *(payloadStart + offset) << 24 | + *(payloadStart + offset + 1) << 16 | + *(payloadStart + offset + 2) << 8 | *(payloadStart + offset + 3); + offset += 4; + bootStatusReport.activeNvm = *(payloadStart + offset); + offset += 1; + bootStatusReport.bp0State = *(payloadStart + offset); + offset += 1; + bootStatusReport.bp1State = *(payloadStart + offset); + offset += 1; + bootStatusReport.bp2State = *(payloadStart + offset); + offset += 1; + bootStatusReport.bootState = *(payloadStart + offset); + offset += 1; + bootStatusReport.bootCycles = *(payloadStart + offset); + + nextReplyId = supv::EXE_REPORT; + bootStatusReport.setValidity(true, true); + +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_PLOC_SUPERVISOR == 1 + sif::info << "PlocSupervisorHandler::handleBootStatusReport: SoC State (0 - off, 1 - booting, 2 " + "- Update, 3 " + "- operating, 4 - Shutdown, 5 - Reset): " + << static_cast(bootStatusReport.socState.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: Power Cycles: " + << static_cast(bootStatusReport.powerCycles.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BootAfterMs: " + << bootStatusReport.bootAfterMs << " ms" << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BootTimeoutMs: " << std::dec + << bootStatusReport.bootTimeoutMs << " ms" << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: Active NVM: " + << static_cast(bootStatusReport.activeNvm.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BP0: " + << static_cast(bootStatusReport.bp0State.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BP1: " + << static_cast(bootStatusReport.bp1State.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BP2: " + << static_cast(bootStatusReport.bp2State.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: Boot state: " + << static_cast(bootStatusReport.bootState.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: Boot cycles: " + << static_cast(bootStatusReport.bootCycles.value) << std::endl; +#endif + + return result; +} + +ReturnValue_t PlocSupervisorHandler::handleLatchupStatusReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, tmReader.getFullPacketLen()); + + if (result == result::CRC_FAILURE) { + sif::error << "PlocSupervisorHandler::handleLatchupStatusReport: Latchup status report has " + << "invalid crc" << std::endl; + return result; + } + + const uint8_t* payloadData = tmReader.getPayloadStart(); + uint16_t offset = 0; + latchupStatusReport.id = *(payloadData + offset); + offset += 1; + latchupStatusReport.cnt0 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt1 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt2 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt3 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt4 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt5 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt6 = *(payloadData + offset) << 8 | *(data + offset + 1); + offset += 2; + uint16_t msec = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + latchupStatusReport.isSet = msec >> supv::LatchupStatusReport::IS_SET_BIT_POS; + latchupStatusReport.timeMsec = msec & (~(1 << latchupStatusReport.IS_SET_BIT_POS)); + offset += 2; + latchupStatusReport.timeSec = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeMin = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeHour = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeDay = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeMon = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeYear = *(payloadData + offset); + + nextReplyId = supv::EXE_REPORT; + +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_PLOC_SUPERVISOR == 1 + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Latchup ID: " + << static_cast(latchupStatusReport.id.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT0: " + << latchupStatusReport.cnt0 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT1: " + << latchupStatusReport.cnt1 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT2: " + << latchupStatusReport.cnt2 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT3: " + << latchupStatusReport.cnt3 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT4: " + << latchupStatusReport.cnt4 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT5: " + << latchupStatusReport.cnt5 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT6: " + << latchupStatusReport.cnt6 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Sec: " + << static_cast(latchupStatusReport.timeSec.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Min: " + << static_cast(latchupStatusReport.timeMin.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Hour: " + << static_cast(latchupStatusReport.timeHour.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Day: " + << static_cast(latchupStatusReport.timeDay.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Mon: " + << static_cast(latchupStatusReport.timeMon.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Year: " + << static_cast(latchupStatusReport.timeYear.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Msec: " + << static_cast(latchupStatusReport.timeMsec.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: isSet: " + << static_cast(latchupStatusReport.isSet.value) << std::endl; +#endif + + return result; +} + +ReturnValue_t PlocSupervisorHandler::genericHandleTm(const char* contextString, const uint8_t* data, + LocalPoolDataSetBase& set) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, tmReader.getFullPacketLen()); + + if (result == result::CRC_FAILURE) { + sif::warning << "PlocSupervisorHandler: " << contextString << " report has " + << "invalid CRC" << std::endl; + return result; + } + + const uint8_t* dataField = data + supv::PAYLOAD_OFFSET; + PoolReadGuard pg(&set); + if (pg.getReadResult() != returnvalue::OK) { + return result; + } + set.setValidityBufferGeneration(false); + size_t size = set.getSerializedSize(); + result = set.deSerialize(&dataField, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler: Deserialization failed" << std::endl; + } + set.setValidityBufferGeneration(true); + set.setValidity(true, true); + nextReplyId = supv::EXE_REPORT; + return result; +} + +void PlocSupervisorHandler::setNextReplyId() { + switch (getPendingCommand()) { + case supv::GET_HK_REPORT: + nextReplyId = supv::HK_REPORT; + break; + case supv::GET_BOOT_STATUS_REPORT: + nextReplyId = supv::BOOT_STATUS_REPORT; + break; + case supv::GET_LATCHUP_STATUS_REPORT: + nextReplyId = supv::LATCHUP_REPORT; + break; + case supv::FIRST_MRAM_DUMP: + nextReplyId = supv::FIRST_MRAM_DUMP; + break; + case supv::CONSECUTIVE_MRAM_DUMP: + nextReplyId = supv::CONSECUTIVE_MRAM_DUMP; + break; + case supv::REQUEST_LOGGING_COUNTERS: + nextReplyId = supv::COUNTERS_REPORT; + break; + case supv::REQUEST_ADC_REPORT: + nextReplyId = supv::ADC_REPORT; + break; + default: + /* If no telemetry is expected the next reply is always the execution report */ + nextReplyId = supv::EXE_REPORT; + break; + } +} + +size_t PlocSupervisorHandler::getNextReplyLength(DeviceCommandId_t commandId) { + size_t replyLen = 0; + + if (nextReplyId == supv::NONE) { + return replyLen; + } + + if (nextReplyId == supv::FIRST_MRAM_DUMP || nextReplyId == supv::CONSECUTIVE_MRAM_DUMP) { + /** + * Try to read 20 MRAM packets. If reply is larger, the packets will be read with the + * next doSendRead call. The command will be as long active as the packet with the sequence + * count indicating the last packet has not been received. + */ + replyLen = supv::MAX_PACKET_SIZE * 20; + return replyLen; + } + + DeviceReplyIter iter = deviceReplyMap.find(nextReplyId); + if (iter != deviceReplyMap.end()) { + if ((iter->second.delayCycles == 0 && iter->second.countdown == nullptr) || + (not iter->second.active && iter->second.countdown != nullptr)) { + /* Reply inactive */ + return replyLen; + } + replyLen = iter->second.replyLen; + } else { + sif::debug << "PlocSupervisorHandler::getNextReplyLength: No entry for reply with reply id " + << std::hex << nextReplyId << " in deviceReplyMap" << std::endl; + } + + return replyLen; +} + +void PlocSupervisorHandler::doOffActivity() {} + +void PlocSupervisorHandler::handleDeviceTm(const uint8_t* data, size_t dataSize, + DeviceCommandId_t replyId) { + ReturnValue_t result = returnvalue::OK; + + if (wiretappingMode == RAW) { + /* Data already sent in doGetRead() */ + return; + } + + DeviceReplyMap::iterator iter = deviceReplyMap.find(replyId); + if (iter == deviceReplyMap.end()) { + sif::debug << "PlocSupervisorHandler::handleDeviceTM: Unknown reply id" << std::endl; + return; + } + MessageQueueId_t queueId = iter->second.command->second.sendReplyTo; + + if (queueId == NO_COMMANDER) { + return; + } + + result = actionHelper.reportData(queueId, replyId, data, dataSize); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler::handleDeviceTM: Failed to report data" << std::endl; + } +} + +ReturnValue_t PlocSupervisorHandler::prepareEmptyCmd(uint16_t apid, uint8_t serviceId) { + supv::NoPayloadPacket packet(spParams, apid, serviceId); + ReturnValue_t result = packet.buildPacket(); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareSelBootImageCmd(const uint8_t* commandData) { + supv::MPSoCBootSelect packet(spParams); + ReturnValue_t result = + packet.buildPacket(commandData[0], commandData[1], commandData[2], commandData[3]); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareSetTimeRefCmd() { + Clock::TimeOfDay_t time; + ReturnValue_t result = Clock::getDateAndTime(&time); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::prepareSetTimeRefCmd: Failed to get current time" + << std::endl; + return result::GET_TIME_FAILURE; + } + supv::SetTimeRef packet(spParams); + result = packet.buildPacket(&time); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareDisableHk() { + supv::DisablePeriodicHkTransmission packet(spParams); + ReturnValue_t result = packet.buildPacket(); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareSetBootTimeoutCmd(const uint8_t* commandData) { + supv::SetBootTimeout packet(spParams); + uint32_t timeout = *(commandData) << 24 | *(commandData + 1) << 16 | *(commandData + 2) << 8 | + *(commandData + 3); + ReturnValue_t result = packet.buildPacket(timeout); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareRestartTriesCmd(const uint8_t* commandData) { + uint8_t restartTries = *(commandData); + supv::SetRestartTries packet(spParams); + ReturnValue_t result = packet.buildPacket(restartTries); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareLatchupConfigCmd(const uint8_t* commandData, + DeviceCommandId_t deviceCommand) { + ReturnValue_t result = returnvalue::OK; + uint8_t latchupId = *commandData; + if (latchupId > 6) { + return result::INVALID_LATCHUP_ID; + } + switch (deviceCommand) { + case (supv::ENABLE_LATCHUP_ALERT): { + supv::LatchupAlert packet(spParams); + result = packet.buildPacket(true, latchupId); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + break; + } + case (supv::DISABLE_LATCHUP_ALERT): { + supv::LatchupAlert packet(spParams); + result = packet.buildPacket(false, latchupId); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + break; + } + default: { + sif::debug << "PlocSupervisorHandler::prepareLatchupConfigCmd: Invalid command id" + << std::endl; + result = returnvalue::FAILED; + break; + } + } + return result; +} + +ReturnValue_t PlocSupervisorHandler::prepareSetAlertLimitCmd(const uint8_t* commandData) { + uint8_t offset = 0; + uint8_t latchupId = *commandData; + offset += 1; + uint32_t dutycycle = *(commandData + offset) << 24 | *(commandData + offset + 1) << 16 | + *(commandData + offset + 2) << 8 | *(commandData + offset + 3); + if (latchupId > 6) { + return result::INVALID_LATCHUP_ID; + } + supv::SetAlertlimit packet(spParams); + ReturnValue_t result = packet.buildPacket(latchupId, dutycycle); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareSetAdcEnabledChannelsCmd(const uint8_t* commandData) { + uint16_t ch = *(commandData) << 8 | *(commandData + 1); + supv::SetAdcEnabledChannels packet(spParams); + ReturnValue_t result = packet.buildPacket(ch); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareSetAdcWindowAndStrideCmd(const uint8_t* commandData) { + uint8_t offset = 0; + uint16_t windowSize = *(commandData + offset) << 8 | *(commandData + offset + 1); + offset += 2; + uint16_t stridingStepSize = *(commandData + offset) << 8 | *(commandData + offset + 1); + supv::SetAdcWindowAndStride packet(spParams); + ReturnValue_t result = packet.buildPacket(windowSize, stridingStepSize); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareSetAdcThresholdCmd(const uint8_t* commandData) { + uint32_t threshold = *(commandData) << 24 | *(commandData + 1) << 16 | *(commandData + 2) << 8 | + *(commandData + 3); + supv::SetAdcThreshold packet(spParams); + ReturnValue_t result = packet.buildPacket(threshold); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareRunAutoEmTest(const uint8_t* commandData) { + uint8_t test = *commandData; + if (test != 1 && test != 2) { + return result::INVALID_TEST_PARAM; + } + supv::RunAutoEmTests packet(spParams); + ReturnValue_t result = packet.buildPacket(test); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareSetGpioCmd(const uint8_t* commandData, + size_t commandDataLen) { + if (commandDataLen < 3) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint8_t port = *commandData; + uint8_t pin = *(commandData + 1); + uint8_t val = *(commandData + 2); + supv::SetGpio packet(spParams); + ReturnValue_t result = packet.buildPacket(port, pin, val); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareReadGpioCmd(const uint8_t* commandData, + size_t commandDataLen) { + if (commandDataLen < 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint8_t port = *commandData; + uint8_t pin = *(commandData + 1); + supv::ReadGpio packet(spParams); + ReturnValue_t result = packet.buildPacket(port, pin); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareFactoryResetCmd(const uint8_t* commandData, + size_t len) { + FactoryReset resetCmd(spParams); + if (len < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + ReturnValue_t result = resetCmd.buildPacket(commandData[0]); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(resetCmd); + return returnvalue::OK; +} + +void PlocSupervisorHandler::finishTcPrep(TcBase& tc) { + nextReplyId = supv::ACK_REPORT; + rawPacket = commandBuffer; + rawPacketLen = tc.getFullPacketLen(); + if (DEBUG_PLOC_SUPV) { + sif::debug << "PLOC SUPV: SEND PACKET Size " << tc.getFullPacketLen() << " Module APID " + << (int)tc.getModuleApid() << " Service ID " << (int)tc.getServiceId() << std::endl; + } +} + +ReturnValue_t PlocSupervisorHandler::prepareSetShutdownTimeoutCmd(const uint8_t* commandData) { + uint32_t timeout = 0; + ReturnValue_t result = returnvalue::OK; + size_t size = sizeof(timeout); + result = + SerializeAdapter::deSerialize(&timeout, &commandData, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning + << "PlocSupervisorHandler::prepareSetShutdownTimeoutCmd: Failed to deserialize timeout" + << std::endl; + return result; + } + supv::SetShutdownTimeout packet(spParams); + result = packet.buildPacket(timeout); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +void PlocSupervisorHandler::disableAllReplies() { + using namespace supv; + DeviceReplyMap::iterator iter; + + /* Disable ack reply */ + iter = deviceReplyMap.find(ACK_REPORT); + if (iter == deviceReplyMap.end()) { + return; + } + DeviceReplyInfo* info = &(iter->second); + if (info == nullptr) { + return; + } + info->delayCycles = 0; + info->command = deviceCommandMap.end(); + + DeviceCommandId_t commandId = getPendingCommand(); + + /* If the command expects a telemetry packet the appropriate tm reply will be disabled here */ + switch (commandId) { + case GET_HK_REPORT: { + disableReply(GET_HK_REPORT); + break; + } + case FIRST_MRAM_DUMP: + case CONSECUTIVE_MRAM_DUMP: { + disableReply(commandId); + break; + } + case REQUEST_ADC_REPORT: { + disableReply(ADC_REPORT); + break; + } + case GET_BOOT_STATUS_REPORT: { + disableReply(BOOT_STATUS_REPORT); + break; + } + case GET_LATCHUP_STATUS_REPORT: { + disableReply(LATCHUP_REPORT); + break; + } + case REQUEST_LOGGING_COUNTERS: { + disableReply(COUNTERS_REPORT); + break; + } + default: { + break; + } + } + + /* We must always disable the execution report reply here */ + disableExeReportReply(); +} + +void PlocSupervisorHandler::disableReply(DeviceCommandId_t replyId) { + DeviceReplyMap::iterator iter = deviceReplyMap.find(replyId); + if (iter == deviceReplyMap.end()) { + return; + } + DeviceReplyInfo* info = &(iter->second); + info->delayCycles = 0; + info->active = false; + info->command = deviceCommandMap.end(); +} + +void PlocSupervisorHandler::sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status) { + DeviceReplyIter iter = deviceReplyMap.find(replyId); + + if (iter == deviceReplyMap.end()) { + sif::debug << "PlocSupervisorHandler::sendFailureReport: Reply not in reply map" << std::endl; + return; + } + + DeviceCommandInfo* info = &(iter->second.command->second); + + if (info == nullptr) { + sif::debug << "PlocSupervisorHandler::sendFailureReport: Reply has no active command" + << std::endl; + return; + } + + if (info->sendReplyTo != NO_COMMANDER) { + actionHelper.finish(false, info->sendReplyTo, iter->first, status); + } + info->isExecuting = false; +} + +void PlocSupervisorHandler::disableExeReportReply() { + DeviceReplyIter iter = deviceReplyMap.find(supv::EXE_REPORT); + if (iter == deviceReplyMap.end()) { + return; + } + DeviceReplyInfo* info = &(iter->second); + info->delayCycles = 0; + info->command = deviceCommandMap.end(); + info->active = false; + /* Expected replies is set to one here. The value will set to 0 in replyToReply() */ + info->command->second.expectedReplies = 1; +} + +ReturnValue_t PlocSupervisorHandler::handleMramDumpPacket(DeviceCommandId_t id) { + ReturnValue_t result = returnvalue::FAILED; + + // Prepare packet for downlink + if (packetInBuffer) { + uint16_t packetLen = readSpacePacketLength(spacePacketBuffer); + result = verifyPacket(spacePacketBuffer, ccsds::HEADER_LEN + packetLen + 1); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::handleMramDumpPacket: CRC failure" << std::endl; + return result; + } + result = handleMramDumpFile(id); + if (result != returnvalue::OK) { + DeviceCommandMap::iterator iter = deviceCommandMap.find(id); + if (iter != deviceCommandMap.end()) { + actionHelper.finish(false, iter->second.sendReplyTo, id, result); + } + disableAllReplies(); + nextReplyId = supv::NONE; + return result; + } + packetInBuffer = false; + receivedMramDumpPackets++; + if (expectedMramDumpPackets == receivedMramDumpPackets) { + nextReplyId = supv::EXE_REPORT; + } + increaseExpectedMramReplies(id); + return returnvalue::OK; + } + return result; +} + +void PlocSupervisorHandler::increaseExpectedMramReplies(DeviceCommandId_t id) { + DeviceReplyMap::iterator mramDumpIter = deviceReplyMap.find(id); + DeviceReplyMap::iterator exeReportIter = deviceReplyMap.find(supv::EXE_REPORT); + if (mramDumpIter == deviceReplyMap.end()) { + sif::debug << "PlocSupervisorHandler::increaseExpectedMramReplies: Dump MRAM reply not " + << "in reply map" << std::endl; + return; + } + if (exeReportIter == deviceReplyMap.end()) { + sif::debug << "PlocSupervisorHandler::increaseExpectedMramReplies: Execution report not " + << "in reply map" << std::endl; + return; + } + DeviceReplyInfo* mramReplyInfo = &(mramDumpIter->second); + if (mramReplyInfo == nullptr) { + sif::debug << "PlocSupervisorHandler::increaseExpectedReplies: MRAM reply info nullptr" + << std::endl; + return; + } + DeviceReplyInfo* exeReplyInfo = &(exeReportIter->second); + if (exeReplyInfo == nullptr) { + sif::debug << "PlocSupervisorHandler::increaseExpectedReplies: Execution reply info" + << " nullptr" << std::endl; + return; + } + DeviceCommandInfo* info = &(mramReplyInfo->command->second); + if (info == nullptr) { + sif::debug << "PlocSupervisorHandler::increaseExpectedReplies: Command info nullptr" + << std::endl; + return; + } + uint8_t sequenceFlags = spacePacketBuffer[2] >> 6; + if (sequenceFlags != static_cast(ccsds::SequenceFlags::LAST_SEGMENT) && + (sequenceFlags != static_cast(ccsds::SequenceFlags::UNSEGMENTED))) { + // Command expects at least one MRAM packet more and the execution report + info->expectedReplies = 2; + mramReplyInfo->countdown->resetTimer(); + } else { + // Command expects the execution report + info->expectedReplies = 1; + mramReplyInfo->active = false; + } + exeReplyInfo->countdown->resetTimer(); + return; +} + +ReturnValue_t PlocSupervisorHandler::handleMramDumpFile(DeviceCommandId_t id) { +#ifdef XIPHOS_Q7S + if (not sdcMan->getActiveSdCard()) { + return HasFileSystemIF::FILESYSTEM_INACTIVE; + } +#endif + ReturnValue_t result = returnvalue::OK; + uint16_t packetLen = readSpacePacketLength(spacePacketBuffer); + uint8_t sequenceFlags = readSequenceFlags(spacePacketBuffer); + if (id == supv::FIRST_MRAM_DUMP) { + if (sequenceFlags == static_cast(ccsds::SequenceFlags::FIRST_SEGMENT) || + (sequenceFlags == static_cast(ccsds::SequenceFlags::UNSEGMENTED))) { + result = createMramDumpFile(); + if (result != returnvalue::OK) { + return result; + } + } + } + if (not std::filesystem::exists(activeMramFile)) { + sif::warning << "PlocSupervisorHandler::handleMramDumpFile: MRAM file does not exist" + << std::endl; + return result::MRAM_FILE_NOT_EXISTS; + } + std::ofstream file(activeMramFile, std::ios_base::app | std::ios_base::out); + file.write(reinterpret_cast(spacePacketBuffer + ccsds::HEADER_LEN), packetLen - 1); + file.close(); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::prepareWipeMramCmd(const uint8_t* commandData) { + uint32_t start = 0; + uint32_t stop = 0; + size_t size = sizeof(start) + sizeof(stop); + SerializeAdapter::deSerialize(&start, &commandData, &size, SerializeIF::Endianness::BIG); + SerializeAdapter::deSerialize(&stop, &commandData, &size, SerializeIF::Endianness::BIG); + if ((stop - start) <= 0) { + return result::INVALID_MRAM_ADDRESSES; + } + supv::MramCmd packet(spParams); + ReturnValue_t result = packet.buildPacket(start, stop, supv::MramCmd::MramAction::WIPE); + if (result != returnvalue::OK) { + return result; + } + finishTcPrep(packet); + return returnvalue::OK; +} + +uint16_t PlocSupervisorHandler::readSpacePacketLength(uint8_t* spacePacket) { + return spacePacket[4] << 8 | spacePacket[5]; +} + +uint8_t PlocSupervisorHandler::readSequenceFlags(uint8_t* spacePacket) { + return spacePacketBuffer[2] >> 6; +} + +ReturnValue_t PlocSupervisorHandler::createMramDumpFile() { + ReturnValue_t result = returnvalue::OK; + std::string timeStamp; + result = getTimeStampString(timeStamp); + if (result != returnvalue::OK) { + return result; + } + + std::string filename = "mram-dump--" + timeStamp + ".bin"; + +#ifdef XIPHOS_Q7S + const char* currentMountPrefix = sdcMan->getCurrentMountPrefix(); +#else + const char* currentMountPrefix = "/mnt/sd0"; +#endif /* BOARD_TE0720 == 0 */ + if (currentMountPrefix == nullptr) { + return returnvalue::FAILED; + } + + // Check if path to PLOC directory exists + if (not std::filesystem::exists(std::string(currentMountPrefix) + "/" + supervisorFilePath)) { + sif::warning << "PlocSupervisorHandler::createMramDumpFile: Supervisor path does not exist" + << std::endl; + return result::PATH_DOES_NOT_EXIST; + } + activeMramFile = std::string(currentMountPrefix) + "/" + supervisorFilePath + "/" + filename; + // Create new file + std::ofstream file(activeMramFile, std::ios_base::out); + file.close(); + + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::getTimeStampString(std::string& timeStamp) { + Clock::TimeOfDay_t time; + ReturnValue_t result = Clock::getDateAndTime(&time); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::getTimeStampString: Failed to get current time" + << std::endl; + return result::GET_TIME_FAILURE; + } + timeStamp = std::to_string(time.year) + "-" + std::to_string(time.month) + "-" + + std::to_string(time.day) + "--" + std::to_string(time.hour) + "-" + + std::to_string(time.minute) + "-" + std::to_string(time.second); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::extractUpdateCommand(const uint8_t* commandData, size_t size, + supv::UpdateParams& params) { + size_t remSize = size; + if (size > (config::MAX_FILENAME_SIZE + config::MAX_PATH_SIZE + sizeof(params.memId)) + + sizeof(params.startAddr) + sizeof(params.bytesWritten) + sizeof(params.seqCount) + + sizeof(uint8_t)) { + sif::warning << "PlocSupervisorHandler::extractUpdateCommand: Data size too big" << std::endl; + return result::INVALID_LENGTH; + } + ReturnValue_t result = returnvalue::OK; + result = extractBaseParams(&commandData, size, params); + result = SerializeAdapter::deSerialize(¶ms.bytesWritten, &commandData, &remSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::extractUpdateCommand: Failed to deserialize bytes " + "already written" + << std::endl; + return result; + } + result = SerializeAdapter::deSerialize(¶ms.seqCount, &commandData, &remSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning + << "PlocSupervisorHandler::extractUpdateCommand: Failed to deserialize start sequence count" + << std::endl; + return result; + } + uint8_t delMemRaw = 0; + result = SerializeAdapter::deSerialize(&delMemRaw, &commandData, &remSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning + << "PlocSupervisorHandler::extractUpdateCommand: Failed to deserialize whether to delete " + "memory" + << std::endl; + return result; + } + params.deleteMemory = delMemRaw; + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorHandler::extractBaseParams(const uint8_t** commandData, size_t& remSize, + supv::UpdateParams& params) { + bool nullTermFound = false; + for (size_t idx = 0; idx < remSize; idx++) { + if ((*commandData)[idx] == '\0') { + nullTermFound = true; + break; + } + } + if (not nullTermFound) { + return returnvalue::FAILED; + } + params.file = std::string(reinterpret_cast(*commandData)); + if (params.file.size() > (config::MAX_FILENAME_SIZE + config::MAX_PATH_SIZE)) { + sif::warning << "PlocSupervisorHandler::extractUpdateCommand: Filename too long" << std::endl; + return result::FILENAME_TOO_LONG; + } + *commandData += params.file.size() + SIZE_NULL_TERMINATOR; + remSize -= (params.file.size() + SIZE_NULL_TERMINATOR); + params.memId = **commandData; + *commandData += 1; + remSize -= 1; + ReturnValue_t result = SerializeAdapter::deSerialize(¶ms.startAddr, commandData, &remSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::extractBaseParams: Failed to deserialize start address" + << std::endl; + return result; + } + return result; +} + +ReturnValue_t PlocSupervisorHandler::eventSubscription() { + ReturnValue_t result = returnvalue::OK; + EventManagerIF* manager = ObjectManager::instance()->get(objects::EVENT_MANAGER); + if (manager == nullptr) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "PlocSupervisorHandler::eventSubscritpion: Invalid event manager" << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + ; + } + result = manager->registerListener(eventQueue->getId()); + if (result != returnvalue::OK) { + return result; + } + result = manager->subscribeToEventRange( + eventQueue->getId(), event::getEventId(PlocSupvUartManager::SUPV_UPDATE_FAILED), + event::getEventId(PlocSupvUartManager::SUPV_MEM_CHECK_FAIL)); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "PlocSupervisorHandler::eventSubscritpion: Failed to subscribe to events from " + " ploc supervisor helper" + << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + } + return result; +} + +ReturnValue_t PlocSupervisorHandler::handleExecutionSuccessReport(ExecutionReport& report) { + DeviceCommandId_t commandId = getPendingCommand(); + DeviceCommandMap::iterator iter = deviceCommandMap.find(commandId); + if (iter != deviceCommandMap.end() and iter->second.sendReplyTo != NO_COMMANDER) { + actionHelper.finish(true, iter->second.sendReplyTo, iter->first, returnvalue::OK); + iter->second.isExecuting = false; + } + commandIsPending = false; + switch (commandId) { + case supv::READ_GPIO: { + // TODO: Fix + uint16_t gpioState = report.getStatusCode(); +#if OBSW_DEBUG_PLOC_SUPERVISOR == 1 + sif::info << "PlocSupervisorHandler: Read GPIO TM, State: " << gpioState << std::endl; +#endif /* OBSW_DEBUG_PLOC_SUPERVISOR == 1 */ + if (iter != deviceCommandMap.end() and iter->second.sendReplyTo == NO_COMMAND_ID) { + return returnvalue::OK; + } + uint8_t data[sizeof(gpioState)]; + size_t size = 0; + ReturnValue_t result = SerializeAdapter::serialize(&gpioState, data, &size, sizeof(gpioState), + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler: Failed to deserialize GPIO state" << std::endl; + } + result = actionHelper.reportData(iter->second.sendReplyTo, commandId, data, sizeof(data)); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler: Read GPIO, failed to report data" << std::endl; + } + break; + } + case supv::SET_TIME_REF: { + // We could only allow proper bootup when the time was set successfully, but + // this makes debugging difficult. + + if (startupState == StartupState::WAIT_FOR_TIME_REPLY) { + startupState = StartupState::TIME_WAS_SET; + } + break; + } + default: + break; + } + return returnvalue::OK; +} + +void PlocSupervisorHandler::handleExecutionFailureReport(ExecutionReport& report) { + using namespace supv; + DeviceCommandId_t commandId = getPendingCommand(); + report.printStatusInformation(); + if (commandId != DeviceHandlerIF::NO_COMMAND_ID) { + triggerEvent(SUPV_EXE_FAILURE, commandId, static_cast(report.getStatusCode())); + } + sendFailureReport(EXE_REPORT, result::RECEIVED_EXE_FAILURE); + disableExeReportReply(); +} + +void PlocSupervisorHandler::handleBadApidServiceCombination(Event event, unsigned int apid, + unsigned int serviceId) { + const char* printString = ""; + if (event == SUPV_UNKNOWN_TM) { + printString = "PlocSupervisorHandler: Unknown"; + } else if (event == SUPV_UNINIMPLEMENTED_TM) { + printString = "PlocSupervisorHandler: Unimplemented"; + } + triggerEvent(event, apid, tmReader.getServiceId()); + sif::warning << printString << " APID service combination 0x" << std::setw(2) << std::setfill('0') + << std::hex << apid << ", 0x" << std::setw(2) << serviceId << std::endl; +} + +void PlocSupervisorHandler::printAckFailureInfo(uint16_t statusCode, DeviceCommandId_t commandId) { + switch (commandId) { + case (supv::SET_TIME_REF): { + sif::warning + << "PlocSupervisoHandler: Setting time failed. Make sure the OBC has a valid time" + << std::endl; + break; + } + default: + break; + } +} + +ReturnValue_t PlocSupervisorHandler::getSwitches(const uint8_t** switches, + uint8_t* numberOfSwitches) { + if (powerSwitch == power::NO_SWITCH) { + return DeviceHandlerBase::NO_SWITCH; + } + *numberOfSwitches = 1; + *switches = &powerSwitch; + return returnvalue::OK; +} + +uint32_t PlocSupervisorHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { + return 7000; +} + +void PlocSupervisorHandler::disableCommand(DeviceCommandId_t cmd) { + auto commandIter = deviceCommandMap.find(GET_HK_REPORT); + commandIter->second.isExecuting = false; +} + +ReturnValue_t PlocSupervisorHandler::checkModeCommand(Mode_t commandedMode, + Submode_t commandedSubmode, + uint32_t* msToReachTheMode) { + if (commandedMode != MODE_OFF) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) { + return NON_OP_STATE_OF_CHARGE; + } + } + } + return DeviceHandlerBase::checkModeCommand(commandedMode, commandedSubmode, msToReachTheMode); +} diff --git a/archive/PlocSupervisorHandler.h b/archive/PlocSupervisorHandler.h new file mode 100644 index 0000000..85c3d94 --- /dev/null +++ b/archive/PlocSupervisorHandler.h @@ -0,0 +1,389 @@ +#ifndef MISSION_DEVICES_PLOCSUPERVISORHANDLER_H_ +#define MISSION_DEVICES_PLOCSUPERVISORHANDLER_H_ + +#include +#include +#include + +#include "OBSWConfig.h" +#include "devices/powerSwitcherList.h" +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "fsfw/timemanager/Countdown.h" +#include "fsfw_hal/linux/gpio/Gpio.h" +#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h" +#include "fsfw_hal/linux/serial/SerialComIF.h" + +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/SdCardManager.h" +#endif + +using supv::ExecutionReport; +using supv::TcBase; + +static constexpr bool DEBUG_PLOC_SUPV = true; +static constexpr bool REDUCE_NORMAL_MODE_PRINTOUT = true; + +/** + * @brief This is the device handler for the supervisor of the PLOC which is programmed by + * Thales. + * + * @details The PLOC uses the space packet protocol for communication. On each command the PLOC + * answers with at least one acknowledgment and one execution report. + * Flight manual: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/PLOC_Commands + * ILH ICD: https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/ + * Arbeitsdaten/08_Used%20Components/PLOC&fileid=940960 + * @author J. Meier + */ +class PlocSupervisorHandler : public DeviceHandlerBase { + public: + PlocSupervisorHandler(object_id_t objectId, CookieIF* comCookie, Gpio uartIsolatorSwitch, + power::Switch_t powerSwitch, PlocSupvUartManager& supvHelper); + virtual ~PlocSupervisorHandler(); + + virtual ReturnValue_t initialize() override; + void performOperationHook() override; + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + ReturnValue_t enableReplyInReplyMap(DeviceCommandMap::iterator command, + uint8_t expectedReplies = 1, bool useAlternateId = false, + DeviceCommandId_t alternateReplyID = 0) override; + size_t getNextReplyLength(DeviceCommandId_t deviceCommand) override; + // ReturnValue_t doSendReadHook() override; + void doOffActivity() override; + + private: + static const uint16_t APID_MASK = 0x7FF; + static const uint16_t PACKET_SEQUENCE_COUNT_MASK = 0x3FFF; + static const uint8_t EXE_STATUS_OFFSET = 10; + static const uint8_t SIZE_NULL_TERMINATOR = 1; + // 5 s + static const uint32_t EXECUTION_DEFAULT_TIMEOUT = 5000; + // 70 S + static const uint32_t ACKNOWLEDGE_DEFAULT_TIMEOUT = 5000; + // 60 s + static const uint32_t MRAM_DUMP_EXECUTION_TIMEOUT = 60000; + // 70 s + static const uint32_t COPY_ADC_TO_MRAM_TIMEOUT = 70000; + // 60 s + static const uint32_t MRAM_DUMP_TIMEOUT = 60000; + + enum class StartupState : uint8_t { + OFF, + BOOTING, + SET_TIME, + WAIT_FOR_TIME_REPLY, + TIME_WAS_SET, + ON + }; + + static constexpr bool SET_TIME_DURING_BOOT = true; + + StartupState startupState = StartupState::OFF; + + uint8_t commandBuffer[supv::MAX_COMMAND_SIZE]; + SpacePacketCreator creator; + supv::TcParams spParams = supv::TcParams(creator); + + /** + * This variable is used to store the id of the next reply to receive. This is necessary + * because the PLOC sends as reply to each command at least one acknowledgment and execution + * report. + */ + DeviceCommandId_t nextReplyId = supv::NONE; + + SerialComIF* uartComIf = nullptr; + LinuxLibgpioIF* gpioComIF = nullptr; + Gpio uartIsolatorSwitch; + bool shutdownCmdSent = false; + // Yeah, I am using an extra variable because I once again don't know + // what the hell the base class is doing and I don't care anymore. + bool normalCommandIsPending = false; + // True men implement their reply timeout handling themselves! + Countdown normalCmdCd = Countdown(2000); + bool commandIsPending = false; + Countdown cmdCd = Countdown(2000); + + supv::HkSet hkset; + supv::BootStatusReport bootStatusReport; + supv::LatchupStatusReport latchupStatusReport; + supv::CountersReport countersReport; + supv::AdcReport adcReport; + + const power::Switch_t powerSwitch = power::NO_SWITCH; + supv::TmBase tmReader; + + PlocSupvUartManager& uartManager; + MessageQueueIF* eventQueue = nullptr; + + /** Number of expected replies following the MRAM dump command */ + uint32_t expectedMramDumpPackets = 0; + uint32_t receivedMramDumpPackets = 0; + /** Set to true as soon as a complete space packet is present in the spacePacketBuffer */ + bool packetInBuffer = false; + + /** This buffer is used to concatenate space packets received in two different read steps */ + uint8_t spacePacketBuffer[supv::MAX_PACKET_SIZE]; + +#ifdef XIPHOS_Q7S + SdCardManager* sdcMan = nullptr; +#endif + + // Path to supervisor specific files on SD card + std::string supervisorFilePath = "ploc/supervisor"; + std::string activeMramFile; + + Countdown executionReportTimeout = Countdown(EXECUTION_DEFAULT_TIMEOUT, false); + Countdown acknowledgementReportTimeout = Countdown(ACKNOWLEDGE_DEFAULT_TIMEOUT, false); + // Vorago nees some time to boot properly + Countdown bootTimeout = Countdown(supv::BOOT_TIMEOUT_MS); + Countdown mramDumpTimeout = Countdown(MRAM_DUMP_TIMEOUT); + + PoolEntry adcRawEntry = PoolEntry(16); + PoolEntry adcEngEntry = PoolEntry(16); + PoolEntry latchupCounters = PoolEntry(7); + PoolEntry fmcStateEntry = PoolEntry(1); + PoolEntry bootStateEntry = PoolEntry(1); + PoolEntry bootCyclesEntry = PoolEntry(1); + PoolEntry tempSupEntry = PoolEntry(1); + + /** + * @brief Adjusts the timeout of the execution report dependent on command + */ + void setExecutionTimeout(DeviceCommandId_t command); + + void handlePacketPrint(); + + /** + * @brief Handles event messages received from the supervisor helper + */ + void handleEvent(EventMessage* eventMessage); + + ReturnValue_t getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches); + + /** + * @brief This function checks the crc of the received PLOC reply. + * + * @param start Pointer to the first byte of the reply. + * @param foundLen Pointer to the length of the whole packet. + * + * @return returnvalue::OK if CRC is ok, otherwise CRC_FAILURE. + */ + ReturnValue_t verifyPacket(const uint8_t* start, size_t foundLen); + + /** + * @brief This function handles the acknowledgment report. + * + * @param data Pointer to the data holding the acknowledgment report. + * + * @return returnvalue::OK if successful, otherwise an error code. + */ + ReturnValue_t handleAckReport(const uint8_t* data); + + /** + * @brief This function handles the data of a execution report. + * + * @param data Pointer to the received data packet. + * + * @return returnvalue::OK if successful, otherwise an error code. + */ + ReturnValue_t handleExecutionReport(const uint8_t* data); + + /** + * @brief This function handles the housekeeping report. This means verifying the CRC of the + * reply and filling the appropriate dataset. + * + * @param data Pointer to the data buffer holding the housekeeping read report. + * + * @return returnvalue::OK if successful, otherwise an error code. + */ + ReturnValue_t handleHkReport(const uint8_t* data); + + /** + * @brief This function calls the function to check the CRC of the received boot status report + * and fills the associated dataset with the boot status information. + */ + ReturnValue_t handleBootStatusReport(const uint8_t* data); + + ReturnValue_t handleLatchupStatusReport(const uint8_t* data); + ReturnValue_t handleCounterReport(const uint8_t* data); + void handleBadApidServiceCombination(Event result, unsigned int apid, unsigned int serviceId); + ReturnValue_t handleAdcReport(const uint8_t* data); + ReturnValue_t genericHandleTm(const char* contextString, const uint8_t* data, + LocalPoolDataSetBase& set); + + void disableCommand(DeviceCommandId_t cmd); + + /** + * @brief Depending on the current active command, this function sets the reply id of the + * next reply after a successful acknowledgment report has been received. This is + * required by the function getNextReplyLength() to identify the length of the next + * reply to read. + */ + void setNextReplyId(); + + /** + * @brief This function handles action message replies in case the telemetry has been + * requested by another object. + * + * @param data Pointer to the telemetry data. + * @param dataSize Size of telemetry in bytes. + * @param replyId Id of the reply. This will be added to the ActionMessage. + */ + void handleDeviceTm(const uint8_t* data, size_t dataSize, DeviceCommandId_t replyId); + + /** + * @brief This function prepares a space packet which does not transport any data in the + * packet data field apart from the crc. + */ + ReturnValue_t prepareEmptyCmd(uint16_t apid, uint8_t serviceId); + + /** + * @brief This function initializes the space packet to select the boot image of the MPSoC. + */ + ReturnValue_t prepareSelBootImageCmd(const uint8_t* commandData); + + ReturnValue_t prepareDisableHk(); + + /** + * @brief This function fills the commandBuffer with the data to update the time of the + * PLOC supervisor. + */ + ReturnValue_t prepareSetTimeRefCmd(); + + /** + * @brief This function fills the commandBuffer with the data to change the boot timeout + * value in the PLOC supervisor. + */ + ReturnValue_t prepareSetBootTimeoutCmd(const uint8_t* commandData); + + ReturnValue_t prepareRestartTriesCmd(const uint8_t* commandData); + + ReturnValue_t prepareFactoryResetCmd(const uint8_t* commandData, size_t len); + + /** + * @brief This function fills the command buffer with the packet to enable or disable the + * watchdogs on the PLOC. + */ + void prepareWatchdogsEnableCmd(const uint8_t* commandData); + + /** + * @brief This function fills the command buffer with the packet to set the watchdog timer + * of one of the three watchdogs (PS, PL, INT). + */ + ReturnValue_t prepareWatchdogsConfigTimeoutCmd(const uint8_t* commandData); + + ReturnValue_t prepareLatchupConfigCmd(const uint8_t* commandData, + DeviceCommandId_t deviceCommand); + ReturnValue_t prepareSetAlertLimitCmd(const uint8_t* commandData); + ReturnValue_t prepareSetAdcEnabledChannelsCmd(const uint8_t* commandData); + ReturnValue_t prepareSetAdcWindowAndStrideCmd(const uint8_t* commandData); + ReturnValue_t prepareSetAdcThresholdCmd(const uint8_t* commandData); + ReturnValue_t prepareRunAutoEmTest(const uint8_t* commandData); + ReturnValue_t prepareWipeMramCmd(const uint8_t* commandData); + ReturnValue_t prepareSetGpioCmd(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareReadGpioCmd(const uint8_t* commandData, size_t commandDataLen); + + /** + * @brief Copies the content of a space packet to the command buffer. + */ + void finishTcPrep(TcBase& tc); + + /** + * @brief In case an acknowledgment failure reply has been received this function disables + * all previously enabled commands and resets the exepected replies variable of an + * active command. + */ + void disableAllReplies(); + + void disableReply(DeviceCommandId_t replyId); + + /** + * @brief This function sends a failure report if the active action was commanded by an other + * object. + * + * @param replyId The id of the reply which signals a failure. + * @param status A status byte which gives information about the failure type. + */ + void sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status); + + /** + * @brief This function disables the execution report reply. Within this function also the + * the variable expectedReplies of an active command will be set to 0. + */ + void disableExeReportReply(); + + /** + * @brief This function generates the Service 8 packets for the MRAM dump data. + */ + ReturnValue_t handleMramDumpPacket(DeviceCommandId_t id); + + /** + * @brief With this function the number of expected replies following an MRAM dump command + * will be increased. This is necessary to release the command in case not all replies + * have been received. + */ + void increaseExpectedMramReplies(DeviceCommandId_t id); + + /** + * @brief Writes the data of the MRAM dump to a file. The file will be created when receiving + * the first packet. + */ + ReturnValue_t handleMramDumpFile(DeviceCommandId_t id); + + /** + * @brief Extracts the length field of a spacePacket referenced by the spacePacket pointer. + * + * @param spacePacket Pointer to the buffer holding the space packet. + * + * @return The value stored in the length field of the data field. + */ + uint16_t readSpacePacketLength(uint8_t* spacePacket); + + /** + * @brief Extracts the sequence flags from a space packet referenced by the spacePacket + * pointer. + * + * @param spacePacket Pointer to the buffer holding the space packet. + * + * @return uint8_t where the two least significant bits hold the sequence flags. + */ + uint8_t readSequenceFlags(uint8_t* spacePacket); + + ReturnValue_t createMramDumpFile(); + ReturnValue_t getTimeStampString(std::string& timeStamp); + + ReturnValue_t prepareSetShutdownTimeoutCmd(const uint8_t* commandData); + + ReturnValue_t extractUpdateCommand(const uint8_t* commandData, size_t size, + supv::UpdateParams& params); + ReturnValue_t extractBaseParams(const uint8_t** commandData, size_t& remSize, + supv::UpdateParams& params); + ReturnValue_t eventSubscription(); + + ReturnValue_t handleExecutionSuccessReport(ExecutionReport& report); + void handleExecutionFailureReport(ExecutionReport& report); + + void printAckFailureInfo(uint16_t statusCode, DeviceCommandId_t commandId); + + pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER); + ReturnValue_t checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t* msToReachTheMode) override; +}; + +#endif /* MISSION_DEVICES_PLOCSUPERVISORHANDLER_H_ */ diff --git a/archive/gpio/CMakeLists.txt b/archive/gpio/CMakeLists.txt new file mode 100644 index 0000000..9b20b35 --- /dev/null +++ b/archive/gpio/CMakeLists.txt @@ -0,0 +1,12 @@ +target_sources(${TARGET_NAME} PUBLIC + GpioCookie.cpp + LinuxLibgpioIF.cpp +) + +target_link_libraries(${TARGET_NAME} PUBLIC + gpiod +) + + + + diff --git a/archive/gpio/GpioCookie.cpp b/archive/gpio/GpioCookie.cpp new file mode 100644 index 0000000..b1bb2db --- /dev/null +++ b/archive/gpio/GpioCookie.cpp @@ -0,0 +1,32 @@ +#include "GpioCookie.h" + +#include + +GpioCookie::GpioCookie() {} + +ReturnValue_t GpioCookie::addGpio(gpioId_t gpioId, GpioBase* gpioConfig) { + if (gpioConfig == nullptr) { + sif::debug << "GpioCookie::addGpio: gpioConfig is nullpointer" << std::endl; + return HasReturnvaluesIF::RETURN_FAILED; + } + auto gpioMapIter = gpioMap.find(gpioId); + if (gpioMapIter == gpioMap.end()) { + auto statusPair = gpioMap.emplace(gpioId, gpioConfig); + if (statusPair.second == false) { +#if FSFW_VERBOSE_LEVEL >= 1 + sif::error << "GpioCookie::addGpio: Failed to add GPIO " << gpioId << " to GPIO map" + << std::endl; +#endif + return HasReturnvaluesIF::RETURN_FAILED; + } + return HasReturnvaluesIF::RETURN_OK; + } +#if FSFW_VERBOSE_LEVEL >= 1 + sif::error << "GpioCookie::addGpio: GPIO already exists in GPIO map " << std::endl; +#endif + return HasReturnvaluesIF::RETURN_FAILED; +} + +GpioMap GpioCookie::getGpioMap() const { return gpioMap; } + +GpioCookie::~GpioCookie() {} diff --git a/archive/gpio/GpioCookie.h b/archive/gpio/GpioCookie.h new file mode 100644 index 0000000..9295a4a --- /dev/null +++ b/archive/gpio/GpioCookie.h @@ -0,0 +1,39 @@ +#ifndef LINUX_GPIO_GPIOCOOKIE_H_ +#define LINUX_GPIO_GPIOCOOKIE_H_ + +#include +#include + +#include "GpioIF.h" +#include "gpioDefinitions.h" + +/** + * @brief Cookie for the GpioIF. Allows the GpioIF to determine which + * GPIOs to initialize and whether they should be configured as in- or + * output. + * @details One GpioCookie can hold multiple GPIO configurations. To add a new + * GPIO configuration to a GpioCookie use the GpioCookie::addGpio + * function. + * + * @author J. Meier + */ +class GpioCookie : public CookieIF { + public: + GpioCookie(); + + virtual ~GpioCookie(); + + ReturnValue_t addGpio(gpioId_t gpioId, GpioBase* gpioConfig); + /** + * @brief Get map with registered GPIOs. + */ + GpioMap getGpioMap() const; + + private: + /** + * Returns a copy of the internal GPIO map. + */ + GpioMap gpioMap; +}; + +#endif /* LINUX_GPIO_GPIOCOOKIE_H_ */ diff --git a/archive/gpio/GpioIF.h b/archive/gpio/GpioIF.h new file mode 100644 index 0000000..045af55 --- /dev/null +++ b/archive/gpio/GpioIF.h @@ -0,0 +1,54 @@ +#ifndef LINUX_GPIO_GPIOIF_H_ +#define LINUX_GPIO_GPIOIF_H_ + +#include +#include + +#include "gpioDefinitions.h" + +class GpioCookie; + +/** + * @brief This class defines the interface for objects requiring the control + * over GPIOs. + * @author J. Meier + */ +class GpioIF : public HasReturnvaluesIF { + public: + virtual ~GpioIF(){}; + + /** + * @brief Called by the GPIO using object. + * @param cookie Cookie specifying informations of the GPIOs required + * by a object. + */ + virtual ReturnValue_t addGpios(GpioCookie* cookie) = 0; + + /** + * @brief By implementing this function a child must provide the + * functionality to pull a certain GPIO to high logic level. + * + * @param gpioId A unique number which specifies the GPIO to drive. + * @return Returns RETURN_OK for success. This should never return RETURN_FAILED. + */ + virtual ReturnValue_t pullHigh(gpioId_t gpioId) = 0; + + /** + * @brief By implementing this function a child must provide the + * functionality to pull a certain GPIO to low logic level. + * + * @param gpioId A unique number which specifies the GPIO to drive. + */ + virtual ReturnValue_t pullLow(gpioId_t gpioId) = 0; + + /** + * @brief This function requires a child to implement the functionality to read the state of + * an ouput or input gpio. + * + * @param gpioId A unique number which specifies the GPIO to read. + * @param gpioState State of GPIO will be written to this pointer. + */ + virtual ReturnValue_t readGpio(gpioId_t gpioId, int* gpioState) = 0; +}; + +#endif /* LINUX_GPIO_GPIOIF_H_ */ diff --git a/archive/gpio/LinuxLibgpioIF.cpp b/archive/gpio/LinuxLibgpioIF.cpp new file mode 100644 index 0000000..e5dcc1f --- /dev/null +++ b/archive/gpio/LinuxLibgpioIF.cpp @@ -0,0 +1,295 @@ +#include "LinuxLibgpioIF.h" + +#include +#include +#include +#include + +#include + +#include "GpioCookie.h" + +LinuxLibgpioIF::LinuxLibgpioIF(object_id_t objectId) : SystemObject(objectId) { + struct gpiod_chip* chip = gpiod_chip_open_by_label("/amba_pl/gpio@42030000"); + + sif::debug << chip->name << std::endl; +} + +LinuxLibgpioIF::~LinuxLibgpioIF() {} + +ReturnValue_t LinuxLibgpioIF::addGpios(GpioCookie* gpioCookie) { + ReturnValue_t result; + if (gpioCookie == nullptr) { + sif::error << "LinuxLibgpioIF::initialize: Invalid cookie" << std::endl; + return RETURN_FAILED; + } + + GpioMap mapToAdd = gpioCookie->getGpioMap(); + + /* Check whether this ID already exists in the map and remove duplicates */ + result = checkForConflicts(mapToAdd); + if (result != RETURN_OK) { + return result; + } + + result = configureGpios(mapToAdd); + if (result != RETURN_OK) { + return RETURN_FAILED; + } + + /* Register new GPIOs in gpioMap */ + gpioMap.insert(mapToAdd.begin(), mapToAdd.end()); + + return RETURN_OK; +} + +ReturnValue_t LinuxLibgpioIF::configureGpios(GpioMap& mapToAdd) { + for (auto& gpioConfig : mapToAdd) { + switch (gpioConfig.second->gpioType) { + case (gpio::GpioTypes::NONE): { + return GPIO_INVALID_INSTANCE; + } + case (gpio::GpioTypes::GPIOD_REGULAR): { + GpiodRegular* regularGpio = dynamic_cast(gpioConfig.second); + if (regularGpio == nullptr) { + return GPIO_INVALID_INSTANCE; + } + configureRegularGpio(gpioConfig.first, regularGpio); + break; + } + case (gpio::GpioTypes::CALLBACK): { + auto gpioCallback = dynamic_cast(gpioConfig.second); + if (gpioCallback->callback == nullptr) { + return GPIO_INVALID_INSTANCE; + } + gpioCallback->callback(gpioConfig.first, gpio::GpioOperation::WRITE, + gpioCallback->initValue, gpioCallback->callbackArgs); + } + } + } + return RETURN_OK; +} + +ReturnValue_t LinuxLibgpioIF::configureRegularGpio(gpioId_t gpioId, GpiodRegular* regularGpio) { + std::string chipname; + unsigned int lineNum; + struct gpiod_chip* chip; + gpio::Direction direction; + std::string consumer; + struct gpiod_line* lineHandle; + int result = 0; + + chipname = regularGpio->chipname; + chip = gpiod_chip_open_by_name(chipname.c_str()); + if (!chip) { + sif::error << "LinuxLibgpioIF::configureGpios: Failed to open chip " << chipname + << ". Gpio ID: " << gpioId << std::endl; + return RETURN_FAILED; + } + + lineNum = regularGpio->lineNum; + lineHandle = gpiod_chip_get_line(chip, lineNum); + if (!lineHandle) { + sif::error << "LinuxLibgpioIF::configureGpios: Failed to open line for GPIO with id " << gpioId + << std::endl; + gpiod_chip_close(chip); + return RETURN_FAILED; + } + + direction = regularGpio->direction; + consumer = regularGpio->consumer; + /* Configure direction and add a description to the GPIO */ + switch (direction) { + case (gpio::OUT): { + result = gpiod_line_request_output(lineHandle, consumer.c_str(), regularGpio->initValue); + if (result < 0) { + sif::error << "LinuxLibgpioIF::configureGpios: Failed to request line " << lineNum + << " from GPIO instance with ID: " << gpioId << std::endl; + gpiod_line_release(lineHandle); + return RETURN_FAILED; + } + break; + } + case (gpio::IN): { + result = gpiod_line_request_input(lineHandle, consumer.c_str()); + if (result < 0) { + sif::error << "LinuxLibgpioIF::configureGpios: Failed to request line " << lineNum + << " from GPIO instance with ID: " << gpioId << std::endl; + gpiod_line_release(lineHandle); + return RETURN_FAILED; + } + break; + } + default: { + sif::error << "LinuxLibgpioIF::configureGpios: Invalid direction specified" << std::endl; + return GPIO_INVALID_INSTANCE; + } + } + /** + * Write line handle to GPIO configuration instance so it can later be used to set or + * read states of GPIOs. + */ + regularGpio->lineHandle = lineHandle; + return RETURN_OK; +} + +ReturnValue_t LinuxLibgpioIF::pullHigh(gpioId_t gpioId) { + gpioMapIter = gpioMap.find(gpioId); + if (gpioMapIter == gpioMap.end()) { + sif::warning << "LinuxLibgpioIF::driveGpio: Unknown GPIOD ID " << gpioId << std::endl; + return UNKNOWN_GPIO_ID; + } + + if (gpioMapIter->second->gpioType == gpio::GpioTypes::GPIOD_REGULAR) { + return driveGpio(gpioId, dynamic_cast(gpioMapIter->second), 1); + } else { + auto gpioCallback = dynamic_cast(gpioMapIter->second); + if (gpioCallback->callback == nullptr) { + return GPIO_INVALID_INSTANCE; + } + gpioCallback->callback(gpioMapIter->first, gpio::GpioOperation::WRITE, 1, + gpioCallback->callbackArgs); + } + return GPIO_TYPE_FAILURE; +} + +ReturnValue_t LinuxLibgpioIF::pullLow(gpioId_t gpioId) { + gpioMapIter = gpioMap.find(gpioId); + if (gpioMapIter == gpioMap.end()) { + sif::warning << "LinuxLibgpioIF::driveGpio: Unknown GPIOD ID " << gpioId << std::endl; + return UNKNOWN_GPIO_ID; + } + + if (gpioMapIter->second->gpioType == gpio::GpioTypes::GPIOD_REGULAR) { + return driveGpio(gpioId, dynamic_cast(gpioMapIter->second), 0); + } else { + auto gpioCallback = dynamic_cast(gpioMapIter->second); + if (gpioCallback->callback == nullptr) { + return GPIO_INVALID_INSTANCE; + } + gpioCallback->callback(gpioMapIter->first, gpio::GpioOperation::WRITE, 0, + gpioCallback->callbackArgs); + } + return GPIO_TYPE_FAILURE; +} + +ReturnValue_t LinuxLibgpioIF::driveGpio(gpioId_t gpioId, GpiodRegular* regularGpio, + unsigned int logicLevel) { + if (regularGpio == nullptr) { + return GPIO_TYPE_FAILURE; + } + + int result = gpiod_line_set_value(regularGpio->lineHandle, logicLevel); + if (result < 0) { + sif::warning << "LinuxLibgpioIF::driveGpio: Failed to pull GPIO with ID " << gpioId + << " to logic level " << logicLevel << std::endl; + return DRIVE_GPIO_FAILURE; + } + + return RETURN_OK; +} + +ReturnValue_t LinuxLibgpioIF::readGpio(gpioId_t gpioId, int* gpioState) { + gpioMapIter = gpioMap.find(gpioId); + if (gpioMapIter == gpioMap.end()) { + sif::warning << "LinuxLibgpioIF::readGpio: Unknown GPIOD ID " << gpioId << std::endl; + return UNKNOWN_GPIO_ID; + } + + if (gpioMapIter->second->gpioType == gpio::GpioTypes::GPIOD_REGULAR) { + GpiodRegular* regularGpio = dynamic_cast(gpioMapIter->second); + if (regularGpio == nullptr) { + return GPIO_TYPE_FAILURE; + } + *gpioState = gpiod_line_get_value(regularGpio->lineHandle); + } else { + } + + return RETURN_OK; +} + +ReturnValue_t LinuxLibgpioIF::checkForConflicts(GpioMap& mapToAdd) { + ReturnValue_t status = HasReturnvaluesIF::RETURN_OK; + ReturnValue_t result = HasReturnvaluesIF::RETURN_OK; + for (auto& gpioConfig : mapToAdd) { + switch (gpioConfig.second->gpioType) { + case (gpio::GpioTypes::GPIOD_REGULAR): { + auto regularGpio = dynamic_cast(gpioConfig.second); + if (regularGpio == nullptr) { + return GPIO_TYPE_FAILURE; + } + /* Check for conflicts and remove duplicates if necessary */ + result = checkForConflictsRegularGpio(gpioConfig.first, regularGpio, mapToAdd); + if (result != HasReturnvaluesIF::RETURN_OK) { + status = result; + } + break; + } + case (gpio::GpioTypes::CALLBACK): { + auto callbackGpio = dynamic_cast(gpioConfig.second); + if (callbackGpio == nullptr) { + return GPIO_TYPE_FAILURE; + } + /* Check for conflicts and remove duplicates if necessary */ + result = checkForConflictsCallbackGpio(gpioConfig.first, callbackGpio, mapToAdd); + if (result != HasReturnvaluesIF::RETURN_OK) { + status = result; + } + break; + } + default: { + } + } + } + return status; +} + +ReturnValue_t LinuxLibgpioIF::checkForConflictsRegularGpio(gpioId_t gpioIdToCheck, + GpiodRegular* gpioToCheck, + GpioMap& mapToAdd) { + /* Cross check with private map */ + gpioMapIter = gpioMap.find(gpioIdToCheck); + if (gpioMapIter != gpioMap.end()) { + if (gpioMapIter->second->gpioType != gpio::GpioTypes::GPIOD_REGULAR) { + sif::warning << "LinuxLibgpioIF::checkForConflicts: ID already exists for different " + "GPIO type" + << gpioIdToCheck << ". Removing duplicate." << std::endl; + mapToAdd.erase(gpioIdToCheck); + return HasReturnvaluesIF::RETURN_OK; + } + auto ownRegularGpio = dynamic_cast(gpioMapIter->second); + if (ownRegularGpio == nullptr) { + return GPIO_TYPE_FAILURE; + } + + /* Remove element from map to add because a entry for this GPIO + already exists */ + sif::warning << "LinuxLibgpioIF::checkForConflictsRegularGpio: Duplicate GPIO definition" + << " detected. Duplicate will be removed from map to add." << std::endl; + mapToAdd.erase(gpioIdToCheck); + } + return HasReturnvaluesIF::RETURN_OK; +} + +ReturnValue_t LinuxLibgpioIF::checkForConflictsCallbackGpio(gpioId_t gpioIdToCheck, + GpioCallback* callbackGpio, + GpioMap& mapToAdd) { + /* Cross check with private map */ + gpioMapIter = gpioMap.find(gpioIdToCheck); + if (gpioMapIter != gpioMap.end()) { + if (gpioMapIter->second->gpioType != gpio::GpioTypes::CALLBACK) { + sif::warning << "LinuxLibgpioIF::checkForConflicts: ID already exists for different " + "GPIO type" + << gpioIdToCheck << ". Removing duplicate." << std::endl; + mapToAdd.erase(gpioIdToCheck); + return HasReturnvaluesIF::RETURN_OK; + } + + /* Remove element from map to add because a entry for this GPIO + already exists */ + sif::warning << "LinuxLibgpioIF::checkForConflictsRegularGpio: Duplicate GPIO definition" + << " detected. Duplicate will be removed from map to add." << std::endl; + mapToAdd.erase(gpioIdToCheck); + } + return HasReturnvaluesIF::RETURN_OK; +} diff --git a/archive/gpio/LinuxLibgpioIF.h b/archive/gpio/LinuxLibgpioIF.h new file mode 100644 index 0000000..1c974ef --- /dev/null +++ b/archive/gpio/LinuxLibgpioIF.h @@ -0,0 +1,75 @@ +#ifndef LINUX_GPIO_LINUXLIBGPIOIF_H_ +#define LINUX_GPIO_LINUXLIBGPIOIF_H_ + +#include +#include +#include + +class GpioCookie; + +/** + * @brief This class implements the GpioIF for a linux based system. The + * implementation is based on the libgpiod lib which requires linux 4.8 + * or higher. + * @note The Petalinux SDK from Xilinx supports libgpiod since Petalinux + * 2019.1. + */ +class LinuxLibgpioIF : public GpioIF, public SystemObject { + public: + static const uint8_t gpioRetvalId = CLASS_ID::LINUX_LIBGPIO_IF; + + static constexpr ReturnValue_t UNKNOWN_GPIO_ID = + HasReturnvaluesIF::makeReturnCode(gpioRetvalId, 1); + static constexpr ReturnValue_t DRIVE_GPIO_FAILURE = + HasReturnvaluesIF::makeReturnCode(gpioRetvalId, 2); + static constexpr ReturnValue_t GPIO_TYPE_FAILURE = + HasReturnvaluesIF::makeReturnCode(gpioRetvalId, 3); + static constexpr ReturnValue_t GPIO_INVALID_INSTANCE = + HasReturnvaluesIF::makeReturnCode(gpioRetvalId, 4); + + LinuxLibgpioIF(object_id_t objectId); + virtual ~LinuxLibgpioIF(); + + ReturnValue_t addGpios(GpioCookie* gpioCookie) override; + ReturnValue_t pullHigh(gpioId_t gpioId) override; + ReturnValue_t pullLow(gpioId_t gpioId) override; + ReturnValue_t readGpio(gpioId_t gpioId, int* gpioState) override; + + private: + /* Holds the information and configuration of all used GPIOs */ + GpioMap gpioMap; + GpioMapIter gpioMapIter; + + /** + * @brief This functions drives line of a GPIO specified by the GPIO ID. + * + * @param gpioId The GPIO ID of the GPIO to drive. + * @param logiclevel The logic level to set. O or 1. + */ + ReturnValue_t driveGpio(gpioId_t gpioId, GpiodRegularBase& regularGpio, unsigned int logiclevel); + + ReturnValue_t configureRegularGpio(gpioId_t gpioId, GpiodRegularBase& regularGpio); + + /** + * @brief This function checks if GPIOs are already registered and whether + * there exists a conflict in the GPIO configuration. E.g. the + * direction. + * + * @param mapToAdd The GPIOs which shall be added to the gpioMap. + * + * @return RETURN_OK if successful, otherwise RETURN_FAILED + */ + ReturnValue_t checkForConflicts(GpioMap& mapToAdd); + + ReturnValue_t checkForConflictsRegularGpio(gpioId_t gpiodId, GpiodRegular* regularGpio, + GpioMap& mapToAdd); + ReturnValue_t checkForConflictsCallbackGpio(gpioId_t gpiodId, GpioCallback* regularGpio, + GpioMap& mapToAdd); + + /** + * @brief Performs the initial configuration of all GPIOs specified in the GpioMap mapToAdd. + */ + ReturnValue_t configureGpios(GpioMap& mapToAdd); +}; + +#endif /* LINUX_GPIO_LINUXLIBGPIOIF_H_ */ diff --git a/archive/gpio/gpioDefinitions.h b/archive/gpio/gpioDefinitions.h new file mode 100644 index 0000000..46050d1 --- /dev/null +++ b/archive/gpio/gpioDefinitions.h @@ -0,0 +1,83 @@ +#ifndef LINUX_GPIO_GPIODEFINITIONS_H_ +#define LINUX_GPIO_GPIODEFINITIONS_H_ + +#include +#include + +using gpioId_t = uint16_t; + +namespace gpio { + +enum class Levels : uint8_t { LOW = 0, HIGH = 1 }; + +enum class Direction : uint8_t { IN = 0, OUT = 1 }; + +enum class GpioOperation { READ, WRITE }; + +enum class GpioTypes { NONE, GPIOD_REGULAR, CALLBACK }; + +static constexpr gpioId_t NO_GPIO = -1; +} // namespace gpio + +/** + * @brief Struct containing information about the GPIO to use. This is + * required by the libgpiod to access and drive a GPIO. + * @param chipname String of the chipname specifying the group which contains the GPIO to + * access. E.g. gpiochip0. To detect names of GPIO groups run gpiodetect on + * the linux command line. + * @param lineNum The offset of the GPIO within the GPIO group. + * @param consumer Name of the consumer. Simply a description of the GPIO configuration. + * @param direction Specifies whether the GPIO should be used as in- or output. + * @param initValue Defines the initial state of the GPIO when configured as output. + * Only required for output GPIOs. + * @param lineHandle The handle returned by gpiod_chip_get_line will be later written to this + * pointer. + */ +class GpioBase { + public: + GpioBase() = default; + + GpioBase(gpio::GpioTypes gpioType, std::string consumer, gpio::Direction direction, int initValue) + : gpioType(gpioType), consumer(consumer), direction(direction), initValue(initValue) {} + + virtual ~GpioBase(){}; + + /* Can be used to cast GpioBase to a concrete child implementation */ + gpio::GpioTypes gpioType = gpio::GpioTypes::NONE; + std::string consumer; + gpio::Direction direction = gpio::Direction::IN; + int initValue = 0; +}; + +class GpiodRegular : public GpioBase { + public: + GpiodRegular() + : GpioBase(gpio::GpioTypes::GPIOD_REGULAR, std::string(), gpio::Direction::IN, 0){}; + + GpiodRegular(std::string chipname_, int lineNum_, std::string consumer_, + gpio::Direction direction_, int initValue_) + : GpioBase(gpio::GpioTypes::GPIOD_REGULAR, consumer_, direction_, initValue_), + chipname(chipname_), + lineNum(lineNum_) {} + std::string chipname; + int lineNum = 0; + struct gpiod_line* lineHandle = nullptr; +}; + +class GpioCallback : public GpioBase { + public: + GpioCallback(std::string consumer, gpio::Direction direction_, int initValue_, + void (*callback)(gpioId_t gpioId, gpio::GpioOperation gpioOp, int value, void* args), + void* callbackArgs) + : GpioBase(gpio::GpioTypes::CALLBACK, consumer, direction_, initValue_), + callback(callback), + callbackArgs(callbackArgs) {} + + void (*callback)(gpioId_t gpioId, gpio::GpioOperation gpioOp, int value, void* args) = nullptr; + void* callbackArgs = nullptr; +}; + +using GpioMap = std::unordered_map; +using GpioMapIter = GpioMap::iterator; + +#endif /* LINUX_GPIO_GPIODEFINITIONS_H_ */ diff --git a/archive/tmtc/CCSDSIPCoreBridge.cpp b/archive/tmtc/CCSDSIPCoreBridge.cpp new file mode 100644 index 0000000..607cceb --- /dev/null +++ b/archive/tmtc/CCSDSIPCoreBridge.cpp @@ -0,0 +1,128 @@ +#include +#include +#include + +CCSDSIPCoreBridge::CCSDSIPCoreBridge(object_id_t objectId, object_id_t tcDestination, + object_id_t tmStoreId, object_id_t tcStoreId, + LinuxLibgpioIF* gpioComIF, std::string uioPtme, + gpioId_t papbBusyId, gpioId_t papbEmptyId) + : TmTcBridge(objectId, tcDestination, tmStoreId, tcStoreId), + gpioComIF(gpioComIF), + uioPtme(uioPtme), + papbBusyId(papbBusyId), + papbEmptyId(papbEmptyId) {} + +CCSDSIPCoreBridge::~CCSDSIPCoreBridge() {} + +ReturnValue_t CCSDSIPCoreBridge::initialize() { + ReturnValue_t result = TmTcBridge::initialize(); + + fd = open("/dev/uio0", O_RDWR); + if (fd < 1) { + sif::debug << "CCSDSIPCoreBridge::initialize: Invalid UIO device file" << std::endl; + return RETURN_FAILED; + } + + /** + * Map uio device in virtual address space + * PROT_WRITE: Map uio device in writable only mode + */ + ptmeBaseAddress = + static_cast(mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); + + if (ptmeBaseAddress == MAP_FAILED) { + sif::error << "CCSDSIPCoreBridge::initialize: Failed to map uio address" << std::endl; + return RETURN_FAILED; + } + + return result; +} + +ReturnValue_t CCSDSIPCoreBridge::handleTm() { +#if OBSW_TEST_CCSDS_PTME == 1 + return sendTestFrame(); +#else + return TmTcBridge::handleTm(); +#endif +} + +ReturnValue_t CCSDSIPCoreBridge::sendTm(const uint8_t* data, size_t dataLen) { + if (pollPapbBusySignal() == RETURN_OK) { + startPacketTransfer(); + } + + for (size_t idx = 0; idx < dataLen; idx++) { + if (pollPapbBusySignal() == RETURN_OK) { + *(ptmeBaseAddress + PTME_DATA_REG_OFFSET) = static_cast(*(data + idx)); + } else { + sif::debug << "CCSDSIPCoreBridge::sendTm: Only written " << idx - 1 << " of " << dataLen + << " data" << std::endl; + return RETURN_FAILED; + } + } + + if (pollPapbBusySignal() == RETURN_OK) { + endPacketTransfer(); + } + return RETURN_OK; +} + +void CCSDSIPCoreBridge::startPacketTransfer() { *ptmeBaseAddress = PTME_CONFIG_START; } + +void CCSDSIPCoreBridge::endPacketTransfer() { *ptmeBaseAddress = PTME_CONFIG_END; } + +ReturnValue_t CCSDSIPCoreBridge::pollPapbBusySignal() { + int papbBusyState = 0; + ReturnValue_t result = RETURN_OK; + + /** Check if PAPB interface is ready to receive data */ + result = gpioComIF->readGpio(papbBusyId, &papbBusyState); + if (result != RETURN_OK) { + sif::debug << "CCSDSIPCoreBridge::pollPapbBusySignal: Failed to read papb busy signal" + << std::endl; + return RETURN_FAILED; + } + if (!papbBusyState) { + sif::debug << "CCSDSIPCoreBridge::pollPapbBusySignal: PAPB busy" << std::endl; + return PAPB_BUSY; + } + + return RETURN_OK; +} + +void CCSDSIPCoreBridge::isPtmeBufferEmpty() { + ReturnValue_t result = RETURN_OK; + int papbEmptyState = 1; + + result = gpioComIF->readGpio(papbEmptyId, &papbEmptyState); + + if (result != RETURN_OK) { + sif::debug << "CCSDSIPCoreBridge::isPtmeBufferEmpty: Failed to read papb empty signal" + << std::endl; + return; + } + + if (papbEmptyState == 1) { + sif::debug << "CCSDSIPCoreBridge::isPtmeBufferEmpty: Buffer is empty" << std::endl; + } else { + sif::debug << "CCSDSIPCoreBridge::isPtmeBufferEmpty: Buffer is not empty" << std::endl; + } + return; +} + +ReturnValue_t CCSDSIPCoreBridge::sendTestFrame() { + /** Size of one complete transfer frame data field amounts to 1105 bytes */ + uint8_t testPacket[1105]; + + /** Fill one test packet */ + for (int idx = 0; idx < 1105; idx++) { + testPacket[idx] = static_cast(idx & 0xFF); + } + + ReturnValue_t result = sendTm(testPacket, 1105); + if (result != RETURN_OK) { + return result; + } + + return RETURN_OK; +} diff --git a/archive/tmtc/CCSDSIPCoreBridge.h b/archive/tmtc/CCSDSIPCoreBridge.h new file mode 100644 index 0000000..074769c --- /dev/null +++ b/archive/tmtc/CCSDSIPCoreBridge.h @@ -0,0 +1,129 @@ +#ifndef MISSION_OBC_CCSDSIPCOREBRIDGE_H_ +#define MISSION_OBC_CCSDSIPCOREBRIDGE_H_ + +#include +#include +#include + +#include + +#include "OBSWConfig.h" + +/** + * @brief This class handles the interfacing to the telemetry (PTME) and telecommand (PDEC) IP + * cores responsible for the CCSDS encoding and decoding. The IP cores are implemented + * on the programmable logic and are accessible through the linux UIO driver. + */ +class CCSDSIPCoreBridge : public TmTcBridge { + public: + /** + * @brief Constructor + * + * @param objectId + * @param tcDestination + * @param tmStoreId + * @param tcStoreId + * @param uioPtme Name of the uio device file which provides access to the PTME IP Core. + * @param papbBusyId The ID of the GPIO which is connected to the PAPBBusy_N signal of the + * PTME IP Core. A low logic level indicates the PTME is not ready to + * receive more data. + * @param papbEmptyId The ID of the GPIO which is connected to the PAPBEmpty signal of the + * PTME IP Core. The signal is high when there are no packets in the + * external buffer memory (BRAM). + */ + CCSDSIPCoreBridge(object_id_t objectId, object_id_t tcDestination, object_id_t tmStoreId, + object_id_t tcStoreId, LinuxLibgpioIF* gpioComIF, std::string uioPtme, + gpioId_t papbBusyId, gpioId_t papbEmptyId); + virtual ~CCSDSIPCoreBridge(); + + ReturnValue_t initialize() override; + + protected: + /** + * Overwriting this function to provide the capability of testing the PTME IP Core + * implementation. + */ + virtual ReturnValue_t handleTm() override; + + virtual ReturnValue_t sendTm(const uint8_t* data, size_t dataLen) override; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::CCSDS_IP_CORE_BRIDGE; + + static const ReturnValue_t PAPB_BUSY = MAKE_RETURN_CODE(0xA0); + + /** Size of mapped address space. 4k (minimal size of pl device) */ + // static const int MAP_SIZE = 0xFA0; + static const int MAP_SIZE = 0x1000; + + /** + * Configuration bits: + * bit[1:0]: Size of data (1,2,3 or 4 bytes). 1 Byte <=> b00 + * bit[2]: Set this bit to 1 to abort a transfered packet + * bit[3]: Signals to PTME the start of a new telemetry packet + */ + static const uint32_t PTME_CONFIG_START = 0x8; + + /** + * Writing this word to the ptme base address signals to the PTME that a complete tm packet has + * been transferred. + */ + static const uint32_t PTME_CONFIG_END = 0x0; + + /** + * Writing to this offset within the PTME memory space will insert data for encoding to the + * PTME IP core. + * The address offset is 0x400 (= 4 * 256) + */ + static const int PTME_DATA_REG_OFFSET = 256; + + LinuxLibgpioIF* gpioComIF = nullptr; + + /** The uio device file related to the PTME IP Core */ + std::string uioPtme; + + /** Pulled to low when PTME not ready to receive data */ + gpioId_t papbBusyId = gpio::NO_GPIO; + + /** High when externally buffer memory of PTME is empty */ + gpioId_t papbEmptyId = gpio::NO_GPIO; + + /** The file descriptor of the UIO driver */ + int fd; + + uint32_t* ptmeBaseAddress = nullptr; + + /** + * @brief This function sends the config byte to the PTME IP Core to initiate a packet + * transfer. + */ + void startPacketTransfer(); + + /** + * @brief This function sends the config byte to the PTME IP Core to signal the end of a + * packet transfer. + */ + void endPacketTransfer(); + + /** + * @brief This function reads the papb busy signal indicating whether the PAPB interface is + * ready to receive more data or not. PAPB is ready when PAPB_Busy_N == '1'. + * + * @return RETURN_OK when ready to receive data else PAPB_BUSY. + */ + ReturnValue_t pollPapbBusySignal(); + + /** + * @brief This function can be used for debugging to check wheter there are packets in + * the packet buffer of the PTME or not. + */ + void isPtmeBufferEmpty(); + + /** + * @brief This function sends a complete telemetry transfer frame data field (1105 bytes) + * to the input of the PTME IP Core. Can be used to test the implementation. + */ + ReturnValue_t sendTestFrame(); +}; + +#endif /* MISSION_OBC_CCSDSIPCOREBRIDGE_H_ */ diff --git a/arduino b/arduino new file mode 160000 index 0000000..3ea528d --- /dev/null +++ b/arduino @@ -0,0 +1 @@ +Subproject commit 3ea528dc5f890985f7d889b6e6496fc252a770ce diff --git a/automation/Dockerfile b/automation/Dockerfile new file mode 100644 index 0000000..73275ff --- /dev/null +++ b/automation/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:focal + +RUN apt-get update +RUN apt-get --yes upgrade +#tzdata is a dependency, won't install otherwise +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get --yes install cmake libgpiod-dev xz-utils nano curl git gcc g++ lcov valgrind libgps-dev python3 + +ARG XIPHOS_SDK_NAME=sdk-xiphos-eive-v0.2.0 +# Install Xiphos ARK SDK, which also installs Q7S root filesystem, required for cross-compilation. +RUN curl https://buggy.irs.uni-stuttgart.de/eive/tools/${XIPHOS_SDK_NAME}.tar | tar -x && \ + cd ${XIPHOS_SDK_NAME} && \ + ./ark-glibc-x86_64-eive-image-cortexa9hf-neon-toolchain-nodistro.0.sh -y + +# Cross compiler +RUN mkdir -p /usr/tools; \ +curl https://buggy.irs.uni-stuttgart.de/eive/tools/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.gz \ + | tar -xz -C /usr/tools + +RUN git clone https://github.com/catchorg/Catch2.git && \ + cd Catch2 && \ + git checkout v3.0.0-preview5 && \ + cmake -Bbuild -H. -DBUILD_TESTING=OFF && \ + cmake --build build/ --target install + +ENV ZYNQ_7020_SYSROOT="/opt/xiphos/sdk/ark/sysroots/cortexa9hf-neon-xiphos-linux-gnueabi" +ENV PATH=$PATH:"/usr/tools/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin" diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile new file mode 100644 index 0000000..12c4bdf --- /dev/null +++ b/automation/Jenkinsfile @@ -0,0 +1,47 @@ +pipeline { + environment { + BUILDDIR_Q7S = 'build_q7s_fm' + BUILDDIR_Q7S_EM = 'build_q7s_em' + BUILDDIR_LINUX = 'build_linux' + } + agent { + docker { + image 'eive-obsw-ci:d5' + args '--sysctl fs.mqueue.msg_max=100' + } + } + stages { + stage('Clean') { + steps { + sh 'rm -rf $BUILDDIR_Q7S' + sh 'rm -rf $BUILDDIR_Q7S_EM' + sh 'rm -rf $BUILDDIR_LINUX' + } + } + stage('Build Q7S') { + steps { + dir(BUILDDIR_Q7S) { + sh 'cmake -DTGT_BSP="arm/q7s" -DCMAKE_BUILD_TYPE=Debug ..' + sh 'cmake --build . -j8' + } + } + } + stage('Build Q7S EM') { + steps { + dir(BUILDDIR_Q7S_EM) { + sh 'cmake -DTGT_BSP="arm/q7s" -DEIVE_Q7S_EM=ON -DCMAKE_BUILD_TYPE=Debug ..' + sh 'cmake --build . -j8' + } + } + } + stage('Build Host and Tests') { + steps { + dir(BUILDDIR_LINUX) { + sh 'cmake ..' + sh 'cmake --build . -j8' + sh './eive-unittest' + } + } + } + } +} diff --git a/bsp_egse/CMakeLists.txt b/bsp_egse/CMakeLists.txt new file mode 100644 index 0000000..d104354 --- /dev/null +++ b/bsp_egse/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(${OBSW_NAME} PUBLIC InitMission.cpp main.cpp ObjectFactory.cpp) + +add_subdirectory(boardconfig) diff --git a/bsp_egse/InitMission.cpp b/bsp_egse/InitMission.cpp new file mode 100644 index 0000000..5a72f53 --- /dev/null +++ b/bsp_egse/InitMission.cpp @@ -0,0 +1,192 @@ +#include "InitMission.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "OBSWConfig.h" +#include "ObjectFactory.h" +#include "objects/systemObjectList.h" +#include "pollingsequence/pollingSequenceFactory.h" + +ServiceInterfaceStream sif::debug("DEBUG"); +ServiceInterfaceStream sif::info("INFO"); +ServiceInterfaceStream sif::warning("WARNING"); +ServiceInterfaceStream sif::error("ERROR"); + +ObjectManagerIF* objectManager = nullptr; + +void initmission::initMission() { + sif::info << "Make sure the systemd service ser2net on the egse has been stopped " + << "(alias stop-ser2net)" << std::endl; + sif::info << "Make sure the power lines of the star tracker have been enabled " + << "(alias enable-startracker)" << std::endl; + sif::info << "Building global objects.." << std::endl; + /* Instantiate global object manager and also create all objects */ + ObjectManager::instance()->setObjectFactoryFunction(ObjectFactory::produce, nullptr); + sif::info << "Initializing all objects.." << std::endl; + ObjectManager::instance()->initialize(); + + /* This function creates and starts all tasks */ + initTasks(); +} + +void initmission::initTasks() { + TaskFactory* factory = TaskFactory::instance(); + ReturnValue_t result = returnvalue::OK; + if (factory == nullptr) { + /* Should never happen ! */ + return; + } +#if OBSW_PRINT_MISSED_DEADLINES == 1 + void (*missedDeadlineFunc)(void) = TaskFactory::printMissedDeadline; +#else + void (*missedDeadlineFunc)(void) = nullptr; +#endif + + /* TMTC Distribution */ + PeriodicTaskIF* tmtcDistributor = factory->createPeriodicTask( + "DIST", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = tmtcDistributor->addComponent(objects::CCSDS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + result = tmtcDistributor->addComponent(objects::PUS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + result = tmtcDistributor->addComponent(objects::TM_FUNNEL); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + + PeriodicTaskIF* tmtcBridgeTask = factory->createPeriodicTask( + "TMTC_BRIDGE", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = tmtcBridgeTask->addComponent(objects::TMTC_BRIDGE); + if (result != returnvalue::OK) { + sif::error << "Add component TMTC Bridge failed" << std::endl; + } + PeriodicTaskIF* tmtcPollingTask = factory->createPeriodicTask( + "TMTC_POLLING", 80, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = tmtcPollingTask->addComponent(objects::TMTC_POLLING_TASK); + if (result != returnvalue::OK) { + sif::error << "Add component TMTC Polling failed" << std::endl; + } + + /* PUS Services */ + std::vector pusTasks; + createPusTasks(*factory, missedDeadlineFunc, pusTasks); + + std::vector pstTasks; + FixedTimeslotTaskIF* pst = factory->createFixedTimeslotTask( + "STAR_TRACKER_PST", 70, PeriodicTaskIF::MINIMUM_STACK_SIZE * 4, 0.5, missedDeadlineFunc); + result = pst::pstUart(pst); + if (result != returnvalue::OK) { + sif::error << "InitMission::initTasks: Creating PST failed!" << std::endl; + } + pstTasks.push_back(pst); + + PeriodicTaskIF* strHelperTask = factory->createPeriodicTask( + "STR_HELPER", 20, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = strHelperTask->addComponent(objects::STR_HELPER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("STR_HELPER", objects::STR_HELPER); + } + pstTasks.push_back(strHelperTask); + + auto taskStarter = [](std::vector& taskVector, std::string name) { + for (const auto& task : taskVector) { + if (task != nullptr) { + task->startTask(); + } else { + sif::error << "Task in vector " << name << " is invalid!" << std::endl; + } + } + }; + + sif::info << "Starting tasks.." << std::endl; + tmtcDistributor->startTask(); + tmtcBridgeTask->startTask(); + tmtcPollingTask->startTask(); + + taskStarter(pstTasks, "PST Tasks"); + taskStarter(pusTasks, "PUS Tasks"); + + sif::info << "Tasks started.." << std::endl; +} + +void initmission::createPusTasks(TaskFactory& factory, + TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec) { + ReturnValue_t result = returnvalue::OK; + PeriodicTaskIF* pusVerification = factory.createPeriodicTask( + "PUS_VERIF", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusVerification->addComponent(objects::PUS_SERVICE_1_VERIFICATION); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + taskVec.push_back(pusVerification); + + PeriodicTaskIF* pusEvents = factory.createPeriodicTask( + "PUS_EVENTS", 60, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusEvents->addComponent(objects::PUS_SERVICE_5_EVENT_REPORTING); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS_EVENTS", objects::PUS_SERVICE_5_EVENT_REPORTING); + } + result = pusEvents->addComponent(objects::EVENT_MANAGER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS_MGMT", objects::EVENT_MANAGER); + } + taskVec.push_back(pusEvents); + + PeriodicTaskIF* pusHighPrio = factory.createPeriodicTask( + "PUS_HIGH_PRIO", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusHighPrio->addComponent(objects::PUS_SERVICE_2_DEVICE_ACCESS); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS2", objects::PUS_SERVICE_2_DEVICE_ACCESS); + } + result = pusHighPrio->addComponent(objects::PUS_SERVICE_9_TIME_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS9", objects::PUS_SERVICE_9_TIME_MGMT); + } + taskVec.push_back(pusHighPrio); + + PeriodicTaskIF* pusMedPrio = factory.createPeriodicTask( + "PUS_MED_PRIO", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.8, missedDeadlineFunc); + result = pusMedPrio->addComponent(objects::PUS_SERVICE_8_FUNCTION_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS8", objects::PUS_SERVICE_8_FUNCTION_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_200_MODE_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS200", objects::PUS_SERVICE_200_MODE_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_20_PARAMETERS); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS20", objects::PUS_SERVICE_20_PARAMETERS); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_3_HOUSEKEEPING); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS3", objects::PUS_SERVICE_3_HOUSEKEEPING); + } + taskVec.push_back(pusMedPrio); + + PeriodicTaskIF* pusLowPrio = factory.createPeriodicTask( + "PUS_LOW_PRIO", 30, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.6, missedDeadlineFunc); + result = pusLowPrio->addComponent(objects::PUS_SERVICE_17_TEST); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS17", objects::PUS_SERVICE_17_TEST); + } + result = pusLowPrio->addComponent(objects::INTERNAL_ERROR_REPORTER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("INT_ERR_RPRT", objects::INTERNAL_ERROR_REPORTER); + } + taskVec.push_back(pusLowPrio); +} diff --git a/bsp_egse/InitMission.h b/bsp_egse/InitMission.h new file mode 100644 index 0000000..c3ba58e --- /dev/null +++ b/bsp_egse/InitMission.h @@ -0,0 +1,21 @@ +#ifndef BSP_LINUX_INITMISSION_H_ +#define BSP_LINUX_INITMISSION_H_ + +#include + +#include "fsfw/tasks/Typedef.h" + +class PeriodicTaskIF; +class TaskFactory; + +namespace initmission { +void initMission(); +void initTasks(); + +void createPstTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +void createPusTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +}; // namespace initmission + +#endif /* BSP_LINUX_INITMISSION_H_ */ diff --git a/bsp_egse/ObjectFactory.cpp b/bsp_egse/ObjectFactory.cpp new file mode 100644 index 0000000..b13af72 --- /dev/null +++ b/bsp_egse/ObjectFactory.cpp @@ -0,0 +1,48 @@ +#include "ObjectFactory.h" + +#include +#include +#include + +#include "OBSWConfig.h" +#include "busConf.h" +#include "fsfw/datapoollocal/LocalDataPoolManager.h" +#include "fsfw/tmtcpacket/pus/tm.h" +#include "fsfw/tmtcservices/CommandingServiceBase.h" +#include "fsfw/tmtcservices/PusServiceBase.h" +#include "linux/devices/devicedefinitions/StarTrackerDefinitions.h" +#include "linux/devices/startracker/StarTrackerHandler.h" +#include "mission/core/GenericFactory.h" +#include "mission/utility/TmFunnel.h" +#include "objects/systemObjectList.h" +#include "tmtc/apid.h" +#include "tmtc/pusIds.h" + +void Factory::setStaticFrameworkObjectIds() { + PusServiceBase::packetSource = objects::PUS_PACKET_DISTRIBUTOR; + PusServiceBase::packetDestination = objects::TM_FUNNEL; + + CommandingServiceBase::defaultPacketSource = objects::PUS_PACKET_DISTRIBUTOR; + CommandingServiceBase::defaultPacketDestination = objects::TM_FUNNEL; + + TmFunnel::downlinkDestination = objects::TMTC_BRIDGE; + TmFunnel::storageDestination = objects::NO_OBJECT; + + VerificationReporter::messageReceiver = objects::PUS_SERVICE_1_VERIFICATION; + TmPacketBase::timeStamperId = objects::TIME_STAMPER; +} + +void ObjectFactory::produce(void* args) { + Factory::setStaticFrameworkObjectIds(); + ObjectFactory::produceGenericObjects(); + + UartCookie* starTrackerCookie = + new UartCookie(objects::STAR_TRACKER, egse::STAR_TRACKER_UART, UartModes::NON_CANONICAL, + uart::STAR_TRACKER_BAUD, startracker::MAX_FRAME_SIZE * 2 + 2); + newSerialComIF(objects::UART_COM_IF); + starTrackerCookie->setNoFixedSizeReply(); + StrHelper* strHelper = new StrHelper(objects::STR_HELPER); + StarTrackerHandler* starTrackerHandler = new StarTrackerHandler( + objects::STAR_TRACKER, objects::UART_COM_IF, starTrackerCookie, strHelper); + starTrackerHandler->setStartUpImmediately(); +} diff --git a/bsp_egse/ObjectFactory.h b/bsp_egse/ObjectFactory.h new file mode 100644 index 0000000..b24dd32 --- /dev/null +++ b/bsp_egse/ObjectFactory.h @@ -0,0 +1,8 @@ +#ifndef BSP_LINUX_OBJECTFACTORY_H_ +#define BSP_LINUX_OBJECTFACTORY_H_ + +namespace ObjectFactory { +void produce(void* args); +}; // namespace ObjectFactory + +#endif /* BSP_LINUX_OBJECTFACTORY_H_ */ diff --git a/bsp_egse/boardconfig/CMakeLists.txt b/bsp_egse/boardconfig/CMakeLists.txt new file mode 100644 index 0000000..f08670d --- /dev/null +++ b/bsp_egse/boardconfig/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(${OBSW_NAME} PRIVATE print.c) + +target_include_directories(${OBSW_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/bsp_egse/boardconfig/busConf.h b/bsp_egse/boardconfig/busConf.h new file mode 100644 index 0000000..4df55ed --- /dev/null +++ b/bsp_egse/boardconfig/busConf.h @@ -0,0 +1,8 @@ +#ifndef BSP_EGSE_BOARDCONFIG_BUSCONF_H_ +#define BSP_EGSE_BOARDCONFIG_BUSCONF_H_ + +namespace egse { +static constexpr char STAR_TRACKER_UART[] = "/dev/serial0"; +} + +#endif /* BSP_EGSE_BOARDCONFIG_BUSCONF_H_ */ diff --git a/bsp_egse/boardconfig/etl_profile.h b/bsp_egse/boardconfig/etl_profile.h new file mode 100644 index 0000000..54aca34 --- /dev/null +++ b/bsp_egse/boardconfig/etl_profile.h @@ -0,0 +1,38 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2019 jwellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ +#ifndef __ETL_PROFILE_H__ +#define __ETL_PROFILE_H__ + +#define ETL_CHECK_PUSH_POP + +#define ETL_CPP11_SUPPORTED 1 +#define ETL_NO_NULLPTR_SUPPORT 0 + +#endif diff --git a/bsp_egse/boardconfig/gcov.h b/bsp_egse/boardconfig/gcov.h new file mode 100644 index 0000000..80acdd8 --- /dev/null +++ b/bsp_egse/boardconfig/gcov.h @@ -0,0 +1,15 @@ +#ifndef LINUX_GCOV_H_ +#define LINUX_GCOV_H_ +#include + +#ifdef GCOV +extern "C" void __gcov_flush(); +#else +void __gcov_flush() { + sif::info << "GCC GCOV: Please supply GCOV=1 in Makefile if " + "coverage information is desired.\n" + << std::flush; +} +#endif + +#endif /* LINUX_GCOV_H_ */ diff --git a/bsp_egse/boardconfig/print.c b/bsp_egse/boardconfig/print.c new file mode 100644 index 0000000..5b3a0f9 --- /dev/null +++ b/bsp_egse/boardconfig/print.c @@ -0,0 +1,10 @@ +#include +#include + +void printChar(const char* character, bool errStream) { + if (errStream) { + putc(*character, stderr); + return; + } + putc(*character, stdout); +} diff --git a/bsp_egse/boardconfig/print.h b/bsp_egse/boardconfig/print.h new file mode 100644 index 0000000..8e7e2e5 --- /dev/null +++ b/bsp_egse/boardconfig/print.h @@ -0,0 +1,8 @@ +#ifndef HOSTED_BOARDCONFIG_PRINT_H_ +#define HOSTED_BOARDCONFIG_PRINT_H_ + +#include + +void printChar(const char* character, bool errStream); + +#endif /* HOSTED_BOARDCONFIG_PRINT_H_ */ diff --git a/bsp_egse/boardconfig/rpiConfig.h.in b/bsp_egse/boardconfig/rpiConfig.h.in new file mode 100644 index 0000000..af4f0dd --- /dev/null +++ b/bsp_egse/boardconfig/rpiConfig.h.in @@ -0,0 +1,6 @@ +#ifndef BSP_RPI_BOARDCONFIG_RPI_CONFIG_H_ +#define BSP_RPI_BOARDCONFIG_RPI_CONFIG_H_ + +#include + +#endif /* BSP_RPI_BOARDCONFIG_RPI_CONFIG_H_ */ diff --git a/bsp_egse/main.cpp b/bsp_egse/main.cpp new file mode 100644 index 0000000..75fb4f1 --- /dev/null +++ b/bsp_egse/main.cpp @@ -0,0 +1,28 @@ +#include + +#include "InitMission.h" +#include "OBSWConfig.h" +#include "OBSWVersion.h" +#include "fsfw/tasks/TaskFactory.h" +#include "fsfw/version.h" + +/** + * @brief This is the main program entry point for the egse (raspberry pi 4) + * @return + */ +int main(void) { + std::cout << "-- EIVE OBSW --" << std::endl; + std::cout << "-- Compiled for EGSE from Arcsec" + << " --" << std::endl; + std::cout << "-- OBSW " << SW_NAME << " v" << SW_VERSION << "." << SW_SUBVERSION << "." + << SW_REVISION << ", FSFW v" << FSFW_VERSION << "." << FSFW_SUBVERSION << FSFW_REVISION + << "--" << std::endl; + std::cout << "-- " << __DATE__ << " " << __TIME__ << " --" << std::endl; + + initmission::initMission(); + + for (;;) { + /* Suspend main thread by sleeping it. */ + TaskFactory::delayTask(5000); + } +} diff --git a/bsp_hosted/CMakeLists.txt b/bsp_hosted/CMakeLists.txt new file mode 100644 index 0000000..1300375 --- /dev/null +++ b/bsp_hosted/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(${OBSW_NAME} PUBLIC scheduling.cpp main.cpp objectFactory.cpp) + +add_subdirectory(fsfwconfig) +add_subdirectory(boardconfig) diff --git a/bsp_hosted/Dockerfile b/bsp_hosted/Dockerfile new file mode 100644 index 0000000..4d89742 --- /dev/null +++ b/bsp_hosted/Dockerfile @@ -0,0 +1,21 @@ +FROM ubuntu:latest +# FROM alpine:latest + +ENV TZ=Europe/Berlin +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN apt-get update && apt-get install -y cmake g++ +# RUN apk add cmake make g++ + +WORKDIR /usr/src/app +COPY . . + +RUN set -ex; \ + rm -rf build-hosted; \ + mkdir build-hosted; \ + cd build-hosted; \ + cmake -DCMAKE_BUILD_TYPE=Release -DOSAL_FSFW=host ..; + +ENTRYPOINT ["cmake", "--build", "build-hosted"] +CMD ["-j"] +# CMD ["bash"] diff --git a/bsp_hosted/OBSWConfig.h.in b/bsp_hosted/OBSWConfig.h.in new file mode 100644 index 0000000..4151557 --- /dev/null +++ b/bsp_hosted/OBSWConfig.h.in @@ -0,0 +1,127 @@ +/** + * @brief This file can be used to add preprocessor define for conditional + * code inclusion exclusion or various other project constants and + * properties in one place. + */ +#ifndef FSFWCONFIG_OBSWCONFIG_H_ +#define FSFWCONFIG_OBSWCONFIG_H_ + +#include "commonConfig.h" + +/*******************************************************************/ +/** All of the following flags should be enabled for mission code */ +/*******************************************************************/ + +#define OBSW_ENABLE_TIMERS 1 +#define OBSW_ADD_STAR_TRACKER 0 +#define OBSW_ADD_PLOC_SUPERVISOR 0 +#define OBSW_ADD_PLOC_MPSOC 0 +#define OBSW_ADD_SUN_SENSORS 0 +#define OBSW_ADD_MGT 0 +#define OBSW_ADD_ACS_BOARD 0 +#define OBSW_ADD_ACS_HANDLERS 0 +#define OBSW_ADD_GPS_0 0 +#define OBSW_ADD_GPS_1 0 +#define OBSW_ADD_RW 0 +#define OBSW_DEBUG_TMP1075 0 +#define OBSW_ADD_BPX_BATTERY_HANDLER 0 +#define OBSW_ADD_RTD_DEVICES 0 +#define OBSW_ADD_PL_PCDU 0 +#define OBSW_ADD_TMP_DEVICES 0 +#define OBSW_ADD_RAD_SENSORS 0 +#define OBSW_ADD_SYRLINKS 0 +#define OBSW_STAR_TRACKER_GROUND_CONFIG 1 + +// This is a really tricky switch.. It initializes the PCDU switches to their default states +// at powerup. I think it would be better +// to leave it off for now. It makes testing a lot more difficult and it might mess with +// something the operators might want to do by giving the software too much intelligence +// at the wrong place. The system component might command all the Switches accordingly anyway +#define OBSW_INITIALIZE_SWITCHES 0 +#define OBSW_ENABLE_PERIODIC_HK 0 + +/*******************************************************************/ +/** All of the following flags should be disabled for mission code */ +/*******************************************************************/ + +// Can be used to switch device to NORMAL mode immediately +#define OBSW_SWITCH_TO_NORMAL_MODE_AFTER_STARTUP 1 +#define OBSW_PRINT_MISSED_DEADLINES 1 + +#define OBSW_MPSOC_JTAG_BOOT 0 +#define OBSW_SYRLINKS_SIMULATED 1 +#define OBSW_ADD_TEST_CODE 0 +#define OBSW_ADD_TEST_TASK 0 +#define OBSW_ADD_TEST_PST 0 +// If this is enabled, all other SPI code should be disabled +#define OBSW_ADD_SPI_TEST_CODE 0 +// If this is enabled, all other I2C code should be disabled +#define OBSW_ADD_I2C_TEST_CODE 0 +#define OBSW_ADD_UART_TEST_CODE 0 + +#define OBSW_TEST_ACS 0 +#define OBSW_DEBUG_ACS 0 +#define OBSW_TEST_SUS 0 +#define OBSW_DEBUG_SUS 0 +#define OBSW_TEST_RTD 0 +#define OBSW_DEBUG_RTD 0 +#define OBSW_TEST_RAD_SENSOR 0 +#define OBSW_DEBUG_RAD_SENSOR 0 +#define OBSW_TEST_PL_PCDU 0 +#define OBSW_DEBUG_PL_PCDU 0 +#define OBSW_TEST_BPX_BATT 0 +#define OBSW_DEBUG_BPX_BATT 0 +#define OBSW_TEST_IMTQ 0 +#define OBSW_DEBUG_IMTQ 0 +#define OBSW_TEST_RW 0 +#define OBSW_DEBUG_RW 0 + +#define OBSW_TEST_LIBGPIOD 0 +#define OBSW_TEST_PLOC_HANDLER 0 +#define OBSW_TEST_CCSDS_BRIDGE 0 +#define OBSW_TEST_CCSDS_PTME 0 +#define OBSW_TEST_TE7020_HEATER 0 +#define OBSW_TEST_GPIO_OPEN_BY_LABEL 0 +#define OBSW_TEST_GPIO_OPEN_BY_LINE_NAME 0 +#define OBSW_DEBUG_P60DOCK 0 + +#define OBSW_PRINT_CORE_HK 0 +#define OBSW_DEBUG_PDU1 0 +#define OBSW_DEBUG_PDU2 0 +#define OBSW_DEBUG_GPS 0 +#define OBSW_DEBUG_ACU 0 +#define OBSW_DEBUG_SYRLINKS 0 + +#define OBSW_DEBUG_PDEC_HANDLER 0 +#define OBSW_DEBUG_PLOC_SUPERVISOR 0 +#define OBSW_DEBUG_PLOC_MPSOC 0 +#define OBSW_DEBUG_STARTRACKER 0 +#define OBSW_TCP_SERVER_WIRETAPPING 0 + +/*******************************************************************/ +/** CMake Defines */ +/*******************************************************************/ + +#define OBSW_ADD_TMTC_UDP_SERVER 0 +#define OBSW_ADD_TMTC_TCP_SERVER 1 + +#cmakedefine EIVE_BUILD_GPSD_GPS_HANDLER + +#cmakedefine LIBGPS_VERSION_MAJOR @LIBGPS_VERSION_MAJOR@ +#cmakedefine LIBGPS_VERSION_MINOR @LIBGPS_VERSION_MINOR@ + +#ifdef RASPBERRY_PI +#include "rpiConfig.h" +#elif defined(XIPHOS_Q7S) +#include "q7sConfig.h" +#endif + +#ifdef __cplusplus + +#include "objects/systemObjectList.h" +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +#endif + +#endif /* FSFWCONFIG_OBSWCONFIG_H_ */ diff --git a/bsp_hosted/boardconfig/CMakeLists.txt b/bsp_hosted/boardconfig/CMakeLists.txt new file mode 100644 index 0000000..f08670d --- /dev/null +++ b/bsp_hosted/boardconfig/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(${OBSW_NAME} PRIVATE print.c) + +target_include_directories(${OBSW_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/bsp_hosted/boardconfig/etl_profile.h b/bsp_hosted/boardconfig/etl_profile.h new file mode 100644 index 0000000..54aca34 --- /dev/null +++ b/bsp_hosted/boardconfig/etl_profile.h @@ -0,0 +1,38 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2019 jwellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ +#ifndef __ETL_PROFILE_H__ +#define __ETL_PROFILE_H__ + +#define ETL_CHECK_PUSH_POP + +#define ETL_CPP11_SUPPORTED 1 +#define ETL_NO_NULLPTR_SUPPORT 0 + +#endif diff --git a/bsp_hosted/boardconfig/gcov.h b/bsp_hosted/boardconfig/gcov.h new file mode 100644 index 0000000..80acdd8 --- /dev/null +++ b/bsp_hosted/boardconfig/gcov.h @@ -0,0 +1,15 @@ +#ifndef LINUX_GCOV_H_ +#define LINUX_GCOV_H_ +#include + +#ifdef GCOV +extern "C" void __gcov_flush(); +#else +void __gcov_flush() { + sif::info << "GCC GCOV: Please supply GCOV=1 in Makefile if " + "coverage information is desired.\n" + << std::flush; +} +#endif + +#endif /* LINUX_GCOV_H_ */ diff --git a/bsp_hosted/boardconfig/print.c b/bsp_hosted/boardconfig/print.c new file mode 100644 index 0000000..9653fe5 --- /dev/null +++ b/bsp_hosted/boardconfig/print.c @@ -0,0 +1,11 @@ +#include "print.h" + +#include + +void printChar(const char* character, bool errStream) { + if (errStream) { + putc(*character, stderr); + return; + } + putc(*character, stdout); +} diff --git a/bsp_hosted/boardconfig/print.h b/bsp_hosted/boardconfig/print.h new file mode 100644 index 0000000..8479849 --- /dev/null +++ b/bsp_hosted/boardconfig/print.h @@ -0,0 +1,8 @@ +#ifndef BSP_HOSTED_BOARDCONFIG_PRINT_H_ +#define BSP_HOSTED_BOARDCONFIG_PRINT_H_ + +#include + +void printChar(const char* character, bool errStream); + +#endif /* BSP_HOSTED_BOARDCONFIG_PRINT_H_ */ diff --git a/bsp_hosted/comIF/ArduinoComIF.cpp b/bsp_hosted/comIF/ArduinoComIF.cpp new file mode 100644 index 0000000..f9206f7 --- /dev/null +++ b/bsp_hosted/comIF/ArduinoComIF.cpp @@ -0,0 +1,349 @@ +#include "ArduinoComIF.h" + +#include +#include +#include + +#include "ArduinoCookie.h" + +// This only works on Linux +#ifdef LINUX +#include +#include +#include +#elif WIN32 +#include +#include +#endif + +#include + +ArduinoComIF::ArduinoComIF(object_id_t setObjectId, bool promptComIF, const char *serialDevice) + : rxBuffer(MAX_PACKET_SIZE * MAX_NUMBER_OF_SPI_DEVICES * 10, true), SystemObject(setObjectId) { +#ifdef LINUX + initialized = false; + serialPort = ::open("/dev/ttyUSB0", O_RDWR); + + if (serialPort < 0) { + // configuration error + printf("Error %i from open: %s\n", errno, strerror(errno)); + return; + } + + struct termios tty; + memset(&tty, 0, sizeof tty); + + // Read in existing settings, and handle any error + if (tcgetattr(serialPort, &tty) != 0) { + printf("Error %i from tcgetattr: %s\n", errno, strerror(errno)); + return; + } + + tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity + tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication + tty.c_cflag |= CS8; // 8 bits per byte + tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control + tty.c_lflag &= ~ICANON; // Disable Canonical Mode + tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) + tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed + tty.c_cc[VTIME] = 0; // Non Blocking + tty.c_cc[VMIN] = 0; + + cfsetispeed(&tty, B9600); // Baudrate + + if (tcsetattr(serialPort, TCSANOW, &tty) != 0) { + // printf("Error %i from tcsetattr: %s\n", errno, strerror(errno)); + return; + } + + initialized = true; +#elif WIN32 + DCB serialParams = {0}; + + // we need to ask the COM port from the user. + if (promptComIF) { + sif::info << "Please enter the COM port (c to cancel): " << std::flush; + std::string comPort; + while (hCom == INVALID_HANDLE_VALUE) { + std::getline(std::cin, comPort); + if (comPort[0] == 'c') { + break; + } + const TCHAR *pcCommPort = comPort.c_str(); + hCom = CreateFileA(pcCommPort, // port name + GENERIC_READ | GENERIC_WRITE, // Read/Write + 0, // No Sharing + NULL, // No Security + OPEN_EXISTING, // Open existing port only + 0, // Non Overlapped I/O + NULL); // Null for Comm Devices + + if (hCom == INVALID_HANDLE_VALUE) { + if (GetLastError() == 2) { + sif::error << "COM Port does not found!" << std::endl; + } else { + TCHAR err[128]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err, sizeof(err), NULL); + // Handle the error. + sif::info << "CreateFileA Error code: " << GetLastError() << std::endl; + sif::error << err << std::flush; + } + sif::info << "Please enter a valid COM port: " << std::flush; + } + } + } + + serialParams.DCBlength = sizeof(serialParams); + if (baudRate == 9600) { + serialParams.BaudRate = CBR_9600; + } + if (baudRate == 115200) { + serialParams.BaudRate = CBR_115200; + } else { + serialParams.BaudRate = baudRate; + } + + serialParams.ByteSize = 8; + serialParams.Parity = NOPARITY; + serialParams.StopBits = ONESTOPBIT; + SetCommState(hCom, &serialParams); + + COMMTIMEOUTS timeout = {0}; + // This will set the read operation to be blocking until data is received + // and then read continuously until there is a gap of one millisecond. + timeout.ReadIntervalTimeout = 1; + timeout.ReadTotalTimeoutConstant = 0; + timeout.ReadTotalTimeoutMultiplier = 0; + timeout.WriteTotalTimeoutConstant = 0; + timeout.WriteTotalTimeoutMultiplier = 0; + SetCommTimeouts(hCom, &timeout); + // Serial port should now be read for operations. +#endif +} + +ArduinoComIF::~ArduinoComIF() { +#ifdef LINUX + ::close(serialPort); +#elif WIN32 + CloseHandle(hCom); +#endif +} +ReturnValue_t ArduinoComIF::initializeInterface(CookieIF *cookie) { return returnvalue::OK; } + +ReturnValue_t ArduinoComIF::sendMessage(CookieIF *cookie, const uint8_t *data, size_t len) { + ArduinoCookie *arduinoCookie = dynamic_cast(cookie); + if (arduinoCookie == nullptr) { + return INVALID_COOKIE_TYPE; + } + + return sendMessage(arduinoCookie->command, arduinoCookie->address, data, len); +} + +ReturnValue_t ArduinoComIF::getSendSuccess(CookieIF *cookie) { return returnvalue::OK; } + +ReturnValue_t ArduinoComIF::requestReceiveMessage(CookieIF *cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t ArduinoComIF::readReceivedMessage(CookieIF *cookie, uint8_t **buffer, size_t *size) { + handleSerialPortRx(); + + ArduinoCookie *arduinoCookie = dynamic_cast(cookie); + if (arduinoCookie == nullptr) { + return INVALID_COOKIE_TYPE; + } + + *buffer = arduinoCookie->replyBuffer.data(); + *size = arduinoCookie->receivedDataLen; + return returnvalue::OK; +} + +ReturnValue_t ArduinoComIF::sendMessage(uint8_t command, uint8_t address, const uint8_t *data, + size_t dataLen) { + if (dataLen > UINT16_MAX) { + return TOO_MUCH_DATA; + } + + // being conservative here + uint8_t sendBuffer[(dataLen + 6) * 2 + 2]; + + sendBuffer[0] = DleEncoder::STX_CHAR; + + uint8_t *currentPosition = sendBuffer + 1; + size_t remainingLen = sizeof(sendBuffer) - 1; + size_t encodedLen = 0; + + ReturnValue_t result = + DleEncoder::encode(&command, 1, currentPosition, remainingLen, &encodedLen, false); + if (result != returnvalue::OK) { + return result; + } + currentPosition += encodedLen; + remainingLen -= encodedLen; // DleEncoder will never return encodedLen > remainingLen + + result = DleEncoder::encode(&address, 1, currentPosition, remainingLen, &encodedLen, false); + if (result != returnvalue::OK) { + return result; + } + currentPosition += encodedLen; + remainingLen -= encodedLen; // DleEncoder will never return encodedLen > remainingLen + + uint8_t temporaryBuffer[2]; + + // note to Lukas: yes we _could_ use Serialize here, but for 16 bit it is a bit too much... + temporaryBuffer[0] = dataLen >> 8; // we checked dataLen above + temporaryBuffer[1] = dataLen; + + result = + DleEncoder::encode(temporaryBuffer, 2, currentPosition, remainingLen, &encodedLen, false); + if (result != returnvalue::OK) { + return result; + } + currentPosition += encodedLen; + remainingLen -= encodedLen; // DleEncoder will never return encodedLen > remainingLen + + // encoding the actual data + result = DleEncoder::encode(data, dataLen, currentPosition, remainingLen, &encodedLen, false); + if (result != returnvalue::OK) { + return result; + } + currentPosition += encodedLen; + remainingLen -= encodedLen; // DleEncoder will never return encodedLen > remainingLen + + uint16_t crc = CRC::crc16ccitt(&command, 1); + crc = CRC::crc16ccitt(&address, 1, crc); + // fortunately the length is still there + crc = CRC::crc16ccitt(temporaryBuffer, 2, crc); + crc = CRC::crc16ccitt(data, dataLen, crc); + + temporaryBuffer[0] = crc >> 8; + temporaryBuffer[1] = crc; + + result = + DleEncoder::encode(temporaryBuffer, 2, currentPosition, remainingLen, &encodedLen, false); + if (result != returnvalue::OK) { + return result; + } + currentPosition += encodedLen; + remainingLen -= encodedLen; // DleEncoder will never return encodedLen > remainingLen + + if (remainingLen > 0) { + *currentPosition = DleEncoder::ETX_CHAR; + } + remainingLen -= 1; + + encodedLen = sizeof(sendBuffer) - remainingLen; + +#ifdef LINUX + ssize_t writtenlen = ::write(serialPort, sendBuffer, encodedLen); + if (writtenlen < 0) { + // we could try to find out what happened... + return returnvalue::FAILED; + } + if (writtenlen != encodedLen) { + // the OS failed us, we do not try to block until everything is written, as + // we can not block the whole system here + return returnvalue::FAILED; + } + return returnvalue::OK; +#elif WIN32 + return returnvalue::OK; +#endif +} + +void ArduinoComIF::handleSerialPortRx() { +#ifdef LINUX + uint32_t availableSpace = rxBuffer.availableWriteSpace(); + + uint8_t dataFromSerial[availableSpace]; + + ssize_t bytesRead = read(serialPort, dataFromSerial, sizeof(dataFromSerial)); + + if (bytesRead < 0) { + return; + } + + rxBuffer.writeData(dataFromSerial, bytesRead); + + uint8_t dataReceivedSoFar[rxBuffer.getMaxSize()]; + + uint32_t dataLenReceivedSoFar = 0; + + rxBuffer.readData(dataReceivedSoFar, sizeof(dataReceivedSoFar), true, &dataLenReceivedSoFar); + + // look for STX + size_t firstSTXinRawData = 0; + while ((firstSTXinRawData < dataLenReceivedSoFar) && + (dataReceivedSoFar[firstSTXinRawData] != DleEncoder::STX_CHAR)) { + firstSTXinRawData++; + } + + if (dataReceivedSoFar[firstSTXinRawData] != DleEncoder::STX_CHAR) { + // there is no STX in our data, throw it away... + rxBuffer.deleteData(dataLenReceivedSoFar); + return; + } + + uint8_t packet[MAX_PACKET_SIZE]; + size_t packetLen = 0; + + size_t readSize = 0; + + ReturnValue_t result = DleEncoder::decode(dataReceivedSoFar + firstSTXinRawData, + dataLenReceivedSoFar - firstSTXinRawData, &readSize, + packet, sizeof(packet), &packetLen); + + size_t toDelete = firstSTXinRawData; + if (result == returnvalue::OK) { + handlePacket(packet, packetLen); + + // after handling the packet, we can delete it from the raw stream, + // it has been copied to packet + toDelete += readSize; + } + + // remove Data which was processed + rxBuffer.deleteData(toDelete); +#elif WIN32 +#endif +} + +void ArduinoComIF::setBaudrate(uint32_t baudRate) { this->baudRate = baudRate; } + +void ArduinoComIF::handlePacket(uint8_t *packet, size_t packetLen) { + uint16_t crc = CRC::crc16ccitt(packet, packetLen); + if (crc != 0) { + // CRC error + return; + } + + uint8_t command = packet[0]; + uint8_t address = packet[1]; + + uint16_t size = (packet[2] << 8) + packet[3]; + + if (size != packetLen - 6) { + // Invalid Length + return; + } + + switch (command) { + case ArduinoCookie::SPI: { + // ArduinoCookie **itsComplicated; + auto findIter = spiMap.find(address); + if (findIter == spiMap.end()) { + // we do no know this address + return; + } + ArduinoCookie &cookie = findIter->second; + if (packetLen > cookie.maxReplySize + 6) { + packetLen = cookie.maxReplySize + 6; + } + std::memcpy(cookie.replyBuffer.data(), packet + 4, packetLen - 6); + cookie.receivedDataLen = packetLen - 6; + } break; + default: + return; + } +} diff --git a/bsp_hosted/comIF/ArduinoComIF.h b/bsp_hosted/comIF/ArduinoComIF.h new file mode 100644 index 0000000..af84974 --- /dev/null +++ b/bsp_hosted/comIF/ArduinoComIF.h @@ -0,0 +1,66 @@ +#ifndef MISSION_ARDUINOCOMMINTERFACE_H_ +#define MISSION_ARDUINOCOMMINTERFACE_H_ + +#include +#include +#include +#include +#include + +#include +#include + +#ifdef WIN32 +#include +#endif + +// Forward declaration, so users don't peek +class ArduinoCookie; + +class ArduinoComIF : public SystemObject, public DeviceCommunicationIF { + public: + static const uint8_t MAX_NUMBER_OF_SPI_DEVICES = 8; + static const uint8_t MAX_PACKET_SIZE = 64; + + static const uint8_t COMMAND_INVALID = -1; + static const uint8_t COMMAND_SPI = 1; + + ArduinoComIF(object_id_t setObjectId, bool promptComIF = false, + const char *serialDevice = nullptr); + void setBaudrate(uint32_t baudRate); + + virtual ~ArduinoComIF(); + + /** DeviceCommunicationIF overrides */ + virtual ReturnValue_t initializeInterface(CookieIF *cookie) override; + virtual ReturnValue_t sendMessage(CookieIF *cookie, const uint8_t *sendData, + size_t sendLen) override; + virtual ReturnValue_t getSendSuccess(CookieIF *cookie) override; + virtual ReturnValue_t requestReceiveMessage(CookieIF *cookie, size_t requestLen) override; + virtual ReturnValue_t readReceivedMessage(CookieIF *cookie, uint8_t **buffer, + size_t *size) override; + + private: +#ifdef LINUX +#elif WIN32 + HANDLE hCom = INVALID_HANDLE_VALUE; +#endif + // remembering if the initialization in the ctor worked + // if not, all calls are disabled + bool initialized = false; + int serialPort = 0; + // Default baud rate is 9600 for now. + uint32_t baudRate = 9600; + + // used to know where to put the data if a reply is received + std::map spiMap; + + SimpleRingBuffer rxBuffer; + + ReturnValue_t sendMessage(uint8_t command, uint8_t address, const uint8_t *data, size_t dataLen); + void handleSerialPortRx(); + + void handlePacket(uint8_t *packet, size_t packetLen); +}; + +#endif /* MISSION_ARDUINOCOMMINTERFACE_H_ */ diff --git a/bsp_hosted/comIF/ArduinoCookie.cpp b/bsp_hosted/comIF/ArduinoCookie.cpp new file mode 100644 index 0000000..89cb156 --- /dev/null +++ b/bsp_hosted/comIF/ArduinoCookie.cpp @@ -0,0 +1,8 @@ +#include + +ArduinoCookie::ArduinoCookie(Protocol_t protocol, uint8_t address, const size_t maxReplySize) + : protocol(protocol), + command(protocol), + address(address), + maxReplySize(maxReplySize), + replyBuffer(maxReplySize) {} diff --git a/bsp_hosted/comIF/ArduinoCookie.h b/bsp_hosted/comIF/ArduinoCookie.h new file mode 100644 index 0000000..04d4bd8 --- /dev/null +++ b/bsp_hosted/comIF/ArduinoCookie.h @@ -0,0 +1,22 @@ +#ifndef MISSION_ARDUINO_ARDUINOCOOKIE_H_ +#define MISSION_ARDUINO_ARDUINOCOOKIE_H_ + +#include + +#include + +class ArduinoCookie : public CookieIF { + public: + enum Protocol_t : uint8_t { INVALID, SPI, I2C }; + + ArduinoCookie(Protocol_t protocol, uint8_t address, const size_t maxReplySize); + + Protocol_t protocol; + uint8_t command; + uint8_t address; + std::vector replyBuffer; + size_t receivedDataLen = 0; + size_t maxReplySize; +}; + +#endif /* MISSION_ARDUINO_ARDUINOCOOKIE_H_ */ diff --git a/bsp_hosted/comIF/CMakeLists.txt b/bsp_hosted/comIF/CMakeLists.txt new file mode 100644 index 0000000..568cf56 --- /dev/null +++ b/bsp_hosted/comIF/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PUBLIC ArduinoComIF.cpp ArduinoCookie.cpp) diff --git a/bsp_hosted/fsfwconfig/CMakeLists.txt b/bsp_hosted/fsfwconfig/CMakeLists.txt new file mode 100644 index 0000000..95e43c2 --- /dev/null +++ b/bsp_hosted/fsfwconfig/CMakeLists.txt @@ -0,0 +1,17 @@ +target_sources(${OBSW_NAME} PRIVATE ipc/MissionMessageTypes.cpp) + +target_include_directories(${OBSW_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# If a special translation file for object IDs exists, compile it. +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/objects/translateObjects.cpp") + target_sources(${OBSW_NAME} PRIVATE objects/translateObjects.cpp) + target_sources(${UNITTEST_NAME} PRIVATE objects/translateObjects.cpp) +endif() + +# If a special translation file for events exists, compile it. +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/objects/translateObjects.cpp") + target_sources(${OBSW_NAME} PRIVATE events/translateEvents.cpp) + target_sources(${UNITTEST_NAME} PRIVATE events/translateEvents.cpp) +endif() + +add_subdirectory(pollingsequence) diff --git a/bsp_hosted/fsfwconfig/FSFWConfig.h.in b/bsp_hosted/fsfwconfig/FSFWConfig.h.in new file mode 100644 index 0000000..cf4b941 --- /dev/null +++ b/bsp_hosted/fsfwconfig/FSFWConfig.h.in @@ -0,0 +1,77 @@ +#ifndef CONFIG_FSFWCONFIG_H_ +#define CONFIG_FSFWCONFIG_H_ + +#include +#include + +//! Used to determine whether C++ ostreams are used which can increase +//! the binary size significantly. If this is disabled, +//! the C stdio functions can be used alternatively +#define FSFW_CPP_OSTREAM_ENABLED 1 + +//! More FSFW related printouts depending on level. Useful for development. +#define FSFW_VERBOSE_LEVEL 1 + +//! Can be used to completely disable printouts, even the C stdio ones. +#if FSFW_CPP_OSTREAM_ENABLED == 0 && FSFW_VERBOSE_LEVEL == 0 +#define FSFW_DISABLE_PRINTOUT 0 +#endif + +#define FSFW_USE_PUS_C_TELEMETRY 1 +#define FSFW_USE_PUS_C_TELECOMMANDS 1 + +//! Can be used to disable the ANSI color sequences for C stdio. +#define FSFW_COLORED_OUTPUT 1 + +//! If FSFW_OBJ_EVENT_TRANSLATION is set to one, +//! additional output which requires the translation files translateObjects +//! and translateEvents (and their compiled source files) +#define FSFW_OBJ_EVENT_TRANSLATION 1 + +#if FSFW_OBJ_EVENT_TRANSLATION == 1 +//! Specify whether info events are printed too. +#define FSFW_DEBUG_INFO 1 +#include "events/translateEvents.h" +#include "objects/translateObjects.h" +#else +#endif + +//! When using the newlib nano library, C99 support for stdio facilities +//! will not be provided. This define should be set to 1 if this is the case. +#define FSFW_NO_C99_IO 1 + +//! Specify whether a special mode store is used for Subsystem components. +#define FSFW_USE_MODESTORE 0 + +//! Defines if the real time scheduler for linux should be used. +//! If set to 0, this will also disable priority settings for linux +//! as most systems will not allow to set nice values without privileges +//! For embedded linux system set this to 1. +//! If set to 1 the binary needs "cap_sys_nice=eip" privileges to run +#define FSFW_USE_REALTIME_FOR_LINUX 0 + +#define FSFW_UDP_SEND_WIRETAPPING_ENABLED 0 + +namespace fsfwconfig { + +//! Default timestamp size. The default timestamp will be an seven byte CDC short timestamp. +static constexpr uint8_t FSFW_MISSION_TIMESTAMP_SIZE = 7; + +//! Configure the allocated pool sizes for the event manager. +static constexpr size_t FSFW_EVENTMGMR_MATCHTREE_NODES = 240; +static constexpr size_t FSFW_EVENTMGMT_EVENTIDMATCHERS = 120; +static constexpr size_t FSFW_EVENTMGMR_RANGEMATCHERS = 120; + +//! Defines the FIFO depth of each commanding service base which +//! also determines how many commands a CSB service can handle in one cycle +//! simultaneously. This will increase the required RAM for +//! each CSB service ! +static constexpr uint8_t FSFW_CSB_FIFO_DEPTH = 6; + +static constexpr size_t FSFW_PRINT_BUFFER_SIZE = 124; + +static constexpr size_t FSFW_MAX_TM_PACKET_SIZE = 2048; + +} // namespace fsfwconfig + +#endif /* CONFIG_FSFWCONFIG_H_ */ diff --git a/bsp_hosted/fsfwconfig/OBSWConfig.h.in b/bsp_hosted/fsfwconfig/OBSWConfig.h.in new file mode 100644 index 0000000..b7de30f --- /dev/null +++ b/bsp_hosted/fsfwconfig/OBSWConfig.h.in @@ -0,0 +1,46 @@ +/** + * @brief This file can be used to add preprocessor define for conditional + * code inclusion exclusion or various other project constants and + * properties in one place. + */ +#ifndef CONFIG_OBSWCONFIG_H_ +#define CONFIG_OBSWCONFIG_H_ + +#include "commonConfig.h" + +#define OBSW_PRINT_MISSED_DEADLINES 1 + +#define OBSW_ADD_TEST_CODE 1 + +/* These defines should be disabled for mission code but are useful for +debugging. */ +#define OBSW_VEBOSE_LEVEL 1 + +#define OBSW_ADD_CCSDS_IP_CORES 0 +// Set to 1 if all telemetry should be sent to the PTME IP Core +#define OBSW_TM_TO_PTME 0 +// Set to 1 if telecommands are received via the PDEC IP Core +#define OBSW_TC_FROM_PDEC 0 + +#define OBSW_SYRLINKS_SIMULATED 0 + +#define OBSW_INITIALIZE_SWITCHES 0 + +#define OBSW_TCP_SERVER_WIRETAPPING 0 + +#ifdef __cplusplus + +#include "objects/systemObjectList.h" +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +namespace config { +#endif + +/* Add mission configuration flags here */ + +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_OBSWCONFIG_H_ */ diff --git a/bsp_hosted/fsfwconfig/events/subsystemIdRanges.h b/bsp_hosted/fsfwconfig/events/subsystemIdRanges.h new file mode 100644 index 0000000..b99164d --- /dev/null +++ b/bsp_hosted/fsfwconfig/events/subsystemIdRanges.h @@ -0,0 +1,16 @@ +#ifndef CONFIG_EVENTS_SUBSYSTEMIDRANGES_H_ +#define CONFIG_EVENTS_SUBSYSTEMIDRANGES_H_ + +#include + +#include "eive/eventSubsystemIds.h" + +/** + * These IDs are part of the ID for an event thrown by a subsystem. + * Numbers 0-80 are reserved for FSFW Subsystem IDs (framework/events/) + */ +namespace SUBSYSTEM_ID { +enum : uint8_t { SUBSYSTEM_ID_START = COMMON_SUBSYSTEM_ID_END }; +} + +#endif /* CONFIG_EVENTS_SUBSYSTEMIDRANGES_H_ */ diff --git a/bsp_hosted/fsfwconfig/events/translateEvents.cpp b/bsp_hosted/fsfwconfig/events/translateEvents.cpp new file mode 100644 index 0000000..7a8da30 --- /dev/null +++ b/bsp_hosted/fsfwconfig/events/translateEvents.cpp @@ -0,0 +1,990 @@ +/** + * @brief Auto-generated event translation file. Contains 325 translations. + * @details + * Generated on: 2024-05-06 13:47:38 + */ +#include "translateEvents.h" + +const char *STORE_SEND_WRITE_FAILED_STRING = "STORE_SEND_WRITE_FAILED"; +const char *STORE_WRITE_FAILED_STRING = "STORE_WRITE_FAILED"; +const char *STORE_SEND_READ_FAILED_STRING = "STORE_SEND_READ_FAILED"; +const char *STORE_READ_FAILED_STRING = "STORE_READ_FAILED"; +const char *UNEXPECTED_MSG_STRING = "UNEXPECTED_MSG"; +const char *STORING_FAILED_STRING = "STORING_FAILED"; +const char *TM_DUMP_FAILED_STRING = "TM_DUMP_FAILED"; +const char *STORE_INIT_FAILED_STRING = "STORE_INIT_FAILED"; +const char *STORE_INIT_EMPTY_STRING = "STORE_INIT_EMPTY"; +const char *STORE_CONTENT_CORRUPTED_STRING = "STORE_CONTENT_CORRUPTED"; +const char *STORE_INITIALIZE_STRING = "STORE_INITIALIZE"; +const char *INIT_DONE_STRING = "INIT_DONE"; +const char *DUMP_FINISHED_STRING = "DUMP_FINISHED"; +const char *DELETION_FINISHED_STRING = "DELETION_FINISHED"; +const char *DELETION_FAILED_STRING = "DELETION_FAILED"; +const char *AUTO_CATALOGS_SENDING_FAILED_STRING = "AUTO_CATALOGS_SENDING_FAILED"; +const char *GET_DATA_FAILED_STRING = "GET_DATA_FAILED"; +const char *STORE_DATA_FAILED_STRING = "STORE_DATA_FAILED"; +const char *DEVICE_BUILDING_COMMAND_FAILED_STRING = "DEVICE_BUILDING_COMMAND_FAILED"; +const char *DEVICE_SENDING_COMMAND_FAILED_STRING = "DEVICE_SENDING_COMMAND_FAILED"; +const char *DEVICE_REQUESTING_REPLY_FAILED_STRING = "DEVICE_REQUESTING_REPLY_FAILED"; +const char *DEVICE_READING_REPLY_FAILED_STRING = "DEVICE_READING_REPLY_FAILED"; +const char *DEVICE_INTERPRETING_REPLY_FAILED_STRING = "DEVICE_INTERPRETING_REPLY_FAILED"; +const char *DEVICE_MISSED_REPLY_STRING = "DEVICE_MISSED_REPLY"; +const char *DEVICE_UNKNOWN_REPLY_STRING = "DEVICE_UNKNOWN_REPLY"; +const char *DEVICE_UNREQUESTED_REPLY_STRING = "DEVICE_UNREQUESTED_REPLY"; +const char *INVALID_DEVICE_COMMAND_STRING = "INVALID_DEVICE_COMMAND"; +const char *MONITORING_LIMIT_EXCEEDED_STRING = "MONITORING_LIMIT_EXCEEDED"; +const char *MONITORING_AMBIGUOUS_STRING = "MONITORING_AMBIGUOUS"; +const char *DEVICE_WANTS_HARD_REBOOT_STRING = "DEVICE_WANTS_HARD_REBOOT"; +const char *SWITCH_WENT_OFF_STRING = "SWITCH_WENT_OFF"; +const char *FUSE_CURRENT_HIGH_STRING = "FUSE_CURRENT_HIGH"; +const char *FUSE_WENT_OFF_STRING = "FUSE_WENT_OFF"; +const char *POWER_ABOVE_HIGH_LIMIT_STRING = "POWER_ABOVE_HIGH_LIMIT"; +const char *POWER_BELOW_LOW_LIMIT_STRING = "POWER_BELOW_LOW_LIMIT"; +const char *HEATER_ON_STRING = "HEATER_ON"; +const char *HEATER_OFF_STRING = "HEATER_OFF"; +const char *HEATER_TIMEOUT_STRING = "HEATER_TIMEOUT"; +const char *HEATER_STAYED_ON_STRING = "HEATER_STAYED_ON"; +const char *HEATER_STAYED_OFF_STRING = "HEATER_STAYED_OFF"; +const char *TEMP_SENSOR_HIGH_STRING = "TEMP_SENSOR_HIGH"; +const char *TEMP_SENSOR_LOW_STRING = "TEMP_SENSOR_LOW"; +const char *TEMP_SENSOR_GRADIENT_STRING = "TEMP_SENSOR_GRADIENT"; +const char *COMPONENT_TEMP_LOW_STRING = "COMPONENT_TEMP_LOW"; +const char *COMPONENT_TEMP_HIGH_STRING = "COMPONENT_TEMP_HIGH"; +const char *COMPONENT_TEMP_OOL_LOW_STRING = "COMPONENT_TEMP_OOL_LOW"; +const char *COMPONENT_TEMP_OOL_HIGH_STRING = "COMPONENT_TEMP_OOL_HIGH"; +const char *TEMP_NOT_IN_OP_RANGE_STRING = "TEMP_NOT_IN_OP_RANGE"; +const char *FDIR_CHANGED_STATE_STRING = "FDIR_CHANGED_STATE"; +const char *FDIR_STARTS_RECOVERY_STRING = "FDIR_STARTS_RECOVERY"; +const char *FDIR_TURNS_OFF_DEVICE_STRING = "FDIR_TURNS_OFF_DEVICE"; +const char *MONITOR_CHANGED_STATE_STRING = "MONITOR_CHANGED_STATE"; +const char *VALUE_BELOW_LOW_LIMIT_STRING = "VALUE_BELOW_LOW_LIMIT"; +const char *VALUE_ABOVE_HIGH_LIMIT_STRING = "VALUE_ABOVE_HIGH_LIMIT"; +const char *VALUE_OUT_OF_RANGE_STRING = "VALUE_OUT_OF_RANGE"; +const char *CHANGING_MODE_STRING = "CHANGING_MODE"; +const char *MODE_INFO_STRING = "MODE_INFO"; +const char *FALLBACK_FAILED_STRING = "FALLBACK_FAILED"; +const char *MODE_TRANSITION_FAILED_STRING = "MODE_TRANSITION_FAILED"; +const char *CANT_KEEP_MODE_STRING = "CANT_KEEP_MODE"; +const char *OBJECT_IN_INVALID_MODE_STRING = "OBJECT_IN_INVALID_MODE"; +const char *FORCING_MODE_STRING = "FORCING_MODE"; +const char *MODE_CMD_REJECTED_STRING = "MODE_CMD_REJECTED"; +const char *HEALTH_INFO_STRING = "HEALTH_INFO"; +const char *CHILD_CHANGED_HEALTH_STRING = "CHILD_CHANGED_HEALTH"; +const char *CHILD_PROBLEMS_STRING = "CHILD_PROBLEMS"; +const char *OVERWRITING_HEALTH_STRING = "OVERWRITING_HEALTH"; +const char *TRYING_RECOVERY_STRING = "TRYING_RECOVERY"; +const char *RECOVERY_STEP_STRING = "RECOVERY_STEP"; +const char *RECOVERY_DONE_STRING = "RECOVERY_DONE"; +const char *HANDLE_PACKET_FAILED_STRING = "HANDLE_PACKET_FAILED"; +const char *RF_AVAILABLE_STRING = "RF_AVAILABLE"; +const char *RF_LOST_STRING = "RF_LOST"; +const char *BIT_LOCK_STRING = "BIT_LOCK"; +const char *BIT_LOCK_LOST_STRING = "BIT_LOCK_LOST"; +const char *FRAME_PROCESSING_FAILED_STRING = "FRAME_PROCESSING_FAILED"; +const char *CLOCK_SET_STRING = "CLOCK_SET"; +const char *CLOCK_DUMP_LEGACY_STRING = "CLOCK_DUMP_LEGACY"; +const char *CLOCK_SET_FAILURE_STRING = "CLOCK_SET_FAILURE"; +const char *CLOCK_DUMP_STRING = "CLOCK_DUMP"; +const char *CLOCK_DUMP_BEFORE_SETTING_TIME_STRING = "CLOCK_DUMP_BEFORE_SETTING_TIME"; +const char *CLOCK_DUMP_AFTER_SETTING_TIME_STRING = "CLOCK_DUMP_AFTER_SETTING_TIME"; +const char *TC_DELETION_FAILED_STRING = "TC_DELETION_FAILED"; +const char *TEST_STRING = "TEST"; +const char *CHANGE_OF_SETUP_PARAMETER_STRING = "CHANGE_OF_SETUP_PARAMETER"; +const char *STORE_ERROR_STRING = "STORE_ERROR"; +const char *MSG_QUEUE_ERROR_STRING = "MSG_QUEUE_ERROR"; +const char *SERIALIZATION_ERROR_STRING = "SERIALIZATION_ERROR"; +const char *FILESTORE_ERROR_STRING = "FILESTORE_ERROR"; +const char *FILENAME_TOO_LARGE_ERROR_STRING = "FILENAME_TOO_LARGE_ERROR"; +const char *HANDLING_CFDP_REQUEST_FAILED_STRING = "HANDLING_CFDP_REQUEST_FAILED"; +const char *SAFE_RATE_VIOLATION_STRING = "SAFE_RATE_VIOLATION"; +const char *RATE_RECOVERY_STRING = "RATE_RECOVERY"; +const char *MULTIPLE_RW_INVALID_STRING = "MULTIPLE_RW_INVALID"; +const char *MEKF_INVALID_INFO_STRING = "MEKF_INVALID_INFO"; +const char *MEKF_RECOVERY_STRING = "MEKF_RECOVERY"; +const char *MEKF_AUTOMATIC_RESET_STRING = "MEKF_AUTOMATIC_RESET"; +const char *PTG_CTRL_NO_ATTITUDE_INFORMATION_STRING = "PTG_CTRL_NO_ATTITUDE_INFORMATION"; +const char *SAFE_MODE_CONTROLLER_FAILURE_STRING = "SAFE_MODE_CONTROLLER_FAILURE"; +const char *TLE_TOO_OLD_STRING = "TLE_TOO_OLD"; +const char *TLE_FILE_READ_FAILED_STRING = "TLE_FILE_READ_FAILED"; +const char *PTG_RATE_VIOLATION_STRING = "PTG_RATE_VIOLATION"; +const char *DETUMBLE_TRANSITION_FAILED_STRING = "DETUMBLE_TRANSITION_FAILED"; +const char *SWITCH_CMD_SENT_STRING = "SWITCH_CMD_SENT"; +const char *SWITCH_HAS_CHANGED_STRING = "SWITCH_HAS_CHANGED"; +const char *SWITCHING_Q7S_DENIED_STRING = "SWITCHING_Q7S_DENIED"; +const char *FDIR_REACTION_IGNORED_STRING = "FDIR_REACTION_IGNORED"; +const char *DATASET_READ_FAILED_STRING = "DATASET_READ_FAILED"; +const char *VOLTAGE_OUT_OF_BOUNDS_STRING = "VOLTAGE_OUT_OF_BOUNDS"; +const char *TIMEDELTA_OUT_OF_BOUNDS_STRING = "TIMEDELTA_OUT_OF_BOUNDS"; +const char *POWER_LEVEL_LOW_STRING = "POWER_LEVEL_LOW"; +const char *POWER_LEVEL_CRITICAL_STRING = "POWER_LEVEL_CRITICAL"; +const char *GPIO_PULL_HIGH_FAILED_STRING = "GPIO_PULL_HIGH_FAILED"; +const char *GPIO_PULL_LOW_FAILED_STRING = "GPIO_PULL_LOW_FAILED"; +const char *HEATER_WENT_ON_STRING = "HEATER_WENT_ON"; +const char *HEATER_WENT_OFF_STRING = "HEATER_WENT_OFF"; +const char *SWITCH_ALREADY_ON_STRING = "SWITCH_ALREADY_ON"; +const char *SWITCH_ALREADY_OFF_STRING = "SWITCH_ALREADY_OFF"; +const char *MAIN_SWITCH_TIMEOUT_STRING = "MAIN_SWITCH_TIMEOUT"; +const char *FAULTY_HEATER_WAS_ON_STRING = "FAULTY_HEATER_WAS_ON"; +const char *BURN_PHASE_START_STRING = "BURN_PHASE_START"; +const char *BURN_PHASE_DONE_STRING = "BURN_PHASE_DONE"; +const char *MAIN_SWITCH_ON_TIMEOUT_STRING = "MAIN_SWITCH_ON_TIMEOUT"; +const char *MAIN_SWITCH_OFF_TIMEOUT_STRING = "MAIN_SWITCH_OFF_TIMEOUT"; +const char *DEPL_SA1_GPIO_SWTICH_ON_FAILED_STRING = "DEPL_SA1_GPIO_SWTICH_ON_FAILED"; +const char *DEPL_SA2_GPIO_SWTICH_ON_FAILED_STRING = "DEPL_SA2_GPIO_SWTICH_ON_FAILED"; +const char *DEPL_SA1_GPIO_SWTICH_OFF_FAILED_STRING = "DEPL_SA1_GPIO_SWTICH_OFF_FAILED"; +const char *DEPL_SA2_GPIO_SWTICH_OFF_FAILED_STRING = "DEPL_SA2_GPIO_SWTICH_OFF_FAILED"; +const char *AUTONOMOUS_DEPLOYMENT_COMPLETED_STRING = "AUTONOMOUS_DEPLOYMENT_COMPLETED"; +const char *MEMORY_READ_RPT_CRC_FAILURE_STRING = "MEMORY_READ_RPT_CRC_FAILURE"; +const char *ACK_FAILURE_STRING = "ACK_FAILURE"; +const char *EXE_FAILURE_STRING = "EXE_FAILURE"; +const char *MPSOC_HANDLER_CRC_FAILURE_STRING = "MPSOC_HANDLER_CRC_FAILURE"; +const char *MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH_STRING = "MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH"; +const char *MPSOC_SHUTDOWN_FAILED_STRING = "MPSOC_SHUTDOWN_FAILED"; +const char *SUPV_NOT_ON_STRING = "SUPV_NOT_ON"; +const char *SUPV_REPLY_TIMEOUT_STRING = "SUPV_REPLY_TIMEOUT"; +const char *CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE_STRING = "CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE"; +const char *SELF_TEST_I2C_FAILURE_STRING = "SELF_TEST_I2C_FAILURE"; +const char *SELF_TEST_SPI_FAILURE_STRING = "SELF_TEST_SPI_FAILURE"; +const char *SELF_TEST_ADC_FAILURE_STRING = "SELF_TEST_ADC_FAILURE"; +const char *SELF_TEST_PWM_FAILURE_STRING = "SELF_TEST_PWM_FAILURE"; +const char *SELF_TEST_TC_FAILURE_STRING = "SELF_TEST_TC_FAILURE"; +const char *SELF_TEST_MTM_RANGE_FAILURE_STRING = "SELF_TEST_MTM_RANGE_FAILURE"; +const char *SELF_TEST_COIL_CURRENT_FAILURE_STRING = "SELF_TEST_COIL_CURRENT_FAILURE"; +const char *INVALID_ERROR_BYTE_STRING = "INVALID_ERROR_BYTE"; +const char *ERROR_STATE_STRING = "ERROR_STATE"; +const char *RESET_OCCURED_STRING = "RESET_OCCURED"; +const char *BOOTING_FIRMWARE_FAILED_EVENT_STRING = "BOOTING_FIRMWARE_FAILED_EVENT"; +const char *BOOTING_BOOTLOADER_FAILED_EVENT_STRING = "BOOTING_BOOTLOADER_FAILED_EVENT"; +const char *COM_ERROR_REPLY_RECEIVED_STRING = "COM_ERROR_REPLY_RECEIVED"; +const char *SUPV_MEMORY_READ_RPT_CRC_FAILURE_STRING = "SUPV_MEMORY_READ_RPT_CRC_FAILURE"; +const char *SUPV_UNKNOWN_TM_STRING = "SUPV_UNKNOWN_TM"; +const char *SUPV_UNINIMPLEMENTED_TM_STRING = "SUPV_UNINIMPLEMENTED_TM"; +const char *SUPV_ACK_FAILURE_STRING = "SUPV_ACK_FAILURE"; +const char *SUPV_EXE_FAILURE_STRING = "SUPV_EXE_FAILURE"; +const char *SUPV_CRC_FAILURE_EVENT_STRING = "SUPV_CRC_FAILURE_EVENT"; +const char *SUPV_HELPER_EXECUTING_STRING = "SUPV_HELPER_EXECUTING"; +const char *SUPV_MPSOC_SHUTDOWN_BUILD_FAILED_STRING = "SUPV_MPSOC_SHUTDOWN_BUILD_FAILED"; +const char *SUPV_ACK_UNKNOWN_COMMAND_STRING = "SUPV_ACK_UNKNOWN_COMMAND"; +const char *SUPV_EXE_ACK_UNKNOWN_COMMAND_STRING = "SUPV_EXE_ACK_UNKNOWN_COMMAND"; +const char *SANITIZATION_FAILED_STRING = "SANITIZATION_FAILED"; +const char *MOUNTED_SD_CARD_STRING = "MOUNTED_SD_CARD"; +const char *SEND_MRAM_DUMP_FAILED_STRING = "SEND_MRAM_DUMP_FAILED"; +const char *MRAM_DUMP_FAILED_STRING = "MRAM_DUMP_FAILED"; +const char *MRAM_DUMP_FINISHED_STRING = "MRAM_DUMP_FINISHED"; +const char *INVALID_TC_FRAME_STRING = "INVALID_TC_FRAME"; +const char *INVALID_FAR_STRING = "INVALID_FAR"; +const char *CARRIER_LOCK_STRING = "CARRIER_LOCK"; +const char *BIT_LOCK_PDEC_STRING = "BIT_LOCK_PDEC"; +const char *LOST_CARRIER_LOCK_PDEC_STRING = "LOST_CARRIER_LOCK_PDEC"; +const char *LOST_BIT_LOCK_PDEC_STRING = "LOST_BIT_LOCK_PDEC"; +const char *TOO_MANY_IRQS_STRING = "TOO_MANY_IRQS"; +const char *POLL_SYSCALL_ERROR_PDEC_STRING = "POLL_SYSCALL_ERROR_PDEC"; +const char *WRITE_SYSCALL_ERROR_PDEC_STRING = "WRITE_SYSCALL_ERROR_PDEC"; +const char *PDEC_TRYING_RESET_WITH_INIT_STRING = "PDEC_TRYING_RESET_WITH_INIT"; +const char *PDEC_TRYING_RESET_NO_INIT_STRING = "PDEC_TRYING_RESET_NO_INIT"; +const char *PDEC_RESET_FAILED_STRING = "PDEC_RESET_FAILED"; +const char *OPEN_IRQ_FILE_FAILED_STRING = "OPEN_IRQ_FILE_FAILED"; +const char *PDEC_INIT_FAILED_STRING = "PDEC_INIT_FAILED"; +const char *PDEC_CONFIG_CORRUPTED_STRING = "PDEC_CONFIG_CORRUPTED"; +const char *IMAGE_UPLOAD_FAILED_STRING = "IMAGE_UPLOAD_FAILED"; +const char *IMAGE_DOWNLOAD_FAILED_STRING = "IMAGE_DOWNLOAD_FAILED"; +const char *IMAGE_UPLOAD_SUCCESSFUL_STRING = "IMAGE_UPLOAD_SUCCESSFUL"; +const char *IMAGE_DOWNLOAD_SUCCESSFUL_STRING = "IMAGE_DOWNLOAD_SUCCESSFUL"; +const char *FLASH_WRITE_SUCCESSFUL_STRING = "FLASH_WRITE_SUCCESSFUL"; +const char *FLASH_READ_SUCCESSFUL_STRING = "FLASH_READ_SUCCESSFUL"; +const char *FLASH_READ_FAILED_STRING = "FLASH_READ_FAILED"; +const char *FIRMWARE_UPDATE_SUCCESSFUL_STRING = "FIRMWARE_UPDATE_SUCCESSFUL"; +const char *FIRMWARE_UPDATE_FAILED_STRING = "FIRMWARE_UPDATE_FAILED"; +const char *STR_HELPER_READING_REPLY_FAILED_STRING = "STR_HELPER_READING_REPLY_FAILED"; +const char *STR_HELPER_COM_ERROR_STRING = "STR_HELPER_COM_ERROR"; +const char *STR_COM_REPLY_TIMEOUT_STRING = "STR_COM_REPLY_TIMEOUT"; +const char *STR_HELPER_DEC_ERROR_STRING = "STR_HELPER_DEC_ERROR"; +const char *POSITION_MISMATCH_STRING = "POSITION_MISMATCH"; +const char *STR_HELPER_FILE_NOT_EXISTS_STRING = "STR_HELPER_FILE_NOT_EXISTS"; +const char *STR_HELPER_SENDING_PACKET_FAILED_STRING = "STR_HELPER_SENDING_PACKET_FAILED"; +const char *STR_HELPER_REQUESTING_MSG_FAILED_STRING = "STR_HELPER_REQUESTING_MSG_FAILED"; +const char *MPSOC_FLASH_WRITE_FAILED_STRING = "MPSOC_FLASH_WRITE_FAILED"; +const char *MPSOC_FLASH_WRITE_SUCCESSFUL_STRING = "MPSOC_FLASH_WRITE_SUCCESSFUL"; +const char *MPSOC_SENDING_COMMAND_FAILED_STRING = "MPSOC_SENDING_COMMAND_FAILED"; +const char *MPSOC_HELPER_REQUESTING_REPLY_FAILED_STRING = "MPSOC_HELPER_REQUESTING_REPLY_FAILED"; +const char *MPSOC_HELPER_READING_REPLY_FAILED_STRING = "MPSOC_HELPER_READING_REPLY_FAILED"; +const char *MPSOC_MISSING_ACK_STRING = "MPSOC_MISSING_ACK"; +const char *MPSOC_MISSING_EXE_STRING = "MPSOC_MISSING_EXE"; +const char *MPSOC_ACK_FAILURE_REPORT_STRING = "MPSOC_ACK_FAILURE_REPORT"; +const char *MPSOC_EXE_FAILURE_REPORT_STRING = "MPSOC_EXE_FAILURE_REPORT"; +const char *MPSOC_ACK_INVALID_APID_STRING = "MPSOC_ACK_INVALID_APID"; +const char *MPSOC_EXE_INVALID_APID_STRING = "MPSOC_EXE_INVALID_APID"; +const char *MPSOC_HELPER_SEQ_CNT_MISMATCH_STRING = "MPSOC_HELPER_SEQ_CNT_MISMATCH"; +const char *MPSOC_TM_SIZE_ERROR_STRING = "MPSOC_TM_SIZE_ERROR"; +const char *MPSOC_TM_CRC_MISSMATCH_STRING = "MPSOC_TM_CRC_MISSMATCH"; +const char *MPSOC_FLASH_READ_PACKET_ERROR_STRING = "MPSOC_FLASH_READ_PACKET_ERROR"; +const char *MPSOC_FLASH_READ_FAILED_STRING = "MPSOC_FLASH_READ_FAILED"; +const char *MPSOC_FLASH_READ_SUCCESSFUL_STRING = "MPSOC_FLASH_READ_SUCCESSFUL"; +const char *MPSOC_READ_TIMEOUT_STRING = "MPSOC_READ_TIMEOUT"; +const char *TRANSITION_BACK_TO_OFF_STRING = "TRANSITION_BACK_TO_OFF"; +const char *NEG_V_OUT_OF_BOUNDS_STRING = "NEG_V_OUT_OF_BOUNDS"; +const char *U_DRO_OUT_OF_BOUNDS_STRING = "U_DRO_OUT_OF_BOUNDS"; +const char *I_DRO_OUT_OF_BOUNDS_STRING = "I_DRO_OUT_OF_BOUNDS"; +const char *U_X8_OUT_OF_BOUNDS_STRING = "U_X8_OUT_OF_BOUNDS"; +const char *I_X8_OUT_OF_BOUNDS_STRING = "I_X8_OUT_OF_BOUNDS"; +const char *U_TX_OUT_OF_BOUNDS_STRING = "U_TX_OUT_OF_BOUNDS"; +const char *I_TX_OUT_OF_BOUNDS_STRING = "I_TX_OUT_OF_BOUNDS"; +const char *U_MPA_OUT_OF_BOUNDS_STRING = "U_MPA_OUT_OF_BOUNDS"; +const char *I_MPA_OUT_OF_BOUNDS_STRING = "I_MPA_OUT_OF_BOUNDS"; +const char *U_HPA_OUT_OF_BOUNDS_STRING = "U_HPA_OUT_OF_BOUNDS"; +const char *I_HPA_OUT_OF_BOUNDS_STRING = "I_HPA_OUT_OF_BOUNDS"; +const char *TRANSITION_OTHER_SIDE_FAILED_STRING = "TRANSITION_OTHER_SIDE_FAILED"; +const char *NOT_ENOUGH_DEVICES_DUAL_MODE_STRING = "NOT_ENOUGH_DEVICES_DUAL_MODE"; +const char *POWER_STATE_MACHINE_TIMEOUT_STRING = "POWER_STATE_MACHINE_TIMEOUT"; +const char *SIDE_SWITCH_TRANSITION_NOT_ALLOWED_STRING = "SIDE_SWITCH_TRANSITION_NOT_ALLOWED"; +const char *DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY_STRING = "DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY"; +const char *TRANSITION_OTHER_SIDE_FAILED_12900_STRING = "TRANSITION_OTHER_SIDE_FAILED_12900"; +const char *NOT_ENOUGH_DEVICES_DUAL_MODE_12901_STRING = "NOT_ENOUGH_DEVICES_DUAL_MODE_12901"; +const char *POWER_STATE_MACHINE_TIMEOUT_12902_STRING = "POWER_STATE_MACHINE_TIMEOUT_12902"; +const char *SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903_STRING = "SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903"; +const char *CHILDREN_LOST_MODE_STRING = "CHILDREN_LOST_MODE"; +const char *GPS_FIX_CHANGE_STRING = "GPS_FIX_CHANGE"; +const char *CANT_GET_FIX_STRING = "CANT_GET_FIX"; +const char *RESET_FAIL_STRING = "RESET_FAIL"; +const char *P60_BOOT_COUNT_STRING = "P60_BOOT_COUNT"; +const char *BATT_MODE_STRING = "BATT_MODE"; +const char *BATT_MODE_CHANGED_STRING = "BATT_MODE_CHANGED"; +const char *SUPV_UPDATE_FAILED_STRING = "SUPV_UPDATE_FAILED"; +const char *SUPV_UPDATE_SUCCESSFUL_STRING = "SUPV_UPDATE_SUCCESSFUL"; +const char *SUPV_CONTINUE_UPDATE_FAILED_STRING = "SUPV_CONTINUE_UPDATE_FAILED"; +const char *SUPV_CONTINUE_UPDATE_SUCCESSFUL_STRING = "SUPV_CONTINUE_UPDATE_SUCCESSFUL"; +const char *TERMINATED_UPDATE_PROCEDURE_STRING = "TERMINATED_UPDATE_PROCEDURE"; +const char *SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL_STRING = "SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL"; +const char *SUPV_EVENT_BUFFER_REQUEST_FAILED_STRING = "SUPV_EVENT_BUFFER_REQUEST_FAILED"; +const char *SUPV_EVENT_BUFFER_REQUEST_TERMINATED_STRING = "SUPV_EVENT_BUFFER_REQUEST_TERMINATED"; +const char *SUPV_MEM_CHECK_OK_STRING = "SUPV_MEM_CHECK_OK"; +const char *SUPV_MEM_CHECK_FAIL_STRING = "SUPV_MEM_CHECK_FAIL"; +const char *SUPV_SENDING_COMMAND_FAILED_STRING = "SUPV_SENDING_COMMAND_FAILED"; +const char *SUPV_HELPER_REQUESTING_REPLY_FAILED_STRING = "SUPV_HELPER_REQUESTING_REPLY_FAILED"; +const char *SUPV_HELPER_READING_REPLY_FAILED_STRING = "SUPV_HELPER_READING_REPLY_FAILED"; +const char *SUPV_MISSING_ACK_STRING = "SUPV_MISSING_ACK"; +const char *SUPV_MISSING_EXE_STRING = "SUPV_MISSING_EXE"; +const char *SUPV_ACK_FAILURE_REPORT_STRING = "SUPV_ACK_FAILURE_REPORT"; +const char *SUPV_EXE_FAILURE_REPORT_STRING = "SUPV_EXE_FAILURE_REPORT"; +const char *SUPV_ACK_INVALID_APID_STRING = "SUPV_ACK_INVALID_APID"; +const char *SUPV_EXE_INVALID_APID_STRING = "SUPV_EXE_INVALID_APID"; +const char *ACK_RECEPTION_FAILURE_STRING = "ACK_RECEPTION_FAILURE"; +const char *EXE_RECEPTION_FAILURE_STRING = "EXE_RECEPTION_FAILURE"; +const char *WRITE_MEMORY_FAILED_STRING = "WRITE_MEMORY_FAILED"; +const char *SUPV_REPLY_SIZE_MISSMATCH_STRING = "SUPV_REPLY_SIZE_MISSMATCH"; +const char *SUPV_REPLY_CRC_MISSMATCH_STRING = "SUPV_REPLY_CRC_MISSMATCH"; +const char *SUPV_UPDATE_PROGRESS_STRING = "SUPV_UPDATE_PROGRESS"; +const char *HDLC_FRAME_REMOVAL_ERROR_STRING = "HDLC_FRAME_REMOVAL_ERROR"; +const char *HDLC_CRC_ERROR_STRING = "HDLC_CRC_ERROR"; +const char *TX_ON_STRING = "TX_ON"; +const char *TX_OFF_STRING = "TX_OFF"; +const char *MISSING_PACKET_STRING = "MISSING_PACKET"; +const char *EXPERIMENT_TIMEDOUT_STRING = "EXPERIMENT_TIMEDOUT"; +const char *MULTI_PACKET_COMMAND_DONE_STRING = "MULTI_PACKET_COMMAND_DONE"; +const char *FS_UNUSABLE_STRING = "FS_UNUSABLE"; +const char *SET_CONFIGFILEVALUE_FAILED_STRING = "SET_CONFIGFILEVALUE_FAILED"; +const char *GET_CONFIGFILEVALUE_FAILED_STRING = "GET_CONFIGFILEVALUE_FAILED"; +const char *INSERT_CONFIGFILEVALUE_FAILED_STRING = "INSERT_CONFIGFILEVALUE_FAILED"; +const char *WRITE_CONFIGFILE_FAILED_STRING = "WRITE_CONFIGFILE_FAILED"; +const char *READ_CONFIGFILE_FAILED_STRING = "READ_CONFIGFILE_FAILED"; +const char *ALLOC_FAILURE_STRING = "ALLOC_FAILURE"; +const char *REBOOT_SW_STRING = "REBOOT_SW"; +const char *REBOOT_MECHANISM_TRIGGERED_STRING = "REBOOT_MECHANISM_TRIGGERED"; +const char *REBOOT_HW_STRING = "REBOOT_HW"; +const char *NO_SD_CARD_ACTIVE_STRING = "NO_SD_CARD_ACTIVE"; +const char *VERSION_INFO_STRING = "VERSION_INFO"; +const char *CURRENT_IMAGE_INFO_STRING = "CURRENT_IMAGE_INFO"; +const char *REBOOT_COUNTER_STRING = "REBOOT_COUNTER"; +const char *INDIVIDUAL_BOOT_COUNTS_STRING = "INDIVIDUAL_BOOT_COUNTS"; +const char *TRYING_I2C_RECOVERY_STRING = "TRYING_I2C_RECOVERY"; +const char *I2C_REBOOT_STRING = "I2C_REBOOT"; +const char *PDEC_REBOOT_STRING = "PDEC_REBOOT"; +const char *FIRMWARE_INFO_STRING = "FIRMWARE_INFO"; +const char *ACTIVE_SD_INFO_STRING = "ACTIVE_SD_INFO"; +const char *NO_VALID_SENSOR_TEMPERATURE_STRING = "NO_VALID_SENSOR_TEMPERATURE"; +const char *NO_HEALTHY_HEATER_AVAILABLE_STRING = "NO_HEALTHY_HEATER_AVAILABLE"; +const char *SYRLINKS_OVERHEATING_STRING = "SYRLINKS_OVERHEATING"; +const char *OBC_OVERHEATING_STRING = "OBC_OVERHEATING"; +const char *CAMERA_OVERHEATING_STRING = "CAMERA_OVERHEATING"; +const char *PCDU_SYSTEM_OVERHEATING_STRING = "PCDU_SYSTEM_OVERHEATING"; +const char *HEATER_NOT_OFF_FOR_OFF_MODE_STRING = "HEATER_NOT_OFF_FOR_OFF_MODE"; +const char *MGT_OVERHEATING_STRING = "MGT_OVERHEATING"; +const char *TCS_SWITCHING_HEATER_ON_STRING = "TCS_SWITCHING_HEATER_ON"; +const char *TCS_SWITCHING_HEATER_OFF_STRING = "TCS_SWITCHING_HEATER_OFF"; +const char *TCS_HEATER_MAX_BURN_TIME_REACHED_STRING = "TCS_HEATER_MAX_BURN_TIME_REACHED"; +const char *TX_TIMER_EXPIRED_STRING = "TX_TIMER_EXPIRED"; +const char *BIT_LOCK_TX_ON_STRING = "BIT_LOCK_TX_ON"; +const char *POSSIBLE_FILE_CORRUPTION_STRING = "POSSIBLE_FILE_CORRUPTION"; +const char *FILE_TOO_LARGE_STRING = "FILE_TOO_LARGE"; +const char *BUSY_DUMPING_EVENT_STRING = "BUSY_DUMPING_EVENT"; +const char *DUMP_OK_STORE_DONE_STRING = "DUMP_OK_STORE_DONE"; +const char *DUMP_NOK_STORE_DONE_STRING = "DUMP_NOK_STORE_DONE"; +const char *DUMP_MISC_STORE_DONE_STRING = "DUMP_MISC_STORE_DONE"; +const char *DUMP_HK_STORE_DONE_STRING = "DUMP_HK_STORE_DONE"; +const char *DUMP_CFDP_STORE_DONE_STRING = "DUMP_CFDP_STORE_DONE"; +const char *DUMP_OK_CANCELLED_STRING = "DUMP_OK_CANCELLED"; +const char *DUMP_NOK_CANCELLED_STRING = "DUMP_NOK_CANCELLED"; +const char *DUMP_MISC_CANCELLED_STRING = "DUMP_MISC_CANCELLED"; +const char *DUMP_HK_CANCELLED_STRING = "DUMP_HK_CANCELLED"; +const char *DUMP_CFDP_CANCELLED_STRING = "DUMP_CFDP_CANCELLED"; +const char *TEMPERATURE_ALL_ONES_START_STRING = "TEMPERATURE_ALL_ONES_START"; +const char *TEMPERATURE_ALL_ONES_RECOVERY_STRING = "TEMPERATURE_ALL_ONES_RECOVERY"; +const char *FAULT_HANDLER_TRIGGERED_STRING = "FAULT_HANDLER_TRIGGERED"; + +const char *translateEvents(Event event) { + switch ((event & 0xFFFF)) { + case (2200): + return STORE_SEND_WRITE_FAILED_STRING; + case (2201): + return STORE_WRITE_FAILED_STRING; + case (2202): + return STORE_SEND_READ_FAILED_STRING; + case (2203): + return STORE_READ_FAILED_STRING; + case (2204): + return UNEXPECTED_MSG_STRING; + case (2205): + return STORING_FAILED_STRING; + case (2206): + return TM_DUMP_FAILED_STRING; + case (2207): + return STORE_INIT_FAILED_STRING; + case (2208): + return STORE_INIT_EMPTY_STRING; + case (2209): + return STORE_CONTENT_CORRUPTED_STRING; + case (2210): + return STORE_INITIALIZE_STRING; + case (2211): + return INIT_DONE_STRING; + case (2212): + return DUMP_FINISHED_STRING; + case (2213): + return DELETION_FINISHED_STRING; + case (2214): + return DELETION_FAILED_STRING; + case (2215): + return AUTO_CATALOGS_SENDING_FAILED_STRING; + case (2600): + return GET_DATA_FAILED_STRING; + case (2601): + return STORE_DATA_FAILED_STRING; + case (2800): + return DEVICE_BUILDING_COMMAND_FAILED_STRING; + case (2801): + return DEVICE_SENDING_COMMAND_FAILED_STRING; + case (2802): + return DEVICE_REQUESTING_REPLY_FAILED_STRING; + case (2803): + return DEVICE_READING_REPLY_FAILED_STRING; + case (2804): + return DEVICE_INTERPRETING_REPLY_FAILED_STRING; + case (2805): + return DEVICE_MISSED_REPLY_STRING; + case (2806): + return DEVICE_UNKNOWN_REPLY_STRING; + case (2807): + return DEVICE_UNREQUESTED_REPLY_STRING; + case (2808): + return INVALID_DEVICE_COMMAND_STRING; + case (2809): + return MONITORING_LIMIT_EXCEEDED_STRING; + case (2810): + return MONITORING_AMBIGUOUS_STRING; + case (2811): + return DEVICE_WANTS_HARD_REBOOT_STRING; + case (4300): + return SWITCH_WENT_OFF_STRING; + case (4301): + return FUSE_CURRENT_HIGH_STRING; + case (4302): + return FUSE_WENT_OFF_STRING; + case (4304): + return POWER_ABOVE_HIGH_LIMIT_STRING; + case (4305): + return POWER_BELOW_LOW_LIMIT_STRING; + case (5000): + return HEATER_ON_STRING; + case (5001): + return HEATER_OFF_STRING; + case (5002): + return HEATER_TIMEOUT_STRING; + case (5003): + return HEATER_STAYED_ON_STRING; + case (5004): + return HEATER_STAYED_OFF_STRING; + case (5200): + return TEMP_SENSOR_HIGH_STRING; + case (5201): + return TEMP_SENSOR_LOW_STRING; + case (5202): + return TEMP_SENSOR_GRADIENT_STRING; + case (5901): + return COMPONENT_TEMP_LOW_STRING; + case (5902): + return COMPONENT_TEMP_HIGH_STRING; + case (5903): + return COMPONENT_TEMP_OOL_LOW_STRING; + case (5904): + return COMPONENT_TEMP_OOL_HIGH_STRING; + case (5905): + return TEMP_NOT_IN_OP_RANGE_STRING; + case (7101): + return FDIR_CHANGED_STATE_STRING; + case (7102): + return FDIR_STARTS_RECOVERY_STRING; + case (7103): + return FDIR_TURNS_OFF_DEVICE_STRING; + case (7201): + return MONITOR_CHANGED_STATE_STRING; + case (7202): + return VALUE_BELOW_LOW_LIMIT_STRING; + case (7203): + return VALUE_ABOVE_HIGH_LIMIT_STRING; + case (7204): + return VALUE_OUT_OF_RANGE_STRING; + case (7400): + return CHANGING_MODE_STRING; + case (7401): + return MODE_INFO_STRING; + case (7402): + return FALLBACK_FAILED_STRING; + case (7403): + return MODE_TRANSITION_FAILED_STRING; + case (7404): + return CANT_KEEP_MODE_STRING; + case (7405): + return OBJECT_IN_INVALID_MODE_STRING; + case (7406): + return FORCING_MODE_STRING; + case (7407): + return MODE_CMD_REJECTED_STRING; + case (7506): + return HEALTH_INFO_STRING; + case (7507): + return CHILD_CHANGED_HEALTH_STRING; + case (7508): + return CHILD_PROBLEMS_STRING; + case (7509): + return OVERWRITING_HEALTH_STRING; + case (7510): + return TRYING_RECOVERY_STRING; + case (7511): + return RECOVERY_STEP_STRING; + case (7512): + return RECOVERY_DONE_STRING; + case (7600): + return HANDLE_PACKET_FAILED_STRING; + case (7900): + return RF_AVAILABLE_STRING; + case (7901): + return RF_LOST_STRING; + case (7902): + return BIT_LOCK_STRING; + case (7903): + return BIT_LOCK_LOST_STRING; + case (7905): + return FRAME_PROCESSING_FAILED_STRING; + case (8900): + return CLOCK_SET_STRING; + case (8901): + return CLOCK_DUMP_LEGACY_STRING; + case (8902): + return CLOCK_SET_FAILURE_STRING; + case (8903): + return CLOCK_DUMP_STRING; + case (8904): + return CLOCK_DUMP_BEFORE_SETTING_TIME_STRING; + case (8905): + return CLOCK_DUMP_AFTER_SETTING_TIME_STRING; + case (9100): + return TC_DELETION_FAILED_STRING; + case (9700): + return TEST_STRING; + case (10600): + return CHANGE_OF_SETUP_PARAMETER_STRING; + case (10800): + return STORE_ERROR_STRING; + case (10801): + return MSG_QUEUE_ERROR_STRING; + case (10802): + return SERIALIZATION_ERROR_STRING; + case (10803): + return FILESTORE_ERROR_STRING; + case (10804): + return FILENAME_TOO_LARGE_ERROR_STRING; + case (10805): + return HANDLING_CFDP_REQUEST_FAILED_STRING; + case (11200): + return SAFE_RATE_VIOLATION_STRING; + case (11201): + return RATE_RECOVERY_STRING; + case (11202): + return MULTIPLE_RW_INVALID_STRING; + case (11203): + return MEKF_INVALID_INFO_STRING; + case (11204): + return MEKF_RECOVERY_STRING; + case (11205): + return MEKF_AUTOMATIC_RESET_STRING; + case (11206): + return PTG_CTRL_NO_ATTITUDE_INFORMATION_STRING; + case (11207): + return SAFE_MODE_CONTROLLER_FAILURE_STRING; + case (11208): + return TLE_TOO_OLD_STRING; + case (11209): + return TLE_FILE_READ_FAILED_STRING; + case (11210): + return PTG_RATE_VIOLATION_STRING; + case (11211): + return DETUMBLE_TRANSITION_FAILED_STRING; + case (11300): + return SWITCH_CMD_SENT_STRING; + case (11301): + return SWITCH_HAS_CHANGED_STRING; + case (11302): + return SWITCHING_Q7S_DENIED_STRING; + case (11303): + return FDIR_REACTION_IGNORED_STRING; + case (11304): + return DATASET_READ_FAILED_STRING; + case (11305): + return VOLTAGE_OUT_OF_BOUNDS_STRING; + case (11306): + return TIMEDELTA_OUT_OF_BOUNDS_STRING; + case (11307): + return POWER_LEVEL_LOW_STRING; + case (11308): + return POWER_LEVEL_CRITICAL_STRING; + case (11400): + return GPIO_PULL_HIGH_FAILED_STRING; + case (11401): + return GPIO_PULL_LOW_FAILED_STRING; + case (11402): + return HEATER_WENT_ON_STRING; + case (11403): + return HEATER_WENT_OFF_STRING; + case (11404): + return SWITCH_ALREADY_ON_STRING; + case (11405): + return SWITCH_ALREADY_OFF_STRING; + case (11406): + return MAIN_SWITCH_TIMEOUT_STRING; + case (11407): + return FAULTY_HEATER_WAS_ON_STRING; + case (11500): + return BURN_PHASE_START_STRING; + case (11501): + return BURN_PHASE_DONE_STRING; + case (11502): + return MAIN_SWITCH_ON_TIMEOUT_STRING; + case (11503): + return MAIN_SWITCH_OFF_TIMEOUT_STRING; + case (11504): + return DEPL_SA1_GPIO_SWTICH_ON_FAILED_STRING; + case (11505): + return DEPL_SA2_GPIO_SWTICH_ON_FAILED_STRING; + case (11506): + return DEPL_SA1_GPIO_SWTICH_OFF_FAILED_STRING; + case (11507): + return DEPL_SA2_GPIO_SWTICH_OFF_FAILED_STRING; + case (11508): + return AUTONOMOUS_DEPLOYMENT_COMPLETED_STRING; + case (11601): + return MEMORY_READ_RPT_CRC_FAILURE_STRING; + case (11602): + return ACK_FAILURE_STRING; + case (11603): + return EXE_FAILURE_STRING; + case (11604): + return MPSOC_HANDLER_CRC_FAILURE_STRING; + case (11605): + return MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH_STRING; + case (11606): + return MPSOC_SHUTDOWN_FAILED_STRING; + case (11607): + return SUPV_NOT_ON_STRING; + case (11608): + return SUPV_REPLY_TIMEOUT_STRING; + case (11609): + return CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE_STRING; + case (11701): + return SELF_TEST_I2C_FAILURE_STRING; + case (11702): + return SELF_TEST_SPI_FAILURE_STRING; + case (11703): + return SELF_TEST_ADC_FAILURE_STRING; + case (11704): + return SELF_TEST_PWM_FAILURE_STRING; + case (11705): + return SELF_TEST_TC_FAILURE_STRING; + case (11706): + return SELF_TEST_MTM_RANGE_FAILURE_STRING; + case (11707): + return SELF_TEST_COIL_CURRENT_FAILURE_STRING; + case (11708): + return INVALID_ERROR_BYTE_STRING; + case (11801): + return ERROR_STATE_STRING; + case (11802): + return RESET_OCCURED_STRING; + case (11901): + return BOOTING_FIRMWARE_FAILED_EVENT_STRING; + case (11902): + return BOOTING_BOOTLOADER_FAILED_EVENT_STRING; + case (11903): + return COM_ERROR_REPLY_RECEIVED_STRING; + case (12001): + return SUPV_MEMORY_READ_RPT_CRC_FAILURE_STRING; + case (12002): + return SUPV_UNKNOWN_TM_STRING; + case (12003): + return SUPV_UNINIMPLEMENTED_TM_STRING; + case (12004): + return SUPV_ACK_FAILURE_STRING; + case (12005): + return SUPV_EXE_FAILURE_STRING; + case (12006): + return SUPV_CRC_FAILURE_EVENT_STRING; + case (12007): + return SUPV_HELPER_EXECUTING_STRING; + case (12008): + return SUPV_MPSOC_SHUTDOWN_BUILD_FAILED_STRING; + case (12009): + return SUPV_ACK_UNKNOWN_COMMAND_STRING; + case (12010): + return SUPV_EXE_ACK_UNKNOWN_COMMAND_STRING; + case (12100): + return SANITIZATION_FAILED_STRING; + case (12101): + return MOUNTED_SD_CARD_STRING; + case (12300): + return SEND_MRAM_DUMP_FAILED_STRING; + case (12301): + return MRAM_DUMP_FAILED_STRING; + case (12302): + return MRAM_DUMP_FINISHED_STRING; + case (12401): + return INVALID_TC_FRAME_STRING; + case (12402): + return INVALID_FAR_STRING; + case (12403): + return CARRIER_LOCK_STRING; + case (12404): + return BIT_LOCK_PDEC_STRING; + case (12405): + return LOST_CARRIER_LOCK_PDEC_STRING; + case (12406): + return LOST_BIT_LOCK_PDEC_STRING; + case (12407): + return TOO_MANY_IRQS_STRING; + case (12408): + return POLL_SYSCALL_ERROR_PDEC_STRING; + case (12409): + return WRITE_SYSCALL_ERROR_PDEC_STRING; + case (12410): + return PDEC_TRYING_RESET_WITH_INIT_STRING; + case (12411): + return PDEC_TRYING_RESET_NO_INIT_STRING; + case (12412): + return PDEC_RESET_FAILED_STRING; + case (12413): + return OPEN_IRQ_FILE_FAILED_STRING; + case (12414): + return PDEC_INIT_FAILED_STRING; + case (12415): + return PDEC_CONFIG_CORRUPTED_STRING; + case (12500): + return IMAGE_UPLOAD_FAILED_STRING; + case (12501): + return IMAGE_DOWNLOAD_FAILED_STRING; + case (12502): + return IMAGE_UPLOAD_SUCCESSFUL_STRING; + case (12503): + return IMAGE_DOWNLOAD_SUCCESSFUL_STRING; + case (12504): + return FLASH_WRITE_SUCCESSFUL_STRING; + case (12505): + return FLASH_READ_SUCCESSFUL_STRING; + case (12506): + return FLASH_READ_FAILED_STRING; + case (12507): + return FIRMWARE_UPDATE_SUCCESSFUL_STRING; + case (12508): + return FIRMWARE_UPDATE_FAILED_STRING; + case (12509): + return STR_HELPER_READING_REPLY_FAILED_STRING; + case (12510): + return STR_HELPER_COM_ERROR_STRING; + case (12511): + return STR_COM_REPLY_TIMEOUT_STRING; + case (12513): + return STR_HELPER_DEC_ERROR_STRING; + case (12514): + return POSITION_MISMATCH_STRING; + case (12515): + return STR_HELPER_FILE_NOT_EXISTS_STRING; + case (12516): + return STR_HELPER_SENDING_PACKET_FAILED_STRING; + case (12517): + return STR_HELPER_REQUESTING_MSG_FAILED_STRING; + case (12600): + return MPSOC_FLASH_WRITE_FAILED_STRING; + case (12601): + return MPSOC_FLASH_WRITE_SUCCESSFUL_STRING; + case (12602): + return MPSOC_SENDING_COMMAND_FAILED_STRING; + case (12603): + return MPSOC_HELPER_REQUESTING_REPLY_FAILED_STRING; + case (12604): + return MPSOC_HELPER_READING_REPLY_FAILED_STRING; + case (12605): + return MPSOC_MISSING_ACK_STRING; + case (12606): + return MPSOC_MISSING_EXE_STRING; + case (12607): + return MPSOC_ACK_FAILURE_REPORT_STRING; + case (12608): + return MPSOC_EXE_FAILURE_REPORT_STRING; + case (12609): + return MPSOC_ACK_INVALID_APID_STRING; + case (12610): + return MPSOC_EXE_INVALID_APID_STRING; + case (12611): + return MPSOC_HELPER_SEQ_CNT_MISMATCH_STRING; + case (12612): + return MPSOC_TM_SIZE_ERROR_STRING; + case (12613): + return MPSOC_TM_CRC_MISSMATCH_STRING; + case (12614): + return MPSOC_FLASH_READ_PACKET_ERROR_STRING; + case (12615): + return MPSOC_FLASH_READ_FAILED_STRING; + case (12616): + return MPSOC_FLASH_READ_SUCCESSFUL_STRING; + case (12617): + return MPSOC_READ_TIMEOUT_STRING; + case (12700): + return TRANSITION_BACK_TO_OFF_STRING; + case (12701): + return NEG_V_OUT_OF_BOUNDS_STRING; + case (12702): + return U_DRO_OUT_OF_BOUNDS_STRING; + case (12703): + return I_DRO_OUT_OF_BOUNDS_STRING; + case (12704): + return U_X8_OUT_OF_BOUNDS_STRING; + case (12705): + return I_X8_OUT_OF_BOUNDS_STRING; + case (12706): + return U_TX_OUT_OF_BOUNDS_STRING; + case (12707): + return I_TX_OUT_OF_BOUNDS_STRING; + case (12708): + return U_MPA_OUT_OF_BOUNDS_STRING; + case (12709): + return I_MPA_OUT_OF_BOUNDS_STRING; + case (12710): + return U_HPA_OUT_OF_BOUNDS_STRING; + case (12711): + return I_HPA_OUT_OF_BOUNDS_STRING; + case (12800): + return TRANSITION_OTHER_SIDE_FAILED_STRING; + case (12801): + return NOT_ENOUGH_DEVICES_DUAL_MODE_STRING; + case (12802): + return POWER_STATE_MACHINE_TIMEOUT_STRING; + case (12803): + return SIDE_SWITCH_TRANSITION_NOT_ALLOWED_STRING; + case (12804): + return DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY_STRING; + case (12900): + return TRANSITION_OTHER_SIDE_FAILED_12900_STRING; + case (12901): + return NOT_ENOUGH_DEVICES_DUAL_MODE_12901_STRING; + case (12902): + return POWER_STATE_MACHINE_TIMEOUT_12902_STRING; + case (12903): + return SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903_STRING; + case (13000): + return CHILDREN_LOST_MODE_STRING; + case (13100): + return GPS_FIX_CHANGE_STRING; + case (13101): + return CANT_GET_FIX_STRING; + case (13102): + return RESET_FAIL_STRING; + case (13200): + return P60_BOOT_COUNT_STRING; + case (13201): + return BATT_MODE_STRING; + case (13202): + return BATT_MODE_CHANGED_STRING; + case (13600): + return SUPV_UPDATE_FAILED_STRING; + case (13601): + return SUPV_UPDATE_SUCCESSFUL_STRING; + case (13602): + return SUPV_CONTINUE_UPDATE_FAILED_STRING; + case (13603): + return SUPV_CONTINUE_UPDATE_SUCCESSFUL_STRING; + case (13604): + return TERMINATED_UPDATE_PROCEDURE_STRING; + case (13605): + return SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL_STRING; + case (13606): + return SUPV_EVENT_BUFFER_REQUEST_FAILED_STRING; + case (13607): + return SUPV_EVENT_BUFFER_REQUEST_TERMINATED_STRING; + case (13608): + return SUPV_MEM_CHECK_OK_STRING; + case (13609): + return SUPV_MEM_CHECK_FAIL_STRING; + case (13616): + return SUPV_SENDING_COMMAND_FAILED_STRING; + case (13617): + return SUPV_HELPER_REQUESTING_REPLY_FAILED_STRING; + case (13618): + return SUPV_HELPER_READING_REPLY_FAILED_STRING; + case (13619): + return SUPV_MISSING_ACK_STRING; + case (13620): + return SUPV_MISSING_EXE_STRING; + case (13621): + return SUPV_ACK_FAILURE_REPORT_STRING; + case (13622): + return SUPV_EXE_FAILURE_REPORT_STRING; + case (13623): + return SUPV_ACK_INVALID_APID_STRING; + case (13624): + return SUPV_EXE_INVALID_APID_STRING; + case (13625): + return ACK_RECEPTION_FAILURE_STRING; + case (13626): + return EXE_RECEPTION_FAILURE_STRING; + case (13627): + return WRITE_MEMORY_FAILED_STRING; + case (13628): + return SUPV_REPLY_SIZE_MISSMATCH_STRING; + case (13629): + return SUPV_REPLY_CRC_MISSMATCH_STRING; + case (13630): + return SUPV_UPDATE_PROGRESS_STRING; + case (13631): + return HDLC_FRAME_REMOVAL_ERROR_STRING; + case (13632): + return HDLC_CRC_ERROR_STRING; + case (13701): + return TX_ON_STRING; + case (13702): + return TX_OFF_STRING; + case (13800): + return MISSING_PACKET_STRING; + case (13801): + return EXPERIMENT_TIMEDOUT_STRING; + case (13802): + return MULTI_PACKET_COMMAND_DONE_STRING; + case (13803): + return FS_UNUSABLE_STRING; + case (13901): + return SET_CONFIGFILEVALUE_FAILED_STRING; + case (13902): + return GET_CONFIGFILEVALUE_FAILED_STRING; + case (13903): + return INSERT_CONFIGFILEVALUE_FAILED_STRING; + case (13904): + return WRITE_CONFIGFILE_FAILED_STRING; + case (13905): + return READ_CONFIGFILE_FAILED_STRING; + case (14000): + return ALLOC_FAILURE_STRING; + case (14001): + return REBOOT_SW_STRING; + case (14002): + return REBOOT_MECHANISM_TRIGGERED_STRING; + case (14003): + return REBOOT_HW_STRING; + case (14004): + return NO_SD_CARD_ACTIVE_STRING; + case (14005): + return VERSION_INFO_STRING; + case (14006): + return CURRENT_IMAGE_INFO_STRING; + case (14007): + return REBOOT_COUNTER_STRING; + case (14008): + return INDIVIDUAL_BOOT_COUNTS_STRING; + case (14010): + return TRYING_I2C_RECOVERY_STRING; + case (14011): + return I2C_REBOOT_STRING; + case (14012): + return PDEC_REBOOT_STRING; + case (14013): + return FIRMWARE_INFO_STRING; + case (14014): + return ACTIVE_SD_INFO_STRING; + case (14100): + return NO_VALID_SENSOR_TEMPERATURE_STRING; + case (14101): + return NO_HEALTHY_HEATER_AVAILABLE_STRING; + case (14102): + return SYRLINKS_OVERHEATING_STRING; + case (14104): + return OBC_OVERHEATING_STRING; + case (14105): + return CAMERA_OVERHEATING_STRING; + case (14106): + return PCDU_SYSTEM_OVERHEATING_STRING; + case (14107): + return HEATER_NOT_OFF_FOR_OFF_MODE_STRING; + case (14108): + return MGT_OVERHEATING_STRING; + case (14109): + return TCS_SWITCHING_HEATER_ON_STRING; + case (14110): + return TCS_SWITCHING_HEATER_OFF_STRING; + case (14111): + return TCS_HEATER_MAX_BURN_TIME_REACHED_STRING; + case (14201): + return TX_TIMER_EXPIRED_STRING; + case (14202): + return BIT_LOCK_TX_ON_STRING; + case (14300): + return POSSIBLE_FILE_CORRUPTION_STRING; + case (14301): + return FILE_TOO_LARGE_STRING; + case (14302): + return BUSY_DUMPING_EVENT_STRING; + case (14305): + return DUMP_OK_STORE_DONE_STRING; + case (14306): + return DUMP_NOK_STORE_DONE_STRING; + case (14307): + return DUMP_MISC_STORE_DONE_STRING; + case (14308): + return DUMP_HK_STORE_DONE_STRING; + case (14309): + return DUMP_CFDP_STORE_DONE_STRING; + case (14310): + return DUMP_OK_CANCELLED_STRING; + case (14311): + return DUMP_NOK_CANCELLED_STRING; + case (14312): + return DUMP_MISC_CANCELLED_STRING; + case (14313): + return DUMP_HK_CANCELLED_STRING; + case (14314): + return DUMP_CFDP_CANCELLED_STRING; + case (14500): + return TEMPERATURE_ALL_ONES_START_STRING; + case (14501): + return TEMPERATURE_ALL_ONES_RECOVERY_STRING; + case (14600): + return FAULT_HANDLER_TRIGGERED_STRING; + default: + return "UNKNOWN_EVENT"; + } + return 0; +} diff --git a/bsp_hosted/fsfwconfig/events/translateEvents.h b/bsp_hosted/fsfwconfig/events/translateEvents.h new file mode 100644 index 0000000..9955431 --- /dev/null +++ b/bsp_hosted/fsfwconfig/events/translateEvents.h @@ -0,0 +1,8 @@ +#ifndef FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ +#define FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ + +#include "fsfw/events/Event.h" + +const char *translateEvents(Event event); + +#endif /* FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ */ diff --git a/bsp_hosted/fsfwconfig/ipc/MissionMessageTypes.cpp b/bsp_hosted/fsfwconfig/ipc/MissionMessageTypes.cpp new file mode 100644 index 0000000..fa1c487 --- /dev/null +++ b/bsp_hosted/fsfwconfig/ipc/MissionMessageTypes.cpp @@ -0,0 +1,10 @@ +#include "MissionMessageTypes.h" + +#include + +void messagetypes::clearMissionMessage(CommandMessage* message) { + switch (message->getMessageType()) { + default: + break; + } +} diff --git a/bsp_hosted/fsfwconfig/ipc/MissionMessageTypes.h b/bsp_hosted/fsfwconfig/ipc/MissionMessageTypes.h new file mode 100644 index 0000000..1d93ba2 --- /dev/null +++ b/bsp_hosted/fsfwconfig/ipc/MissionMessageTypes.h @@ -0,0 +1,22 @@ +#ifndef CONFIG_IPC_MISSIONMESSAGETYPES_H_ +#define CONFIG_IPC_MISSIONMESSAGETYPES_H_ + +#include + +class CommandMessage; + +/** + * Custom command messages are specified here. + * Most messages needed to use FSFW are already located in + * + * @param message Generic Command Message + */ +namespace messagetypes { +enum MESSAGE_TYPE { + MISSION_MESSAGE_TYPE_START = FW_MESSAGES_COUNT, +}; + +void clearMissionMessage(CommandMessage* message); +} // namespace messagetypes + +#endif /* CONFIG_IPC_MISSIONMESSAGETYPES_H_ */ diff --git a/bsp_hosted/fsfwconfig/objects/systemObjectList.h b/bsp_hosted/fsfwconfig/objects/systemObjectList.h new file mode 100644 index 0000000..539ef0d --- /dev/null +++ b/bsp_hosted/fsfwconfig/objects/systemObjectList.h @@ -0,0 +1,31 @@ +#ifndef HOSTED_CONFIG_OBJECTS_SYSTEMOBJECTLIST_H_ +#define HOSTED_CONFIG_OBJECTS_SYSTEMOBJECTLIST_H_ + +#include + +#include "eive/objects.h" + +// The objects will be instantiated in the ID order +namespace objects { +enum sourceObjects : uint32_t { + + PUS_SERVICE_3 = 0x51000300, + PUS_SERVICE_5 = 0x51000400, + PUS_SERVICE_6 = 0x51000500, + PUS_SERVICE_8 = 0x51000800, + PUS_SERVICE_23 = 0x51002300, + PUS_SERVICE_201 = 0x51020100, + + /* Test Task */ + + TEST_TASK = 0x42694269, + DUMMY_INTERFACE = 0xCAFECAFE, + DUMMY_HANDLER = 0x44000001, + /* 0x49 ('I') for Communication Interfaces **/ + ARDUINO_COM_IF = 0x49000001, + + DUMMY_COM_IF = 0x49000002, +}; +} + +#endif /* BSP_CONFIG_OBJECTS_SYSTEMOBJECTLIST_H_ */ diff --git a/bsp_hosted/fsfwconfig/objects/translateObjects.cpp b/bsp_hosted/fsfwconfig/objects/translateObjects.cpp new file mode 100644 index 0000000..53332c1 --- /dev/null +++ b/bsp_hosted/fsfwconfig/objects/translateObjects.cpp @@ -0,0 +1,544 @@ +/** + * @brief Auto-generated object translation file. + * @details + * Contains 176 translations. + * Generated on: 2024-05-06 13:47:38 + */ +#include "translateObjects.h" + +const char *TEST_TASK_STRING = "TEST_TASK"; +const char *ACS_CONTROLLER_STRING = "ACS_CONTROLLER"; +const char *CORE_CONTROLLER_STRING = "CORE_CONTROLLER"; +const char *POWER_CONTROLLER_STRING = "POWER_CONTROLLER"; +const char *GLOBAL_JSON_CFG_STRING = "GLOBAL_JSON_CFG"; +const char *XIPHOS_WDT_STRING = "XIPHOS_WDT"; +const char *THERMAL_CONTROLLER_STRING = "THERMAL_CONTROLLER"; +const char *DUMMY_HANDLER_STRING = "DUMMY_HANDLER"; +const char *MGM_0_LIS3_HANDLER_STRING = "MGM_0_LIS3_HANDLER"; +const char *GYRO_0_ADIS_HANDLER_STRING = "GYRO_0_ADIS_HANDLER"; +const char *SUS_0_N_LOC_XFYFZM_PT_XF_STRING = "SUS_0_N_LOC_XFYFZM_PT_XF"; +const char *SUS_1_N_LOC_XBYFZM_PT_XB_STRING = "SUS_1_N_LOC_XBYFZM_PT_XB"; +const char *SUS_2_N_LOC_XFYBZB_PT_YB_STRING = "SUS_2_N_LOC_XFYBZB_PT_YB"; +const char *SUS_3_N_LOC_XFYBZF_PT_YF_STRING = "SUS_3_N_LOC_XFYBZF_PT_YF"; +const char *SUS_4_N_LOC_XMYFZF_PT_ZF_STRING = "SUS_4_N_LOC_XMYFZF_PT_ZF"; +const char *SUS_5_N_LOC_XFYMZB_PT_ZB_STRING = "SUS_5_N_LOC_XFYMZB_PT_ZB"; +const char *SUS_6_R_LOC_XFYBZM_PT_XF_STRING = "SUS_6_R_LOC_XFYBZM_PT_XF"; +const char *SUS_7_R_LOC_XBYBZM_PT_XB_STRING = "SUS_7_R_LOC_XBYBZM_PT_XB"; +const char *SUS_8_R_LOC_XBYBZB_PT_YB_STRING = "SUS_8_R_LOC_XBYBZB_PT_YB"; +const char *SUS_9_R_LOC_XBYBZB_PT_YF_STRING = "SUS_9_R_LOC_XBYBZB_PT_YF"; +const char *SUS_10_N_LOC_XMYBZF_PT_ZF_STRING = "SUS_10_N_LOC_XMYBZF_PT_ZF"; +const char *SUS_11_R_LOC_XBYMZB_PT_ZB_STRING = "SUS_11_R_LOC_XBYMZB_PT_ZB"; +const char *RW1_STRING = "RW1"; +const char *MGM_1_RM3100_HANDLER_STRING = "MGM_1_RM3100_HANDLER"; +const char *GYRO_1_L3G_HANDLER_STRING = "GYRO_1_L3G_HANDLER"; +const char *RW2_STRING = "RW2"; +const char *MGM_2_LIS3_HANDLER_STRING = "MGM_2_LIS3_HANDLER"; +const char *GYRO_2_ADIS_HANDLER_STRING = "GYRO_2_ADIS_HANDLER"; +const char *RW3_STRING = "RW3"; +const char *MGM_3_RM3100_HANDLER_STRING = "MGM_3_RM3100_HANDLER"; +const char *GYRO_3_L3G_HANDLER_STRING = "GYRO_3_L3G_HANDLER"; +const char *RW4_STRING = "RW4"; +const char *STAR_TRACKER_STRING = "STAR_TRACKER"; +const char *GPS_CONTROLLER_STRING = "GPS_CONTROLLER"; +const char *GPS_0_HEALTH_DEV_STRING = "GPS_0_HEALTH_DEV"; +const char *GPS_1_HEALTH_DEV_STRING = "GPS_1_HEALTH_DEV"; +const char *IMTQ_POLLING_STRING = "IMTQ_POLLING"; +const char *IMTQ_HANDLER_STRING = "IMTQ_HANDLER"; +const char *PCDU_HANDLER_STRING = "PCDU_HANDLER"; +const char *P60DOCK_HANDLER_STRING = "P60DOCK_HANDLER"; +const char *PDU1_HANDLER_STRING = "PDU1_HANDLER"; +const char *PDU2_HANDLER_STRING = "PDU2_HANDLER"; +const char *ACU_HANDLER_STRING = "ACU_HANDLER"; +const char *BPX_BATT_HANDLER_STRING = "BPX_BATT_HANDLER"; +const char *PLPCDU_HANDLER_STRING = "PLPCDU_HANDLER"; +const char *RAD_SENSOR_STRING = "RAD_SENSOR"; +const char *PLOC_UPDATER_STRING = "PLOC_UPDATER"; +const char *PLOC_MEMORY_DUMPER_STRING = "PLOC_MEMORY_DUMPER"; +const char *STR_COM_IF_STRING = "STR_COM_IF"; +const char *PLOC_MPSOC_HELPER_STRING = "PLOC_MPSOC_HELPER"; +const char *AXI_PTME_CONFIG_STRING = "AXI_PTME_CONFIG"; +const char *PTME_CONFIG_STRING = "PTME_CONFIG"; +const char *PTME_VC0_LIVE_TM_STRING = "PTME_VC0_LIVE_TM"; +const char *PTME_VC1_LOG_TM_STRING = "PTME_VC1_LOG_TM"; +const char *PTME_VC2_HK_TM_STRING = "PTME_VC2_HK_TM"; +const char *PTME_VC3_CFDP_TM_STRING = "PTME_VC3_CFDP_TM"; +const char *PLOC_MPSOC_HANDLER_STRING = "PLOC_MPSOC_HANDLER"; +const char *PLOC_SUPERVISOR_HANDLER_STRING = "PLOC_SUPERVISOR_HANDLER"; +const char *PLOC_SUPERVISOR_HELPER_STRING = "PLOC_SUPERVISOR_HELPER"; +const char *PLOC_MPSOC_COMMUNICATION_STRING = "PLOC_MPSOC_COMMUNICATION"; +const char *SCEX_STRING = "SCEX"; +const char *SOLAR_ARRAY_DEPL_HANDLER_STRING = "SOLAR_ARRAY_DEPL_HANDLER"; +const char *HEATER_HANDLER_STRING = "HEATER_HANDLER"; +const char *TMP1075_HANDLER_TCS_0_STRING = "TMP1075_HANDLER_TCS_0"; +const char *TMP1075_HANDLER_TCS_1_STRING = "TMP1075_HANDLER_TCS_1"; +const char *TMP1075_HANDLER_PLPCDU_0_STRING = "TMP1075_HANDLER_PLPCDU_0"; +const char *TMP1075_HANDLER_PLPCDU_1_STRING = "TMP1075_HANDLER_PLPCDU_1"; +const char *TMP1075_HANDLER_IF_BOARD_STRING = "TMP1075_HANDLER_IF_BOARD"; +const char *RTD_0_IC3_PLOC_HEATSPREADER_STRING = "RTD_0_IC3_PLOC_HEATSPREADER"; +const char *RTD_1_IC4_PLOC_MISSIONBOARD_STRING = "RTD_1_IC4_PLOC_MISSIONBOARD"; +const char *RTD_2_IC5_4K_CAMERA_STRING = "RTD_2_IC5_4K_CAMERA"; +const char *RTD_3_IC6_DAC_HEATSPREADER_STRING = "RTD_3_IC6_DAC_HEATSPREADER"; +const char *RTD_4_IC7_STARTRACKER_STRING = "RTD_4_IC7_STARTRACKER"; +const char *RTD_5_IC8_RW1_MX_MY_STRING = "RTD_5_IC8_RW1_MX_MY"; +const char *RTD_6_IC9_DRO_STRING = "RTD_6_IC9_DRO"; +const char *RTD_7_IC10_SCEX_STRING = "RTD_7_IC10_SCEX"; +const char *RTD_8_IC11_X8_STRING = "RTD_8_IC11_X8"; +const char *RTD_9_IC12_HPA_STRING = "RTD_9_IC12_HPA"; +const char *RTD_10_IC13_PL_TX_STRING = "RTD_10_IC13_PL_TX"; +const char *RTD_11_IC14_MPA_STRING = "RTD_11_IC14_MPA"; +const char *RTD_12_IC15_ACU_STRING = "RTD_12_IC15_ACU"; +const char *RTD_13_IC16_PLPCDU_HEATSPREADER_STRING = "RTD_13_IC16_PLPCDU_HEATSPREADER"; +const char *RTD_14_IC17_TCS_BOARD_STRING = "RTD_14_IC17_TCS_BOARD"; +const char *RTD_15_IC18_IMTQ_STRING = "RTD_15_IC18_IMTQ"; +const char *SYRLINKS_HANDLER_STRING = "SYRLINKS_HANDLER"; +const char *SYRLINKS_COM_HANDLER_STRING = "SYRLINKS_COM_HANDLER"; +const char *ARDUINO_COM_IF_STRING = "ARDUINO_COM_IF"; +const char *DUMMY_COM_IF_STRING = "DUMMY_COM_IF"; +const char *SCEX_UART_READER_STRING = "SCEX_UART_READER"; +const char *UART_COM_IF_STRING = "UART_COM_IF"; +const char *ACS_BOARD_POLLING_TASK_STRING = "ACS_BOARD_POLLING_TASK"; +const char *RW_POLLING_TASK_STRING = "RW_POLLING_TASK"; +const char *SPI_RTD_COM_IF_STRING = "SPI_RTD_COM_IF"; +const char *SUS_POLLING_TASK_STRING = "SUS_POLLING_TASK"; +const char *CCSDS_PACKET_DISTRIBUTOR_STRING = "CCSDS_PACKET_DISTRIBUTOR"; +const char *PUS_PACKET_DISTRIBUTOR_STRING = "PUS_PACKET_DISTRIBUTOR"; +const char *TCP_TMTC_SERVER_STRING = "TCP_TMTC_SERVER"; +const char *UDP_TMTC_SERVER_STRING = "UDP_TMTC_SERVER"; +const char *TCP_TMTC_POLLING_TASK_STRING = "TCP_TMTC_POLLING_TASK"; +const char *UDP_TMTC_POLLING_TASK_STRING = "UDP_TMTC_POLLING_TASK"; +const char *FILE_SYSTEM_HANDLER_STRING = "FILE_SYSTEM_HANDLER"; +const char *SDC_MANAGER_STRING = "SDC_MANAGER"; +const char *PTME_STRING = "PTME"; +const char *PDEC_HANDLER_STRING = "PDEC_HANDLER"; +const char *CCSDS_HANDLER_STRING = "CCSDS_HANDLER"; +const char *PUS_SERVICE_3_STRING = "PUS_SERVICE_3"; +const char *PUS_SERVICE_5_STRING = "PUS_SERVICE_5"; +const char *PUS_SERVICE_6_STRING = "PUS_SERVICE_6"; +const char *PUS_SERVICE_8_STRING = "PUS_SERVICE_8"; +const char *PUS_SERVICE_23_STRING = "PUS_SERVICE_23"; +const char *PUS_SERVICE_201_STRING = "PUS_SERVICE_201"; +const char *FSFW_OBJECTS_START_STRING = "FSFW_OBJECTS_START"; +const char *PUS_SERVICE_1_VERIFICATION_STRING = "PUS_SERVICE_1_VERIFICATION"; +const char *PUS_SERVICE_2_DEVICE_ACCESS_STRING = "PUS_SERVICE_2_DEVICE_ACCESS"; +const char *PUS_SERVICE_3_HOUSEKEEPING_STRING = "PUS_SERVICE_3_HOUSEKEEPING"; +const char *PUS_SERVICE_5_EVENT_REPORTING_STRING = "PUS_SERVICE_5_EVENT_REPORTING"; +const char *PUS_SERVICE_8_FUNCTION_MGMT_STRING = "PUS_SERVICE_8_FUNCTION_MGMT"; +const char *PUS_SERVICE_9_TIME_MGMT_STRING = "PUS_SERVICE_9_TIME_MGMT"; +const char *PUS_SERVICE_11_TC_SCHEDULER_STRING = "PUS_SERVICE_11_TC_SCHEDULER"; +const char *PUS_SERVICE_15_TM_STORAGE_STRING = "PUS_SERVICE_15_TM_STORAGE"; +const char *PUS_SERVICE_17_TEST_STRING = "PUS_SERVICE_17_TEST"; +const char *PUS_SERVICE_20_PARAMETERS_STRING = "PUS_SERVICE_20_PARAMETERS"; +const char *PUS_SERVICE_200_MODE_MGMT_STRING = "PUS_SERVICE_200_MODE_MGMT"; +const char *PUS_SERVICE_201_HEALTH_STRING = "PUS_SERVICE_201_HEALTH"; +const char *CFDP_PACKET_DISTRIBUTOR_STRING = "CFDP_PACKET_DISTRIBUTOR"; +const char *HEALTH_TABLE_STRING = "HEALTH_TABLE"; +const char *MODE_STORE_STRING = "MODE_STORE"; +const char *EVENT_MANAGER_STRING = "EVENT_MANAGER"; +const char *INTERNAL_ERROR_REPORTER_STRING = "INTERNAL_ERROR_REPORTER"; +const char *TC_STORE_STRING = "TC_STORE"; +const char *TM_STORE_STRING = "TM_STORE"; +const char *IPC_STORE_STRING = "IPC_STORE"; +const char *TIME_STAMPER_STRING = "TIME_STAMPER"; +const char *VERIFICATION_REPORTER_STRING = "VERIFICATION_REPORTER"; +const char *FSFW_OBJECTS_END_STRING = "FSFW_OBJECTS_END"; +const char *HEATER_0_PLOC_PROC_BRD_STRING = "HEATER_0_PLOC_PROC_BRD"; +const char *HEATER_1_PCDU_BRD_STRING = "HEATER_1_PCDU_BRD"; +const char *HEATER_2_ACS_BRD_STRING = "HEATER_2_ACS_BRD"; +const char *HEATER_3_OBC_BRD_STRING = "HEATER_3_OBC_BRD"; +const char *HEATER_4_CAMERA_STRING = "HEATER_4_CAMERA"; +const char *HEATER_5_STR_STRING = "HEATER_5_STR"; +const char *HEATER_6_DRO_STRING = "HEATER_6_DRO"; +const char *HEATER_7_SYRLINKS_STRING = "HEATER_7_SYRLINKS"; +const char *ACS_BOARD_ASS_STRING = "ACS_BOARD_ASS"; +const char *SUS_BOARD_ASS_STRING = "SUS_BOARD_ASS"; +const char *TCS_BOARD_ASS_STRING = "TCS_BOARD_ASS"; +const char *RW_ASSY_STRING = "RW_ASSY"; +const char *CAM_SWITCHER_STRING = "CAM_SWITCHER"; +const char *SYRLINKS_ASSY_STRING = "SYRLINKS_ASSY"; +const char *IMTQ_ASSY_STRING = "IMTQ_ASSY"; +const char *STR_ASSY_STRING = "STR_ASSY"; +const char *TM_FUNNEL_STRING = "TM_FUNNEL"; +const char *PUS_TM_FUNNEL_STRING = "PUS_TM_FUNNEL"; +const char *CFDP_TM_FUNNEL_STRING = "CFDP_TM_FUNNEL"; +const char *CFDP_HANDLER_STRING = "CFDP_HANDLER"; +const char *CFDP_DISTRIBUTOR_STRING = "CFDP_DISTRIBUTOR"; +const char *CFDP_FAULT_HANDLER_STRING = "CFDP_FAULT_HANDLER"; +const char *EIVE_SYSTEM_STRING = "EIVE_SYSTEM"; +const char *ACS_SUBSYSTEM_STRING = "ACS_SUBSYSTEM"; +const char *PL_SUBSYSTEM_STRING = "PL_SUBSYSTEM"; +const char *TCS_SUBSYSTEM_STRING = "TCS_SUBSYSTEM"; +const char *COM_SUBSYSTEM_STRING = "COM_SUBSYSTEM"; +const char *EPS_SUBSYSTEM_STRING = "EPS_SUBSYSTEM"; +const char *MISC_TM_STORE_STRING = "MISC_TM_STORE"; +const char *OK_TM_STORE_STRING = "OK_TM_STORE"; +const char *NOT_OK_TM_STORE_STRING = "NOT_OK_TM_STORE"; +const char *HK_TM_STORE_STRING = "HK_TM_STORE"; +const char *CFDP_TM_STORE_STRING = "CFDP_TM_STORE"; +const char *LIVE_TM_TASK_STRING = "LIVE_TM_TASK"; +const char *LOG_STORE_AND_TM_TASK_STRING = "LOG_STORE_AND_TM_TASK"; +const char *HK_STORE_AND_TM_TASK_STRING = "HK_STORE_AND_TM_TASK"; +const char *CFDP_STORE_AND_TM_TASK_STRING = "CFDP_STORE_AND_TM_TASK"; +const char *DOWNLINK_RAM_STORE_STRING = "DOWNLINK_RAM_STORE"; +const char *THERMAL_TEMP_INSERTER_STRING = "THERMAL_TEMP_INSERTER"; +const char *DUMMY_INTERFACE_STRING = "DUMMY_INTERFACE"; +const char *NO_OBJECT_STRING = "NO_OBJECT"; + +const char *translateObject(object_id_t object) { + switch ((object & 0xFFFFFFFF)) { + case 0x42694269: + return TEST_TASK_STRING; + case 0x43000002: + return ACS_CONTROLLER_STRING; + case 0x43000003: + return CORE_CONTROLLER_STRING; + case 0x43000004: + return POWER_CONTROLLER_STRING; + case 0x43000006: + return GLOBAL_JSON_CFG_STRING; + case 0x43000007: + return XIPHOS_WDT_STRING; + case 0x43400001: + return THERMAL_CONTROLLER_STRING; + case 0x44000001: + return DUMMY_HANDLER_STRING; + case 0x44120006: + return MGM_0_LIS3_HANDLER_STRING; + case 0x44120010: + return GYRO_0_ADIS_HANDLER_STRING; + case 0x44120032: + return SUS_0_N_LOC_XFYFZM_PT_XF_STRING; + case 0x44120033: + return SUS_1_N_LOC_XBYFZM_PT_XB_STRING; + case 0x44120034: + return SUS_2_N_LOC_XFYBZB_PT_YB_STRING; + case 0x44120035: + return SUS_3_N_LOC_XFYBZF_PT_YF_STRING; + case 0x44120036: + return SUS_4_N_LOC_XMYFZF_PT_ZF_STRING; + case 0x44120037: + return SUS_5_N_LOC_XFYMZB_PT_ZB_STRING; + case 0x44120038: + return SUS_6_R_LOC_XFYBZM_PT_XF_STRING; + case 0x44120039: + return SUS_7_R_LOC_XBYBZM_PT_XB_STRING; + case 0x44120040: + return SUS_8_R_LOC_XBYBZB_PT_YB_STRING; + case 0x44120041: + return SUS_9_R_LOC_XBYBZB_PT_YF_STRING; + case 0x44120042: + return SUS_10_N_LOC_XMYBZF_PT_ZF_STRING; + case 0x44120043: + return SUS_11_R_LOC_XBYMZB_PT_ZB_STRING; + case 0x44120047: + return RW1_STRING; + case 0x44120107: + return MGM_1_RM3100_HANDLER_STRING; + case 0x44120111: + return GYRO_1_L3G_HANDLER_STRING; + case 0x44120148: + return RW2_STRING; + case 0x44120208: + return MGM_2_LIS3_HANDLER_STRING; + case 0x44120212: + return GYRO_2_ADIS_HANDLER_STRING; + case 0x44120249: + return RW3_STRING; + case 0x44120309: + return MGM_3_RM3100_HANDLER_STRING; + case 0x44120313: + return GYRO_3_L3G_HANDLER_STRING; + case 0x44120350: + return RW4_STRING; + case 0x44130001: + return STAR_TRACKER_STRING; + case 0x44130045: + return GPS_CONTROLLER_STRING; + case 0x44130046: + return GPS_0_HEALTH_DEV_STRING; + case 0x44130047: + return GPS_1_HEALTH_DEV_STRING; + case 0x44140013: + return IMTQ_POLLING_STRING; + case 0x44140014: + return IMTQ_HANDLER_STRING; + case 0x442000A1: + return PCDU_HANDLER_STRING; + case 0x44250000: + return P60DOCK_HANDLER_STRING; + case 0x44250001: + return PDU1_HANDLER_STRING; + case 0x44250002: + return PDU2_HANDLER_STRING; + case 0x44250003: + return ACU_HANDLER_STRING; + case 0x44260000: + return BPX_BATT_HANDLER_STRING; + case 0x44300000: + return PLPCDU_HANDLER_STRING; + case 0x443200A5: + return RAD_SENSOR_STRING; + case 0x44330000: + return PLOC_UPDATER_STRING; + case 0x44330001: + return PLOC_MEMORY_DUMPER_STRING; + case 0x44330002: + return STR_COM_IF_STRING; + case 0x44330003: + return PLOC_MPSOC_HELPER_STRING; + case 0x44330004: + return AXI_PTME_CONFIG_STRING; + case 0x44330005: + return PTME_CONFIG_STRING; + case 0x44330006: + return PTME_VC0_LIVE_TM_STRING; + case 0x44330007: + return PTME_VC1_LOG_TM_STRING; + case 0x44330008: + return PTME_VC2_HK_TM_STRING; + case 0x44330009: + return PTME_VC3_CFDP_TM_STRING; + case 0x44330015: + return PLOC_MPSOC_HANDLER_STRING; + case 0x44330016: + return PLOC_SUPERVISOR_HANDLER_STRING; + case 0x44330017: + return PLOC_SUPERVISOR_HELPER_STRING; + case 0x44330018: + return PLOC_MPSOC_COMMUNICATION_STRING; + case 0x44330032: + return SCEX_STRING; + case 0x444100A2: + return SOLAR_ARRAY_DEPL_HANDLER_STRING; + case 0x444100A4: + return HEATER_HANDLER_STRING; + case 0x44420004: + return TMP1075_HANDLER_TCS_0_STRING; + case 0x44420005: + return TMP1075_HANDLER_TCS_1_STRING; + case 0x44420006: + return TMP1075_HANDLER_PLPCDU_0_STRING; + case 0x44420007: + return TMP1075_HANDLER_PLPCDU_1_STRING; + case 0x44420008: + return TMP1075_HANDLER_IF_BOARD_STRING; + case 0x44420016: + return RTD_0_IC3_PLOC_HEATSPREADER_STRING; + case 0x44420017: + return RTD_1_IC4_PLOC_MISSIONBOARD_STRING; + case 0x44420018: + return RTD_2_IC5_4K_CAMERA_STRING; + case 0x44420019: + return RTD_3_IC6_DAC_HEATSPREADER_STRING; + case 0x44420020: + return RTD_4_IC7_STARTRACKER_STRING; + case 0x44420021: + return RTD_5_IC8_RW1_MX_MY_STRING; + case 0x44420022: + return RTD_6_IC9_DRO_STRING; + case 0x44420023: + return RTD_7_IC10_SCEX_STRING; + case 0x44420024: + return RTD_8_IC11_X8_STRING; + case 0x44420025: + return RTD_9_IC12_HPA_STRING; + case 0x44420026: + return RTD_10_IC13_PL_TX_STRING; + case 0x44420027: + return RTD_11_IC14_MPA_STRING; + case 0x44420028: + return RTD_12_IC15_ACU_STRING; + case 0x44420029: + return RTD_13_IC16_PLPCDU_HEATSPREADER_STRING; + case 0x44420030: + return RTD_14_IC17_TCS_BOARD_STRING; + case 0x44420031: + return RTD_15_IC18_IMTQ_STRING; + case 0x445300A3: + return SYRLINKS_HANDLER_STRING; + case 0x445300A4: + return SYRLINKS_COM_HANDLER_STRING; + case 0x49000001: + return ARDUINO_COM_IF_STRING; + case 0x49000002: + return DUMMY_COM_IF_STRING; + case 0x49010006: + return SCEX_UART_READER_STRING; + case 0x49030003: + return UART_COM_IF_STRING; + case 0x49060004: + return ACS_BOARD_POLLING_TASK_STRING; + case 0x49060005: + return RW_POLLING_TASK_STRING; + case 0x49060006: + return SPI_RTD_COM_IF_STRING; + case 0x49060007: + return SUS_POLLING_TASK_STRING; + case 0x50000100: + return CCSDS_PACKET_DISTRIBUTOR_STRING; + case 0x50000200: + return PUS_PACKET_DISTRIBUTOR_STRING; + case 0x50000300: + return TCP_TMTC_SERVER_STRING; + case 0x50000301: + return UDP_TMTC_SERVER_STRING; + case 0x50000400: + return TCP_TMTC_POLLING_TASK_STRING; + case 0x50000401: + return UDP_TMTC_POLLING_TASK_STRING; + case 0x50000500: + return FILE_SYSTEM_HANDLER_STRING; + case 0x50000550: + return SDC_MANAGER_STRING; + case 0x50000600: + return PTME_STRING; + case 0x50000700: + return PDEC_HANDLER_STRING; + case 0x50000800: + return CCSDS_HANDLER_STRING; + case 0x51000300: + return PUS_SERVICE_3_STRING; + case 0x51000400: + return PUS_SERVICE_5_STRING; + case 0x51000500: + return PUS_SERVICE_6_STRING; + case 0x51000800: + return PUS_SERVICE_8_STRING; + case 0x51002300: + return PUS_SERVICE_23_STRING; + case 0x51020100: + return PUS_SERVICE_201_STRING; + case 0x53000000: + return FSFW_OBJECTS_START_STRING; + case 0x53000001: + return PUS_SERVICE_1_VERIFICATION_STRING; + case 0x53000002: + return PUS_SERVICE_2_DEVICE_ACCESS_STRING; + case 0x53000003: + return PUS_SERVICE_3_HOUSEKEEPING_STRING; + case 0x53000005: + return PUS_SERVICE_5_EVENT_REPORTING_STRING; + case 0x53000008: + return PUS_SERVICE_8_FUNCTION_MGMT_STRING; + case 0x53000009: + return PUS_SERVICE_9_TIME_MGMT_STRING; + case 0x53000011: + return PUS_SERVICE_11_TC_SCHEDULER_STRING; + case 0x53000015: + return PUS_SERVICE_15_TM_STORAGE_STRING; + case 0x53000017: + return PUS_SERVICE_17_TEST_STRING; + case 0x53000020: + return PUS_SERVICE_20_PARAMETERS_STRING; + case 0x53000200: + return PUS_SERVICE_200_MODE_MGMT_STRING; + case 0x53000201: + return PUS_SERVICE_201_HEALTH_STRING; + case 0x53001000: + return CFDP_PACKET_DISTRIBUTOR_STRING; + case 0x53010000: + return HEALTH_TABLE_STRING; + case 0x53010100: + return MODE_STORE_STRING; + case 0x53030000: + return EVENT_MANAGER_STRING; + case 0x53040000: + return INTERNAL_ERROR_REPORTER_STRING; + case 0x534f0100: + return TC_STORE_STRING; + case 0x534f0200: + return TM_STORE_STRING; + case 0x534f0300: + return IPC_STORE_STRING; + case 0x53500010: + return TIME_STAMPER_STRING; + case 0x53500020: + return VERIFICATION_REPORTER_STRING; + case 0x53ffffff: + return FSFW_OBJECTS_END_STRING; + case 0x60000000: + return HEATER_0_PLOC_PROC_BRD_STRING; + case 0x60000001: + return HEATER_1_PCDU_BRD_STRING; + case 0x60000002: + return HEATER_2_ACS_BRD_STRING; + case 0x60000003: + return HEATER_3_OBC_BRD_STRING; + case 0x60000004: + return HEATER_4_CAMERA_STRING; + case 0x60000005: + return HEATER_5_STR_STRING; + case 0x60000006: + return HEATER_6_DRO_STRING; + case 0x60000007: + return HEATER_7_SYRLINKS_STRING; + case 0x73000001: + return ACS_BOARD_ASS_STRING; + case 0x73000002: + return SUS_BOARD_ASS_STRING; + case 0x73000003: + return TCS_BOARD_ASS_STRING; + case 0x73000004: + return RW_ASSY_STRING; + case 0x73000006: + return CAM_SWITCHER_STRING; + case 0x73000007: + return SYRLINKS_ASSY_STRING; + case 0x73000008: + return IMTQ_ASSY_STRING; + case 0x73000009: + return STR_ASSY_STRING; + case 0x73000100: + return TM_FUNNEL_STRING; + case 0x73000101: + return PUS_TM_FUNNEL_STRING; + case 0x73000102: + return CFDP_TM_FUNNEL_STRING; + case 0x73000205: + return CFDP_HANDLER_STRING; + case 0x73000206: + return CFDP_DISTRIBUTOR_STRING; + case 0x73000207: + return CFDP_FAULT_HANDLER_STRING; + case 0x73010000: + return EIVE_SYSTEM_STRING; + case 0x73010001: + return ACS_SUBSYSTEM_STRING; + case 0x73010002: + return PL_SUBSYSTEM_STRING; + case 0x73010003: + return TCS_SUBSYSTEM_STRING; + case 0x73010004: + return COM_SUBSYSTEM_STRING; + case 0x73010005: + return EPS_SUBSYSTEM_STRING; + case 0x73020001: + return MISC_TM_STORE_STRING; + case 0x73020002: + return OK_TM_STORE_STRING; + case 0x73020003: + return NOT_OK_TM_STORE_STRING; + case 0x73020004: + return HK_TM_STORE_STRING; + case 0x73030000: + return CFDP_TM_STORE_STRING; + case 0x73040000: + return LIVE_TM_TASK_STRING; + case 0x73040001: + return LOG_STORE_AND_TM_TASK_STRING; + case 0x73040002: + return HK_STORE_AND_TM_TASK_STRING; + case 0x73040003: + return CFDP_STORE_AND_TM_TASK_STRING; + case 0x73040004: + return DOWNLINK_RAM_STORE_STRING; + case 0x90000003: + return THERMAL_TEMP_INSERTER_STRING; + case 0xCAFECAFE: + return DUMMY_INTERFACE_STRING; + case 0xFFFFFFFF: + return NO_OBJECT_STRING; + default: + return "UNKNOWN_OBJECT"; + } + return 0; +} diff --git a/bsp_hosted/fsfwconfig/objects/translateObjects.h b/bsp_hosted/fsfwconfig/objects/translateObjects.h new file mode 100644 index 0000000..257912f --- /dev/null +++ b/bsp_hosted/fsfwconfig/objects/translateObjects.h @@ -0,0 +1,8 @@ +#ifndef FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ +#define FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ + +#include + +const char *translateObject(object_id_t object); + +#endif /* FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ */ diff --git a/bsp_hosted/fsfwconfig/pollingsequence/CMakeLists.txt b/bsp_hosted/fsfwconfig/pollingsequence/CMakeLists.txt new file mode 100644 index 0000000..f92d0c3 --- /dev/null +++ b/bsp_hosted/fsfwconfig/pollingsequence/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PRIVATE DummyPst.cpp) diff --git a/bsp_hosted/fsfwconfig/pollingsequence/DummyPst.cpp b/bsp_hosted/fsfwconfig/pollingsequence/DummyPst.cpp new file mode 100644 index 0000000..de1efa0 --- /dev/null +++ b/bsp_hosted/fsfwconfig/pollingsequence/DummyPst.cpp @@ -0,0 +1,139 @@ +#include "DummyPst.h" + +#include +#include +#include +#include + +ReturnValue_t dummy_pst::pst(FixedTimeslotTaskIF *thisSequence) { + uint32_t length = thisSequence->getPeriodMs(); + + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::RW1, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::RW1, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::RW1, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::RW1, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::RW1, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::RW2, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::RW2, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::RW2, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::RW2, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::RW2, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::RW3, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::RW3, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::RW3, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::RW3, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::RW3, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::RW4, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::RW4, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::RW4, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::RW4, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::RW4, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::STAR_TRACKER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::STAR_TRACKER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::STAR_TRACKER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::STAR_TRACKER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::STAR_TRACKER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::ACU_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + if (thisSequence->checkSequence() == returnvalue::OK) { + return returnvalue::OK; + } else { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "pst::pollingSequenceInitDefault: Sequence invalid!" << std::endl; +#endif + return returnvalue::FAILED; + } +} diff --git a/bsp_hosted/fsfwconfig/pollingsequence/DummyPst.h b/bsp_hosted/fsfwconfig/pollingsequence/DummyPst.h new file mode 100644 index 0000000..08bf3ca --- /dev/null +++ b/bsp_hosted/fsfwconfig/pollingsequence/DummyPst.h @@ -0,0 +1,14 @@ +#ifndef POLLINGSEQUENCEFACTORY_H_ +#define POLLINGSEQUENCEFACTORY_H_ + +#include + +class FixedTimeslotTaskIF; + +namespace dummy_pst { + +ReturnValue_t pst(FixedTimeslotTaskIF *thisSequence); + +} + +#endif /* POLLINGSEQUENCEINIT_H_ */ diff --git a/bsp_hosted/fsfwconfig/returnvalues/classIds.h b/bsp_hosted/fsfwconfig/returnvalues/classIds.h new file mode 100644 index 0000000..16e6eab --- /dev/null +++ b/bsp_hosted/fsfwconfig/returnvalues/classIds.h @@ -0,0 +1,20 @@ +#ifndef CONFIG_RETURNVALUES_CLASSIDS_H_ +#define CONFIG_RETURNVALUES_CLASSIDS_H_ + +#include + +#include "eive/resultClassIds.h" + +/** + * Source IDs starts at 73 for now + * Framework IDs for ReturnValues run from 0 to 56 + * and are located inside + */ +namespace CLASS_ID { +enum { + CLASS_ID_START = COMMON_CLASS_ID_END, + CLASS_ID_END // [EXPORT] : [END] +}; +} + +#endif /* CONFIG_RETURNVALUES_CLASSIDS_H_ */ diff --git a/bsp_hosted/main.cpp b/bsp_hosted/main.cpp new file mode 100644 index 0000000..83f6777 --- /dev/null +++ b/bsp_hosted/main.cpp @@ -0,0 +1,44 @@ +#include + +#include + +#include "commonConfig.h" +#include "fsfw/FSFWVersion.h" +#include "fsfw/controller/ControllerBase.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/modes/HasModesIF.h" +#include "fsfw/modes/ModeMessage.h" +#include "fsfw/objectmanager/ObjectManager.h" +#include "fsfw/tasks/TaskFactory.h" +#include "scheduling.h" + +#ifdef WIN32 +static const char* COMPILE_PRINTOUT = "Windows"; +#elif LINUX +static const char* COMPILE_PRINTOUT = "Linux"; +#else +static const char* COMPILE_PRINTOUT = "unknown OS"; +#endif +/** + * @brief This is the main program for the hosted build. It can be run for + * Linux and Windows. + * @return + */ +int main(void) { + std::cout << "-- EIVE OBSW --" << std::endl; + std::cout << "-- Compiled for " << COMPILE_PRINTOUT << " --" << std::endl; + std::cout << "-- OBSW " + << "v" << common::OBSW_VERSION << " | FSFW v" << fsfw::FSFW_VERSION << " --" + << std::endl; + std::cout << "-- " << __DATE__ << " " << __TIME__ << " --" << std::endl; + std::cout << "-- " + << " BSP HOSTED" + << " --" << std::endl; + + scheduling::initMission(); + + for (;;) { + // suspend main thread by sleeping it. + TaskFactory::delayTask(5000); + } +} diff --git a/bsp_hosted/objectFactory.cpp b/bsp_hosted/objectFactory.cpp new file mode 100644 index 0000000..1ffc662 --- /dev/null +++ b/bsp_hosted/objectFactory.cpp @@ -0,0 +1,124 @@ +#include "objectFactory.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "../mission/utility/DummySdCardManager.h" +#include "OBSWConfig.h" +#include "fsfw/platform.h" +#include "fsfw/power/PowerSwitchIF.h" +#include "fsfw_tests/integration/task/TestTask.h" + +#if OBSW_ADD_TMTC_UDP_SERVER == 1 +#include "fsfw/osal/common/UdpTcPollingTask.h" +#include "fsfw/osal/common/UdpTmTcBridge.h" +#endif +#if OBSW_ADD_TMTC_TCP_SERVER == 1 +#include "fsfw/osal/common/TcpTmTcBridge.h" +#include "fsfw/osal/common/TcpTmTcServer.h" +#endif + +#if OBSW_ADD_TEST_CODE == 1 +#include +#endif + +#include +#include + +#include "dummies/helperFactory.h" + +#ifdef PLATFORM_UNIX +#include +#include + +#include "devices/gpioIds.h" +#include "fsfw_hal/linux/gpio/Gpio.h" +#include "linux/payload/FreshSupvHandler.h" +#include "linux/payload/PlocSupvUartMan.h" +#include "test/gpio/DummyGpioIF.h" +#endif + +void Factory::setStaticFrameworkObjectIds() { + PusServiceBase::PUS_DISTRIBUTOR = objects::PUS_PACKET_DISTRIBUTOR; + PusServiceBase::PACKET_DESTINATION = objects::PUS_TM_FUNNEL; + + CommandingServiceBase::defaultPacketSource = objects::PUS_PACKET_DISTRIBUTOR; + CommandingServiceBase::defaultPacketDestination = objects::PUS_TM_FUNNEL; + + VerificationReporter::DEFAULT_RECEIVER = objects::PUS_SERVICE_1_VERIFICATION; +} + +void ObjectFactory::produce(void* args) { + Factory::setStaticFrameworkObjectIds(); + PusTmFunnel* pusFunnel; + CfdpTmFunnel* cfdpFunnel; + StorageManagerIF* tmStore; + StorageManagerIF* ipcStore; + PersistentTmStores persistentStores{}; + bool enableHkSets = false; +#if OBSW_ENABLE_PERIODIC_HK == 1 + enableHkSets = true; +#endif + auto sdcMan = new DummySdCardManager("/tmp"); + ObjectFactory::produceGenericObjects(nullptr, &pusFunnel, &cfdpFunnel, *sdcMan, &ipcStore, + &tmStore, persistentStores, 120, enableHkSets, false); + + new TmFunnelHandler(objects::LIVE_TM_TASK, *pusFunnel, *cfdpFunnel); + auto* dummyGpioIF = new DummyGpioIF(); + auto* dummySwitcher = new DummyPowerSwitcher(objects::PCDU_HANDLER, 18, 0); + std::vector switcherList; + auto initVal = PowerSwitchIF::SWITCH_OFF; + for (unsigned i = 0; i < 18; i++) { + switcherList.emplace_back(initVal); + } + dummySwitcher->setInitialSwitcherList(switcherList); + +#ifdef PLATFORM_UNIX + // Obsolete dev handler.. + /* + new SerialComIF(objects::UART_COM_IF); +#if OBSW_ADD_PLOC_MPSOC == 1 + std::string mpscoDev = ""; + auto mpsocCookie = new UartCookie(objects::PLOC_MPSOC_HANDLER, mpscoDev, uart::PLOC_MPSOC_BAUD, + mpsoc::MAX_REPLY_SIZE, UartModes::NON_CANONICAL); + mpsocCookie->setNoFixedSizeReply(); + auto plocMpsocHelper = new PlocMpsocSpecialComHelper(objects::PLOC_MPSOC_HELPER); + new PlocMpsocHandler(objects::PLOC_MPSOC_HANDLER, objects::UART_COM_IF, mpsocCookie, + plocMpsocHelper, Gpio(gpioIds::ENABLE_MPSOC_UART, dummyGpioIF), + objects::PLOC_SUPERVISOR_HANDLER); +#endif // OBSW_ADD_PLOC_MPSOC == 1 + */ +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + std::string plocSupvString = "/dev/ploc_supv"; + auto supervisorCookie = + new SerialCookie(objects::PLOC_SUPERVISOR_HANDLER, plocSupvString, uart::PLOC_SUPV_BAUD, + supv::MAX_PACKET_SIZE * 20, UartModes::NON_CANONICAL); + supervisorCookie->setNoFixedSizeReply(); + new PlocSupvUartManager(objects::PLOC_SUPERVISOR_HELPER); + DhbConfig dhbConf(objects::PLOC_SUPERVISOR_HANDLER); + auto* supvHandler = + new FreshSupvHandler(dhbConf, supervisorCookie, Gpio(gpioIds::ENABLE_SUPV_UART, dummyGpioIF), + dummySwitcher, power::PDU1_CH6_PLOC_12V); +#endif /* OBSW_ADD_PLOC_SUPERVISOR == 1 */ +#endif + + dummy::DummyCfg cfg; + cfg.addPlPcduDummy = true; + cfg.addCamSwitcherDummy = true; + dummy::createDummies(cfg, *dummySwitcher, dummyGpioIF, enableHkSets); + + HeaterHandler* heaterHandler = nullptr; + // new ThermalController(objects::THERMAL_CONTROLLER); + ObjectFactory::createGenericHeaterComponents(*dummyGpioIF, *dummySwitcher, heaterHandler); + if (heaterHandler == nullptr) { + sif::error << "HeaterHandler could not be created" << std::endl; + } else { + ObjectFactory::createThermalController(*heaterHandler, true); + } + new TestTask(objects::TEST_TASK); +} diff --git a/bsp_hosted/objectFactory.h b/bsp_hosted/objectFactory.h new file mode 100644 index 0000000..b042f9d --- /dev/null +++ b/bsp_hosted/objectFactory.h @@ -0,0 +1,9 @@ +#ifndef BSP_LINUX_OBJECTFACTORY_H_ +#define BSP_LINUX_OBJECTFACTORY_H_ + +namespace ObjectFactory { +void setStatics(); +void produce(void* args); +}; // namespace ObjectFactory + +#endif /* BSP_LINUX_OBJECTFACTORY_H_ */ diff --git a/bsp_hosted/scheduling.cpp b/bsp_hosted/scheduling.cpp new file mode 100644 index 0000000..c771608 --- /dev/null +++ b/bsp_hosted/scheduling.cpp @@ -0,0 +1,280 @@ +#include "linux/scheduling.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "OBSWConfig.h" +#include "mission/scheduling.h" +#include "objectFactory.h" +#include "scheduling.h" + +#ifdef LINUX +ServiceInterfaceStream sif::debug("DEBUG"); +ServiceInterfaceStream sif::info("INFO"); +ServiceInterfaceStream sif::warning("WARNING"); +ServiceInterfaceStream sif::error("ERROR", false, false, true); +#else +ServiceInterfaceStream sif::debug("DEBUG", true); +ServiceInterfaceStream sif::info("INFO", true); +ServiceInterfaceStream sif::warning("WARNING", true); +ServiceInterfaceStream sif::error("ERROR", true, false, true); +#endif + +ObjectManagerIF* objectManager = nullptr; + +void scheduling::initMission() { + sif::info << "Building global objects.." << std::endl; + /* Instantiate global object manager and also create all objects */ + ObjectManager::instance()->setObjectFactoryFunction(ObjectFactory::produce, nullptr); + sif::info << "Initializing all objects.." << std::endl; + ObjectManager::instance()->initialize(); + + /* This function creates and starts all tasks */ + initTasks(); +} + +void scheduling::initTasks() { + TaskFactory* factory = TaskFactory::instance(); + if (factory == nullptr) { + /* Should never happen ! */ + return; + } +#if OBSW_PRINT_MISSED_DEADLINES == 1 + void (*missedDeadlineFunc)(void) = TaskFactory::printMissedDeadline; +#else + void (*missedDeadlineFunc)(void) = nullptr; +#endif + + /* TMTC Distribution */ + PeriodicTaskIF* tmtcDistributor = factory->createPeriodicTask( + "DIST", 60, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + ReturnValue_t result = tmtcDistributor->addComponent(objects::CCSDS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Adding CCSDS distributor failed" << std::endl; + } + result = tmtcDistributor->addComponent(objects::PUS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Adding PUS distributor failed" << std::endl; + } + result = tmtcDistributor->addComponent(objects::CFDP_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Adding CFDP distributor failed" << std::endl; + } +#if OBSW_ADD_TMTC_UDP_SERVER == 1 + result = tmtcDistributor->addComponent(objects::UDP_TMTC_SERVER); + if (result != returnvalue::OK) { + sif::error << "adding UDP server failed" << std::endl; + } +#endif + result = tmtcDistributor->addComponent(objects::TCP_TMTC_SERVER); + if (result != returnvalue::OK) { + sif::error << "adding TCP server failed" << std::endl; + } + +#if OBSW_ADD_TMTC_UDP_SERVER == 1 + PeriodicTaskIF* udpPollingTask = factory->createPeriodicTask( + "UDP_POLLING", 70, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = udpPollingTask->addComponent(objects::UDP_TMTC_POLLING_TASK); + if (result != returnvalue::OK) { + sif::error << "Add component UDP Polling failed" << std::endl; + } +#endif + PeriodicTaskIF* tcpPollingTask = factory->createPeriodicTask( + "TCP_POLLING", 70, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = tcpPollingTask->addComponent(objects::TCP_TMTC_POLLING_TASK); + if (result != returnvalue::OK) { + sif::error << "Add component UDP Polling failed" << std::endl; + } + + PeriodicTaskIF* liveTmTask = factory->createPeriodicTask( + "LIVE_TM", 55, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, nullptr, &RR_SCHEDULING); + result = liveTmTask->addComponent(objects::LIVE_TM_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("LIVE_TM", objects::LIVE_TM_TASK); + } + + PeriodicTaskIF* pusHighPrio = factory->createPeriodicTask( + "PUS_HIGH_PRIO", 60, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusHighPrio->addComponent(objects::PUS_SERVICE_1_VERIFICATION); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + result = pusHighPrio->addComponent(objects::EVENT_MANAGER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("EVENT_MGMT", objects::EVENT_MANAGER); + } + result = pusHighPrio->addComponent(objects::PUS_SERVICE_5_EVENT_REPORTING); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS5", objects::PUS_SERVICE_5_EVENT_REPORTING); + } + result = pusHighPrio->addComponent(objects::PUS_SERVICE_9_TIME_MGMT); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS9", objects::PUS_SERVICE_9_TIME_MGMT); + } + + PeriodicTaskIF* pusMedPrio = factory->createPeriodicTask( + "PUS_MED_PRIO", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.8, missedDeadlineFunc); + result = pusHighPrio->addComponent(objects::PUS_SERVICE_2_DEVICE_ACCESS); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS2", objects::PUS_SERVICE_2_DEVICE_ACCESS); + } + result = pusHighPrio->addComponent(objects::PUS_SERVICE_3_HOUSEKEEPING); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS3", objects::PUS_SERVICE_3_HOUSEKEEPING); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_8_FUNCTION_MGMT); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS8", objects::PUS_SERVICE_8_FUNCTION_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_15_TM_STORAGE); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS15", objects::PUS_SERVICE_15_TM_STORAGE); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_200_MODE_MGMT); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS200", objects::PUS_SERVICE_200_MODE_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_20_PARAMETERS); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS20", objects::PUS_SERVICE_20_PARAMETERS); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_17_TEST); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS17", objects::PUS_SERVICE_17_TEST); + } + + PeriodicTaskIF* thermalTask = factory->createPeriodicTask( + "THERMAL_CTL_TASK", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, missedDeadlineFunc); + result = thermalTask->addComponent(objects::CORE_CONTROLLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CORE_CTRL", objects::CORE_CONTROLLER); + } + result = thermalTask->addComponent(objects::THERMAL_CONTROLLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("THERMAL_CONTROLLER", objects::THERMAL_CONTROLLER); + } + result = thermalTask->addComponent(objects::HEATER_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("HEATER_HANDLER", objects::HEATER_HANDLER); + } + + FixedTimeslotTaskIF* pstTask = factory->createFixedTimeslotTask( + "DUMMY_PST", 75, PeriodicTaskIF::MINIMUM_STACK_SIZE * 4, 0.5, missedDeadlineFunc); + result = dummy_pst::pst(pstTask); + if (result != returnvalue::OK) { + sif::error << "Failed to add dummy pst to fixed timeslot task" << std::endl; + } + +#if OBSW_ADD_CFDP_COMPONENTS == 1 + PeriodicTaskIF* cfdpTask = factory->createPeriodicTask( + "CFDP Handler", 45, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.4, missedDeadlineFunc); + result = cfdpTask->addComponent(objects::CFDP_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CFDP Handler", objects::CFDP_HANDLER); + } +#endif + + // If those are added at a later stage.. + /* + PeriodicTaskIF* logTmTask = factory->createPeriodicTask( + "LOG_PSTORE", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + result = logTmTask->addComponent(objects::LOG_STORE_AND_TM_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("LOG_STORE_AND_TM", objects::LOG_STORE_AND_TM_TASK); + } + PeriodicTaskIF* hkTmTask = + factory->createPeriodicTask("HK_PSTORE", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + result = hkTmTask->addComponent(objects::HK_STORE_AND_TM_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("HK_STORE_AND_TM", objects::HK_STORE_AND_TM_TASK); + } + PeriodicTaskIF* cfdpTmTask = factory->createPeriodicTask( + "CFDP_PSTORE", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + result = cfdpTmTask->addComponent(objects::CFDP_STORE_AND_TM_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CFDP_STORE_AND_TM", objects::CFDP_STORE_AND_TM_TASK); + } + */ + +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + PeriodicTaskIF* supvHelperTask = factory->createPeriodicTask( + "PLOC_SUPV_HELPER", 20, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, missedDeadlineFunc); + result = supvHelperTask->addComponent(objects::PLOC_SUPERVISOR_HELPER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PLOC_SUPV_HELPER", objects::PLOC_SUPERVISOR_HELPER); + } +#endif /* OBSW_ADD_PLOC_SUPERVISOR */ + + PeriodicTaskIF* plTask = factory->createPeriodicTask( + "PL_TASK", 25, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, missedDeadlineFunc); + scheduling::addMpsocSupvHandlers(plTask); +#if OBSW_ADD_TEST_CODE == 1 + result = testTask->addComponent(objects::TEST_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("TEST_TASK", objects::TEST_TASK); + } +#endif /* OBSW_ADD_TEST_CODE == 1 */ + + PeriodicTaskIF* dummyTask = factory->createPeriodicTask( + "DUMMY_TASK", 35, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.8, missedDeadlineFunc); + dummyTask->addComponent(objects::THERMAL_TEMP_INSERTER); + scheduling::scheduleTmpTempSensors(dummyTask, true); + scheduling::scheduleRtdSensors(dummyTask); + dummyTask->addComponent(objects::SUS_0_N_LOC_XFYFZM_PT_XF); + dummyTask->addComponent(objects::SUS_1_N_LOC_XBYFZM_PT_XB); + dummyTask->addComponent(objects::SUS_2_N_LOC_XFYBZB_PT_YB); + dummyTask->addComponent(objects::SUS_3_N_LOC_XFYBZF_PT_YF); + dummyTask->addComponent(objects::SUS_4_N_LOC_XMYFZF_PT_ZF); + dummyTask->addComponent(objects::SUS_5_N_LOC_XFYMZB_PT_ZB); + dummyTask->addComponent(objects::SUS_6_R_LOC_XFYBZM_PT_XF); + dummyTask->addComponent(objects::SUS_7_R_LOC_XBYBZM_PT_XB); + dummyTask->addComponent(objects::SUS_8_R_LOC_XBYBZB_PT_YB); + dummyTask->addComponent(objects::SUS_9_R_LOC_XBYBZB_PT_YF); + dummyTask->addComponent(objects::SUS_10_N_LOC_XMYBZF_PT_ZF); + dummyTask->addComponent(objects::SUS_11_R_LOC_XBYMZB_PT_ZB); + + sif::info << "Starting tasks.." << std::endl; + tmtcDistributor->startTask(); +#if OBSW_ADD_TMTC_UDP_SERVER == 1 + udpPollingTask->startTask(); +#endif + tcpPollingTask->startTask(); + liveTmTask->startTask(); + + pusHighPrio->startTask(); + pusMedPrio->startTask(); + + pstTask->startTask(); + thermalTask->startTask(); + dummyTask->startTask(); + + // If those are added at a later stage.. + // logTmTask->startTask(); + // cfdpTmTask->startTask(); + // hkTmTask->startTask(); + +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + supvHelperTask->startTask(); +#endif +#if OBSW_ADD_PLOC_SUPERVISOR == 1 || OBSW_ADD_PLOC_MPSOC == 1 + plTask->startTask(); +#endif +#if OBSW_ADD_CFDP_COMPONENTS == 1 + cfdpTask->startTask(); +#endif + +#if OBSW_ADD_TEST_CODE == 1 + testTask->startTask(); +#endif /* OBSW_ADD_TEST_CODE == 1 */ + + sif::info << "Tasks started.." << std::endl; +} diff --git a/bsp_hosted/scheduling.h b/bsp_hosted/scheduling.h new file mode 100644 index 0000000..2a2f32a --- /dev/null +++ b/bsp_hosted/scheduling.h @@ -0,0 +1,6 @@ +#pragma once + +namespace scheduling { +void initMission(); +void initTasks(); +}; // namespace scheduling diff --git a/bsp_linux_board/CMakeLists.txt b/bsp_linux_board/CMakeLists.txt new file mode 100644 index 0000000..9e3ec02 --- /dev/null +++ b/bsp_linux_board/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(${OBSW_NAME} PUBLIC InitMission.cpp main.cpp gpioInit.cpp + ObjectFactory.cpp) + +add_subdirectory(boardconfig) +add_subdirectory(boardtest) +add_subdirectory(fsfwconfig) diff --git a/bsp_linux_board/Dockerfile b/bsp_linux_board/Dockerfile new file mode 100644 index 0000000..970b44d --- /dev/null +++ b/bsp_linux_board/Dockerfile @@ -0,0 +1,37 @@ +FROM ubuntu:latest +# FROM alpine:latest + +RUN apt-get update && apt-get install -y curl wget cmake g++ + +# Raspberry Pi rootfs +RUN mkdir -p /usr/rootfs; \ + curl https://eive-cloud.irs.uni-stuttgart.de/index.php/s/kJe3nCnGPRGKFCz/download/rpi-rootfs.tar.gz /usr/rootfs \ + | tar xvz -C /usr/rootfs +# Raspberry Pi toolchain +RUN mkdir -p /opt; \ + cd /opt; \ + wget https://github.com/Pro/raspi-toolchain/releases/latest/download/raspi-toolchain.tar.gz; \ + tar xfz raspi-toolchain.tar.gz --strip-components=1 -C .; \ + rm -rf raspi-toolchain.tar.gz + +# RUN apk add cmake make g++ + +# Required for cmake build +ENV RASPBERRY_VERSION="4" +ENV RASPBIAN_ROOTFS="/usr/rootfs/rootfs" +ENV PATH=$PATH:"/opt/cross-pi-gcc/bin" +ENV CROSS_COMPILE="arm-linux-gnueabihf" + +WORKDIR /usr/src/app +COPY . . + +RUN set -ex; \ + rm -rf build-rpi; \ + mkdir build-rpi; \ + cd build-rpi; \ + cmake -DCMAKE_BUILD_TYPE=Release -DOS_FSFW=linux -DTGT_BSP="arm/raspberrypi" ..; + +ENTRYPOINT ["cmake", "--build", "build-rpi"] +CMD ["-j"] +# CMD ["bash"] + diff --git a/bsp_linux_board/InitMission.cpp b/bsp_linux_board/InitMission.cpp new file mode 100644 index 0000000..21cc5c2 --- /dev/null +++ b/bsp_linux_board/InitMission.cpp @@ -0,0 +1,267 @@ +#include "InitMission.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "OBSWConfig.h" +#include "ObjectFactory.h" +#include "objects/systemObjectList.h" +#include "pollingsequence/pollingSequenceFactory.h" + +ServiceInterfaceStream sif::debug("DEBUG"); +ServiceInterfaceStream sif::info("INFO"); +ServiceInterfaceStream sif::warning("WARNING"); +ServiceInterfaceStream sif::error("ERROR"); + +ObjectManagerIF* objectManager = nullptr; + +void initmission::initMission() { + sif::info << "Building global objects.." << std::endl; + /* Instantiate global object manager and also create all objects */ + ObjectManager::instance()->setObjectFactoryFunction(ObjectFactory::produce, nullptr); + sif::info << "Initializing all objects.." << std::endl; + ObjectManager::instance()->initialize(); + + /* This function creates and starts all tasks */ + initTasks(); +} + +void initmission::initTasks() { + TaskFactory* factory = TaskFactory::instance(); + ReturnValue_t result = returnvalue::OK; + if (factory == nullptr) { + /* Should never happen ! */ + return; + } +#if OBSW_PRINT_MISSED_DEADLINES == 1 + void (*missedDeadlineFunc)(void) = TaskFactory::printMissedDeadline; +#else + void (*missedDeadlineFunc)(void) = nullptr; +#endif + + /* TMTC Distribution */ + PeriodicTaskIF* tmTcDistributor = factory->createPeriodicTask( + "DIST", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = tmTcDistributor->addComponent(objects::CCSDS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + result = tmTcDistributor->addComponent(objects::PUS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + result = tmTcDistributor->addComponent(objects::TM_FUNNEL); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + + /* UDP bridge */ + PeriodicTaskIF* tmtcBridgeTask = factory->createPeriodicTask( + "TMTC_BRIDGE", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = tmtcBridgeTask->addComponent(objects::TMTC_BRIDGE); + if (result != returnvalue::OK) { + sif::error << "Add component TMTC Bridge failed" << std::endl; + } + PeriodicTaskIF* tmtcPollingTask = factory->createPeriodicTask( + "TMTC_POLLING", 80, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = tmtcPollingTask->addComponent(objects::TMTC_POLLING_TASK); + if (result != returnvalue::OK) { + sif::error << "Add component TMTC Polling failed" << std::endl; + } + +#if OBSW_ADD_SCEX_DEVICE == 1 + PeriodicTaskIF* scexDevHandler; + PeriodicTaskIF* scexReaderTask; + scheduling::schedulingScex(*factory, scexDevHandler, scexReaderTask); +#endif + + /* PUS Services */ + std::vector pusTasks; + createPusTasks(*factory, missedDeadlineFunc, pusTasks); + + std::vector pstTasks; + createPstTasks(*factory, missedDeadlineFunc, pstTasks); +#if OBSW_ADD_TEST_CODE == 1 + std::vector testTasks; + createTestTasks(*factory, missedDeadlineFunc, pstTasks); +#endif /* OBSW_ADD_TEST_CODE == 1 */ + + auto taskStarter = [](std::vector& taskVector, std::string name) { + for (const auto& task : taskVector) { + if (task != nullptr) { + task->startTask(); + } else { + sif::error << "Task in vector " << name << " is invalid!" << std::endl; + } + } + }; + + sif::info << "Starting tasks.." << std::endl; + tmTcDistributor->startTask(); + tmtcBridgeTask->startTask(); + tmtcPollingTask->startTask(); + + taskStarter(pusTasks, "PUS Tasks"); +#if OBSW_ADD_TEST_CODE == 1 + taskStarter(testTasks, "Test Tasks"); +#endif /* OBSW_ADD_TEST_CODE == 1 */ + taskStarter(pstTasks, "PST Tasks"); + +#if OBSW_ADD_SCEX_DEVICE == 1 + scexDevHandler->startTask(); + scexReaderTask->startTask(); +#endif +#if OBSW_ADD_TEST_PST == 1 + if (startTestPst) { + pstTestTask->startTask(); + } +#endif /* RPI_TEST_ACS_BOARD == 1 */ + sif::info << "Tasks started.." << std::endl; +} + +void initmission::createPusTasks(TaskFactory& factory, + TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec) { + ReturnValue_t result = returnvalue::OK; + PeriodicTaskIF* pusVerification = factory.createPeriodicTask( + "PUS_VERIF", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusVerification->addComponent(objects::PUS_SERVICE_1_VERIFICATION); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + taskVec.push_back(pusVerification); + + PeriodicTaskIF* pusEvents = factory.createPeriodicTask( + "PUS_EVENTS", 60, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusEvents->addComponent(objects::PUS_SERVICE_5_EVENT_REPORTING); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS_EVENTS", objects::PUS_SERVICE_5_EVENT_REPORTING); + } + result = pusEvents->addComponent(objects::EVENT_MANAGER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS_MGMT", objects::EVENT_MANAGER); + } + taskVec.push_back(pusEvents); + + PeriodicTaskIF* pusHighPrio = factory.createPeriodicTask( + "PUS_HIGH_PRIO", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusHighPrio->addComponent(objects::PUS_SERVICE_2_DEVICE_ACCESS); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS2", objects::PUS_SERVICE_2_DEVICE_ACCESS); + } + result = pusHighPrio->addComponent(objects::PUS_SERVICE_9_TIME_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS9", objects::PUS_SERVICE_9_TIME_MGMT); + } + taskVec.push_back(pusHighPrio); + + PeriodicTaskIF* pusMedPrio = factory.createPeriodicTask( + "PUS_MED_PRIO", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.8, missedDeadlineFunc); + result = pusMedPrio->addComponent(objects::PUS_SERVICE_8_FUNCTION_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS8", objects::PUS_SERVICE_8_FUNCTION_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_200_MODE_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS200", objects::PUS_SERVICE_200_MODE_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_20_PARAMETERS); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS20", objects::PUS_SERVICE_20_PARAMETERS); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_3_HOUSEKEEPING); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS3", objects::PUS_SERVICE_3_HOUSEKEEPING); + } + taskVec.push_back(pusMedPrio); + + PeriodicTaskIF* pusLowPrio = factory.createPeriodicTask( + "PUS_LOW_PRIO", 30, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.6, missedDeadlineFunc); + result = pusLowPrio->addComponent(objects::PUS_SERVICE_17_TEST); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS17", objects::PUS_SERVICE_17_TEST); + } + result = pusLowPrio->addComponent(objects::INTERNAL_ERROR_REPORTER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("INT_ERR_RPRT", objects::INTERNAL_ERROR_REPORTER); + } + taskVec.push_back(pusLowPrio); +} + +void initmission::createPstTasks(TaskFactory& factory, + TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec) { + ReturnValue_t result = returnvalue::OK; +#if OBSW_ADD_SPI_TEST_CODE == 0 + FixedTimeslotTaskIF* spiPst = factory.createFixedTimeslotTask( + "SPI_PST", 70, PeriodicTaskIF::MINIMUM_STACK_SIZE * 4, 1.0, missedDeadlineFunc); + result = pst::pstSpi(spiPst); + if (result != returnvalue::OK) { + if (result != FixedTimeslotTaskIF::SLOT_LIST_EMPTY) { + sif::error << "InitMission::createPstTasks: Creating PST failed!" << std::endl; + } + } else { + taskVec.push_back(spiPst); + } +#endif +} + +void initmission::createTestTasks(TaskFactory& factory, + TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec) { + ReturnValue_t result = returnvalue::OK; + PeriodicTaskIF* testTask = factory.createPeriodicTask( + "TEST_TASK", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = testTask->addComponent(objects::TEST_TASK); + if (result != returnvalue::OK) { + initmission::printAddObjectError("TEST_TASK", objects::TEST_TASK); + } +#if OBSW_ADD_SPI_TEST_CODE == 1 + result = testTask->addComponent(objects::SPI_TEST); + if (result != returnvalue::OK) { + initmission::printAddObjectError("SPI_TEST", objects::SPI_TEST); + } +#endif /* RPI_ADD_SPI_TEST == 1 */ +#if RPI_ADD_GPIO_TEST == 1 + result = testTask->addComponent(objects::LIBGPIOD_TEST); + if (result != returnvalue::OK) { + initmission::printAddObjectError("GPIOD_TEST", objects::LIBGPIOD_TEST); + } +#endif /* RPI_ADD_GPIO_TEST == 1 */ +#if OBSW_ADD_UART_TEST_CODE == 1 + result = testTask->addComponent(objects::UART_TEST); + if (result != returnvalue::OK) { + initmission::printAddObjectError("UART_TEST", objects::UART_TEST); + } + PeriodicTaskIF* scexReaderTask = factory.createPeriodicTask( + "SCEX_UART_READER", 20, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = scexReaderTask->addComponent(objects::SCEX_UART_READER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("SCEX_UART_READER", objects::SCEX_UART_READER); + } + taskVec.push_back(scexReaderTask); +#endif /* RPI_ADD_GPIO_TEST == 1 */ + taskVec.push_back(testTask); + + bool startTestPst = true; + static_cast(startTestPst); +#if OBSW_ADD_TEST_PST == 1 + FixedTimeslotTaskIF* pstTestTask = factory->createFixedTimeslotTask( + "TEST_PST", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, 2.0, missedDeadlineFunc); + result = pst::pstTest(pstTestTask); + if (result != returnvalue::OK) { + sif::info << "initmission::initTasks: ACS PST empty or invalid" << std::endl; + startTestPst = false; + } +#endif /* RPI_TEST_ACS_BOARD == 1 */ +} diff --git a/bsp_linux_board/InitMission.h b/bsp_linux_board/InitMission.h new file mode 100644 index 0000000..6e38fc9 --- /dev/null +++ b/bsp_linux_board/InitMission.h @@ -0,0 +1,23 @@ +#ifndef BSP_LINUX_INITMISSION_H_ +#define BSP_LINUX_INITMISSION_H_ + +#include + +#include "fsfw/tasks/definitions.h" + +class PeriodicTaskIF; +class TaskFactory; + +namespace initmission { +void initMission(); +void initTasks(); + +void createPstTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +void createTestTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +void createPusTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +}; // namespace initmission + +#endif /* BSP_LINUX_INITMISSION_H_ */ diff --git a/bsp_linux_board/OBSWConfig.h.in b/bsp_linux_board/OBSWConfig.h.in new file mode 100644 index 0000000..04b90ed --- /dev/null +++ b/bsp_linux_board/OBSWConfig.h.in @@ -0,0 +1,129 @@ +/** + * @brief This file can be used to add preprocessor define for conditional + * code inclusion exclusion or various other project constants and + * properties in one place. + */ +#ifndef FSFWCONFIG_OBSWCONFIG_H_ +#define FSFWCONFIG_OBSWCONFIG_H_ + +#include "commonConfig.h" +#include "OBSWVersion.h" + +/*******************************************************************/ +/** All of the following flags should be enabled for mission code */ +/*******************************************************************/ + +#define OBSW_ENABLE_TIMERS 1 +#define OBSW_ADD_STAR_TRACKER 0 +#define OBSW_ADD_PLOC_SUPERVISOR 0 +#define OBSW_ADD_PLOC_MPSOC 0 +#define OBSW_ADD_SUN_SENSORS 0 +#define OBSW_ADD_MGT 0 +#define OBSW_ADD_ACS_BOARD 0 +#define OBSW_ADD_ACS_HANDLERS 0 +#define OBSW_ADD_GPS_0 0 +#define OBSW_ADD_GPS_1 0 +#define OBSW_ADD_RW 0 +#define OBSW_ADD_BPX_BATTERY_HANDLER 0 +#define OBSW_ADD_RTD_DEVICES 0 +#define OBSW_ADD_PL_PCDU 0 +#define OBSW_ADD_TMP_DEVICES 0 +#define OBSW_ADD_SCEX_DEVICE 1 +#define OBSW_ADD_RAD_SENSORS 0 +#define OBSW_ADD_SYRLINKS 0 +#define OBSW_STAR_TRACKER_GROUND_CONFIG 1 + +// This is a really tricky switch.. It initializes the PCDU switches to their default states +// at powerup. I think it would be better +// to leave it off for now. It makes testing a lot more difficult and it might mess with +// something the operators might want to do by giving the software too much intelligence +// at the wrong place. The system component might command all the Switches accordingly anyway +#define OBSW_INITIALIZE_SWITCHES 0 +#define OBSW_ENABLE_PERIODIC_HK 0 + +/*******************************************************************/ +/** All of the following flags should be disabled for mission code */ +/*******************************************************************/ + +// Can be used to switch device to NORMAL mode immediately +#define OBSW_SWITCH_TO_NORMAL_MODE_AFTER_STARTUP 1 +#define OBSW_PRINT_MISSED_DEADLINES 1 + +#define OBSW_SYRLINKS_SIMULATED 1 +#define OBSW_ADD_TEST_CODE 0 +#define OBSW_ADD_TEST_TASK 0 +#define OBSW_ADD_TEST_PST 0 +// If this is enabled, all other SPI code should be disabled +#define OBSW_ADD_SPI_TEST_CODE 0 +// If this is enabled, all other I2C code should be disabled +#define OBSW_ADD_I2C_TEST_CODE 0 +#define OBSW_ADD_UART_TEST_CODE 0 + +#define OBSW_TEST_ACS 0 +#define OBSW_DEBUG_ACS 0 +#define OBSW_TEST_SUS 0 +#define OBSW_DEBUG_SUS 0 +#define OBSW_TEST_RTD 0 +#define OBSW_DEBUG_RTD 0 +#define OBSW_TEST_RAD_SENSOR 0 +#define OBSW_DEBUG_RAD_SENSOR 0 +#define OBSW_TEST_PL_PCDU 0 +#define OBSW_DEBUG_PL_PCDU 0 +#define OBSW_TEST_BPX_BATT 0 +#define OBSW_DEBUG_BPX_BATT 0 +#define OBSW_TEST_IMTQ 0 +#define OBSW_DEBUG_IMTQ 0 +#define OBSW_TEST_RW 0 +#define OBSW_DEBUG_RW 0 + +#define OBSW_TEST_LIBGPIOD 0 +#define OBSW_TEST_PLOC_HANDLER 0 +#define OBSW_TEST_CCSDS_BRIDGE 0 +#define OBSW_TEST_CCSDS_PTME 0 +#define OBSW_TEST_TE7020_HEATER 0 +#define OBSW_TEST_GPIO_OPEN_BY_LABEL 0 +#define OBSW_TEST_GPIO_OPEN_BY_LINE_NAME 0 +#define OBSW_DEBUG_P60DOCK 0 + +#define OBSW_PRINT_CORE_HK 0 +#define OBSW_DEBUG_PDU1 0 +#define OBSW_DEBUG_PDU2 0 +#define OBSW_DEBUG_GPS 0 +#define OBSW_DEBUG_ACU 0 +#define OBSW_DEBUG_SYRLINKS 0 + +#define OBSW_DEBUG_PDEC_HANDLER 0 +#define OBSW_DEBUG_PLOC_SUPERVISOR 0 +#define OBSW_DEBUG_PLOC_MPSOC 0 +#define OBSW_DEBUG_STARTRACKER 0 +#define OBSW_TCP_SERVER_WIRETAPPING 0 + +/*******************************************************************/ +/** CMake Defines */ +/*******************************************************************/ +#cmakedefine EIVE_BUILD_GPSD_GPS_HANDLER + +#define OBSW_ADD_CCSDS_IP_CORES 0 +// Set to 1 if all telemetry should be sent to the PTME IP Core +#define OBSW_TM_TO_PTME 0 +// Set to 1 if telecommands are received via the PDEC IP Core +#define OBSW_TC_FROM_PDEC 0 + +#cmakedefine LIBGPS_VERSION_MAJOR @LIBGPS_VERSION_MAJOR@ +#cmakedefine LIBGPS_VERSION_MINOR @LIBGPS_VERSION_MINOR@ + +#ifdef RASPBERRY_PI +#include "rpiConfig.h" +#elif defined(XIPHOS_Q7S) +#include "q7sConfig.h" +#endif + +#ifdef __cplusplus + +#include "objects/systemObjectList.h" +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +#endif + +#endif /* FSFWCONFIG_OBSWCONFIG_H_ */ diff --git a/bsp_linux_board/ObjectFactory.cpp b/bsp_linux_board/ObjectFactory.cpp new file mode 100644 index 0000000..95ea87b --- /dev/null +++ b/bsp_linux_board/ObjectFactory.cpp @@ -0,0 +1,248 @@ +#include "ObjectFactory.h" + +#include +#include + +#include "OBSWConfig.h" +#include "devConf.h" +#include "devices/addresses.h" +#include "devices/gpioIds.h" +#include "fsfw/datapoollocal/LocalDataPoolManager.h" +#include "fsfw/power/DummyPowerSwitcher.h" +#include "fsfw/tasks/TaskFactory.h" +#include "fsfw/tmtcpacket/pus/tm.h" +#include "fsfw/tmtcservices/CommandingServiceBase.h" +#include "fsfw/tmtcservices/PusServiceBase.h" +#include "gpioInit.h" +#include "linux/ObjectFactory.h" +#include "linux/boardtest/LibgpiodTest.h" +#include "linux/boardtest/SpiTestClass.h" +#include "linux/boardtest/UartTestClass.h" +#include "mission/core/GenericFactory.h" +#include "mission/devices/GPSHyperionHandler.h" +#include "mission/devices/GyroADIS1650XHandler.h" +#include "mission/tmtc/TmFunnel.h" +#include "objects/systemObjectList.h" +#include "tmtc/pusIds.h" + +/* UDP server includes */ +#if OBSW_USE_TMTC_TCP_BRIDGE == 1 +#include +#include +#else +#include "fsfw/osal/common/UdpTcPollingTask.h" +#include "fsfw/osal/common/UdpTmTcBridge.h" +#endif + +#include +#include + +#include "fsfw_hal/common/gpio/GpioCookie.h" +#include "fsfw_hal/devicehandlers/GyroL3GD20Handler.h" +#include "fsfw_hal/devicehandlers/MgmLIS3MDLHandler.h" +#include "fsfw_hal/devicehandlers/MgmRM3100Handler.h" +#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h" +#include "fsfw_hal/linux/rpi/GpioRPi.h" +#include "fsfw_hal/linux/spi/SpiComIF.h" +#include "fsfw_hal/linux/spi/SpiCookie.h" + +void Factory::setStaticFrameworkObjectIds() { + PusServiceBase::PUS_DISTRIBUTOR = objects::PUS_PACKET_DISTRIBUTOR; + PusServiceBase::PACKET_DESTINATION = objects::TM_FUNNEL; + + CommandingServiceBase::defaultPacketSource = objects::PUS_PACKET_DISTRIBUTOR; + CommandingServiceBase::defaultPacketDestination = objects::TM_FUNNEL; + + TmFunnel::downlinkDestination = objects::TMTC_BRIDGE; + // No storage object for now. + TmFunnel::storageDestination = objects::NO_OBJECT; +} + +void ObjectFactory::produce(void* args) { + Factory::setStaticFrameworkObjectIds(); + ObjectFactory::produceGenericObjects(); + + GpioIF* gpioIF = new LinuxLibgpioIF(objects::GPIO_IF); + GpioCookie* gpioCookie = nullptr; + static_cast(gpioCookie); + + SpiComIF* spiComIF = new SpiComIF(objects::SPI_MAIN_COM_IF, spi::DEV, gpioIF); + static_cast(spiComIF); + auto pwrSwitcher = new DummyPowerSwitcher(objects::PCDU_HANDLER, 18, 0); + static_cast(pwrSwitcher); + +#if OBSW_ADD_ACS_BOARD == 1 && defined(RASPBERRY_PI) + createRpiAcsBoard(gpioIF, spiDev); +#endif + +#if OBSW_ADD_SUN_SENSORS == 1 || OBSW_ADD_RTD_DEVICES == 1 +#ifdef RASPBERRY_PI + rpi::gpio::initSpiCsDecoder(gpioIF); +#endif +#endif + +#if OBSW_ADD_SCEX_DEVICE == 1 + auto* sdcMan = new DummySdCardManager("/tmp"); + createScexComponents(uart::DEV, pwrSwitcher, *sdcMan, true, std::nullopt); +#endif + +#if OBSW_ADD_SUN_SENSORS == 1 + createSunSensorComponents(gpioIF, spiComIF, pwrSwitcher, spi::DEV); +#endif + +#if OBSW_ADD_RTD_DEVICES == 1 + createRtdComponents(spi::DEV, gpioIF, pwrSwitcher); +#endif + +#if OBSW_ADD_TEST_CODE == 1 + createTestTasks(); +#endif /* OBSW_ADD_TEST_CODE == 1 */ +} + +void ObjectFactory::createRpiAcsBoard(GpioIF* gpioIF, std::string spiDev) { + GpioCookie* gpioCookie = new GpioCookie(); + // TODO: Missing pin for Gyro 2 + gpio::createRpiGpioConfig(gpioCookie, gpioIds::MGM_0_LIS3_CS, gpio::MGM_0_BCM_PIN, "MGM_0_LIS3", + gpio::Direction::OUT, gpio::Levels::HIGH); + gpio::createRpiGpioConfig(gpioCookie, gpioIds::MGM_1_RM3100_CS, gpio::MGM_1_BCM_PIN, + "MGM_1_RM3100", gpio::Direction::OUT, gpio::Levels::HIGH); + gpio::createRpiGpioConfig(gpioCookie, gpioIds::MGM_2_LIS3_CS, gpio::MGM_2_BCM_PIN, "MGM_2_LIS3", + gpio::Direction::OUT, gpio::Levels::HIGH); + gpio::createRpiGpioConfig(gpioCookie, gpioIds::MGM_3_RM3100_CS, gpio::MGM_3_BCM_PIN, + "MGM_3_RM3100", gpio::Direction::OUT, gpio::Levels::HIGH); + gpio::createRpiGpioConfig(gpioCookie, gpioIds::GYRO_0_ADIS_CS, gpio::GYRO_0_BCM_PIN, + "GYRO_0_ADIS", gpio::Direction::OUT, gpio::Levels::HIGH); + gpio::createRpiGpioConfig(gpioCookie, gpioIds::GYRO_1_L3G_CS, gpio::GYRO_1_BCM_PIN, "GYRO_1_L3G", + gpio::Direction::OUT, gpio::Levels::HIGH); + gpio::createRpiGpioConfig(gpioCookie, gpioIds::GYRO_2_ADIS_CS, gpio::GYRO_2_BCM_PIN, + "GYRO_2_ADIS", gpio::Direction::OUT, gpio::Levels::HIGH); + gpio::createRpiGpioConfig(gpioCookie, gpioIds::GYRO_3_L3G_CS, gpio::GYRO_3_BCM_PIN, "GYRO_3_L3G", + gpio::Direction::OUT, gpio::Levels::HIGH); + gpioIF->addGpios(gpioCookie); + SpiCookie* spiCookie = + new SpiCookie(addresses::MGM_0_LIS3, gpioIds::MGM_0_LIS3_CS, MGMLIS3MDL::MAX_BUFFER_SIZE, + spi::DEFAULT_LIS3_MODE, spi::DEFAULT_LIS3_SPEED); + auto mgmLis3Handler = + new MgmLIS3MDLHandler(objects::MGM_0_LIS3_HANDLER, objects::SPI_MAIN_COM_IF, spiCookie, 0); + mgmLis3Handler->setStartUpImmediately(); +#if OBSW_TEST_ACS == 1 + mgmLis3Handler->setToGoToNormalMode(true); +#endif + + spiCookie = + new SpiCookie(addresses::MGM_1_RM3100, gpioIds::MGM_1_RM3100_CS, RM3100::MAX_BUFFER_SIZE, + spi::DEFAULT_RM3100_MODE, spi::DEFAULT_RM3100_SPEED); + auto mgmRm3100Handler = + new MgmRM3100Handler(objects::MGM_1_RM3100_HANDLER, objects::SPI_MAIN_COM_IF, spiCookie, 0); + mgmRm3100Handler->setStartUpImmediately(); +#if OBSW_TEST_ACS == 1 + mgmRm3100Handler->setToGoToNormalMode(true); +#endif + + spiCookie = + new SpiCookie(addresses::MGM_2_LIS3, gpioIds::MGM_2_LIS3_CS, MGMLIS3MDL::MAX_BUFFER_SIZE, + spi::DEFAULT_LIS3_MODE, spi::DEFAULT_LIS3_SPEED); + mgmLis3Handler = + new MgmLIS3MDLHandler(objects::MGM_2_LIS3_HANDLER, objects::SPI_MAIN_COM_IF, spiCookie, 0); + mgmLis3Handler->setStartUpImmediately(); +#if OBSW_TEST_ACS == 1 + mgmLis3Handler->setToGoToNormalMode(true); +#endif + + spiCookie = + new SpiCookie(addresses::MGM_3_RM3100, gpioIds::MGM_3_RM3100_CS, RM3100::MAX_BUFFER_SIZE, + spi::DEFAULT_RM3100_MODE, spi::DEFAULT_RM3100_SPEED); + mgmRm3100Handler = + new MgmRM3100Handler(objects::MGM_3_RM3100_HANDLER, objects::SPI_MAIN_COM_IF, spiCookie, 0); + mgmRm3100Handler->setStartUpImmediately(); +#if OBSW_TEST_ACS == 1 + mgmRm3100Handler->setToGoToNormalMode(true); +#endif + + spiCookie = + new SpiCookie(addresses::GYRO_0_ADIS, gpioIds::GYRO_0_ADIS_CS, ADIS1650X::MAXIMUM_REPLY_SIZE, + spi::DEFAULT_L3G_MODE, spi::DEFAULT_L3G_SPEED); + auto adisHandler = + new GyroADIS1650XHandler(objects::GYRO_0_ADIS_HANDLER, objects::SPI_MAIN_COM_IF, spiCookie, + ADIS1650X::Type::ADIS16505); + adisHandler->setStartUpImmediately(); + spiCookie = new SpiCookie(addresses::GYRO_1_L3G, gpioIds::GYRO_1_L3G_CS, L3GD20H::MAX_BUFFER_SIZE, + spi::DEFAULT_L3G_MODE, spi::DEFAULT_L3G_SPEED); + auto gyroL3gHandler = + new GyroHandlerL3GD20H(objects::GYRO_1_L3G_HANDLER, objects::SPI_MAIN_COM_IF, spiCookie, 0); + gyroL3gHandler->setStartUpImmediately(); +#if OBSW_TEST_ACS == 1 + gyroL3gHandler->setToGoToNormalMode(true); +#endif + + spiCookie = + new SpiCookie(addresses::GYRO_2_ADIS, gpioIds::GYRO_2_ADIS_CS, ADIS1650X::MAXIMUM_REPLY_SIZE, + spi::DEFAULT_L3G_MODE, spi::DEFAULT_L3G_SPEED); + adisHandler = new GyroADIS1650XHandler(objects::GYRO_2_ADIS_HANDLER, objects::SPI_MAIN_COM_IF, + spiCookie, ADIS1650X::Type::ADIS16505); + adisHandler->setStartUpImmediately(); + + spiCookie = new SpiCookie(addresses::GYRO_3_L3G, gpioIds::GYRO_3_L3G_CS, L3GD20H::MAX_BUFFER_SIZE, + spi::DEFAULT_L3G_MODE, spi::DEFAULT_L3G_SPEED); + gyroL3gHandler = + new GyroHandlerL3GD20H(objects::GYRO_3_L3G_HANDLER, objects::SPI_MAIN_COM_IF, spiCookie, 0); + gyroL3gHandler->setStartUpImmediately(); +#if OBSW_TEST_ACS == 1 + gyroL3gHandler->setToGoToNormalMode(true); +#endif +} + +void ObjectFactory::createTestTasks() { + new TestTask(objects::TEST_TASK); + +#if OBSW_ADD_SPI_TEST_CODE == 1 + new SpiTestClass(objects::SPI_TEST, gpioIF); +#endif + +#if OBSW_ADD_UART_TEST_CODE == 1 + new UartTestClass(objects::UART_TEST); +#else + newSerialComIF(objects::UART_COM_IF); +#endif + +#if RPI_LOOPBACK_TEST_GPIO == 1 + GpioCookie* gpioCookieLoopback = new GpioCookie(); + /* Loopback pins. Adapt according to setup */ + gpioId_t gpioIdSender = gpioIds::TEST_ID_0; + int bcmPinSender = 26; + gpioId_t gpioIdReader = gpioIds::TEST_ID_1; + int bcmPinReader = 16; + gpio::createRpiGpioConfig(gpioCookieLoopback, gpioIdSender, bcmPinSender, "GPIO_LB_SENDER", + gpio::Direction::OUT, 0); + gpio::createRpiGpioConfig(gpioCookieLoopback, gpioIdReader, bcmPinReader, "GPIO_LB_READER", + gpio::Direction::IN, 0); + new LibgpiodTest(objects::LIBGPIOD_TEST, objects::GPIO_IF, gpioCookieLoopback); +#endif /* RPI_LOOPBACK_TEST_GPIO == 1 */ + +#if RPI_TEST_ADIS16507 == 1 + if (gpioCookie == nullptr) { + gpioCookie = new GpioCookie(); + } + gpio::createRpiGpioConfig(gpioCookie, gpioIds::GYRO_0_ADIS_CS, gpio::GYRO_0_BCM_PIN, + "GYRO_0_ADIS", gpio::Direction::OUT, 1); + gpioIF->addGpios(gpioCookie); + + spiDev = "/dev/spidev0.1"; + spiCookie = new SpiCookie(addresses::GYRO_0_ADIS, gpioIds::GYRO_0_ADIS_CS, spiDev, + ADIS16507::MAXIMUM_REPLY_SIZE, spi::DEFAULT_ADIS16507_MODE, + spi::DEFAULT_ADIS16507_SPEED, nullptr, nullptr); + auto adisGyroHandler = + new GyroADIS16507Handler(objects::GYRO_0_ADIS_HANDLER, objects::SPI_COM_IF, spiCookie); + adisGyroHandler->setStartUpImmediately(); +#endif /* RPI_TEST_ADIS16507 == 1 */ + +#if RPI_TEST_GPS_HANDLER == 1 + UartCookie* uartCookie = + new UartCookie(objects::GPS0_HANDLER, "/dev/serial0", UartModes::CANONICAL, 9600, 1024); + uartCookie->setToFlushInput(true); + uartCookie->setReadCycles(6); + GPSHyperionHandler* gpsHandler = + new GPSHyperionHandler(objects::GPS0_HANDLER, objects::UART_COM_IF, uartCookie, false); + gpsHandler->setStartUpImmediately(); +#endif +} diff --git a/bsp_linux_board/ObjectFactory.h b/bsp_linux_board/ObjectFactory.h new file mode 100644 index 0000000..7365fb8 --- /dev/null +++ b/bsp_linux_board/ObjectFactory.h @@ -0,0 +1,16 @@ +#ifndef BSP_LINUX_OBJECTFACTORY_H_ +#define BSP_LINUX_OBJECTFACTORY_H_ + +#include + +class GpioIF; + +namespace ObjectFactory { +void setStatics(); +void produce(void* args); + +void createRpiAcsBoard(GpioIF* gpioIF, std::string spiDev); +void createTestTasks(); +}; // namespace ObjectFactory + +#endif /* BSP_LINUX_OBJECTFACTORY_H_ */ diff --git a/bsp_linux_board/boardconfig/CMakeLists.txt b/bsp_linux_board/boardconfig/CMakeLists.txt new file mode 100644 index 0000000..f08670d --- /dev/null +++ b/bsp_linux_board/boardconfig/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(${OBSW_NAME} PRIVATE print.c) + +target_include_directories(${OBSW_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/bsp_linux_board/boardconfig/etl_profile.h b/bsp_linux_board/boardconfig/etl_profile.h new file mode 100644 index 0000000..54aca34 --- /dev/null +++ b/bsp_linux_board/boardconfig/etl_profile.h @@ -0,0 +1,38 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2019 jwellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ +#ifndef __ETL_PROFILE_H__ +#define __ETL_PROFILE_H__ + +#define ETL_CHECK_PUSH_POP + +#define ETL_CPP11_SUPPORTED 1 +#define ETL_NO_NULLPTR_SUPPORT 0 + +#endif diff --git a/bsp_linux_board/boardconfig/gcov.h b/bsp_linux_board/boardconfig/gcov.h new file mode 100644 index 0000000..80acdd8 --- /dev/null +++ b/bsp_linux_board/boardconfig/gcov.h @@ -0,0 +1,15 @@ +#ifndef LINUX_GCOV_H_ +#define LINUX_GCOV_H_ +#include + +#ifdef GCOV +extern "C" void __gcov_flush(); +#else +void __gcov_flush() { + sif::info << "GCC GCOV: Please supply GCOV=1 in Makefile if " + "coverage information is desired.\n" + << std::flush; +} +#endif + +#endif /* LINUX_GCOV_H_ */ diff --git a/bsp_linux_board/boardconfig/print.c b/bsp_linux_board/boardconfig/print.c new file mode 100644 index 0000000..c2b2e15 --- /dev/null +++ b/bsp_linux_board/boardconfig/print.c @@ -0,0 +1,10 @@ +#include +#include + +void printChar(const char* character, bool errStream) { + if (errStream) { + putc(*character, stderr); + return; + } + putc(*character, stdout); +} diff --git a/bsp_linux_board/boardconfig/print.h b/bsp_linux_board/boardconfig/print.h new file mode 100644 index 0000000..8e7e2e5 --- /dev/null +++ b/bsp_linux_board/boardconfig/print.h @@ -0,0 +1,8 @@ +#ifndef HOSTED_BOARDCONFIG_PRINT_H_ +#define HOSTED_BOARDCONFIG_PRINT_H_ + +#include + +void printChar(const char* character, bool errStream); + +#endif /* HOSTED_BOARDCONFIG_PRINT_H_ */ diff --git a/bsp_linux_board/boardconfig/rpiConfig.h.in b/bsp_linux_board/boardconfig/rpiConfig.h.in new file mode 100644 index 0000000..b58a103 --- /dev/null +++ b/bsp_linux_board/boardconfig/rpiConfig.h.in @@ -0,0 +1,20 @@ +#ifndef BSP_RPI_BOARDCONFIG_RPI_CONFIG_H_ +#define BSP_RPI_BOARDCONFIG_RPI_CONFIG_H_ + +#include + +#define RPI_ADD_GPIO_TEST 0 +#define RPI_LOOPBACK_TEST_GPIO 0 + +#define RPI_TEST_ADIS16507 0 +#define RPI_TEST_GPS_HANDLER 0 + +// Only one of those 2 should be enabled! +#define RPI_ADD_SPI_TEST 0 +#if RPI_ADD_SPI_TEST == 0 +#define RPI_TEST_ACS_BOARD 0 +#endif + +#define RPI_ADD_UART_TEST 0 + +#endif /* BSP_RPI_BOARDCONFIG_RPI_CONFIG_H_ */ diff --git a/bsp_linux_board/boardtest/CMakeLists.txt b/bsp_linux_board/boardtest/CMakeLists.txt new file mode 100644 index 0000000..431972e --- /dev/null +++ b/bsp_linux_board/boardtest/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PRIVATE) diff --git a/bsp_linux_board/definitions.h b/bsp_linux_board/definitions.h new file mode 100644 index 0000000..83aeb84 --- /dev/null +++ b/bsp_linux_board/definitions.h @@ -0,0 +1,43 @@ +#ifndef BSP_LINUX_BOARD_DEFINITIONS_H_ +#define BSP_LINUX_BOARD_DEFINITIONS_H_ + +#include + +#include "OBSWConfig.h" + +#ifdef RASPBERRY_PI + +namespace spi { + +static constexpr char DEV[] = "/dev/spidev0.1"; + +} + +namespace uart { + +static constexpr char DEV[] = "/dev/serial0"; + +} + +/* Adapt these values accordingly */ +namespace gpio { +static constexpr uint8_t MGM_0_BCM_PIN = 17; +static constexpr uint8_t MGM_1_BCM_PIN = 27; +static constexpr uint8_t MGM_2_BCM_PIN = 22; +static constexpr uint8_t MGM_3_BCM_PIN = 23; +static constexpr uint8_t GYRO_0_BCM_PIN = 5; +static constexpr uint8_t GYRO_1_BCM_PIN = 6; +static constexpr uint8_t GYRO_2_BCM_PIN = 13; +static constexpr uint8_t GYRO_3_BCM_PIN = 19; + +static constexpr uint8_t SPI_MUX_0_BCM = 17; +static constexpr uint8_t SPI_MUX_1_BCM = 27; +static constexpr uint8_t SPI_MUX_2_BCM = 22; +static constexpr uint8_t SPI_MUX_3_BCM = 23; +static constexpr uint8_t SPI_MUX_4_BCM = 5; +static constexpr uint8_t SPI_MUX_5_BCM = 6; +} // namespace gpio + +#endif + +#endif /* BSP_LINUX_BOARD_DEFINITIONS_H_ */ diff --git a/bsp_linux_board/gpioInit.cpp b/bsp_linux_board/gpioInit.cpp new file mode 100644 index 0000000..b922961 --- /dev/null +++ b/bsp_linux_board/gpioInit.cpp @@ -0,0 +1,56 @@ +#include "gpioInit.h" + +#include +#include +#include +#include + +#include "definitions.h" +#include "fsfw_hal/linux/rpi/GpioRPi.h" + +#ifdef RASPBERRY_PI + +struct MuxInfo { + MuxInfo(gpioId_t gpioId, int bcmNum, std::string consumer) + : gpioId(gpioId), bcmNum(bcmNum), consumer(consumer) {} + gpioId_t gpioId; + int bcmNum; + std::string consumer; +}; + +void rpi::gpio::initSpiCsDecoder(GpioIF* gpioComIF) { + using namespace ::gpio; + ReturnValue_t result; + + if (gpioComIF == nullptr) { + sif::debug << "initSpiCsDecoder: Invalid gpioComIF" << std::endl; + return; + } + + std::array<::MuxInfo, 6> muxInfo{ + MuxInfo(gpioIds::SPI_MUX_BIT_0, SPI_MUX_0_BCM, "SPI_MUX_0"), + MuxInfo(gpioIds::SPI_MUX_BIT_1, SPI_MUX_1_BCM, "SPI_MUX_1"), + MuxInfo(gpioIds::SPI_MUX_BIT_2, SPI_MUX_2_BCM, "SPI_MUX_2"), + MuxInfo(gpioIds::SPI_MUX_BIT_3, SPI_MUX_3_BCM, "SPI_MUX_3"), + MuxInfo(gpioIds::SPI_MUX_BIT_4, SPI_MUX_4_BCM, "SPI_MUX_4"), + MuxInfo(gpioIds::SPI_MUX_BIT_5, SPI_MUX_5_BCM, "SPI_MUX_5"), + }; + GpioCookie* spiMuxGpios = new GpioCookie; + + for (const auto& info : muxInfo) { + result = createRpiGpioConfig(spiMuxGpios, info.gpioId, info.bcmNum, info.consumer, + Direction::OUT, Levels::LOW); + if (result != returnvalue::OK) { + sif::error << "Creating Raspberry Pi SPI Mux GPIO failed with code " << result << std::endl; + return; + } + } + + result = gpioComIF->addGpios(spiMuxGpios); + if (result != returnvalue::OK) { + sif::error << "initSpiCsDecoder: Failed to add mux bit gpios to gpioComIF" << std::endl; + return; + } +} + +#endif diff --git a/bsp_linux_board/gpioInit.h b/bsp_linux_board/gpioInit.h new file mode 100644 index 0000000..0ca6641 --- /dev/null +++ b/bsp_linux_board/gpioInit.h @@ -0,0 +1,20 @@ +#pragma once + +#include "OBSWConfig.h" + +class GpioIF; + +#ifdef RASPBERRY_PI +namespace rpi { +namespace gpio { + +/** + * @brief This function initializes the GPIOs used to control the SN74LVC138APWR decoders on + * the TCS Board and the interface board. + */ +void initSpiCsDecoder(GpioIF* gpioComIF); + +} // namespace gpio +} // namespace rpi + +#endif diff --git a/bsp_linux_board/main.cpp b/bsp_linux_board/main.cpp new file mode 100644 index 0000000..98c4d79 --- /dev/null +++ b/bsp_linux_board/main.cpp @@ -0,0 +1,34 @@ +#include + +#include "InitMission.h" +#include "OBSWConfig.h" +#include "OBSWVersion.h" +#include "fsfw/tasks/TaskFactory.h" +#include "fsfw/version.h" + +#ifdef RASPBERRY_PI +static const char* const BOARD_NAME = "Raspberry Pi"; +#elif defined(BEAGLEBONEBLACK) +static const char* const BOARD_NAME = "Beaglebone Black"; +#else +static const char* const BOARD_NAME = "Unknown Board"; +#endif + +/** + * @brief This is the main program and entry point for the Raspberry Pi. + * @return + */ +int main(void) { + std::cout << "-- EIVE OBSW --" << std::endl; + std::cout << "-- Compiled for Linux board " << BOARD_NAME << " --" << std::endl; + std::cout << "-- OBSW " << SW_NAME << " v" << SW_VERSION << "." << SW_SUBVERSION << "." + << SW_REVISION << ", FSFW v" << fsfw::FSFW_VERSION << " --" << std::endl; + std::cout << "-- " << __DATE__ << " " << __TIME__ << " --" << std::endl; + + initmission::initMission(); + + for (;;) { + /* Suspend main thread by sleeping it. */ + TaskFactory::delayTask(5000); + } +} diff --git a/bsp_q7s/CMakeLists.txt b/bsp_q7s/CMakeLists.txt new file mode 100644 index 0000000..0a1a943 --- /dev/null +++ b/bsp_q7s/CMakeLists.txt @@ -0,0 +1,28 @@ +# simple mode +add_executable(${SIMPLE_OBSW_NAME} EXCLUDE_FROM_ALL) +target_compile_definitions(${SIMPLE_OBSW_NAME} PRIVATE "Q7S_SIMPLE_MODE") +target_sources(${SIMPLE_OBSW_NAME} PUBLIC main.cpp) +# I think this is unintentional? (produces linker errors for stuff in /linux) +target_link_libraries(${SIMPLE_OBSW_NAME} PUBLIC ${LIB_FSFW_NAME}) +target_compile_definitions(${SIMPLE_OBSW_NAME} PRIVATE "Q7S_SIMPLE_MODE") +add_subdirectory(simple) + +target_sources(${OBSW_NAME} PUBLIC main.cpp obsw.cpp scheduling.cpp + objectFactory.cpp) + +add_subdirectory(boardtest) + +add_subdirectory(boardconfig) +add_subdirectory(core) + +if(EIVE_Q7S_EM) + add_subdirectory(em) +else() + target_sources(${OBSW_NAME} PUBLIC fmObjectFactory.cpp) +endif() + +add_subdirectory(memory) +add_subdirectory(callbacks) +add_subdirectory(xadc) +add_subdirectory(fs) +add_subdirectory(acs) diff --git a/bsp_q7s/OBSWConfig.h.in b/bsp_q7s/OBSWConfig.h.in new file mode 100644 index 0000000..2555d7c --- /dev/null +++ b/bsp_q7s/OBSWConfig.h.in @@ -0,0 +1,147 @@ +/** + * @brief This file can be used to add preprocessor define for conditional + * code inclusion exclusion or various other project constants and + * properties in one place. + */ +#ifndef FSFWCONFIG_OBSWCONFIG_H_ +#define FSFWCONFIG_OBSWCONFIG_H_ + +#include "commonConfig.h" +#include "q7sConfig.h" + +/*******************************************************************/ +/** All of the following flags should be enabled for mission code */ +/*******************************************************************/ + +// This enables a lot of periodically generated telemetry, so it can make sense to +// disable this for debugging purposes. +#define OBSW_ENABLE_PERIODIC_HK @OBSW_ENABLE_PERIODIC_HK@ + +// This switch will cause the SW to command the EIVE system object to safe mode. This will +// trigger a lot of events, so it can make sense to disable this for debugging purposes. +#define OBSW_COMMAND_SAFE_MODE_AT_STARTUP 1 + +#define OBSW_ADD_GOMSPACE_PCDU @OBSW_ADD_GOMSPACE_PCDU@ +// This define is necessary because the EM setup has the P60 dock module, but no ACU on the P60 +// module because it broke. +#define OBSW_ADD_GOMSPACE_ACU @OBSW_ADD_GOMSPACE_ACU@ +#define OBSW_ADD_MGT @OBSW_ADD_MGT@ +#define OBSW_ADD_BPX_BATTERY_HANDLER @OBSW_ADD_BPX_BATTERY_HANDLER@ +#define OBSW_ADD_STAR_TRACKER @OBSW_ADD_STAR_TRACKER@ +#define OBSW_ADD_PLOC_SUPERVISOR @OBSW_ADD_PLOC_SUPERVISOR@ +#define OBSW_ADD_PLOC_MPSOC @OBSW_ADD_PLOC_MPSOC@ +#define OBSW_ADD_SUN_SENSORS @OBSW_ADD_SUN_SENSORS@ +#define OBSW_ADD_SUS_BOARD_ASS @OBSW_ADD_SUS_BOARD_ASS@ +#define OBSW_ADD_ACS_BOARD @OBSW_ADD_ACS_BOARD@ +#define OBSW_ADD_ACS_CTRL 1 +#define OBSW_ADD_TCS_CTRL 1 +#define OBSW_ADD_GPS_CTRL @OBSW_ADD_GPS_CTRL@ +#define OBSW_ADD_RW @OBSW_ADD_RW@ +#define OBSW_ADD_RTD_DEVICES @OBSW_ADD_RTD_DEVICES@ +#define OBSW_ADD_SA_DEPL @OBSW_ADD_SA_DEPL@ +#define OBSW_ADD_SCEX_DEVICE @OBSW_ADD_SCEX_DEVICE@ +#define OBSW_ADD_HEATERS @OBSW_ADD_HEATERS@ +#define OBSW_ADD_TMP_DEVICES @OBSW_ADD_TMP_DEVICES@ +#define OBSW_ADD_RAD_SENSORS @OBSW_ADD_RAD_SENSORS@ +#define OBSW_ADD_PL_PCDU @OBSW_ADD_PL_PCDU@ +#define OBSW_ADD_SYRLINKS @OBSW_ADD_SYRLINKS@ +#define OBSW_ADD_CCSDS_IP_CORES @OBSW_ADD_CCSDS_IP_CORES@ +// Only relevant for EM for TCS tests. +#define OBSW_ADD_THERMAL_TEMP_INSERTER @OBSW_ADD_THERMAL_TEMP_INSERTER@ + +// Set to 1 if all telemetry should be sent to the PTME IP Core +#define OBSW_TM_TO_PTME @OBSW_TM_TO_PTME@ +// Set to 1 if telecommands are received via the PDEC IP Core +#define OBSW_TC_FROM_PDEC @OBSW_TC_FROM_PDEC@ + +// Configuration parameter which causes the core controller to try to keep at least one SD card +// working +#define OBSW_SD_CARD_MUST_BE_ON 1 +#define OBSW_ENABLE_TIMERS 1 + +/*******************************************************************/ +/** All of the following flags should be disabled for mission code */ +/*******************************************************************/ + +// Use TCP instead of UDP for the TMTC bridge. This allows using the TMTC client locally +// because UDP packets are not allowed in the VPN +// This will cause the OBSW to initialize the TMTC bridge responsible for exchanging data with the +// CCSDS IP Cores. +#define OBSW_ADD_TMTC_TCP_SERVER @OBSW_ADD_TMTC_TCP_SERVER@ +#define OBSW_ADD_TMTC_UDP_SERVER @OBSW_ADD_TMTC_UDP_SERVER@ + +// Can be used to switch device to NORMAL mode immediately +#define OBSW_SWITCH_TO_NORMAL_MODE_AFTER_STARTUP 0 +#define OBSW_PRINT_MISSED_DEADLINES 0 + +#define OBSW_MPSOC_JTAG_BOOT 0 +#define OBSW_STAR_TRACKER_GROUND_CONFIG @OBSW_STAR_TRACKER_GROUND_CONFIG@ +#define OBSW_SYRLINKS_SIMULATED @OBSW_SYRLINKS_SIMULATED@ +#define OBSW_ADD_TEST_CODE 0 +#define OBSW_ADD_TEST_TASK 0 +#define OBSW_ADD_TEST_PST 0 +// If this is enabled, all other SPI code should be disabled +#define OBSW_ADD_SPI_TEST_CODE 0 +// If this is enabled, all other I2C code should be disabled +#define OBSW_ADD_I2C_TEST_CODE 0 +#define OBSW_ADD_UART_TEST_CODE 0 + +#define OBSW_TEST_ACS 0 +#define OBSW_DEBUG_ACS 0 +#define OBSW_TEST_SUS 0 +#define OBSW_DEBUG_SUS 0 +#define OBSW_TEST_RTD 0 +#define OBSW_DEBUG_RTD 0 +#define OBSW_TEST_RAD_SENSOR 0 +#define OBSW_DEBUG_RAD_SENSOR 0 +#define OBSW_TEST_PL_PCDU 0 +#define OBSW_DEBUG_PL_PCDU 0 +#define OBSW_TEST_BPX_BATT 0 +#define OBSW_DEBUG_BPX_BATT 0 +#define OBSW_TEST_IMTQ 0 +#define OBSW_DEBUG_IMTQ 0 +#define OBSW_TEST_RW 0 +#define OBSW_DEBUG_RW 0 + +#define OBSW_TEST_LIBGPIOD 0 +#define OBSW_TEST_PLOC_HANDLER 0 +#define OBSW_TEST_CCSDS_BRIDGE 0 +#define OBSW_TEST_CCSDS_PTME 0 +#define OBSW_TEST_TE7020_HEATER 0 +#define OBSW_TEST_GPIO_OPEN_BY_LABEL 0 +#define OBSW_TEST_GPIO_OPEN_BY_LINE_NAME 0 +#define OBSW_DEBUG_P60DOCK 0 + +#define OBSW_PRINT_CORE_HK 0 +#define OBSW_DEBUG_PDU1 0 +#define OBSW_DEBUG_PDU2 0 +#define OBSW_DEBUG_TMP1075 0 +#define OBSW_DEBUG_GPS 0 +#define OBSW_DEBUG_ACU 0 +#define OBSW_DEBUG_SYRLINKS 0 + +#define OBSW_DEBUG_PDEC_HANDLER 0 +#define OBSW_DEBUG_PLOC_SUPERVISOR 0 +#define OBSW_DEBUG_PLOC_MPSOC 0 +#define OBSW_DEBUG_STARTRACKER 0 + +#define OBSW_TCP_SERVER_WIRETAPPING 0 + +/*******************************************************************/ +/** CMake Defines */ +/*******************************************************************/ + +#cmakedefine EIVE_BUILD_GPSD_GPS_HANDLER + +#cmakedefine LIBGPS_VERSION_MAJOR @LIBGPS_VERSION_MAJOR@ +#cmakedefine LIBGPS_VERSION_MINOR @LIBGPS_VERSION_MINOR@ + +#ifdef __cplusplus + +#include "objects/systemObjectList.h" +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +#endif + +#endif /* FSFWCONFIG_OBSWCONFIG_H_ */ diff --git a/bsp_q7s/acs/CMakeLists.txt b/bsp_q7s/acs/CMakeLists.txt new file mode 100644 index 0000000..87bf46f --- /dev/null +++ b/bsp_q7s/acs/CMakeLists.txt @@ -0,0 +1 @@ +# target_sources(${OBSW_NAME} PUBLIC ) diff --git a/bsp_q7s/acs/StrConfigPathGetter.h b/bsp_q7s/acs/StrConfigPathGetter.h new file mode 100644 index 0000000..58d6964 --- /dev/null +++ b/bsp_q7s/acs/StrConfigPathGetter.h @@ -0,0 +1,23 @@ +#include + +#include "bsp_q7s/fs/SdCardManager.h" +#include "mission/acs/str/strHelpers.h" + +class StrConfigPathGetter : public startracker::SdCardConfigPathGetter { + public: + StrConfigPathGetter(SdCardManager& sdcMan) : sdcMan(sdcMan) {} + + std::optional getCfgPath() override { + if (!sdcMan.isSdCardUsable(std::nullopt)) { + return std::nullopt; + } + if (sdcMan.getActiveSdCard() == sd::SdCard::SLOT_1) { + return std::string("/mnt/sd1/startracker/flight-config.json"); + } else { + return std::string("/mnt/sd0/startracker/flight-config.json"); + } + } + + private: + SdCardManager& sdcMan; +}; diff --git a/bsp_q7s/boardconfig/CMakeLists.txt b/bsp_q7s/boardconfig/CMakeLists.txt new file mode 100644 index 0000000..3224d75 --- /dev/null +++ b/bsp_q7s/boardconfig/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(${OBSW_NAME} PRIVATE print.c) + +target_sources(${SIMPLE_OBSW_NAME} PRIVATE print.c) + +target_include_directories(${OBSW_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/bsp_q7s/boardconfig/busConf.h b/bsp_q7s/boardconfig/busConf.h new file mode 100644 index 0000000..aab8ce4 --- /dev/null +++ b/bsp_q7s/boardconfig/busConf.h @@ -0,0 +1,121 @@ +#ifndef BSP_Q7S_BOARDCONFIG_BUSCONF_H_ +#define BSP_Q7S_BOARDCONFIG_BUSCONF_H_ + +namespace q7s { + +static constexpr char SPI_DEFAULT_DEV[] = "/dev/spi_main"; +static constexpr uint32_t SPI_MAIN_BUS_LOCK_TIMEOUT = 50; + +static constexpr char SPI_RW_DEV[] = "/dev/spi_rw"; + +//! I2C bus using an I2C IP core in the programmable logic (PL) +static constexpr char I2C_PL_EIVE[] = "/dev/i2c_pl"; +//! I2C bus using the I2C peripheral of the ARM processing system (PS) +static constexpr char I2C_PS_EIVE[] = "/dev/i2c_ps"; +//! I2C bus using the first I2C peripheral of the ARM processing system (PS). +//! Named like this because it is used by default for the Q7 devices. +static constexpr char I2C_Q7_EIVE[] = "/dev/i2c_q7"; + +static constexpr char UART_GNSS_DEV[] = "/dev/gps0"; +static constexpr char UART_PLOC_MPSOC_DEV[] = "/dev/ul_plmpsoc"; +static constexpr char UART_PLOC_SUPERVISOR_DEV_FALLBACK[] = "/dev/ttyUL4"; +static constexpr char UART_PLOC_SUPERVISOR_DEV[] = "/dev/ploc_supv"; +static constexpr char UART_SYRLINKS_DEV[] = "/dev/ul_syrlinks"; +static constexpr char UART_STAR_TRACKER_DEV[] = "/dev/ul_str"; +static constexpr char UART_SCEX_DEV[] = "/dev/scex"; + +static constexpr char UIO_PDEC_REGISTERS[] = "/dev/uio_pdec_regs"; +static constexpr char UIO_PTME[] = "/dev/uio_ptme"; +static constexpr char UIO_PDEC_CONFIG_MEMORY[] = "/dev/uio_pdec_cfg_mem"; +static constexpr char UIO_SYS_ROM[] = "/dev/uio_sys_rom"; +static constexpr char UIO_PDEC_RAM[] = "/dev/uio_pdec_ram"; +static constexpr char UIO_PDEC_IRQ[] = "/dev/uio_pdec_irq"; +static constexpr int MAP_ID_PTME_CONFIG = 3; + +namespace uiomapids { +// Live TM +static const int PTME_VC0 = 0; +// OK/NOK/MISC Store +static const int PTME_VC1 = 1; +// HK store +static const int PTME_VC2 = 2; +// CFDP +static const int PTME_VC3 = 3; +static const int PTME_CONFIG = 4; +} // namespace uiomapids + +namespace gpioNames { + +static constexpr char GYRO_0_ADIS_CS[] = "gyro_0_adis_chip_select"; +static constexpr char GYRO_1_L3G_CS[] = "gyro_1_l3g_chip_select"; +static constexpr char GYRO_2_ADIS_CS[] = "gyro_2_adis_chip_select"; +static constexpr char GYRO_3_L3G_CS[] = "gyro_3_l3g_chip_select"; +static constexpr char MGM_0_CS[] = "mgm_0_lis3_chip_select"; +static constexpr char MGM_1_CS[] = "mgm_1_rm3100_chip_select"; +static constexpr char MGM_2_CS[] = "mgm_2_lis3_chip_select"; +static constexpr char MGM_3_CS[] = "mgm_3_rm3100_chip_select"; +static constexpr char RESET_GNSS_0[] = "reset_gnss_0"; +static constexpr char RESET_GNSS_1[] = "reset_gnss_1"; +static constexpr char GNSS_0_ENABLE[] = "enable_gnss_0"; +static constexpr char GNSS_1_ENABLE[] = "enable_gnss_1"; +static constexpr char GYRO_0_ENABLE[] = "enable_gyro_0"; +static constexpr char GYRO_2_ENABLE[] = "enable_gyro_2"; +static constexpr char GNSS_SELECT[] = "gnss_mux_select"; +static constexpr char GNSS_MUX_SELECT[] = "gnss_mux_select"; +static constexpr char PL_I2C_ARESETN[] = "pl_i2c_aresetn"; + +static constexpr char HEATER_0[] = "heater0"; +static constexpr char HEATER_1[] = "heater1"; +static constexpr char HEATER_2[] = "heater2"; +static constexpr char HEATER_3[] = "heater3"; +static constexpr char HEATER_4[] = "heater4"; +static constexpr char HEATER_5[] = "heater5"; +static constexpr char HEATER_6[] = "heater6"; +static constexpr char HEATER_7[] = "heater7"; +static constexpr char SA_DPL_PIN_0[] = "sa_dpl_0"; +static constexpr char SA_DPL_PIN_1[] = "sa_dpl_1"; +static constexpr char SPI_MUX_BIT_0_PIN[] = "spi_mux_bit_0"; +static constexpr char SPI_MUX_BIT_1_PIN[] = "spi_mux_bit_1"; +static constexpr char SPI_MUX_BIT_2_PIN[] = "spi_mux_bit_2"; +static constexpr char SPI_MUX_BIT_3_PIN[] = "spi_mux_bit_3"; +static constexpr char SPI_MUX_BIT_4_PIN[] = "spi_mux_bit_4"; +static constexpr char SPI_MUX_BIT_5_PIN[] = "spi_mux_bit_5"; +static constexpr char EN_RW_CS[] = "en_rw_cs"; +static constexpr char EN_RW_1[] = "enable_rw_1"; +static constexpr char EN_RW_2[] = "enable_rw_2"; +static constexpr char EN_RW_3[] = "enable_rw_3"; +static constexpr char EN_RW_4[] = "enable_rw_4"; + +static constexpr char RAD_SENSOR_CHIP_SELECT[] = "rad_sensor_chip_select"; +static constexpr char ENABLE_RADFET[] = "enable_radfet"; + +static constexpr char PAPB_EMPTY_SIGNAL_VC0[] = "papb_empty_signal_vc0"; +static constexpr char PAPB_EMPTY_SIGNAL_VC1[] = "papb_empty_signal_vc1"; +static constexpr char PAPB_EMPTY_SIGNAL_VC2[] = "papb_empty_signal_vc2"; +static constexpr char PAPB_EMPTY_SIGNAL_VC3[] = "papb_empty_signal_vc3"; + +static constexpr char PTME_RESETN[] = "ptme_resetn"; + +static constexpr char RS485_EN_TX_CLOCK[] = "tx_clock_enable_ltc2872"; +static constexpr char RS485_EN_TX_DATA[] = "tx_data_enable_ltc2872"; +static constexpr char RS485_EN_RX_CLOCK[] = "rx_clock_enable_ltc2872"; +static constexpr char RS485_EN_RX_DATA[] = "rx_data_enable_ltc2872"; +static constexpr char PDEC_RESET[] = "pdec_reset"; +static constexpr char SYRLINKS_FAULT[] = "syrlinks_fault"; + +static constexpr char PL_PCDU_ENABLE_VBAT0[] = "enable_plpcdu_vbat0"; +static constexpr char PL_PCDU_ENABLE_VBAT1[] = "enable_plpcdu_vbat1"; +static constexpr char PL_PCDU_ENABLE_DRO[] = "enable_plpcdu_dro"; +static constexpr char PL_PCDU_ENABLE_X8[] = "enable_plpcdu_x8"; +static constexpr char PL_PCDU_ENABLE_TX[] = "enable_plpcdu_tx"; +static constexpr char PL_PCDU_ENABLE_HPA[] = "enable_plpcdu_hpa"; +static constexpr char PL_PCDU_ENABLE_MPA[] = "enable_plpcdu_mpa"; +static constexpr char PL_PCDU_ADC_CS[] = "plpcdu_adc_chip_select"; + +static constexpr char ENABLE_SUPV_UART[] = "enable_supv_uart"; +static constexpr char ENABLE_MPSOC_UART[] = "enable_mpsoc_uart"; + +} // namespace gpioNames +} // namespace q7s + +#endif /* BSP_Q7S_BOARDCONFIG_BUSCONF_H_ */ diff --git a/bsp_q7s/boardconfig/etl_profile.h b/bsp_q7s/boardconfig/etl_profile.h new file mode 100644 index 0000000..86534d1 --- /dev/null +++ b/bsp_q7s/boardconfig/etl_profile.h @@ -0,0 +1,39 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2019 jwellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ +#ifndef __ETL_PROFILE_H__ +#define __ETL_PROFILE_H__ + +#define ETL_CHECK_PUSH_POP + +#define ETL_CPP11_SUPPORTED 1 +#define ETL_NO_NULLPTR_SUPPORT 0 +#define ETL_HAS_ERROR_ON_STRING_TRUNCATION 1 + +#endif diff --git a/bsp_q7s/boardconfig/gcov.h b/bsp_q7s/boardconfig/gcov.h new file mode 100644 index 0000000..80acdd8 --- /dev/null +++ b/bsp_q7s/boardconfig/gcov.h @@ -0,0 +1,15 @@ +#ifndef LINUX_GCOV_H_ +#define LINUX_GCOV_H_ +#include + +#ifdef GCOV +extern "C" void __gcov_flush(); +#else +void __gcov_flush() { + sif::info << "GCC GCOV: Please supply GCOV=1 in Makefile if " + "coverage information is desired.\n" + << std::flush; +} +#endif + +#endif /* LINUX_GCOV_H_ */ diff --git a/bsp_q7s/boardconfig/print.c b/bsp_q7s/boardconfig/print.c new file mode 100644 index 0000000..3aba2d7 --- /dev/null +++ b/bsp_q7s/boardconfig/print.c @@ -0,0 +1,10 @@ +#include +#include + +void printChar(const char* character, bool errStream) { + if (errStream) { + putc(*character, stderr); + return; + } + putc(*character, stdout); +} diff --git a/bsp_q7s/boardconfig/print.h b/bsp_q7s/boardconfig/print.h new file mode 100644 index 0000000..8e7e2e5 --- /dev/null +++ b/bsp_q7s/boardconfig/print.h @@ -0,0 +1,8 @@ +#ifndef HOSTED_BOARDCONFIG_PRINT_H_ +#define HOSTED_BOARDCONFIG_PRINT_H_ + +#include + +void printChar(const char* character, bool errStream); + +#endif /* HOSTED_BOARDCONFIG_PRINT_H_ */ diff --git a/bsp_q7s/boardconfig/q7sConfig.h.in b/bsp_q7s/boardconfig/q7sConfig.h.in new file mode 100644 index 0000000..8fe0f65 --- /dev/null +++ b/bsp_q7s/boardconfig/q7sConfig.h.in @@ -0,0 +1,34 @@ +#ifndef BSP_Q7S_BOARDCONFIG_Q7S_CONFIG_H_ +#define BSP_Q7S_BOARDCONFIG_Q7S_CONFIG_H_ + +#include + +#define OBSW_Q7S_EM @OBSW_Q7S_EM@ + +/*******************************************************************/ +/** All of the following flags should be enabled for mission code */ +/*******************************************************************/ + +//! Timers can mess up the code when debugging +//! All of this should be enabled for mission code! + +/*******************************************************************/ +/** Other flags */ +/*******************************************************************/ + +// Probably better if this is disabled for mission code. Convenient for development +#define Q7S_CHECK_FOR_ALREADY_RUNNING_IMG @Q7S_CHECK_FOR_ALREADY_RUNNING_IMG@ + +#define Q7S_SIMPLE_ADD_FILE_SYSTEM_TEST 0 + +#ifndef Q7S_SIMPLE_MODE +#define Q7S_SIMPLE_MODE 0 +#endif + +namespace config { + +static const uint32_t SD_CARD_ACCESS_MUTEX_TIMEOUT = 50; + +} + +#endif /* BSP_Q7S_BOARDCONFIG_Q7S_CONFIG_H_ */ diff --git a/bsp_q7s/boardtest/CMakeLists.txt b/bsp_q7s/boardtest/CMakeLists.txt new file mode 100644 index 0000000..9520a62 --- /dev/null +++ b/bsp_q7s/boardtest/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(${OBSW_NAME} PRIVATE FileSystemTest.cpp Q7STestTask.cpp) + +if(EIVE_BUILD_Q7S_SIMPLE_MODE) + target_sources(${SIMPLE_OBSW_NAME} PRIVATE FileSystemTest.cpp) +endif() diff --git a/bsp_q7s/boardtest/FileSystemTest.cpp b/bsp_q7s/boardtest/FileSystemTest.cpp new file mode 100644 index 0000000..e1dd564 --- /dev/null +++ b/bsp_q7s/boardtest/FileSystemTest.cpp @@ -0,0 +1,23 @@ +#include "FileSystemTest.h" + +#include +#include + +#include "fsfw/timemanager/Stopwatch.h" + +enum SdCard { SDC0, SDC1 }; + +FileSystemTest::FileSystemTest() { + using namespace std; + SdCard sdCard = SdCard::SDC0; + cout << "SD Card Test for SD card " << static_cast(sdCard) << std::endl; + // Stopwatch stopwatch; + std::system("q7hw sd info all > /tmp/sd_status.txt"); + // stopwatch.stop(true); + std::system("q7hw sd set 0 on > /tmp/sd_set.txt"); + // stopwatch.stop(true); + std::system("q7hw sd set 0 off > /tmp/sd_set.txt"); + // stopwatch.stop(true); +} + +FileSystemTest::~FileSystemTest() {} diff --git a/bsp_q7s/boardtest/FileSystemTest.h b/bsp_q7s/boardtest/FileSystemTest.h new file mode 100644 index 0000000..bdb7989 --- /dev/null +++ b/bsp_q7s/boardtest/FileSystemTest.h @@ -0,0 +1,12 @@ +#ifndef BSP_Q7S_BOARDTEST_FILESYSTEMTEST_H_ +#define BSP_Q7S_BOARDTEST_FILESYSTEMTEST_H_ + +class FileSystemTest { + public: + FileSystemTest(); + virtual ~FileSystemTest(); + + private: +}; + +#endif /* BSP_Q7S_BOARDTEST_FILESYSTEMTEST_H_ */ diff --git a/bsp_q7s/boardtest/Q7STestTask.cpp b/bsp_q7s/boardtest/Q7STestTask.cpp new file mode 100644 index 0000000..03805fd --- /dev/null +++ b/bsp_q7s/boardtest/Q7STestTask.cpp @@ -0,0 +1,458 @@ +#include "Q7STestTask.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "OBSWConfig.h" +#include "bsp_q7s/fs/SdCardManager.h" +#include "bsp_q7s/fs/helpers.h" +#include "bsp_q7s/memory/scratchApi.h" +#include "fsfw/tasks/TaskFactory.h" +#include "fsfw/timemanager/Stopwatch.h" +#include "p60pdu.h" +#include "test/DummyParameter.h" + +using namespace returnvalue; + +Q7STestTask::Q7STestTask(object_id_t objectId) : TestTask(objectId) { + doTestSdCard = false; + doTestScratchApi = false; + doTestGpsShm = false; + doTestGpsSocket = false; + doTestXadc = false; +} + +ReturnValue_t Q7STestTask::performOneShotAction() { + if (doTestSdCard) { + testSdCard(); + } + if (doTestScratchApi) { + testScratchApi(); + } + if (DO_TEST_GOMSPACE_API) { + uint8_t p60pdu_node = 3; + uint8_t hk_mem[P60PDU_HK_SIZE]; + param_index_t p60pdu_hk{}; + p60pdu_hk.physaddr = hk_mem; + if (!p60pdu_get_hk(&p60pdu_hk, p60pdu_node, 1000)) { + printf("Error getting p60pdu hk\n"); + } else { + param_list(&p60pdu_hk, 1); + } + } + + if (DO_TEST_GOMSPACE_GET_CONFIG) { + uint8_t p60pdu_node = 3; + param_index_t requestStruct{}; + requestStruct.table = p60pdu_config; + requestStruct.mem_id = P60PDU_PARAM; + uint8_t hk_mem[P60PDU_PARAM_SIZE]; + requestStruct.count = p60pdu_config_count; + requestStruct.size = P60PDU_PARAM_SIZE; + requestStruct.physaddr = hk_mem; + int result = rparam_get_full_table(&requestStruct, p60pdu_node, P60_PORT_RPARAM, + requestStruct.mem_id, 1000); + param_list(&requestStruct, 1); + return (result == 0); + } + + // testJsonLibDirect(); + // testDummyParams(); + if (doTestProtHandler) { + testProtHandler(); + } + if (DO_TEST_FS_HANDLER) { + FsOpCodes opCode = FsOpCodes::CREATE_EMPTY_FILE_IN_TMP; + testFileSystemHandlerDirect(opCode); + } + return TestTask::performOneShotAction(); +} + +ReturnValue_t Q7STestTask::performPeriodicAction() { + if (doTestGpsShm) { + testGpsDaemonShm(); + } + if (doTestGpsSocket) { + testGpsDaemonSocket(); + } + if (doTestXadc) { + xadcTest(); + } + return TestTask::performPeriodicAction(); +} + +void Q7STestTask::testSdCard() { + using namespace std; + Stopwatch stopwatch; + int result = std::system("q7hw sd info all > /tmp/sd_status.txt"); + if (result != 0) { + sif::debug << "system call failed with " << result << endl; + } + ifstream sdStatus("/tmp/sd_status.txt"); + string line; + uint8_t idx = 0; + while (std::getline(sdStatus, line)) { + std::istringstream iss(line); + string word; + while (iss >> word) { + if (word == "on") { + sif::info << "SD card " << static_cast(idx) << " is on" << endl; + } else if (word == "off") { + sif::info << "SD card " << static_cast(idx) << " is off" << endl; + } + } + idx++; + } + std::remove("/tmp/sd_status.txt"); +} + +void Q7STestTask::fileTests() { + using namespace std; + ofstream testFile("/tmp/test.txt"); + testFile << "Hallo Welt" << endl; + testFile.close(); + + system("echo \"Hallo Welt\" > /tmp/test2.txt"); + system("echo \"Hallo Welt\""); +} + +void Q7STestTask::testScratchApi() { + ReturnValue_t result = scratch::writeNumber("TEST", 1); + if (result != returnvalue::OK) { + sif::debug << "Q7STestTask::scratchApiTest: Writing number failed" << std::endl; + } + int number = 0; + result = scratch::readNumber("TEST", number); + sif::info << "Q7STestTask::testScratchApi: Value for key \"TEST\": " << number << std::endl; + if (result != returnvalue::OK) { + sif::debug << "Q7STestTask::scratchApiTest: Reading number failed" << std::endl; + } + + result = scratch::writeString("TEST2", "halloWelt"); + if (result != returnvalue::OK) { + sif::debug << "Q7STestTask::scratchApiTest: Writing string failed" << std::endl; + } + std::string string; + result = scratch::readString("TEST2", string); + if (result != returnvalue::OK) { + sif::debug << "Q7STestTask::scratchApiTest: Reading number failed" << std::endl; + } + sif::info << "Q7STestTask::testScratchApi: Value for key \"TEST2\": " << string << std::endl; + + result = scratch::clearValue("TEST"); + result = scratch::clearValue("TEST2"); +} + +void Q7STestTask::testJsonLibDirect() { + Stopwatch stopwatch; + // for convenience + using json = nlohmann::json; + json helloTest; + // add a number that is stored as double (note the implicit conversion of j to an object) + helloTest["pi"] = 3.141; + std::string mntPrefix = SdCardManager::instance()->getCurrentMountPrefix(); + std::string fileName = mntPrefix + "/pretty.json"; + std::ofstream o(fileName); + o << std::setw(4) << helloTest << std::endl; +} + +void Q7STestTask::testDummyParams() { + std::string mntPrefix = SdCardManager::instance()->getCurrentMountPrefix(); + DummyParameter param(mntPrefix, "dummy_json.txt"); + param.printKeys(); + param.print(); + if (not param.getJsonFileExists()) { + param.writeJsonFile(); + } + + ReturnValue_t result = param.readJsonFile(); + if (result != returnvalue::OK) { + } + + param.setValue(DummyParameter::DUMMY_KEY_PARAM_1, 3); + param.setValue(DummyParameter::DUMMY_KEY_PARAM_2, "blirb"); + + param.writeJsonFile(); + param.print(); + + int test = 0; + result = param.getValue(DummyParameter::DUMMY_KEY_PARAM_1, test); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testDummyParams: Key " << DummyParameter::DUMMY_KEY_PARAM_1 + << " does not exist" << std::endl; + } + std::string test2; + result = param.getValue(DummyParameter::DUMMY_KEY_PARAM_2, test2); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testDummyParams: Key " << DummyParameter::DUMMY_KEY_PARAM_1 + << " does not exist" << std::endl; + } + sif::info << "Test value (3 expected): " << test << std::endl; + sif::info << "Test value 2 (\"blirb\" expected): " << test2 << std::endl; +} + +ReturnValue_t Q7STestTask::initialize() { + coreController = ObjectManager::instance()->get(objects::CORE_CONTROLLER); + if (coreController == nullptr) { + sif::warning << "Q7STestTask::initialize: Could not retrieve CORE_CONTROLLER object" + << std::endl; + } + return TestTask::initialize(); +} + +void Q7STestTask::testProtHandler() { + bool opPerformed = false; + ReturnValue_t result = returnvalue::OK; + // If any chips are unlocked, lock them here + result = coreController->setBootCopyProtectionAndUpdateFile(xsc::Chip::CHIP_0, xsc::Copy::COPY_0, + true); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testProtHandler: Op failed" << std::endl; + } + result = coreController->setBootCopyProtectionAndUpdateFile(xsc::Chip::CHIP_0, xsc::Copy::COPY_1, + true); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testProtHandler: Op failed" << std::endl; + } + result = coreController->setBootCopyProtectionAndUpdateFile(xsc::Chip::CHIP_1, xsc::Copy::COPY_0, + true); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testProtHandler: Op failed" << std::endl; + } + result = coreController->setBootCopyProtectionAndUpdateFile(xsc::Chip::CHIP_1, xsc::Copy::COPY_1, + true); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testProtHandler: Op failed" << std::endl; + } + + // unlock own copy + result = coreController->setBootCopyProtectionAndUpdateFile(xsc::Chip::SELF_CHIP, + xsc::Copy::SELF_COPY, false); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testProtHandler: Op failed" << std::endl; + } + if (not opPerformed) { + sif::warning << "Q7STestTask::testProtHandler: No op performed" << std::endl; + } + int retval = std::system("print-chip-prot-status.sh"); + if (retval != 0) { + utility::handleSystemError(retval, "Q7STestTask::testProtHandler"); + } + + // lock own copy + result = coreController->setBootCopyProtectionAndUpdateFile(xsc::Chip::SELF_CHIP, + xsc::Copy::SELF_COPY, true); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testProtHandler: Op failed" << std::endl; + } + if (not opPerformed) { + sif::warning << "Q7STestTask::testProtHandler: No op performed" << std::endl; + } + retval = std::system("print-chip-prot-status.sh"); + if (retval != 0) { + utility::handleSystemError(retval, "Q7STestTask::testProtHandler"); + } + + // unlock specific copy + result = coreController->setBootCopyProtectionAndUpdateFile(xsc::Chip::CHIP_1, xsc::Copy::COPY_1, + false); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testProtHandler: Op failed" << std::endl; + } + if (not opPerformed) { + sif::warning << "Q7STestTask::testProtHandler: No op performed" << std::endl; + } + retval = std::system("print-chip-prot-status.sh"); + if (retval != 0) { + utility::handleSystemError(retval, "Q7STestTask::testProtHandler"); + } + + // lock specific copy + result = coreController->setBootCopyProtectionAndUpdateFile(xsc::Chip::CHIP_1, xsc::Copy::COPY_1, + true); + if (result != returnvalue::OK) { + sif::warning << "Q7STestTask::testProtHandler: Op failed" << std::endl; + } + if (not opPerformed) { + sif::warning << "Q7STestTask::testProtHandler: No op performed" << std::endl; + } + retval = std::system("print-chip-prot-status.sh"); + if (retval != 0) { + utility::handleSystemError(retval, "Q7STestTask::testProtHandler"); + } +} + +void Q7STestTask::testGpsDaemonShm() { + gpsmm gpsmm(GPSD_SHARED_MEMORY, ""); + gps_data_t* gps; + gps = gpsmm.read(); + if (gps == nullptr) { + sif::warning << "Q7STestTask: Reading GPS data failed" << std::endl; + } + sif::info << "-- Q7STestTask: GPS shared memory read test --" << std::endl; +#if LIBGPS_VERSION_MINOR <= 17 + time_t timeRaw = gps->fix.time; +#else + time_t timeRaw = gps->fix.time.tv_sec; +#endif + std::tm* time = gmtime(&timeRaw); + sif::info << "Time: " << std::put_time(time, "%c %Z") << std::endl; + sif::info << "Visible satellites: " << gps->satellites_visible << std::endl; + sif::info << "Satellites used: " << gps->satellites_used << std::endl; + sif::info << "Fix (0:Not Seen|1:No Fix|2:2D|3:3D): " << gps->fix.mode << std::endl; + sif::info << "Latitude: " << gps->fix.latitude << std::endl; + sif::info << "Longitude: " << gps->fix.longitude << std::endl; +#if LIBGPS_VERSION_MINOR <= 17 + sif::info << "Altitude(MSL): " << gps->fix.altitude << std::endl; +#else + sif::info << "Altitude(MSL): " << gps->fix.altMSL << std::endl; +#endif + sif::info << "Speed(m/s): " << gps->fix.speed << std::endl; +} + +void Q7STestTask::testGpsDaemonSocket() { + if (gpsmmShmPtr == nullptr) { + gpsmmShmPtr = new gpsmm("localhost", DEFAULT_GPSD_PORT); + } + // The data from the device will generally be read all at once. Therefore, we + // can set all field here + if (not gpsmmShmPtr->is_open()) { + if (gpsNotOpenSwitch) { + // Opening failed +#if FSFW_VERBOSE_LEVEL >= 1 + sif::warning << "Q7STestTask::testGpsDaemonSocket: Opening GPSMM failed | " + << "Error " << errno << " | " << gps_errstr(errno) << std::endl; +#endif + + gpsNotOpenSwitch = false; + } + return; + } + // Stopwatch watch; + gps_data_t* gps = nullptr; + gpsmmShmPtr->stream(WATCH_ENABLE | WATCH_JSON); + if (not gpsmmShmPtr->waiting(50000000)) { + return; + } + gps = gpsmmShmPtr->read(); + if (gps == nullptr) { + if (gpsReadFailedSwitch) { + gpsReadFailedSwitch = false; + sif::warning << "Q7STestTask::testGpsDaemonSocket: Reading GPS data failed" << std::endl; + } + return; + } + if (MODE_SET != (MODE_SET & gps->set)) { + if (noModeSetCntr >= 0) { + noModeSetCntr++; + } + if (noModeSetCntr == 10) { + // TODO: Trigger event here + sif::warning << "Q7STestTask::testGpsDaemonSocket: No mode could be " + "read for 10 consecutive reads" + << std::endl; + noModeSetCntr = -1; + } + return; + } else { + noModeSetCntr = 0; + } + sif::info << "-- Q7STestTask: GPS socket read test --" << std::endl; +#if LIBGPS_VERSION_MINOR <= 17 + time_t timeRaw = gps->fix.time; +#else + time_t timeRaw = gps->fix.time.tv_sec; +#endif + std::tm* time = gmtime(&timeRaw); + sif::info << "Time: " << std::put_time(time, "%c %Z") << std::endl; + sif::info << "Visible satellites: " << gps->satellites_visible << std::endl; + sif::info << "Satellites used: " << gps->satellites_used << std::endl; + sif::info << "Fix (0:Not Seen|1:No Fix|2:2D|3:3D): " << gps->fix.mode << std::endl; + sif::info << "Latitude: " << gps->fix.latitude << std::endl; + sif::info << "Longitude: " << gps->fix.longitude << std::endl; +} + +void Q7STestTask::testFileSystemHandlerDirect(FsOpCodes opCode) { + HostFilesystem hostFs; + auto* sdcMan = SdCardManager::instance(); + std::string mountPrefix = sdcMan->getCurrentMountPrefix(); + sif::info << "Current mount prefix: " << mountPrefix << std::endl; + auto prefixedPath = fshelpers::getPrefixedPath(*sdcMan, "conf/test.txt"); + sif::info << "Prefixed path: " << prefixedPath << std::endl; + if (opCode == FsOpCodes::CREATE_EMPTY_FILE_IN_TMP) { + FilesystemParams params("/tmp/hello.txt"); + auto res = hostFs.createFile(params); + if (res != OK) { + sif::warning << "Creating empty file in /tmp failed" << std::endl; + } + bool fileExists = std::filesystem::exists("/tmp/hello.txt"); + if (not fileExists) { + sif::warning << "File was not created!" << std::endl; + } + hostFs.removeFile("/tmp/hello.txt"); + } +} + +void Q7STestTask::xadcTest() { + ReturnValue_t result = returnvalue::OK; + float temperature = 0; + float vccPint = 0; + float vccPaux = 0; + float vccInt = 0; + float vccAux = 0; + float vccBram = 0; + float vccOddr = 0; + float vrefp = 0; + float vrefn = 0; + Xadc xadc; + result = xadc.getTemperature(temperature); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: Chip Temperature: " << temperature << " °C" << std::endl; + } + result = xadc.getVccPint(vccPint); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: VCC PS internal: " << vccPint << " mV" << std::endl; + } + result = xadc.getVccPaux(vccPaux); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: VCC PS auxilliary: " << vccPaux << " mV" << std::endl; + } + result = xadc.getVccInt(vccInt); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: VCC PL internal: " << vccInt << " mV" << std::endl; + } + result = xadc.getVccAux(vccAux); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: VCC PL auxilliary: " << vccAux << " mV" << std::endl; + } + result = xadc.getVccBram(vccBram); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: VCC BRAM: " << vccBram << " mV" << std::endl; + } + result = xadc.getVccOddr(vccOddr); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: VCC PS I/O DDR : " << vccOddr << " mV" << std::endl; + } + result = xadc.getVrefp(vrefp); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: Vrefp : " << vrefp << " mV" << std::endl; + } + result = xadc.getVrefn(vrefn); + if (result == returnvalue::OK) { + sif::info << "Q7STestTask::xadcTest: Vrefn : " << vrefn << " mV" << std::endl; + } +} diff --git a/bsp_q7s/boardtest/Q7STestTask.h b/bsp_q7s/boardtest/Q7STestTask.h new file mode 100644 index 0000000..9ba00c0 --- /dev/null +++ b/bsp_q7s/boardtest/Q7STestTask.h @@ -0,0 +1,61 @@ +#ifndef BSP_Q7S_BOARDTEST_Q7STESTTASK_H_ +#define BSP_Q7S_BOARDTEST_Q7STESTTASK_H_ + +#include + +#include "test/TestTask.h" + +class CoreController; + +class Q7STestTask : public TestTask { + public: + Q7STestTask(object_id_t objectId); + + ReturnValue_t initialize() override; + + private: + bool doTestSdCard = false; + bool doTestScratchApi = false; + static constexpr bool DO_TEST_GOMSPACE_API = false; + static constexpr bool DO_TEST_GOMSPACE_GET_CONFIG = false; + static constexpr bool DO_TEST_FS_HANDLER = false; + bool doTestGpsShm = false; + bool doTestGpsSocket = false; + bool doTestProtHandler = false; + bool doTestXadc = false; + + bool gpsNotOpenSwitch = false; + bool gpsReadFailedSwitch = false; + int32_t noModeSetCntr = 0; + gpsmm* gpsmmShmPtr = nullptr; + + CoreController* coreController = nullptr; + ReturnValue_t performOneShotAction() override; + ReturnValue_t performPeriodicAction() override; + + void testGpsDaemonShm(); + void testGpsDaemonSocket(); + + void testSdCard(); + void fileTests(); + void xadcTest(); + + void testScratchApi(); + void testJsonLibDirect(); + void testDummyParams(); + void testProtHandler(); + + enum FsOpCodes { + CREATE_EMPTY_FILE_IN_TMP, + REMOVE_TMP_FILE, + CREATE_DIR_IN_TMP, + REMOVE_EMPTY_DIR_IN_TMP, + ATTEMPT_DIR_REMOVAL_NON_EMPTY, + REMOVE_FILLED_DIR_IN_TMP, + RENAME_FILE, + APPEND_TO_FILE, + }; + void testFileSystemHandlerDirect(FsOpCodes opCode); +}; + +#endif /* BSP_Q7S_BOARDTEST_Q7STESTTASK_H_ */ diff --git a/bsp_q7s/callbacks/CMakeLists.txt b/bsp_q7s/callbacks/CMakeLists.txt new file mode 100644 index 0000000..ed2e16f --- /dev/null +++ b/bsp_q7s/callbacks/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(${OBSW_NAME} PRIVATE rwSpiCallback.cpp gnssCallback.cpp + pcduSwitchCb.cpp q7sGpioCallbacks.cpp) diff --git a/bsp_q7s/callbacks/gnssCallback.cpp b/bsp_q7s/callbacks/gnssCallback.cpp new file mode 100644 index 0000000..22ceb3a --- /dev/null +++ b/bsp_q7s/callbacks/gnssCallback.cpp @@ -0,0 +1,29 @@ +#include "gnssCallback.h" + +#include "devices/gpioIds.h" +#include "fsfw/action/HasActionsIF.h" +#include "fsfw/tasks/TaskFactory.h" + +ReturnValue_t gps::triggerGpioResetPin(const uint8_t* actionData, size_t len, void* args) { + // At least one byte which denotes which GPS to reset is required + if (len < 1 or actionData == nullptr) { + return HasActionsIF::INVALID_PARAMETERS; + } + ResetArgs* resetArgs = reinterpret_cast(args); + if (args == nullptr) { + return returnvalue::FAILED; + } + if (resetArgs->gpioComIF == nullptr) { + return returnvalue::FAILED; + } + gpioId_t gpioId; + if (actionData[0] == 0) { + gpioId = gpioIds::GNSS_0_NRESET; + } else { + gpioId = gpioIds::GNSS_1_NRESET; + } + resetArgs->gpioComIF->pullLow(gpioId); + TaskFactory::delayTask(resetArgs->waitPeriodMs); + resetArgs->gpioComIF->pullHigh(gpioId); + return returnvalue::OK; +} diff --git a/bsp_q7s/callbacks/gnssCallback.h b/bsp_q7s/callbacks/gnssCallback.h new file mode 100644 index 0000000..331f97e --- /dev/null +++ b/bsp_q7s/callbacks/gnssCallback.h @@ -0,0 +1,18 @@ +#ifndef BSP_Q7S_CALLBACKS_GNSSCALLBACK_H_ +#define BSP_Q7S_CALLBACKS_GNSSCALLBACK_H_ + +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h" + +struct ResetArgs { + LinuxLibgpioIF* gpioComIF = nullptr; + uint32_t waitPeriodMs = 100; +}; + +namespace gps { + +ReturnValue_t triggerGpioResetPin(const uint8_t* actionData, size_t len, void* args); + +} + +#endif /* BSP_Q7S_CALLBACKS_GNSSCALLBACK_H_ */ diff --git a/bsp_q7s/callbacks/pcduSwitchCb.cpp b/bsp_q7s/callbacks/pcduSwitchCb.cpp new file mode 100644 index 0000000..f9b6c76 --- /dev/null +++ b/bsp_q7s/callbacks/pcduSwitchCb.cpp @@ -0,0 +1,32 @@ +#include "pcduSwitchCb.h" + +#include + +#include "devices/gpioIds.h" + +void pcdu::switchCallback(GOMSPACE::Pdu pdu, uint8_t channel, bool state, void* args) { + LinuxLibgpioIF* gpioComIF = reinterpret_cast(args); + if (gpioComIF == nullptr) { + return; + } + if (pdu == GOMSPACE::Pdu::PDU1) { + PDU1::Channels typedChannel = static_cast(channel); + if (typedChannel == PDU1::Channels::ACS_A_SIDE) { + if (state) { + gpioComIF->pullHigh(gpioIds::GNSS_0_NRESET); + } else { + gpioComIF->pullLow(gpioIds::GNSS_0_NRESET); + } + } + + } else if (pdu == GOMSPACE::Pdu::PDU2) { + PDU2::Channels typedChannel = static_cast(channel); + if (typedChannel == PDU2::Channels::ACS_B_SIDE) { + if (state) { + gpioComIF->pullHigh(gpioIds::GNSS_1_NRESET); + } else { + gpioComIF->pullLow(gpioIds::GNSS_1_NRESET); + } + } + } +} diff --git a/bsp_q7s/callbacks/pcduSwitchCb.h b/bsp_q7s/callbacks/pcduSwitchCb.h new file mode 100644 index 0000000..4c00697 --- /dev/null +++ b/bsp_q7s/callbacks/pcduSwitchCb.h @@ -0,0 +1,14 @@ +#ifndef BSP_Q7S_CALLBACKS_PCDUSWITCHCB_H_ +#define BSP_Q7S_CALLBACKS_PCDUSWITCHCB_H_ + +#include + +#include + +namespace pcdu { + +void switchCallback(GOMSPACE::Pdu pdu, uint8_t channel, bool state, void* args); + +} + +#endif /* BSP_Q7S_CALLBACKS_PCDUSWITCHCB_H_ */ diff --git a/bsp_q7s/callbacks/q7sGpioCallbacks.cpp b/bsp_q7s/callbacks/q7sGpioCallbacks.cpp new file mode 100644 index 0000000..512050e --- /dev/null +++ b/bsp_q7s/callbacks/q7sGpioCallbacks.cpp @@ -0,0 +1,54 @@ +#include "q7sGpioCallbacks.h" + +#include +#include +#include +#include + +#include "busConf.h" + +void q7s::gpioCallbacks::initSpiCsDecoder(GpioIF* gpioComIF) { + using namespace gpio; + ReturnValue_t result; + + if (gpioComIF == nullptr) { + sif::debug << "initSpiCsDecoder: Invalid gpioComIF" << std::endl; + return; + } + + GpioCookie* spiMuxGpios = new GpioCookie; + + GpiodRegularByLineName* spiMuxBit = nullptr; + /** Setting mux bit 1 to low will disable IC21 on the interface board */ + spiMuxBit = new GpiodRegularByLineName(q7s::gpioNames::SPI_MUX_BIT_0_PIN, "SPI Mux Bit 1", + Direction::OUT, Levels::HIGH); + spiMuxGpios->addGpio(gpioIds::SPI_MUX_BIT_0, spiMuxBit); + /** Setting mux bit 2 to low disables IC1 on the TCS board */ + spiMuxBit = new GpiodRegularByLineName(q7s::gpioNames::SPI_MUX_BIT_1_PIN, "SPI Mux Bit 2", + Direction::OUT, Levels::HIGH); + spiMuxGpios->addGpio(gpioIds::SPI_MUX_BIT_1, spiMuxBit); + /** Setting mux bit 3 to low disables IC2 on the TCS board and IC22 on the interface board */ + spiMuxBit = new GpiodRegularByLineName(q7s::gpioNames::SPI_MUX_BIT_2_PIN, "SPI Mux Bit 3", + Direction::OUT, Levels::LOW); + spiMuxGpios->addGpio(gpioIds::SPI_MUX_BIT_2, spiMuxBit); + + /** The following gpios can take arbitrary initial values */ + spiMuxBit = new GpiodRegularByLineName(q7s::gpioNames::SPI_MUX_BIT_3_PIN, "SPI Mux Bit 4", + Direction::OUT, Levels::LOW); + spiMuxGpios->addGpio(gpioIds::SPI_MUX_BIT_3, spiMuxBit); + spiMuxBit = new GpiodRegularByLineName(q7s::gpioNames::SPI_MUX_BIT_4_PIN, "SPI Mux Bit 5", + Direction::OUT, Levels::LOW); + spiMuxGpios->addGpio(gpioIds::SPI_MUX_BIT_4, spiMuxBit); + spiMuxBit = new GpiodRegularByLineName(q7s::gpioNames::SPI_MUX_BIT_5_PIN, "SPI Mux Bit 6", + Direction::OUT, Levels::LOW); + spiMuxGpios->addGpio(gpioIds::SPI_MUX_BIT_5, spiMuxBit); + GpiodRegularByLineName* enRwDecoder = new GpiodRegularByLineName( + q7s::gpioNames::EN_RW_CS, "EN_RW_CS", Direction::OUT, Levels::HIGH); + spiMuxGpios->addGpio(gpioIds::EN_RW_CS, enRwDecoder); + + result = gpioComIF->addGpios(spiMuxGpios); + if (result != returnvalue::OK) { + sif::error << "initSpiCsDecoder: Failed to add SPI MUX bit GPIOs" << std::endl; + return; + } +} diff --git a/bsp_q7s/callbacks/q7sGpioCallbacks.h b/bsp_q7s/callbacks/q7sGpioCallbacks.h new file mode 100644 index 0000000..e330655 --- /dev/null +++ b/bsp_q7s/callbacks/q7sGpioCallbacks.h @@ -0,0 +1,15 @@ +#pragma once + +class GpioIF; + +namespace q7s { +namespace gpioCallbacks { + +/** + * @brief This function initializes the GPIOs used to control the SN74LVC138APWR decoders on + * the TCS Board and the interface board. + */ +void initSpiCsDecoder(GpioIF* gpioComIF); + +} // namespace gpioCallbacks +} // namespace q7s diff --git a/bsp_q7s/callbacks/rwSpiCallback.cpp b/bsp_q7s/callbacks/rwSpiCallback.cpp new file mode 100644 index 0000000..60ad566 --- /dev/null +++ b/bsp_q7s/callbacks/rwSpiCallback.cpp @@ -0,0 +1,283 @@ +#include "rwSpiCallback.h" + +#include + +#include "devices/gpioIds.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "fsfw_hal/linux/UnixFileGuard.h" +#include "fsfw_hal/linux/spi/SpiCookie.h" +#include "mission/acs/RwHandler.h" + +namespace rwSpiCallback { + +namespace { +static bool MODE_SET = false; + +ReturnValue_t openSpi(const std::string& devname, int flags, GpioIF* gpioIF, gpioId_t gpioId, + MutexIF* mutex, MutexIF::TimeoutType timeoutType, uint32_t timeoutMs, + int& fd); +/** + * @brief This function closes a spi session. Pulls the chip select to high an releases the + * mutex. + * @param gpioId Gpio ID of chip select + * @param gpioIF Pointer to gpio interface to drive the chip select + * @param mutex The spi mutex + */ +void closeSpi(int fd, gpioId_t gpioId, GpioIF* gpioIF, MutexIF* mutex); +} // namespace + +ReturnValue_t spiCallback(SpiComIF* comIf, SpiCookie* cookie, const uint8_t* sendData, + size_t sendLen, void* args) { + // Stopwatch watch; + ReturnValue_t result = returnvalue::OK; + + RwHandler* handler = reinterpret_cast(args); + if (handler == nullptr) { + sif::error << "rwSpiCallback::spiCallback: Pointer to handler is invalid" << std::endl; + return returnvalue::FAILED; + } + + uint8_t writeBuffer[2] = {}; + uint8_t writeSize = 0; + + gpioId_t gpioId = cookie->getChipSelectPin(); + GpioIF& gpioIF = comIf->getGpioInterface(); + MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING; + uint32_t timeoutMs = 0; + MutexIF* mutex = comIf->getCsMutex(); + cookie->getMutexParams(timeoutType, timeoutMs); + if (mutex == nullptr) { + sif::debug << "rwSpiCallback::spiCallback: Mutex or GPIO interface invalid" << std::endl; + return returnvalue::FAILED; + } + + int fileDescriptor = 0; + const std::string& dev = comIf->getSpiDev(); + result = openSpi(dev, O_RDWR, &gpioIF, gpioId, mutex, timeoutType, timeoutMs, fileDescriptor); + if (result != returnvalue::OK) { + return result; + } + + spi::SpiModes spiMode = spi::SpiModes::MODE_0; + uint32_t spiSpeed = 0; + cookie->getSpiParameters(spiMode, spiSpeed, nullptr); + // We are in protected section, so we can use the static variable here without issues. + // We don't need to set the speed because a SPI core is used, but the mode has to be set once + // correctly for all RWs + if (not MODE_SET) { + comIf->setSpiSpeedAndMode(fileDescriptor, spiMode, spiSpeed); + MODE_SET = true; + } + + /** Sending frame start sign */ + writeBuffer[0] = FLAG_BYTE; + writeSize = 1; + + if (write(fileDescriptor, writeBuffer, writeSize) != static_cast(writeSize)) { + sif::error << "rwSpiCallback::spiCallback: Write failed!" << std::endl; + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + return rws::SPI_WRITE_FAILURE; + } + + /** Encoding and sending command */ + size_t idx = 0; + while (idx < sendLen) { + switch (*(sendData + idx)) { + case 0x7E: + writeBuffer[0] = 0x7D; + writeBuffer[1] = 0x5E; + writeSize = 2; + break; + case 0x7D: + writeBuffer[0] = 0x7D; + writeBuffer[1] = 0x5D; + writeSize = 2; + break; + default: + writeBuffer[0] = *(sendData + idx); + writeSize = 1; + break; + } + if (write(fileDescriptor, writeBuffer, writeSize) != static_cast(writeSize)) { + sif::error << "rwSpiCallback::spiCallback: Write failed!" << std::endl; + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + return rws::SPI_WRITE_FAILURE; + } + idx++; + } + + /** Sending frame end sign */ + writeBuffer[0] = FLAG_BYTE; + writeSize = 1; + + if (write(fileDescriptor, writeBuffer, writeSize) != static_cast(writeSize)) { + sif::error << "rwSpiCallback::spiCallback: Write failed!" << std::endl; + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + return rws::SPI_WRITE_FAILURE; + } + + uint8_t* rxBuf = nullptr; + result = comIf->getReadBuffer(cookie->getSpiAddress(), &rxBuf); + if (result != returnvalue::OK) { + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + return result; + } + + size_t replyBufferSize = cookie->getMaxBufferSize(); + + // There must be a delay of at least 20 ms after sending the command. + // Delay for 70 ms here and release the SPI bus for that duration. + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + usleep(rws::SPI_REPLY_DELAY); + result = openSpi(dev, O_RDWR, &gpioIF, gpioId, mutex, timeoutType, timeoutMs, fileDescriptor); + if (result != returnvalue::OK) { + return result; + } + + /** + * The reaction wheel responds with empty frames while preparing the reply data. + * However, receiving more than 5 empty frames will be interpreted as an error. + */ + uint8_t byteRead = 0; + for (idx = 0; idx < 10; idx++) { + if (read(fileDescriptor, &byteRead, 1) != 1) { + sif::error << "rwSpiCallback::spiCallback: Read failed" << std::endl; + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + return rws::SPI_READ_FAILURE; + } + if (idx == 0) { + if (byteRead != FLAG_BYTE) { + sif::error << "Invalid data, expected start marker" << std::endl; + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + return rws::NO_START_MARKER; + } + } + + if (byteRead != FLAG_BYTE) { + break; + } + + if (idx == 9) { + sif::error << "rwSpiCallback::spiCallback: Empty frame timeout" << std::endl; + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + return rws::NO_REPLY; + } + } + +#if FSFW_HAL_SPI_WIRETAPPING == 1 + sif::info << "RW start marker detected" << std::endl; +#endif + + size_t decodedFrameLen = 0; + while (decodedFrameLen < replyBufferSize) { + /** First byte already read in */ + if (decodedFrameLen != 0) { + byteRead = 0; + if (read(fileDescriptor, &byteRead, 1) != 1) { + sif::error << "rwSpiCallback::spiCallback: Read failed" << std::endl; + result = rws::SPI_READ_FAILURE; + break; + } + } + + if (byteRead == FLAG_BYTE) { + /** Reached end of frame */ + break; + } else if (byteRead == 0x7D) { + if (read(fileDescriptor, &byteRead, 1) != 1) { + sif::error << "rwSpiCallback::spiCallback: Read failed" << std::endl; + result = rws::SPI_READ_FAILURE; + break; + } + if (byteRead == 0x5E) { + *(rxBuf + decodedFrameLen) = 0x7E; + decodedFrameLen++; + continue; + } else if (byteRead == 0x5D) { + *(rxBuf + decodedFrameLen) = 0x7D; + decodedFrameLen++; + continue; + } else { + sif::error << "rwSpiCallback::spiCallback: Invalid substitute" << std::endl; + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + result = rws::INVALID_SUBSTITUTE; + break; + } + } else { + *(rxBuf + decodedFrameLen) = byteRead; + decodedFrameLen++; + continue; + } + + /** + * There might be the unlikely case that each byte in a get-telemetry reply has been + * replaced by its substitute. Than the next byte must correspond to the end sign 0x7E. + * Otherwise there might be something wrong. + */ + if (decodedFrameLen == replyBufferSize) { + if (read(fileDescriptor, &byteRead, 1) != 1) { + sif::error << "rwSpiCallback::spiCallback: Failed to read last byte" << std::endl; + result = rws::SPI_READ_FAILURE; + break; + } + if (byteRead != FLAG_BYTE) { + sif::error << "rwSpiCallback::spiCallback: Missing end sign " << static_cast(FLAG_BYTE) + << std::endl; + decodedFrameLen--; + result = rws::MISSING_END_SIGN; + break; + } + } + result = returnvalue::OK; + } + + cookie->setTransferSize(decodedFrameLen); + + closeSpi(fileDescriptor, gpioId, &gpioIF, mutex); + + return result; +} + +namespace { + +ReturnValue_t openSpi(const std::string& devname, int flags, GpioIF* gpioIF, gpioId_t gpioId, + MutexIF* mutex, MutexIF::TimeoutType timeoutType, uint32_t timeoutMs, + int& fd) { + ReturnValue_t result = mutex->lockMutex(timeoutType, timeoutMs); + if (result != returnvalue::OK) { + sif::debug << "rwSpiCallback::spiCallback: Failed to lock mutex" << std::endl; + return result; + } + + fd = open(devname.c_str(), flags); + if (fd < 0) { + sif::error << "rwSpiCallback::spiCallback: Failed to open device file" << std::endl; + return spi::OPENING_FILE_FAILED; + } + + // Pull SPI CS low. For now, no support for active high given + if (gpioId != gpio::NO_GPIO) { + result = gpioIF->pullLow(gpioId); + if (result != returnvalue::OK) { + sif::error << "rwSpiCallback::spiCallback: Failed to pull chip select low" << std::endl; + return result; + } + } + return returnvalue::OK; +} +void closeSpi(int fd, gpioId_t gpioId, GpioIF* gpioIF, MutexIF* mutex) { + close(fd); + if (gpioId != gpio::NO_GPIO) { + if (gpioIF->pullHigh(gpioId) != returnvalue::OK) { + sif::error << "closeSpi: Failed to pull chip select high" << std::endl; + } + } + if (mutex->unlockMutex() != returnvalue::OK) { + sif::error << "rwSpiCallback::closeSpi: Failed to unlock mutex" << std::endl; + ; + } +} + +} // namespace + +} // namespace rwSpiCallback diff --git a/bsp_q7s/callbacks/rwSpiCallback.h b/bsp_q7s/callbacks/rwSpiCallback.h new file mode 100644 index 0000000..7390db5 --- /dev/null +++ b/bsp_q7s/callbacks/rwSpiCallback.h @@ -0,0 +1,37 @@ +#ifndef BSP_Q7S_RW_SPI_CALLBACK_H_ +#define BSP_Q7S_RW_SPI_CALLBACK_H_ + +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw_hal/common/gpio/GpioCookie.h" +#include "fsfw_hal/linux/spi/SpiComIF.h" + +namespace rwSpiCallback { + +//! This is the end and start marker of the frame datalinklayer +static constexpr uint8_t FLAG_BYTE = 0x7E; + +/** + * @brief This is the callback function to send commands to the nano avionics reaction wheels and + * receive the replies. + * + * @details The data to sent are additionally encoded according to the HDLC framing defined in the + * datasheet of the reaction wheels: + * https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/ + * Arbeitsdaten/08_Used%20Components/Nanoavionics_Reactionwheels&fileid=181622 + * Each command entails exactly one reply which will also be read in and decoded by this + * function. + * Because the reaction wheels require a spi clock frequency of maximum 300 kHZ and minimum + * 150 kHz which is not supported by the processing system SPI peripheral an AXI SPI core + * has been implemented in the programmable logic. This AXI SPI core works with a fixed + * frequency of 250 kHz. + * To allow the parallel usage of the same physical SPI bus, a VHDL module has been + * implemented which is able to disconnect the hard-wired SPI peripheral of the PS and + * route the AXI SPI to the SPI lines. + * To switch between the to SPI peripherals, an EMIO is used which will also be controlled + * by this function. + */ +ReturnValue_t spiCallback(SpiComIF* comIf, SpiCookie* cookie, const uint8_t* sendData, + size_t sendLen, void* args); + +} // namespace rwSpiCallback +#endif /* BSP_Q7S_RW_SPI_CALLBACK_H_ */ diff --git a/bsp_q7s/core/CMakeLists.txt b/bsp_q7s/core/CMakeLists.txt new file mode 100644 index 0000000..530b53e --- /dev/null +++ b/bsp_q7s/core/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(${OBSW_NAME} PRIVATE CoreController.cpp WatchdogHandler.cpp + XiphosWdtHandler.cpp) diff --git a/bsp_q7s/core/CoreController.cpp b/bsp_q7s/core/CoreController.cpp new file mode 100644 index 0000000..30eff4e --- /dev/null +++ b/bsp_q7s/core/CoreController.cpp @@ -0,0 +1,2633 @@ +#include "CoreController.h" + +#include +#include +#include +#include +#include + +#include "commonConfig.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "fsfw/timemanager/Stopwatch.h" +#include "fsfw/version.h" +#include "watchdog/definitions.h" +#if OBSW_ADD_TMTC_UDP_SERVER == 1 +#include "fsfw/osal/common/UdpTmTcBridge.h" +#endif +#if OBSW_ADD_TMTC_TCP_SERVER == 1 +#include "fsfw/osal/common/TcpTmTcServer.h" +#endif +#include +#include + +#include +#include + +#include "bsp_q7s/boardconfig/busConf.h" +#include "bsp_q7s/fs/SdCardManager.h" +#include "bsp_q7s/memory/scratchApi.h" +#include "bsp_q7s/xadc/Xadc.h" +#include "eive/definitions.h" +#include "linux/utility/utility.h" + +xsc::Chip CoreController::CURRENT_CHIP = xsc::Chip::NO_CHIP; +xsc::Copy CoreController::CURRENT_COPY = xsc::Copy::NO_COPY; + +CoreController::CoreController(object_id_t objectId, bool enableHkSet) + : ExtendedControllerBase(objectId, 5), + enableHkSet(enableHkSet), + cmdExecutor(4096), + cmdReplyBuf(4096, true), + cmdRepliesSizes(128), + opDivider5(5), + opDivider10(10), + hkSet(this), + paramHelper(this) { + cmdExecutor.setRingBuffer(&cmdReplyBuf, &cmdRepliesSizes); + try { + sdcMan = SdCardManager::instance(); + if (sdcMan == nullptr) { + sif::error << "CoreController::CoreController: SD card manager invalid!" << std::endl; + } + + if (not BLOCKING_SD_INIT) { + sdcMan->setBlocking(false); + } + // Set up state of SD card manager and own initial state. + // Stopwatch watch; + sdcMan->updateSdCardStateFile(); + SdCardManager::SdStatePair sdStates; + sdcMan->getSdCardsStatus(sdStates); + auto sdCard = sdcMan->getPreferredSdCard(); + if (not sdCard.has_value()) { + sif::error << "CoreController::initializeAfterTaskCreation: " + "Issues getting preferred SD card, setting to 0" + << std::endl; + sdCard = sd::SdCard::SLOT_0; + } + sdInfo.active = sdCard.value(); + if (sdStates.first == sd::SdState::MOUNTED) { + sdcMan->setActiveSdCard(sd::SdCard::SLOT_0); + } else if (sdStates.second == sd::SdState::MOUNTED) { + sdcMan->setActiveSdCard(sd::SdCard::SLOT_1); + } + currMntPrefix = sdcMan->getCurrentMountPrefix(); + + getCurrentBootCopy(CURRENT_CHIP, CURRENT_COPY); + + initClockFromTimeFile(); + } catch (const std::filesystem::filesystem_error &e) { + sif::error << "CoreController::CoreController: Failed with exception " << e.what() << std::endl; + } + // Add script folder to path + char *currentEnvPath = getenv("PATH"); + std::string updatedEnvPath = std::string(currentEnvPath) + ":/home/root/scripts:/usr/local/bin"; + setenv("PATH", updatedEnvPath.c_str(), true); + sdCardCheckCd.timeOut(); + eventQueue = QueueFactory::instance()->createMessageQueue(5, EventMessage::MAX_MESSAGE_SIZE); +} + +CoreController::~CoreController() {} + +ReturnValue_t CoreController::handleCommandMessage(CommandMessage *message) { + ReturnValue_t result = paramHelper.handleParameterMessage(message); + if (result == returnvalue::OK) { + return result; + } + return ExtendedControllerBase::handleCommandMessage(message); +} + +void CoreController::performControlOperation() { +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "CORE CTRL"); +#endif + EventMessage event; + for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::OK; + result = eventQueue->receiveMessage(&event)) { + switch (event.getEvent()) { + case (GpsHyperion::GPS_FIX_CHANGE): { + gpsFix = static_cast(event.getParameter2()); + break; + } + } + } + sdStateMachine(); + performMountedSdCardOperations(); + readHkData(); + if (dumpContext.active) { + dirListingDumpHandler(); + } + + if (shellCmdIsExecuting) { + bool replyReceived = false; + // TODO: We could read the data in the ring buffer and send it as an action data reply. + if (cmdExecutor.check(replyReceived) == CommandExecutor::EXECUTION_FINISHED) { + actionHelper.finish(true, successRecipient, core::EXECUTE_SHELL_CMD_BLOCKING); + shellCmdIsExecuting = false; + cmdReplyBuf.clear(); + while (not cmdRepliesSizes.empty()) { + cmdRepliesSizes.pop(); + } + successRecipient = MessageQueueIF::NO_QUEUE; + } + } + opDivider5.checkAndIncrement(); + opDivider10.checkAndIncrement(); +} + +ReturnValue_t CoreController::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(core::TEMPERATURE, &tempPoolEntry); + localDataPoolMap.emplace(core::PS_VOLTAGE, &psVoltageEntry); + localDataPoolMap.emplace(core::PL_VOLTAGE, &plVoltageEntry); + poolManager.subscribeForRegularPeriodicPacket({hkSet.getSid(), enableHkSet, 60.0}); + return returnvalue::OK; +} + +LocalPoolDataSetBase *CoreController::getDataSetHandle(sid_t sid) { + if (sid.ownerSetId == core::HK_SET_ID) { + return &hkSet; + } + return nullptr; +} + +ReturnValue_t CoreController::initialize() { + ReturnValue_t result = ExtendedControllerBase::initialize(); + if (result != returnvalue::OK) { + sif::warning << "CoreController::initialize: Base init failed" << std::endl; + } + + result = paramHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + + EventManagerIF *eventManager = + ObjectManager::instance()->get(objects::EVENT_MANAGER); + if (eventManager == nullptr or eventQueue == nullptr) { + sif::warning << "CoreController::initialize: No valid event manager found or " + "queue invalid" + << std::endl; + } + result = eventManager->registerListener(eventQueue->getId()); + if (result != returnvalue::OK) { + sif::warning << "CoreController::initialize: Registering as event listener failed" << std::endl; + } + result = eventManager->subscribeToEvent(eventQueue->getId(), + event::getEventId(GpsHyperion::GPS_FIX_CHANGE)); + if (result != returnvalue::OK) { + sif::warning << "Subscribing for GPS GPS_FIX_CHANGE event failed" << std::endl; + } + triggerEvent(core::REBOOT_SW, CURRENT_CHIP, CURRENT_COPY); + announceCurrentImageInfo(); + announceVersionInfo(); + SdCardManager::SdStatePair sdStates; + sdcMan->getSdCardsStatus(sdStates); + announceSdInfo(sdStates); + sdStateMachine(); + result = scratch::writeNumber(scratch::ALLOC_FAILURE_COUNT, 0); + if (result != returnvalue::OK) { + sif::warning << "CoreController::initialize: Setting up alloc failure " + "count failed" + << std::endl; + } + return result; +} + +ReturnValue_t CoreController::initializeAfterTaskCreation() { + ReturnValue_t result = returnvalue::OK; + if (BLOCKING_SD_INIT) { + result = initSdCardBlocking(); + if (result != returnvalue::OK and result != SdCardManager::ALREADY_MOUNTED) { + sif::warning << "CoreController::CoreController: SD card init failed" << std::endl; + } + } + sdStateMachine(); + performMountedSdCardOperations(); + if (result != returnvalue::OK) { + sif::warning << "CoreController::initialize: Version initialization failed" << std::endl; + } + updateProtInfo(); + return ExtendedControllerBase::initializeAfterTaskCreation(); +} + +ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t *data, size_t size) { + using namespace core; + switch (actionId) { + case (ANNOUNCE_VERSION): { + announceVersionInfo(); + return HasActionsIF::EXECUTION_FINISHED; + } + case (ANNOUNCE_BOOT_COUNTS): { + announceBootCounts(); + return HasActionsIF::EXECUTION_FINISHED; + } + case (ANNOUNCE_CURRENT_IMAGE): { + announceCurrentImageInfo(); + return HasActionsIF::EXECUTION_FINISHED; + } + case (LIST_DIRECTORY_INTO_FILE): { + return actionListDirectoryIntoFile(actionId, commandedBy, data, size); + } + case (LIST_DIRECTORY_DUMP_DIRECTLY): { + return actionListDirectoryDumpDirectly(actionId, commandedBy, data, size); + } + case (CP_HELPER): { + CpHelperParser parser(data, size); + ReturnValue_t result = parser.parse(); + if (result != returnvalue::OK) { + return result; + } + std::ostringstream oss("cp ", std::ostringstream::ate); + if (parser.isForceOptSet()) { + oss << "-f "; + } + if (parser.isRecursiveOptSet()) { + oss << "-r "; + } + auto &sourceTgt = parser.destTgtPair(); + oss << sourceTgt.sourceName << " " << sourceTgt.targetName; + sif::info << "CoreController: Performing copy command: " << oss.str() << std::endl; + int ret = std::system(oss.str().c_str()); + if (ret != 0) { + return returnvalue::FAILED; + } + return EXECUTION_FINISHED; + } + case (MV_HELPER): { + MvHelperParser parser(data, size); + ReturnValue_t result = parser.parse(); + if (result != returnvalue::OK) { + return result; + } + std::ostringstream oss("mv ", std::ostringstream::ate); + auto &sourceTgt = parser.destTgtPair(); + oss << sourceTgt.sourceName << " " << sourceTgt.targetName; + sif::info << "CoreController: Performing move command: " << oss.str() << std::endl; + int ret = std::system(oss.str().c_str()); + if (ret != 0) { + return returnvalue::FAILED; + } + return EXECUTION_FINISHED; + } + case (RM_HELPER): { + RmHelperParser parser(data, size); + ReturnValue_t result = parser.parse(); + if (result != returnvalue::OK) { + return result; + } + std::ostringstream oss("rm ", std::ostringstream::ate); + if (parser.isRecursiveOptSet() or parser.isForceOptSet()) { + oss << "-"; + } + if (parser.isRecursiveOptSet()) { + oss << "r"; + } + if (parser.isForceOptSet()) { + oss << "f"; + } + size_t removeTargetSize = 0; + const char *removeTgt = parser.getRemoveTarget(removeTargetSize); + oss << " " << removeTgt; + sif::info << "CoreController: Performing remove command: " << oss.str() << std::endl; + int ret = std::system(oss.str().c_str()); + if (ret != 0) { + return returnvalue::FAILED; + } + return EXECUTION_FINISHED; + } + case (MKDIR_HELPER): { + if (size < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + std::string createdDir = std::string(reinterpret_cast(data), size); + std::ostringstream oss("mkdir ", std::ostringstream::ate); + oss << createdDir; + sif::info << "CoreController: Performing directory creation: " << oss.str() << std::endl; + int ret = std::system(oss.str().c_str()); + if (ret != 0) { + return returnvalue::FAILED; + } + return EXECUTION_FINISHED; + } + case (SWITCH_REBOOT_FILE_HANDLING): { + if (size < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE; + parseRebootWatchdogFile(path, rebootWatchdogFile); + if (data[0] == 0) { + rebootWatchdogFile.enabled = false; + rewriteRebootWatchdogFile(rebootWatchdogFile); + } else if (data[0] == 1) { + rebootWatchdogFile.enabled = true; + rewriteRebootWatchdogFile(rebootWatchdogFile); + } else { + return HasActionsIF::INVALID_PARAMETERS; + } + return HasActionsIF::EXECUTION_FINISHED; + } + case (READ_REBOOT_MECHANISM_INFO): { + std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE; + parseRebootWatchdogFile(path, rebootWatchdogFile); + RebootWatchdogPacket packet(rebootWatchdogFile); + ReturnValue_t result = actionHelper.reportData(commandedBy, actionId, &packet); + if (result != returnvalue::OK) { + return result; + } + return HasActionsIF::EXECUTION_FINISHED; + } + case (RESET_REBOOT_COUNTERS): { + if (size == 0) { + resetRebootWatchdogCounters(xsc::ALL_CHIP, xsc::ALL_COPY); + } else if (size == 2) { + if (data[0] > 1 or data[1] > 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + resetRebootWatchdogCounters(static_cast(data[0]), + static_cast(data[1])); + } + return HasActionsIF::EXECUTION_FINISHED; + } + case (OBSW_UPDATE_FROM_SD_0): { + return executeSwUpdate(SwUpdateSources::SD_0, data, size); + } + case (OBSW_UPDATE_FROM_SD_1): { + return executeSwUpdate(SwUpdateSources::SD_1, data, size); + } + case (OBSW_UPDATE_FROM_TMP): { + return executeSwUpdate(SwUpdateSources::TMP_DIR, data, size); + } + case (SWITCH_TO_SD_0): { + if (not startSdStateMachine(sd::SdCard::SLOT_0, SdCfgMode::COLD_REDUNDANT, commandedBy, + actionId)) { + return HasActionsIF::IS_BUSY; + } + // Completion will be reported by SD card state machine + return returnvalue::OK; + } + case (SWITCH_TO_SD_1): { + if (not startSdStateMachine(sd::SdCard::SLOT_1, SdCfgMode::COLD_REDUNDANT, commandedBy, + actionId)) { + return HasActionsIF::IS_BUSY; + } + // Completion will be reported by SD card state machine + return returnvalue::OK; + } + case (SWITCH_TO_BOTH_SD_CARDS): { + // An active SD still needs to be specified because the system needs to know which SD + // card to use for regular operations like telemetry storage. + if (size != 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + if (data[0] != 0 and data[0] != 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + auto active = static_cast(data[0]); + if (not startSdStateMachine(active, SdCfgMode::HOT_REDUNDANT, commandedBy, actionId)) { + return HasActionsIF::IS_BUSY; + } + // Completion will be reported by SD card state machine + return returnvalue::OK; + } + case (SYSTEMCTL_CMD_EXECUTOR): { + // Expect one byte systemctl command type and a unit name with at least one byte as minimum. + if (size < 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + if (data[0] >= core::SystemctlCmd::NUM_CMDS) { + return HasActionsIF::INVALID_PARAMETERS; + } + core::SystemctlCmd cmdType = static_cast(data[0]); + std::string unitName = std::string(reinterpret_cast(data + 1), size - 1); + std::ostringstream oss("systemctl ", std::ostringstream::ate); + switch (cmdType) { + case (core::SystemctlCmd::START): { + oss << "start "; + break; + } + case (core::SystemctlCmd::STOP): { + oss << "stop "; + break; + } + case (core::SystemctlCmd::RESTART): { + oss << "restart "; + break; + } + default: { + return HasActionsIF::INVALID_PARAMETERS; + } + } + oss << unitName; + int result = std::system(oss.str().c_str()); + if (result != 0) { + return returnvalue::FAILED; + } + return EXECUTION_FINISHED; + } + case (SWITCH_IMG_LOCK): { + if (size != 3) { + return HasActionsIF::INVALID_PARAMETERS; + } + if (data[1] > 1 or data[2] > 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + setRebootMechanismLock(data[0], static_cast(data[1]), + static_cast(data[2])); + return HasActionsIF::EXECUTION_FINISHED; + } + case (SET_MAX_REBOOT_CNT): { + if (size < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + std::string path = sdcMan->getCurrentMountPrefix() + REBOOT_WATCHDOG_FILE; + // Disable the reboot file mechanism + parseRebootWatchdogFile(path, rebootWatchdogFile); + rebootWatchdogFile.maxCount = data[0]; + rewriteRebootWatchdogFile(rebootWatchdogFile); + return HasActionsIF::EXECUTION_FINISHED; + } + case (XSC_REBOOT_OBC): { + // Warning: This function will never return, because it reboots the system + return actionXscReboot(data, size); + } + case (REBOOT_OBC): { + // Warning: This function will never return, because it reboots the system + return actionReboot(data, size); + } + case (EXECUTE_SHELL_CMD_BLOCKING): { + std::string cmdToExecute = std::string(reinterpret_cast(data), size); + int result = std::system(cmdToExecute.c_str()); + if (result != 0) { + // TODO: Data reply with returnalue maybe? + return returnvalue::FAILED; + } + return EXECUTION_FINISHED; + } + case (EXECUTE_SHELL_CMD_NON_BLOCKING): { + std::string cmdToExecute = std::string(reinterpret_cast(data), size); + if (cmdExecutor.getCurrentState() == CommandExecutor::States::PENDING or + shellCmdIsExecuting) { + return HasActionsIF::IS_BUSY; + } + cmdExecutor.load(cmdToExecute, false, false); + ReturnValue_t result = cmdExecutor.execute(); + if (result != returnvalue::OK) { + return result; + } + shellCmdIsExecuting = true; + successRecipient = commandedBy; + return returnvalue::OK; + } + case (UPDATE_LEAP_SECONDS): { + if (size != sizeof(uint16_t)) { + return HasActionsIF::INVALID_PARAMETERS; + } + ReturnValue_t result = actionUpdateLeapSeconds(data); + if (result != returnvalue::OK) { + return result; + } + return HasActionsIF::EXECUTION_FINISHED; + } + default: { + return HasActionsIF::INVALID_ACTION_ID; + } + } +} + +ReturnValue_t CoreController::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t *msToReachTheMode) { + return returnvalue::OK; +} + +ReturnValue_t CoreController::initSdCardBlocking() { + // Create update status file + ReturnValue_t result = sdcMan->updateSdCardStateFile(); + if (result != returnvalue::OK) { + sif::warning << "CoreController::initialize: Updating SD card state file failed" << std::endl; + } + if (sdInfo.cfgMode == SdCfgMode::PASSIVE) { + sif::info << "No SD card initialization will be performed" << std::endl; + return returnvalue::OK; + } + + result = sdcMan->getSdCardsStatus(sdInfo.currentState); + if (result != returnvalue::OK) { + sif::warning << "Getting SD card activity status failed" << std::endl; + } + + if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) { + updateInternalSdInfo(); + sif::info << "Cold redundant SD card configuration, preferred SD card: " + << static_cast(sdInfo.active) << std::endl; + result = sdColdRedundantBlockingInit(); + // Update status file + sdcMan->updateSdCardStateFile(); + return result; + } + if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) { + sif::info << "Hot redundant SD card configuration" << std::endl; + sdCardSetup(sd::SdCard::SLOT_0, sd::SdState::MOUNTED, "0", false); + sdCardSetup(sd::SdCard::SLOT_1, sd::SdState::MOUNTED, "1", false); + // Update status file + sdcMan->updateSdCardStateFile(); + } + return returnvalue::OK; +} + +ReturnValue_t CoreController::sdStateMachine() { + ReturnValue_t result = returnvalue::OK; + SdCardManager::Operations operation; + + if (sdFsmState == SdStates::IDLE) { + // Nothing to do + return result; + } + + if (sdFsmState == SdStates::START) { + // Init will be performed by separate function + if (BLOCKING_SD_INIT) { + sdFsmState = SdStates::IDLE; + sdInfo.initFinished = true; + return result; + } else { + // Still update SD state file + if (sdInfo.cfgMode == SdCfgMode::PASSIVE) { + sdFsmState = SdStates::UPDATE_SD_INFO_END; + } else { + sdInfo.cycleCount = 0; + sdInfo.commandPending = false; + sdFsmState = SdStates::UPDATE_SD_INFO_START; + } + } + } + + // This lambda checks the non-blocking operation of the SD card manager and assigns the new + // state on success. It returns 0 for an operation success, -1 for failed operations, and 1 + // for pending operations + auto nonBlockingSdcOpChecking = [&](SdStates newStateOnSuccess, uint16_t maxCycleCount, + std::string opPrintout) { + SdCardManager::OpStatus status = sdcMan->checkCurrentOp(operation); + if (status == SdCardManager::OpStatus::SUCCESS or sdInfo.cycleCount > maxCycleCount) { + sdFsmState = newStateOnSuccess; + sdInfo.commandPending = false; + if (sdInfo.cycleCount > maxCycleCount) { + sif::warning << "CoreController::sdStateMachine: " << opPrintout << " takes too long" + << std::endl; + sdInfo.cycleCount = 0; + return -1; + } + sdInfo.cycleCount = 0; + return 0; + }; + return 1; + }; + + if (sdFsmState == SdStates::UPDATE_SD_INFO_START) { + if (not sdInfo.commandPending) { + // Create updated status file + result = sdcMan->updateSdCardStateFile(); + if (result != returnvalue::OK) { + sif::warning << "CoreController::sdStateMachine: Updating SD card state file failed" + << std::endl; + } + result = sdcMan->getSdCardsStatus(sdInfo.currentState); + updateInternalSdInfo(); + auto currentlyActiveSdc = sdcMan->getActiveSdCard(); + // Used/active SD card switches, so mark SD card unusable so other tasks have some time + // registering the unavailable SD card. + if (not currentlyActiveSdc.has_value() or + ((currentlyActiveSdc.value() == sd::SdCard::SLOT_0) and + (sdInfo.active == sd::SdCard::SLOT_1)) or + ((currentlyActiveSdc.value() == sd::SdCard::SLOT_1) and + (sdInfo.active == sd::SdCard::SLOT_0))) { + sdInfo.lockSdCardUsage = true; + } + if (sdInfo.lockSdCardUsage) { + sdcMan->markUnusable(); + } + if (sdInfo.active != sd::SdCard::SLOT_0 and sdInfo.active != sd::SdCard::SLOT_1) { + sif::warning << "Preferred SD card invalid. Setting to card 0.." << std::endl; + sdInfo.active = sd::SdCard::SLOT_0; + } + if (result != returnvalue::OK) { + sif::warning << "Getting SD card activity status failed" << std::endl; + } + if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) { + sif::info << "Cold redundant SD card configuration, target SD card: " + << static_cast(sdInfo.active) << std::endl; + } + SdStates tgtState = SdStates::IDLE; + bool skipCycles = sdInfo.lockSdCardUsage; + // Need to do different things depending on state of SD card which will be active. + if (sdInfo.activeState == sd::SdState::MOUNTED) { + // Already mounted, so we can perform handling of the other side. +#if OBSW_VERBOSE_LEVEL >= 1 + std::string mountString; + if (sdInfo.active == sd::SdCard::SLOT_0) { + mountString = config::SD_0_MOUNT_POINT; + } else { + mountString = config::SD_1_MOUNT_POINT; + } + sif::info << "SD card " << sdInfo.activeChar << " already on and mounted at " << mountString + << std::endl; +#endif + sdcMan->setActiveSdCard(sdInfo.active); + currMntPrefix = sdcMan->getCurrentMountPrefix(); + tgtState = SdStates::DETERMINE_OTHER; + } else if (sdInfo.activeState == sd::SdState::OFF) { + // It's okay to do the delay after swichting active SD on, no one can use it anyway.. + sdCardSetup(sdInfo.active, sd::SdState::ON, sdInfo.activeChar, false); + sdInfo.commandPending = true; + // Do not skip cycles here, would mess up the state machine. We skip the cycles after + // the SD card was switched on. + skipCycles = false; + // Remain on the current state. + tgtState = sdFsmState; + } else if (sdInfo.activeState == sd::SdState::ON) { + // We can do the delay before mounting where applicable. + tgtState = SdStates::MOUNT_SELF; + } + if (skipCycles) { + sdFsmState = SdStates::SKIP_TWO_CYCLES_IF_SD_LOCKED; + fsmStateAfterDelay = tgtState; + sdInfo.skippedCyclesCount = 0; + } else { + sdFsmState = tgtState; + } + } else { + if (nonBlockingSdcOpChecking(SdStates::MOUNT_SELF, 10, "Setting SDC state") <= 0) { + sdInfo.activeState = sd::SdState::ON; + currentStateSetter(sdInfo.active, sd::SdState::ON); + // Skip the two cycles now. + if (sdInfo.lockSdCardUsage) { + sdFsmState = SdStates::SKIP_TWO_CYCLES_IF_SD_LOCKED; + fsmStateAfterDelay = SdStates::MOUNT_SELF; + sdInfo.skippedCyclesCount = 0; + } + } + } + } + + if (sdFsmState == SdStates::SKIP_TWO_CYCLES_IF_SD_LOCKED) { + sdInfo.skippedCyclesCount++; + // Count to three because this branch will run in the same FSM cycle. + if (sdInfo.skippedCyclesCount == 3) { + sdFsmState = fsmStateAfterDelay; + fsmStateAfterDelay = SdStates::IDLE; + sdInfo.skippedCyclesCount = 0; + } + } + + if (sdFsmState == SdStates::MOUNT_SELF) { + if (not sdInfo.commandPending) { + result = sdCardSetup(sdInfo.active, sd::SdState::MOUNTED, sdInfo.activeChar); + sdInfo.commandPending = true; + } else { + if (nonBlockingSdcOpChecking(SdStates::DETERMINE_OTHER, 5, "Mounting SD card") <= 0) { + sdcMan->setActiveSdCard(sdInfo.active); + currMntPrefix = sdcMan->getCurrentMountPrefix(); + sdInfo.activeState = sd::SdState::MOUNTED; + currentStateSetter(sdInfo.active, sd::SdState::MOUNTED); + } + } + } + + if (sdFsmState == SdStates::DETERMINE_OTHER) { + // Determine whether any additional operations have to be done for the other SD card + // 1. Cold redundant case: Other SD card needs to be unmounted and switched off + // 2. Hot redundant case: Other SD card needs to be mounted and switched on + if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) { + if (sdInfo.otherState == sd::SdState::ON) { + sdFsmState = SdStates::SET_STATE_OTHER; + } else if (sdInfo.otherState == sd::SdState::MOUNTED) { + sdFsmState = SdStates::MOUNT_UNMOUNT_OTHER; + } else { + // Is already off, update info, but with a small delay + sdFsmState = SdStates::SKIP_CYCLE_BEFORE_INFO_UPDATE; + } + } else if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) { + if (sdInfo.otherState == sd::SdState::OFF) { + sdFsmState = SdStates::SET_STATE_OTHER; + } else if (sdInfo.otherState == sd::SdState::ON) { + sdFsmState = SdStates::MOUNT_UNMOUNT_OTHER; + } else { + // Is already on and mounted, update info + sdFsmState = SdStates::SKIP_CYCLE_BEFORE_INFO_UPDATE; + } + } + } + + if (sdFsmState == SdStates::SET_STATE_OTHER) { + // Set state of other SD card to ON or OFF, depending on redundancy mode + if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) { + if (not sdInfo.commandPending) { + result = sdCardSetup(sdInfo.other, sd::SdState::OFF, sdInfo.otherChar, false); + sdInfo.commandPending = true; + } else { + if (nonBlockingSdcOpChecking(SdStates::SKIP_CYCLE_BEFORE_INFO_UPDATE, 10, + "Switching off other SD card") <= 0) { + sdInfo.otherState = sd::SdState::OFF; + currentStateSetter(sdInfo.other, sd::SdState::OFF); + } + } + } else if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) { + if (not sdInfo.commandPending) { + result = sdCardSetup(sdInfo.other, sd::SdState::ON, sdInfo.otherChar, false); + sdInfo.commandPending = true; + } else { + if (nonBlockingSdcOpChecking(SdStates::MOUNT_UNMOUNT_OTHER, 10, + "Switching on other SD card") <= 0) { + sdInfo.otherState = sd::SdState::ON; + currentStateSetter(sdInfo.other, sd::SdState::ON); + } + } + } + } + + if (sdFsmState == SdStates::MOUNT_UNMOUNT_OTHER) { + // Mount or unmount other SD card, depending on redundancy mode + if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) { + if (not sdInfo.commandPending) { + result = sdCardSetup(sdInfo.other, sd::SdState::ON, sdInfo.otherChar); + sdInfo.commandPending = true; + } else { + if (nonBlockingSdcOpChecking(SdStates::SET_STATE_OTHER, 10, "Unmounting other SD card") <= + 0) { + sdInfo.otherState = sd::SdState::ON; + currentStateSetter(sdInfo.other, sd::SdState::ON); + } else { + sdInfo.otherState = sd::SdState::ON; + currentStateSetter(sdInfo.other, sd::SdState::ON); + sdFsmState = SdStates::SET_STATE_OTHER; + } + } + } else if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) { + if (not sdInfo.commandPending) { + result = sdCardSetup(sdInfo.other, sd::SdState::MOUNTED, sdInfo.otherChar); + sdInfo.commandPending = true; + } else { + if (nonBlockingSdcOpChecking(SdStates::UPDATE_SD_INFO_END, 4, "Mounting other SD card") <= + 0) { + sdInfo.otherState = sd::SdState::MOUNTED; + currentStateSetter(sdInfo.other, sd::SdState::MOUNTED); + } + } + } + } + + if (sdFsmState == SdStates::SKIP_CYCLE_BEFORE_INFO_UPDATE) { + sdFsmState = SdStates::UPDATE_SD_INFO_END; + } else if (sdFsmState == SdStates::UPDATE_SD_INFO_END) { + // Update status file + result = sdcMan->updateSdCardStateFile(); + if (result != returnvalue::OK) { + sif::warning << "CoreController: Updating SD card state file failed" << std::endl; + } + updateInternalSdInfo(); + // Mark usable again in any case. + sdcMan->markUsable(); + sdInfo.commandPending = false; + sdFsmState = SdStates::IDLE; + sdInfo.cycleCount = 0; + sdcMan->setBlocking(false); + sdcMan->getSdCardsStatus(sdInfo.currentState); + if (sdCommandingInfo.cmdPending) { + sdCommandingInfo.cmdPending = false; + actionHelper.finish(true, sdCommandingInfo.commander, sdCommandingInfo.actionId, + returnvalue::OK); + } + const char *modeStr = "UNKNOWN"; + if (sdInfo.cfgMode == SdCfgMode::COLD_REDUNDANT) { + modeStr = "COLD REDUNDANT"; + } else if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT) { + modeStr = "HOT REDUNDANT"; + } + sif::info << "SD card update into " << modeStr + << " mode finished. Active SD: " << sdInfo.activeChar << std::endl; + announceSdInfo(sdInfo.currentState); + if (not sdInfo.initFinished) { + updateInternalSdInfo(); + sdInfo.initFinished = true; + sif::info << "SD card initialization finished" << std::endl; + } + } + + sdInfo.cycleCount++; + return returnvalue::OK; +} + +void CoreController::currentStateSetter(sd::SdCard sdCard, sd::SdState newState) { + if (sdCard == sd::SdCard::SLOT_0) { + sdInfo.currentState.first = newState; + } else { + sdInfo.currentState.second = newState; + } +} + +ReturnValue_t CoreController::sdCardSetup(sd::SdCard sdCard, sd::SdState targetState, + std::string sdChar, bool printOutput) { + std::string mountString; + sdcMan->setPrintCommandOutput(printOutput); + if (sdCard == sd::SdCard::SLOT_0) { + mountString = config::SD_0_MOUNT_POINT; + } else { + mountString = config::SD_1_MOUNT_POINT; + } + + sd::SdState state = sd::SdState::OFF; + if (sdCard == sd::SdCard::SLOT_0) { + state = sdInfo.currentState.first; + } else { + state = sdInfo.currentState.second; + } + if (state == sd::SdState::MOUNTED) { + if (targetState == sd::SdState::OFF) { + sif::info << "Switching off SD card " << sdChar << std::endl; + return sdcMan->switchOffSdCard(sdCard, sdInfo.currentState, true); + } else if (targetState == sd::SdState::ON) { + sif::info << "Unmounting SD card " << sdChar << std::endl; + return sdcMan->unmountSdCard(sdCard); + } else { + std::error_code e; + if (std::filesystem::exists(mountString, e)) { + sif::info << "SD card " << sdChar << " already on and mounted at " << mountString + << std::endl; + return SdCardManager::ALREADY_MOUNTED; + } + sif::error << "SD card mounted but expected mount point " << mountString << " not found!" + << std::endl; + return SdCardManager::MOUNT_ERROR; + } + } + + if (state == sd::SdState::OFF) { + if (targetState == sd::SdState::MOUNTED) { + sif::info << "Switching on and mounting SD card " << sdChar << " at " << mountString + << std::endl; + return sdcMan->switchOnSdCard(sdCard, true, &sdInfo.currentState); + } else if (targetState == sd::SdState::ON) { + sif::info << "Switching on SD card " << sdChar << std::endl; + return sdcMan->switchOnSdCard(sdCard, false, &sdInfo.currentState); + } + } + + else if (state == sd::SdState::ON) { + if (targetState == sd::SdState::MOUNTED) { + sif::info << "Mounting SD card " << sdChar << " at " << mountString << std::endl; + return sdcMan->mountSdCard(sdCard); + } else if (targetState == sd::SdState::OFF) { + sif::info << "Switching off SD card " << sdChar << std::endl; + return sdcMan->switchOffSdCard(sdCard, sdInfo.currentState, false); + } + } else { + sif::warning << "CoreController::sdCardSetup: Invalid state for this call" << std::endl; + } + return returnvalue::OK; +} + +ReturnValue_t CoreController::sdColdRedundantBlockingInit() { + ReturnValue_t result = returnvalue::OK; + + result = sdCardSetup(sdInfo.active, sd::SdState::MOUNTED, sdInfo.activeChar); + if (result != SdCardManager::ALREADY_MOUNTED and result != returnvalue::OK) { + sif::warning << "Setting up preferred card " << sdInfo.otherChar + << " in cold redundant mode failed" << std::endl; + // Try other SD card and mark set up operation as failed + sdCardSetup(sdInfo.active, sd::SdState::MOUNTED, sdInfo.activeChar); + result = returnvalue::FAILED; + } + + if (result != returnvalue::FAILED and sdInfo.otherState != sd::SdState::OFF) { + sif::info << "Switching off secondary SD card " << sdInfo.otherChar << std::endl; + // Switch off other SD card in cold redundant mode if setting up preferred one worked + // without issues + ReturnValue_t result2 = sdcMan->switchOffSdCard(sdInfo.other, sdInfo.currentState, true); + if (result2 != returnvalue::OK and result2 != SdCardManager::ALREADY_OFF) { + sif::warning << "Switching off secondary SD card " << sdInfo.otherChar + << " in cold redundant mode failed" << std::endl; + } + } + return result; +} + +ReturnValue_t CoreController::incrementAllocationFailureCount() { + uint32_t count = 0; + ReturnValue_t result = scratch::readNumber(scratch::ALLOC_FAILURE_COUNT, count); + if (result != returnvalue::OK) { + return result; + } + count++; + return scratch::writeNumber(scratch::ALLOC_FAILURE_COUNT, count); +} + +ReturnValue_t CoreController::initVersionFile() { + using namespace fsfw; + std::string unameFileName = "/tmp/uname_version.txt"; + // TODO: No -v flag for now. If the kernel version is used, need to cut off first few letters + std::string unameCmd = "uname -mnrso > " + unameFileName; + int result = std::system(unameCmd.c_str()); + if (result != 0) { + utility::handleSystemError(result, "CoreController::versionFileInit"); + } + std::ifstream unameFile(unameFileName); + std::string unameLine; + if (not std::getline(unameFile, unameLine)) { + sif::warning << "CoreController::versionFileInit: Retrieving uname line failed" << std::endl; + } + + std::string fullObswVersionString = "OBSW: v" + std::to_string(common::OBSW_VERSION_MAJOR) + "." + + std::to_string(common::OBSW_VERSION_MINOR) + "." + + std::to_string(common::OBSW_VERSION_REVISION); + char versionString[16] = {}; + fsfw::FSFW_VERSION.getVersion(versionString, sizeof(versionString)); + std::string fullFsfwVersionString = "FSFW: v" + std::string(versionString); + std::string systemString = "System: " + unameLine; + std::string versionFilePath = currMntPrefix + VERSION_FILE; + std::fstream versionFile; + + std::error_code e; + if (not std::filesystem::exists(versionFilePath, e)) { + sif::info << "Writing version file " << versionFilePath << ".." << std::endl; + versionFile.open(versionFilePath, std::ios_base::out); + versionFile << fullObswVersionString << std::endl; + versionFile << fullFsfwVersionString << std::endl; + versionFile << systemString << std::endl; + return returnvalue::OK; + } + + // Check whether any version has changed + bool createNewFile = false; + versionFile.open(versionFilePath); + std::string currentVersionString; + uint8_t idx = 0; + while (std::getline(versionFile, currentVersionString)) { + if (idx == 0) { + if (currentVersionString != fullObswVersionString) { + sif::info << "OBSW version changed" << std::endl; + sif::info << "From " << currentVersionString << " to " << fullObswVersionString + << std::endl; + createNewFile = true; + } + } else if (idx == 1) { + if (currentVersionString != fullFsfwVersionString) { + sif::info << "FSFW version changed" << std::endl; + sif::info << "From " << currentVersionString << " to " << fullFsfwVersionString + << std::endl; + createNewFile = true; + } + } else if (idx == 2) { + if (currentVersionString != systemString) { + sif::info << "System version changed" << std::endl; + sif::info << "Old: " << currentVersionString << std::endl; + sif::info << "New: " << systemString << std::endl; + createNewFile = true; + } + } else { + sif::warning << "Invalid version file! Rewriting it.." << std::endl; + createNewFile = true; + } + idx++; + } + + // Overwrite file if necessary + if (createNewFile) { + sif::info << "Rewriting version.txt file with updated versions.." << std::endl; + versionFile.close(); + versionFile.open(versionFilePath, std::ios_base::out | std::ios_base::trunc); + versionFile << fullObswVersionString << std::endl; + versionFile << fullFsfwVersionString << std::endl; + versionFile << systemString << std::endl; + } + + return returnvalue::OK; +} + +ReturnValue_t CoreController::actionListDirectoryDumpDirectly(ActionId_t actionId, + MessageQueueId_t commandedBy, + const uint8_t *data, size_t size) { + core::ListDirectoryCmdBase parser(data, size); + ReturnValue_t result = parser.parse(); + if (result != returnvalue::OK) { + return result; + } + + std::ostringstream oss("ls -l", std::ostringstream::ate); + if (parser.aFlagSet()) { + oss << "a"; + } + if (parser.rFlagSet()) { + oss << "R"; + } + + size_t repoNameLen = 0; + const char *repoName = parser.getRepoName(repoNameLen); + + oss << " " << repoName << " > " << LIST_DIR_DUMP_WORK_FILE; + sif::info << "Executing " << oss.str() << " for direct dump"; + if (parser.compressionOptionSet()) { + sif::info << " with compression"; + } + sif::info << std::endl; + int ret = std::system(oss.str().c_str()); + if (ret != 0) { + utility::handleSystemError(result, "CoreController::actionListDirectoryDumpDirectly"); + return returnvalue::FAILED; + } + if (parser.compressionOptionSet()) { + std::string compressedName = LIST_DIR_DUMP_WORK_FILE + std::string(".gz"); + oss.str(""); + oss << "gzip " << LIST_DIR_DUMP_WORK_FILE; + ret = std::system(oss.str().c_str()); + if (ret != 0) { + utility::handleSystemError(result, "CoreController::actionListDirectoryDumpDirectly"); + return returnvalue::FAILED; + } + oss.str(""); + // Overwrite the work file with the compressed archive. + oss << "mv " << compressedName << " " << LIST_DIR_DUMP_WORK_FILE; + ret = std::system(oss.str().c_str()); + if (ret != 0) { + utility::handleSystemError(result, "CoreController::actionListDirectoryDumpDirectly"); + return returnvalue::FAILED; + } + } + dirListingBuf[8] = parser.compressionOptionSet(); + // First four bytes reserved for segment index. One byte for compression option information + std::strcpy(reinterpret_cast(dirListingBuf.data() + 2 * sizeof(uint32_t) + 1), repoName); + std::ifstream ifile(LIST_DIR_DUMP_WORK_FILE, std::ios::binary); + if (ifile.bad()) { + return returnvalue::FAILED; + } + std::error_code e; + dumpContext.totalFileSize = std::filesystem::file_size(LIST_DIR_DUMP_WORK_FILE, e); + dumpContext.segmentIdx = 0; + dumpContext.dumpedBytes = 0; + size_t nextDumpLen = 0; + size_t dummy = 0; + dumpContext.maxDumpLen = dirListingBuf.size() - 2 * sizeof(uint32_t) - 1 - repoNameLen - 1; + dumpContext.listingDataOffset = 2 * sizeof(uint32_t) + 1 + repoNameLen + 1; + uint32_t chunks = dumpContext.totalFileSize / dumpContext.maxDumpLen; + if (dumpContext.totalFileSize % dumpContext.maxDumpLen != 0) { + chunks++; + } + SerializeAdapter::serialize(&chunks, dirListingBuf.data() + sizeof(uint32_t), &dummy, + dirListingBuf.size() - sizeof(uint32_t), + SerializeIF::Endianness::NETWORK); + while (dumpContext.dumpedBytes < dumpContext.totalFileSize) { + ifile.seekg(dumpContext.dumpedBytes, std::ios::beg); + nextDumpLen = dumpContext.maxDumpLen; + if (dumpContext.totalFileSize - dumpContext.dumpedBytes < dumpContext.maxDumpLen) { + nextDumpLen = dumpContext.totalFileSize - dumpContext.dumpedBytes; + } + SerializeAdapter::serialize(&dumpContext.segmentIdx, dirListingBuf.data(), &dummy, + dirListingBuf.size(), SerializeIF::Endianness::NETWORK); + ifile.read(reinterpret_cast(dirListingBuf.data() + dumpContext.listingDataOffset), + nextDumpLen); + result = actionHelper.reportData(commandedBy, actionId, dirListingBuf.data(), + dumpContext.listingDataOffset + nextDumpLen); + if (result != returnvalue::OK) { + // Remove work file when we are done + std::filesystem::remove(LIST_DIR_DUMP_WORK_FILE, e); + return result; + } + dumpContext.segmentIdx++; + dumpContext.dumpedBytes += nextDumpLen; + // Dump takes multiple task cycles, so cache the dump state and continue dump the next cycles. + if (dumpContext.segmentIdx == 10) { + dumpContext.active = true; + dumpContext.firstDump = true; + dumpContext.commander = commandedBy; + dumpContext.actionId = actionId; + return returnvalue::OK; + } + } + // Remove work file when we are done + std::filesystem::remove(LIST_DIR_DUMP_WORK_FILE, e); + return EXECUTION_FINISHED; +} + +ReturnValue_t CoreController::actionListDirectoryIntoFile(ActionId_t actionId, + MessageQueueId_t commandedBy, + const uint8_t *data, size_t size) { + core::ListDirectoryIntoFile parser(data, size); + ReturnValue_t result = parser.parse(); + if (result != returnvalue::OK) { + return result; + } + + std::ostringstream oss("ls -l", std::ostringstream::ate); + if (parser.aFlagSet()) { + oss << "a"; + } + if (parser.rFlagSet()) { + oss << "R"; + } + + size_t repoNameLen = 0; + const char *repoName = parser.getRepoName(repoNameLen); + size_t targetFileNameLen = 0; + const char *targetFileName = parser.getTargetName(targetFileNameLen); + oss << " " << repoName << " > " << targetFileName; + sif::info << "Executing list directory request, command: " << oss.str() << std::endl; + int ret = std::system(oss.str().c_str()); + if (ret != 0) { + utility::handleSystemError(result, "CoreController::actionListDirectoryIntoFile"); + return returnvalue::FAILED; + } + + // Compression will add a .gz ending. I don't have any issue with this, it makes it explicit + // that this is a compressed file. + if (parser.compressionOptionSet()) { + oss.str(""); + oss << "gzip " << targetFileName; + sif::info << "Compressing directory listing: " << oss.str() << std::endl; + ret = std::system(oss.str().c_str()); + if (ret != 0) { + utility::handleSystemError(result, "CoreController::actionListDirectoryIntoFile"); + return returnvalue::FAILED; + } + } + return EXECUTION_FINISHED; +} + +ReturnValue_t CoreController::initBootCopyFile() { + std::error_code e; + if (not std::filesystem::exists(CURR_COPY_FILE, e)) { + // This file is created by the systemd service eive-early-config so this should + // not happen normally + std::string cmd = "xsc_boot_copy > " + std::string(CURR_COPY_FILE); + int result = std::system(cmd.c_str()); + if (result != 0) { + utility::handleSystemError(result, "CoreController::initBootCopy"); + } + } + return returnvalue::OK; +} + +void CoreController::getCurrentBootCopy(xsc::Chip &chip, xsc::Copy ©) { + xsc_libnor_chip_t xscChip; + xsc_libnor_copy_t xscCopy; + xsc_boot_get_chip_copy(&xscChip, &xscCopy); + // Not really thread-safe but it does not need to be + chip = static_cast(xscChip); + copy = static_cast(xscCopy); +} + +ReturnValue_t CoreController::actionXscReboot(const uint8_t *data, size_t size) { + if (size < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + bool rebootSameBootCopy = data[0]; + bool protOpPerformed = false; + SdCardManager::instance()->setBlocking(true); + if (rebootSameBootCopy) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "CoreController::actionPerformReboot: Rebooting on current image" << std::endl; +#endif + gracefulShutdownTasks(xsc::Chip::SELF_CHIP, xsc::Copy::SELF_COPY, protOpPerformed); + int result = std::system("xsc_boot_copy -r"); + if (result != 0) { + utility::handleSystemError(result, "CoreController::executeAction"); + return returnvalue::FAILED; + } + return HasActionsIF::EXECUTION_FINISHED; + } + if (size < 3 or (data[1] > 1 or data[2] > 1)) { + return HasActionsIF::INVALID_PARAMETERS; + } +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "CoreController::actionPerformReboot: Rebooting on " << static_cast(data[1]) + << " " << static_cast(data[2]) << std::endl; +#endif + + // Check that the target chip and copy is writeprotected first + generateChipStateFile(); + // If any boot copies are unprotected, protect them here + auto tgtChip = static_cast(data[1]); + auto tgtCopy = static_cast(data[2]); + + performGracefulShutdown(tgtChip, tgtCopy); + return returnvalue::FAILED; +} + +ReturnValue_t CoreController::actionReboot(const uint8_t *data, size_t size) { + bool protOpPerformed = false; + gracefulShutdownTasks(xsc::Chip::CHIP_0, xsc::Copy::COPY_0, protOpPerformed); + std::system("reboot"); + return returnvalue::OK; +} + +ReturnValue_t CoreController::gracefulShutdownTasks(xsc::Chip chip, xsc::Copy copy, + bool &protOpPerformed) { + // Store both sequence counters persistently. + core::SAVE_CFDP_SEQUENCE_COUNT = true; + core::SAVE_PUS_SEQUENCE_COUNT = true; + + sdcMan->setBlocking(true); + sdcMan->markUnusable(); + // Wait two seconds to ensure no one uses the SD cards + TaskFactory::delayTask(2000); + + // Ensure that all writes/reads do finish. + sync(); + + // Unmount and switch off SD cards. This could possibly fix issues with the SD card and is + // the more graceful way to reboot the system. This function takes around 400 ms. + ReturnValue_t result = handleSwitchingSdCardsOffNonBlocking(); + if (result != returnvalue::OK) { + sif::error + << "CoreController::gracefulShutdownTasks: Issues unmounting or switching SD cards off" + << std::endl; + } + + // Ensure that the target chip is writeprotected in any case. + bool wasProtected = handleBootCopyProt(chip, copy, true); + if (wasProtected) { + // TODO: Would be nice to notify operator. But we can't use the filesystem anymore + // and a reboot is imminent. Use scratch buffer? + sif::info << "Running slot was writeprotected before reboot" << std::endl; + } + sif::info << "Graceful shutdown handling done" << std::endl; + // Ensure that all diagnostic prinouts arrive. + TaskFactory::delayTask(50); + return result; +} + +void CoreController::updateInternalSdInfo() { + if (sdInfo.active == sd::SdCard::SLOT_0) { + sdInfo.activeChar = "0"; + sdInfo.otherChar = "1"; + sdInfo.otherState = sdInfo.currentState.second; + sdInfo.activeState = sdInfo.currentState.first; + sdInfo.other = sd::SdCard::SLOT_1; + + } else if (sdInfo.active == sd::SdCard::SLOT_1) { + sdInfo.activeChar = "1"; + sdInfo.otherChar = "0"; + sdInfo.otherState = sdInfo.currentState.first; + sdInfo.activeState = sdInfo.currentState.second; + sdInfo.other = sd::SdCard::SLOT_0; + } else { + sif::warning << "CoreController::updateSdInfoOther: Invalid SD card passed" << std::endl; + } +} + +bool CoreController::sdInitFinished() const { return sdInfo.initFinished; } + +ReturnValue_t CoreController::generateChipStateFile() { + int result = std::system(CHIP_PROT_SCRIPT); + if (result != 0) { + utility::handleSystemError(result, "CoreController::generateChipStateFile"); + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t CoreController::setBootCopyProtectionAndUpdateFile(xsc::Chip targetChip, + xsc::Copy targetCopy, + bool protect) { + if (targetChip == xsc::Chip::ALL_CHIP or targetCopy == xsc::Copy::ALL_COPY) { + return returnvalue::FAILED; + } + + bool protOperationPerformed = handleBootCopyProt(targetChip, targetCopy, protect); + if (protOperationPerformed) { + updateProtInfo(); + } + return returnvalue::OK; +} + +bool CoreController::handleBootCopyProt(xsc::Chip targetChip, xsc::Copy targetCopy, bool protect) { + std::ostringstream oss; + oss << "writeprotect "; + if (targetChip == xsc::Chip::SELF_CHIP) { + targetChip = CURRENT_CHIP; + } + if (targetCopy == xsc::Copy::SELF_COPY) { + targetCopy = CURRENT_COPY; + } + if (targetChip == xsc::Chip::CHIP_0) { + oss << "0 "; + } else if (targetChip == xsc::Chip::CHIP_1) { + oss << "1 "; + } + if (targetCopy == xsc::Copy::COPY_0) { + oss << "0 "; + } else if (targetCopy == xsc::Copy::COPY_1) { + oss << "1 "; + } + if (protect) { + oss << "1"; + } else { + oss << "0"; + } + sif::info << "Executing command: " << oss.str() << std::endl; + int result = std::system(oss.str().c_str()); + if (result == 0) { + return true; + } + return false; +} + +ReturnValue_t CoreController::updateProtInfo(bool regenerateChipStateFile) { + using namespace std; + ReturnValue_t result = returnvalue::OK; + if (regenerateChipStateFile) { + result = generateChipStateFile(); + if (result != returnvalue::OK) { + sif::warning << "CoreController::updateProtInfo: Generating chip state file failed" + << std::endl; + return result; + } + } + std::error_code e; + if (not filesystem::exists(CHIP_STATE_FILE, e)) { + return returnvalue::FAILED; + } + ifstream chipStateFile(CHIP_STATE_FILE); + if (not chipStateFile.good()) { + return returnvalue::FAILED; + } + string nextLine; + uint8_t lineCounter = 0; + string word; + while (getline(chipStateFile, nextLine)) { + result = handleProtInfoUpdateLine(nextLine); + if (result != returnvalue::OK) { + sif::warning << "CoreController::updateProtInfo: Protection info update failed!" << std::endl; + return result; + } + ++lineCounter; + if (lineCounter > 4) { + sif::warning << "CoreController::checkAndProtectBootCopy: " + "Line counter larger than 4" + << std::endl; + } + } + return returnvalue::OK; +} + +ReturnValue_t CoreController::handleProtInfoUpdateLine(std::string nextLine) { + using namespace std; + string word; + uint8_t wordIdx = 0; + istringstream iss(nextLine); + xsc::Chip currentChip = xsc::Chip::CHIP_0; + xsc::Copy currentCopy = xsc::Copy::COPY_0; + while (iss >> word) { + if (wordIdx == 1) { + currentChip = static_cast(stoi(word)); + } + if (wordIdx == 3) { + currentCopy = static_cast(stoi(word)); + } + + if (wordIdx == 5) { + if (word == "unlocked.") { + protArray[currentChip][currentCopy] = false; + } else { + protArray[currentChip][currentCopy] = true; + } + } + wordIdx++; + if (wordIdx >= 10) { + break; + } + } + return returnvalue::OK; +} + +void CoreController::performMountedSdCardOperations() { + auto mountedSdCardOp = [&](sd::SdCard sdCard, std::string mntPoint) { + if (not performOneShotSdCardOpsSwitch) { + std::ostringstream path; + path << mntPoint << "/" << core::CONF_FOLDER; + std::error_code e; + if (not std::filesystem::exists(path.str()), e) { + bool created = std::filesystem::create_directory(path.str(), e); + if (not created) { + sif::error << "Could not create CONF folder at " << path.str() << ": " << e.message() + << std::endl; + return; + } + } + initVersionFile(); + ReturnValue_t result = initBootCopyFile(); + if (result != returnvalue::OK) { + sif::warning << "CoreController::CoreController: Boot copy init" << std::endl; + } + if (not timeFileInitDone) { + initClockFromTimeFile(); + } + if (not leapSecondsInitDone) { + initLeapSeconds(); + } + performRebootWatchdogHandling(false); + performRebootCountersHandling(false); + } + backupTimeFileHandler(); + }; + bool someSdCardActive = false; + if (sdInfo.active == sd::SdCard::SLOT_0 and sdcMan->isSdCardUsable(sd::SdCard::SLOT_0)) { + mountedSdCardOp(sd::SdCard::SLOT_0, config::SD_0_MOUNT_POINT); + someSdCardActive = true; + } + if (sdInfo.active == sd::SdCard::SLOT_1 and sdcMan->isSdCardUsable(sd::SdCard::SLOT_1)) { + mountedSdCardOp(sd::SdCard::SLOT_1, config::SD_1_MOUNT_POINT); + someSdCardActive = true; + } + if (someSdCardActive) { + performOneShotSdCardOpsSwitch = true; + } +} + +ReturnValue_t CoreController::performSdCardCheck() { + bool mountedReadOnly = false; + SdCardManager::SdStatePair active; + sdcMan->getSdCardsStatus(active); + if (sdFsmState != SdStates::IDLE) { + return returnvalue::OK; + } + auto sdCardCheck = [&](sd::SdCard sdCard) { + ReturnValue_t result = sdcMan->isSdCardMountedReadOnly(sdCard, mountedReadOnly); + if (result != returnvalue::OK) { + sif::error << "CoreController::performSdCardCheck: Could not check " + "read-only mount state" + << std::endl; + } + if (mountedReadOnly) { + int linuxErrno = 0; + result = sdcMan->performFsck(sdCard, true, linuxErrno); + if (result != returnvalue::OK) { + sif::error << "CoreController::performSdCardCheck: fsck command on SD Card " + << static_cast(sdCard) << " failed with code " << linuxErrno << " | " + << strerror(linuxErrno); + } + result = sdcMan->remountReadWrite(sdCard); + if (result == returnvalue::OK) { + sif::warning << "CoreController::performSdCardCheck: Remounted SD Card " + << static_cast(sdCard) << " read-write"; + } else { + sif::error << "CoreController::performSdCardCheck: Remounting SD Card " + << static_cast(sdCard) << " read-write failed"; + } + } + }; + if (active.first == sd::SdState::MOUNTED) { + sdCardCheck(sd::SdCard::SLOT_0); + } + if (active.second == sd::SdState::MOUNTED) { + sdCardCheck(sd::SdCard::SLOT_1); + } +#if OBSW_SD_CARD_MUST_BE_ON == 1 + // This is FDIR. The core controller will attempt once to get some SD card working + bool someSdCardActive = false; + if ((sdInfo.active == sd::SdCard::SLOT_0 and sdcMan->isSdCardUsable(sd::SdCard::SLOT_0)) or + (sdInfo.active == sd::SdCard::SLOT_1 and sdcMan->isSdCardUsable(sd::SdCard::SLOT_1))) { + someSdCardActive = true; + } + if (not someSdCardActive and remountAttemptFlag) { + triggerEvent(core::NO_SD_CARD_ACTIVE); + initSdCardBlocking(); + remountAttemptFlag = false; + } +#endif + return returnvalue::OK; +} + +void CoreController::performRebootWatchdogHandling(bool recreateFile) { + using namespace std; + std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE; + std::string legacyPath = currMntPrefix + LEGACY_REBOOT_WATCHDOG_FILE; + std::error_code e; + // TODO: Remove at some point in the future. + if (std::filesystem::exists(legacyPath, e)) { + // Old file might still exist, so copy it to new path + std::filesystem::copy(legacyPath, path, std::filesystem::copy_options::overwrite_existing, e); + if (e) { + sif::error << "File copy has failed: " << e.message() << std::endl; + } + } + if (not std::filesystem::exists(path, e) or recreateFile) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "CoreController::performRebootFileHandling: Recreating reboot watchdog file" + << std::endl; +#endif + rebootWatchdogFile.enabled = false; + rebootWatchdogFile.img00Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; + rebootWatchdogFile.lastChip = xsc::Chip::CHIP_0; + rebootWatchdogFile.lastCopy = xsc::Copy::COPY_0; + rebootWatchdogFile.img00Lock = false; + rebootWatchdogFile.img01Lock = false; + rebootWatchdogFile.img10Lock = false; + rebootWatchdogFile.img11Lock = false; + rebootWatchdogFile.mechanismNextChip = xsc::Chip::NO_CHIP; + rebootWatchdogFile.mechanismNextCopy = xsc::Copy::NO_COPY; + rebootWatchdogFile.bootFlag = false; + rewriteRebootWatchdogFile(rebootWatchdogFile); + } else { + if (not parseRebootWatchdogFile(path, rebootWatchdogFile)) { + performRebootWatchdogHandling(true); + return; + } + } + + if (CURRENT_CHIP == xsc::CHIP_0) { + if (CURRENT_COPY == xsc::COPY_0) { + rebootWatchdogFile.img00Cnt++; + } else { + rebootWatchdogFile.img01Cnt++; + } + } else { + if (CURRENT_COPY == xsc::COPY_0) { + rebootWatchdogFile.img10Cnt++; + } else { + rebootWatchdogFile.img11Cnt++; + } + } + + if (rebootWatchdogFile.bootFlag) { + // Trigger event to inform ground that a reboot was triggered + uint32_t p1 = rebootWatchdogFile.lastChip << 16 | rebootWatchdogFile.lastCopy; + triggerEvent(core::REBOOT_MECHANISM_TRIGGERED, p1, 0); + // Clear the boot flag + rebootWatchdogFile.bootFlag = false; + } + + if (rebootWatchdogFile.mechanismNextChip != xsc::NO_CHIP and + rebootWatchdogFile.mechanismNextCopy != xsc::NO_COPY) { + if (CURRENT_CHIP != rebootWatchdogFile.mechanismNextChip or + CURRENT_COPY != rebootWatchdogFile.mechanismNextCopy) { + std::string infoString = std::to_string(rebootWatchdogFile.mechanismNextChip) + " " + + std::to_string(rebootWatchdogFile.mechanismNextCopy); + sif::warning << "CoreController::performRebootFileHandling: Expected to be on image " + << infoString << " but currently on other image. Locking " << infoString + << std::endl; + // Firmware or other component might be corrupt and we are on another image then the target + // image specified by the mechanism. We can't really trust the target image anymore. + // Lock it for now + if (rebootWatchdogFile.mechanismNextChip == xsc::CHIP_0) { + if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) { + rebootWatchdogFile.img00Lock = true; + } else { + rebootWatchdogFile.img01Lock = true; + } + } else { + if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) { + rebootWatchdogFile.img10Lock = true; + } else { + rebootWatchdogFile.img11Lock = true; + } + } + } + } + + rebootWatchdogFile.lastChip = CURRENT_CHIP; + rebootWatchdogFile.lastCopy = CURRENT_COPY; + // Only reboot if the reboot functionality is enabled. + // The handler will still increment the boot counts + if (rebootWatchdogFile.enabled and + (*rebootWatchdogFile.relevantBootCnt >= rebootWatchdogFile.maxCount)) { + // Reboot to other image + bool doReboot = false; + xsc::Chip tgtChip = xsc::NO_CHIP; + xsc::Copy tgtCopy = xsc::NO_COPY; + rebootWatchdogAlgorithm(rebootWatchdogFile, doReboot, tgtChip, tgtCopy); + if (doReboot) { + rebootWatchdogFile.bootFlag = true; +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Boot counter for image " << CURRENT_CHIP << " " << CURRENT_COPY + << " too high. Rebooting to " << tgtChip << " " << tgtCopy << std::endl; +#endif + rebootWatchdogFile.mechanismNextChip = tgtChip; + rebootWatchdogFile.mechanismNextCopy = tgtCopy; + rewriteRebootWatchdogFile(rebootWatchdogFile); + performGracefulShutdown(tgtChip, tgtCopy); + } + } else { + rebootWatchdogFile.mechanismNextChip = xsc::NO_CHIP; + rebootWatchdogFile.mechanismNextCopy = xsc::NO_COPY; + } + rewriteRebootWatchdogFile(rebootWatchdogFile); +} + +void CoreController::rebootWatchdogAlgorithm(RebootWatchdogFile &rf, bool &needsReboot, + xsc::Chip &tgtChip, xsc::Copy &tgtCopy) { + tgtChip = xsc::CHIP_0; + tgtCopy = xsc::COPY_0; + needsReboot = false; + if ((CURRENT_CHIP == xsc::CHIP_0) and (CURRENT_COPY == xsc::COPY_0) and + (rf.img00Cnt >= rf.maxCount)) { + needsReboot = true; + if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) { + tgtCopy = xsc::COPY_1; + return; + } + if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) { + tgtChip = xsc::CHIP_1; + return; + } + if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) { + tgtChip = xsc::CHIP_1; + tgtCopy = xsc::COPY_1; + return; + } + // Can't really do much here. Stay on image + sif::warning + << "All reboot counts too high or all fallback images locked, already on fallback image" + << std::endl; + needsReboot = false; + return; + } + if ((CURRENT_CHIP == xsc::CHIP_0) and (CURRENT_COPY == xsc::COPY_1) and + (rf.img01Cnt >= rf.maxCount)) { + needsReboot = true; + if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) { + // Reboot on fallback image + return; + } + if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) { + tgtChip = xsc::CHIP_1; + return; + } + if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) { + tgtChip = xsc::CHIP_1; + tgtCopy = xsc::COPY_1; + } + if (rf.img00Lock) { + needsReboot = false; + } + // Reboot to fallback image + } + if ((CURRENT_CHIP == xsc::CHIP_1) and (CURRENT_COPY == xsc::COPY_0) and + (rf.img10Cnt >= rf.maxCount)) { + needsReboot = true; + if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) { + tgtChip = xsc::CHIP_1; + tgtCopy = xsc::COPY_1; + return; + } + if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) { + return; + } + if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) { + tgtCopy = xsc::COPY_1; + return; + } + if (rf.img00Lock) { + needsReboot = false; + } + // Reboot to fallback image + } + if ((CURRENT_CHIP == xsc::CHIP_1) and (CURRENT_COPY == xsc::COPY_1) and + (rf.img11Cnt >= rf.maxCount)) { + needsReboot = true; + if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) { + tgtChip = xsc::CHIP_1; + return; + } + if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) { + return; + } + if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) { + tgtCopy = xsc::COPY_1; + return; + } + if (rf.img00Lock) { + needsReboot = false; + } + // Reboot to fallback image + } +} + +bool CoreController::parseRebootWatchdogFile(std::string path, RebootWatchdogFile &rf) { + using namespace std; + std::string selfMatch; + if (CURRENT_CHIP == xsc::CHIP_0) { + if (CURRENT_COPY == xsc::COPY_0) { + selfMatch = "00"; + } else { + selfMatch = "01"; + } + } else { + if (CURRENT_COPY == xsc::COPY_0) { + selfMatch = "10"; + } else { + selfMatch = "11"; + } + } + ifstream file(path); + string word; + string line; + uint8_t lineIdx = 0; + while (std::getline(file, line)) { + istringstream iss(line); + switch (lineIdx) { + case 0: { + iss >> word; + if (word.find("on:") == string::npos) { + // invalid file + return false; + } + iss >> rf.enabled; + break; + } + case 1: { + iss >> word; + if (word.find("maxcnt:") == string::npos) { + return false; + } + iss >> rf.maxCount; + break; + } + case 2: { + iss >> word; + if (word.find("img00:") == string::npos) { + return false; + } + iss >> rf.img00Cnt; + if (word.find(selfMatch) != string::npos) { + rf.relevantBootCnt = &rf.img00Cnt; + } + break; + } + case 3: { + iss >> word; + if (word.find("img01:") == string::npos) { + return false; + } + iss >> rf.img01Cnt; + if (word.find(selfMatch) != string::npos) { + rf.relevantBootCnt = &rf.img01Cnt; + } + break; + } + case 4: { + iss >> word; + if (word.find("img10:") == string::npos) { + return false; + } + iss >> rf.img10Cnt; + if (word.find(selfMatch) != string::npos) { + rf.relevantBootCnt = &rf.img10Cnt; + } + break; + } + case 5: { + iss >> word; + if (word.find("img11:") == string::npos) { + return false; + } + iss >> rf.img11Cnt; + if (word.find(selfMatch) != string::npos) { + rf.relevantBootCnt = &rf.img11Cnt; + } + break; + } + case 6: { + iss >> word; + if (word.find("img00lock:") == string::npos) { + return false; + } + iss >> rf.img00Lock; + break; + } + case 7: { + iss >> word; + if (word.find("img01lock:") == string::npos) { + return false; + } + iss >> rf.img01Lock; + break; + } + case 8: { + iss >> word; + if (word.find("img10lock:") == string::npos) { + return false; + } + iss >> rf.img10Lock; + break; + } + case 9: { + iss >> word; + if (word.find("img11lock:") == string::npos) { + return false; + } + iss >> rf.img11Lock; + break; + } + case 10: { + iss >> word; + if (word.find("bootflag:") == string::npos) { + return false; + } + iss >> rf.bootFlag; + break; + } + case 11: { + iss >> word; + int copyRaw = 0; + int chipRaw = 0; + if (word.find("last:") == string::npos) { + return false; + } + iss >> chipRaw; + if (iss.fail()) { + return false; + } + iss >> copyRaw; + if (iss.fail()) { + return false; + } + + if (chipRaw > 1 or copyRaw > 1) { + return false; + } + rf.lastChip = static_cast(chipRaw); + rf.lastCopy = static_cast(copyRaw); + break; + } + case 12: { + iss >> word; + int copyRaw = 0; + int chipRaw = 0; + if (word.find("next:") == string::npos) { + return false; + } + iss >> chipRaw; + if (iss.fail()) { + return false; + } + iss >> copyRaw; + if (iss.fail()) { + return false; + } + + if (chipRaw > 2 or copyRaw > 2) { + return false; + } + rf.mechanismNextChip = static_cast(chipRaw); + rf.mechanismNextCopy = static_cast(copyRaw); + break; + } + } + if (iss.fail()) { + return false; + } + lineIdx++; + } + if (lineIdx < 12) { + return false; + } + return true; +} + +bool CoreController::parseRebootCountersFile(std::string path, RebootCountersFile &rf) { + using namespace std; + ifstream file(path); + string word; + string line; + uint8_t lineIdx = 0; + while (std::getline(file, line)) { + istringstream iss(line); + switch (lineIdx) { + case 0: { + iss >> word; + if (word.find("img00:") == string::npos) { + return false; + } + iss >> rf.img00Cnt; + + break; + } + case 1: { + iss >> word; + if (word.find("img01:") == string::npos) { + return false; + } + iss >> rf.img01Cnt; + + break; + } + case 2: { + iss >> word; + if (word.find("img10:") == string::npos) { + return false; + } + iss >> rf.img10Cnt; + + break; + } + case 3: { + iss >> word; + if (word.find("img11:") == string::npos) { + return false; + } + iss >> rf.img11Cnt; + break; + } + } + lineIdx++; + } + return true; +} + +void CoreController::resetRebootWatchdogCounters(xsc::Chip tgtChip, xsc::Copy tgtCopy) { + std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE; + parseRebootWatchdogFile(path, rebootWatchdogFile); + if (tgtChip == xsc::ALL_CHIP and tgtCopy == xsc::ALL_COPY) { + rebootWatchdogFile.img00Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; + } else { + if (tgtChip == xsc::CHIP_0) { + if (tgtCopy == xsc::COPY_0) { + rebootWatchdogFile.img00Cnt = 0; + } else { + rebootWatchdogFile.img01Cnt = 0; + } + } else { + if (tgtCopy == xsc::COPY_0) { + rebootWatchdogFile.img10Cnt = 0; + } else { + rebootWatchdogFile.img11Cnt = 0; + } + } + } + rewriteRebootWatchdogFile(rebootWatchdogFile); +} + +void CoreController::performRebootCountersHandling(bool recreateFile) { + std::string path = currMntPrefix + REBOOT_COUNTERS_FILE; + std::error_code e; + if (not std::filesystem::exists(path, e) or recreateFile) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "CoreController::performRebootFileHandling: Recreating reboot counters file" + << std::endl; +#endif + rebootCountersFile.img00Cnt = 0; + rebootCountersFile.img01Cnt = 0; + rebootCountersFile.img10Cnt = 0; + rebootCountersFile.img11Cnt = 0; + rewriteRebootCountersFile(rebootCountersFile); + } else { + if (not parseRebootCountersFile(path, rebootCountersFile)) { + performRebootCountersHandling(true); + return; + } + } + + if (CURRENT_CHIP == xsc::CHIP_0) { + if (CURRENT_COPY == xsc::COPY_0) { + rebootCountersFile.img00Cnt++; + } else { + rebootCountersFile.img01Cnt++; + } + } else { + if (CURRENT_COPY == xsc::COPY_0) { + rebootCountersFile.img10Cnt++; + } else { + rebootCountersFile.img11Cnt++; + } + } + announceBootCounts(); + rewriteRebootCountersFile(rebootCountersFile); +} +void CoreController::rewriteRebootWatchdogFile(RebootWatchdogFile file) { + using namespace std::filesystem; + std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE; + std::string legacyPath = currMntPrefix + LEGACY_REBOOT_WATCHDOG_FILE; + { + std::ofstream rebootFile(path); + if (rebootFile.is_open()) { + // Initiate reboot file first. Reboot handling will be on on initialization + rebootFile << "on: " << file.enabled << "\nmaxcnt: " << file.maxCount + << "\nimg00: " << file.img00Cnt << "\nimg01: " << file.img01Cnt + << "\nimg10: " << file.img10Cnt << "\nimg11: " << file.img11Cnt + << "\nimg00lock: " << file.img00Lock << "\nimg01lock: " << file.img01Lock + << "\nimg10lock: " << file.img10Lock << "\nimg11lock: " << file.img11Lock + << "\nbootflag: " << file.bootFlag << "\nlast: " << static_cast(file.lastChip) + << " " << static_cast(file.lastCopy) + << "\nnext: " << static_cast(file.mechanismNextChip) << " " + << static_cast(file.mechanismNextCopy) << "\n"; + } + } + std::error_code e; + // TODO: Remove at some point in the future when all images have been updated. + if (std::filesystem::exists(legacyPath)) { + // Keep those two files in sync + std::filesystem::copy(path, legacyPath, std::filesystem::copy_options::overwrite_existing, e); + if (e) { + sif::error << "File copy has failed: " << e.message() << std::endl; + } + } +} + +void CoreController::rewriteRebootCountersFile(RebootCountersFile file) { + std::string path = currMntPrefix + REBOOT_COUNTERS_FILE; + std::ofstream rebootFile(path); + if (rebootFile.is_open()) { + rebootFile << "img00: " << file.img00Cnt << "\nimg01: " << file.img01Cnt + << "\nimg10: " << file.img10Cnt << "\nimg11: " << file.img11Cnt << "\n"; + } +} + +void CoreController::setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy) { + std::string path = currMntPrefix + REBOOT_WATCHDOG_FILE; + parseRebootWatchdogFile(path, rebootWatchdogFile); + if (tgtChip == xsc::CHIP_0) { + if (tgtCopy == xsc::COPY_0) { + rebootWatchdogFile.img00Lock = lock; + } else { + rebootWatchdogFile.img01Lock = lock; + } + } else { + if (tgtCopy == xsc::COPY_0) { + rebootWatchdogFile.img10Lock = lock; + } else { + rebootWatchdogFile.img11Lock = lock; + } + } + rewriteRebootWatchdogFile(rebootWatchdogFile); +} + +ReturnValue_t CoreController::backupTimeFileHandler() { + // Always set time. We could only set it if it is updated by GPS, but then the backup time would + // become obsolete on GPS problems. + if (opDivider10.check()) { + // It is assumed that the system time is set from the GPS time + timeval currentTime = {}; + ReturnValue_t result = Clock::getClock_timeval(¤tTime); + if (result != returnvalue::OK) { + return result; + } + std::string fileName = currMntPrefix + BACKUP_TIME_FILE; + std::ofstream timeFile(fileName); + if (not timeFile.good()) { + sif::error << "CoreController::timeFileHandler: Error opening time file: " << strerror(errno) + << std::endl; + return returnvalue::FAILED; + } + timeFile << "UNIX SECONDS: " << currentTime.tv_sec + BOOT_OFFSET_SECONDS << std::endl; + } + return returnvalue::OK; +} + +void CoreController::initLeapSeconds() { + ReturnValue_t result = initLeapSecondsFromFile(); + if (result != returnvalue::OK) { + Clock::setLeapSeconds(config::LEAP_SECONDS); + writeLeapSecondsToFile(config::LEAP_SECONDS); + } + leapSecondsInitDone = true; +} + +ReturnValue_t CoreController::initLeapSecondsFromFile() { + std::string fileName = currMntPrefix + LEAP_SECONDS_FILE; + std::error_code e; + if (sdcMan->isSdCardUsable(std::nullopt) and std::filesystem::exists(fileName, e)) { + std::ifstream leapSecondsFile(fileName); + std::string nextWord; + std::getline(leapSecondsFile, nextWord); + std::istringstream iss(nextWord); + iss >> nextWord; + if (iss.bad() or nextWord != "LEAP") { + return returnvalue::FAILED; + } + iss >> nextWord; + if (iss.bad() or nextWord != "SECONDS:") { + return returnvalue::FAILED; + } + iss >> nextWord; + uint16_t leapSeconds = 0; + leapSeconds = std::stoi(nextWord.c_str()); + if (iss.bad()) { + return returnvalue::FAILED; + } + Clock::setLeapSeconds(leapSeconds); + return returnvalue::OK; + } + sif::error + << "CoreController::leapSecondsFileHandler: Initalization of leap seconds from file failed" + << std::endl; + return returnvalue::FAILED; +}; + +ReturnValue_t CoreController::writeLeapSecondsToFile(const uint16_t leapSeconds) { + std::string fileName = currMntPrefix + LEAP_SECONDS_FILE; + if (not sdcMan->isSdCardUsable(std::nullopt)) { + return returnvalue::FAILED; + } + std::ofstream leapSecondsFile(fileName.c_str(), std::ofstream::out | std::ofstream::trunc); + if (not leapSecondsFile.good()) { + sif::error << "CoreController::leapSecondsFileHandler: Error opening leap seconds file: " + << strerror(errno) << std::endl; + return returnvalue::FAILED; + } + leapSecondsFile << "LEAP SECONDS: " << leapSeconds << std::endl; + return returnvalue::OK; +}; + +ReturnValue_t CoreController::actionUpdateLeapSeconds(const uint8_t *data) { + uint16_t leapSeconds = data[1] | (data[0] << 8); + ReturnValue_t result = writeLeapSecondsToFile(leapSeconds); + if (result != returnvalue::OK) { + return result; + } + Clock::setLeapSeconds(leapSeconds); + return returnvalue::OK; +} + +ReturnValue_t CoreController::initClockFromTimeFile() { + using namespace GpsHyperion; + using namespace std; + std::string fileName = currMntPrefix + BACKUP_TIME_FILE; + std::error_code e; + if (sdcMan->isSdCardUsable(std::nullopt) and std::filesystem::exists(fileName, e) and + ((gpsFix == FixMode::NOT_SEEN) or not utility::timeSanityCheck())) { + ifstream timeFile(fileName); + string nextWord; + getline(timeFile, nextWord); + istringstream iss(nextWord); + iss >> nextWord; + if (iss.bad() or nextWord != "UNIX") { + return returnvalue::FAILED; + } + iss >> nextWord; + if (iss.bad() or nextWord != "SECONDS:") { + return returnvalue::FAILED; + } + iss >> nextWord; + timeval currentTime = {}; + char *checkPtr; + currentTime.tv_sec = strtol(nextWord.c_str(), &checkPtr, 10); + if (iss.bad() or *checkPtr) { + return returnvalue::FAILED; + } +#if OBSW_VERBOSE_LEVEL >= 1 + time_t timeRaw = currentTime.tv_sec; + std::tm *time = std::gmtime(&timeRaw); + sif::info << "Setting system time from time files: " << std::put_time(time, "%c %Z") + << std::endl; +#endif + timeFileInitDone = true; + return Clock::setClock(¤tTime); + } + return returnvalue::OK; +} + +void CoreController::readHkData() { + ReturnValue_t result = returnvalue::OK; + result = hkSet.read(TIMEOUT_TYPE, MUTEX_TIMEOUT); + if (result != returnvalue::OK) { + return; + } + Xadc xadc; + result = xadc.getTemperature(hkSet.temperature.value); + if (result != returnvalue::OK) { + hkSet.temperature.setValid(false); + } else { + hkSet.temperature.setValid(true); + } + result = xadc.getVccPint(hkSet.psVoltage.value); + if (result != returnvalue::OK) { + hkSet.psVoltage.setValid(false); + } else { + hkSet.psVoltage.setValid(true); + } + result = xadc.getVccInt(hkSet.plVoltage.value); + if (result != returnvalue::OK) { + hkSet.plVoltage.setValid(false); + } else { + hkSet.plVoltage.setValid(true); + } +#if OBSW_PRINT_CORE_HK == 1 + hkSet.printSet(); +#endif /* OBSW_PRINT_CORE_HK == 1 */ + result = hkSet.commit(TIMEOUT_TYPE, MUTEX_TIMEOUT); + if (result != returnvalue::OK) { + return; + } +} + +const char *CoreController::getXscMountDir(xsc::Chip chip, xsc::Copy copy) { + if (chip == xsc::Chip::CHIP_0) { + if (copy == xsc::Copy::COPY_0) { + return CHIP_0_COPY_0_MOUNT_DIR; + } else if (copy == xsc::Copy::COPY_1) { + return CHIP_0_COPY_1_MOUNT_DIR; + } + } else if (chip == xsc::Chip::CHIP_1) { + if (copy == xsc::Copy::COPY_0) { + return CHIP_1_COPY_0_MOUNT_DIR; + } else if (copy == xsc::Copy::COPY_1) { + return CHIP_1_COPY_1_MOUNT_DIR; + } + } + sif::error << "Invalid chip or copy passed to CoreController::getXscMountDir" << std::endl; + return CHIP_0_COPY_0_MOUNT_DIR; +} + +ReturnValue_t CoreController::executeSwUpdate(SwUpdateSources sourceDir, const uint8_t *data, + size_t size) { + using namespace std; + using namespace std::filesystem; + // At the very least, chip and copy ID need to be included in the command + if (size < 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + if (data[0] > 1 or data[1] > 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + auto chip = static_cast(data[0]); + auto copy = static_cast(data[1]); + const char *sourceStr = "unknown"; + if (sourceDir == SwUpdateSources::SD_0) { + sourceStr = "SD 0"; + } else if (sourceDir == SwUpdateSources::SD_1) { + sourceStr = "SD 1"; + } else { + sourceStr = "tmp directory"; + } + bool sameChipAndCopy = false; + if (chip == CURRENT_CHIP and copy == CURRENT_COPY) { + // This is problematic if the OBSW is running as a systemd service. + // Do not allow for now. + return HasActionsIF::INVALID_PARAMETERS; + // sameChipAndCopy = true; + } + sif::info << "Executing SW update for Chip " << static_cast(data[0]) << " Copy " + << static_cast(data[1]) << " from " << sourceStr << std::endl; + path prefixPath; + if (sourceDir == SwUpdateSources::SD_0) { + prefixPath = path(config::SD_0_MOUNT_POINT); + } else if (sourceDir == SwUpdateSources::SD_1) { + prefixPath = path(config::SD_1_MOUNT_POINT); + } else if (sourceDir == SwUpdateSources::TMP_DIR) { + prefixPath = path("/tmp"); + } + path archivePath; + // It is optionally possible to supply the source file path + if (size > 2) { + archivePath = prefixPath / std::string(reinterpret_cast(data + 2), size - 2); + } else { + archivePath = prefixPath / path(config::OBSW_UPDATE_ARCHIVE_FILE_NAME); + } + sif::info << "Updating with archive path " << archivePath << std::endl; + std::error_code e; + if (not exists(archivePath, e)) { + return HasFileSystemIF::FILE_DOES_NOT_EXIST; + } + // TODO: Decompressing without limiting memory usage with xz is actually a bit risky.. + // But has not been an issue so far. + ostringstream cmd("tar -xJf", ios::app); + cmd << " " << archivePath << " -C " << prefixPath; + int result = system(cmd.str().c_str()); + if (result != 0) { + utility::handleSystemError(result, "CoreController::executeAction: SW Update Decompression"); + } + path strippedImagePath = prefixPath / path(config::STRIPPED_OBSW_BINARY_FILE_NAME); + if (!exists(strippedImagePath, e)) { + // TODO: Custom returnvalue? + return returnvalue::FAILED; + } + path obswVersionFilePath = prefixPath / path(config::OBSW_VERSION_FILE_NAME); + if (!exists(obswVersionFilePath, e)) { + // TODO: Custom returnvalue? + return returnvalue::FAILED; + } + cmd.str(""); + cmd.clear(); + path obswDestPath; + path obswVersionDestPath; + if (not sameChipAndCopy) { + cmd << "xsc_mount_copy " << std::to_string(data[0]) << " " << std::to_string(data[1]); + result = system(cmd.str().c_str()); + if (result != 0) { + std::string contextString = "CoreController::executeAction: SW Update Mounting " + + std::to_string(data[0]) + " " + std::to_string(data[1]); + utility::handleSystemError(result, contextString); + } + cmd.str(""); + cmd.clear(); + path xscMountDest(getXscMountDir(chip, copy)); + obswDestPath = xscMountDest / path(relative(config::OBSW_PATH, "/")); + obswVersionDestPath = xscMountDest / path(relative(config::OBSW_VERSION_FILE_PATH, "/")); + } else { + obswDestPath = path(config::OBSW_PATH); + obswVersionDestPath = path(config::OBSW_VERSION_FILE_PATH); + cmd << "writeprotect " << std::to_string(CURRENT_CHIP) << " " << std::to_string(CURRENT_COPY) + << " 0"; + result = system(cmd.str().c_str()); + if (result != 0) { + std::string contextString = "CoreController::executeAction: Unlocking current chip"; + utility::handleSystemError(result, contextString); + } + cmd.str(""); + cmd.clear(); + } + + cmd << "cp " << strippedImagePath << " " << obswDestPath; + result = system(cmd.str().c_str()); + if (result != 0) { + utility::handleSystemError(result, "CoreController::executeAction: Copying SW update"); + } + cmd.str(""); + cmd.clear(); + + cmd << "cp " << obswVersionFilePath << " " << obswVersionDestPath; + result = system(cmd.str().c_str()); + if (result != 0) { + utility::handleSystemError(result, "CoreController::executeAction: Copying SW version file"); + } + cmd.str(""); + cmd.clear(); + + // Set correct permission for both files + cmd << "chmod 0755 " << obswDestPath; + result = system(cmd.str().c_str()); + if (result != 0) { + utility::handleSystemError(result, + "CoreController::executeAction: Setting SW permissions 0755"); + } + cmd.str(""); + cmd.clear(); + + cmd << "chmod 0644 " << obswVersionDestPath; + result = system(cmd.str().c_str()); + if (result != 0) { + utility::handleSystemError( + result, "CoreController::executeAction: Setting version file permission 0644"); + } + cmd.str(""); + cmd.clear(); + + // Remove the extracted files to keep directories clean. + std::filesystem::remove(strippedImagePath, e); + std::filesystem::remove(obswVersionFilePath, e); + + // TODO: This takes a long time and will block the core controller.. Maybe use command executor? + // For now dont care.. + cmd << "writeprotect " << std::to_string(data[0]) << " " << std::to_string(data[1]) << " 1"; + result = system(cmd.str().c_str()); + if (result != 0) { + std::string contextString = "CoreController::executeAction: Writeprotecting " + + std::to_string(data[0]) + " " + std::to_string(data[1]); + utility::handleSystemError(result, contextString); + } + sif::info << "SW update complete" << std::endl; + return HasActionsIF::EXECUTION_FINISHED; +} + +bool CoreController::startSdStateMachine(sd::SdCard targetActiveSd, SdCfgMode mode, + MessageQueueId_t commander, DeviceCommandId_t actionId) { + if (sdFsmState != SdStates::IDLE or sdCommandingInfo.cmdPending) { + return false; + } + sdFsmState = SdStates::START; + sdInfo.active = targetActiveSd; + // If we are going from 2 SD cards to one, lock SD card usage in any case because 1 SD card is + // going off. + if (sdInfo.cfgMode == SdCfgMode::HOT_REDUNDANT and mode == SdCfgMode::COLD_REDUNDANT) { + sdInfo.lockSdCardUsage = true; + } + sdInfo.cfgMode = mode; + sdCommandingInfo.actionId = actionId; + sdCommandingInfo.commander = commander; + sdCommandingInfo.cmdPending = true; + return true; +} + +void CoreController::announceBootCounts() { + uint64_t totalBootCount = rebootCountersFile.img00Cnt + rebootCountersFile.img01Cnt + + rebootCountersFile.img10Cnt + rebootCountersFile.img11Cnt; + uint32_t individualBootCountsP1 = + (rebootCountersFile.img00Cnt << 16) | rebootCountersFile.img01Cnt; + uint32_t individualBootCountsP2 = + (rebootCountersFile.img10Cnt << 16) | rebootCountersFile.img11Cnt; + triggerEvent(core::INDIVIDUAL_BOOT_COUNTS, individualBootCountsP1, individualBootCountsP2); + triggerEvent(core::REBOOT_COUNTER, (totalBootCount >> 32) & 0xffffffff, + totalBootCount & 0xffffffff); +} + +MessageQueueId_t CoreController::getCommandQueue() const { + return ExtendedControllerBase::getCommandQueue(); +} + +void CoreController::dirListingDumpHandler() { + if (dumpContext.firstDump) { + dumpContext.firstDump = false; + return; + } + size_t nextDumpLen = 0; + size_t dummy = 0; + ReturnValue_t result; + std::error_code e; + std::ifstream ifile(LIST_DIR_DUMP_WORK_FILE, std::ios::binary); + if (ifile.bad()) { + return; + } + while (dumpContext.dumpedBytes < dumpContext.totalFileSize) { + ifile.seekg(dumpContext.dumpedBytes, std::ios::beg); + nextDumpLen = dumpContext.maxDumpLen; + if (dumpContext.totalFileSize - dumpContext.dumpedBytes < dumpContext.maxDumpLen) { + nextDumpLen = dumpContext.totalFileSize - dumpContext.dumpedBytes; + } + SerializeAdapter::serialize(&dumpContext.segmentIdx, dirListingBuf.data(), &dummy, + dirListingBuf.size(), SerializeIF::Endianness::NETWORK); + ifile.read(reinterpret_cast(dirListingBuf.data() + dumpContext.listingDataOffset), + nextDumpLen); + result = + actionHelper.reportData(dumpContext.commander, dumpContext.actionId, dirListingBuf.data(), + dumpContext.listingDataOffset + nextDumpLen); + if (result != returnvalue::OK) { + // Remove work file when we are done + std::filesystem::remove(LIST_DIR_DUMP_WORK_FILE, e); + dumpContext.active = false; + actionHelper.finish(false, dumpContext.commander, dumpContext.actionId, result); + return; + } + dumpContext.segmentIdx++; + dumpContext.dumpedBytes += nextDumpLen; + // Dump takes multiple task cycles, so cache the dump state and continue dump the next cycles. + if (dumpContext.segmentIdx == 10) { + break; + } + } + if (dumpContext.dumpedBytes >= dumpContext.totalFileSize) { + actionHelper.finish(true, dumpContext.commander, dumpContext.actionId, result); + dumpContext.active = false; + // Remove work file when we are done + std::filesystem::remove(LIST_DIR_DUMP_WORK_FILE, e); + } +} + +void CoreController::announceVersionInfo() { + using namespace core; + uint32_t p1 = (common::OBSW_VERSION_MAJOR << 24) | (common::OBSW_VERSION_MINOR << 16) | + (common::OBSW_VERSION_REVISION << 8); + uint32_t p2 = 0; + if (strcmp("", common::OBSW_VERSION_CST_GIT_SHA1) != 0) { + p1 |= 1; + auto shaAsStr = std::string(common::OBSW_VERSION_CST_GIT_SHA1); + size_t posDash = shaAsStr.find("-"); + auto gitHash = shaAsStr.substr(posDash + 2, 4); + // Only copy first 4 letters of git hash + memcpy(&p2, gitHash.c_str(), 4); + } + + triggerEvent(VERSION_INFO, p1, p2); + p1 = (core::FW_VERSION_MAJOR << 24) | (core::FW_VERSION_MINOR << 16) | + (core::FW_VERSION_REVISION << 8) | (core::FW_VERSION_HAS_SHA); + std::memcpy(&p2, core::FW_VERSION_GIT_SHA, 4); + triggerEvent(FIRMWARE_INFO, p1, p2); +} + +void CoreController::announceCurrentImageInfo() { + using namespace core; + triggerEvent(CURRENT_IMAGE_INFO, CURRENT_CHIP, CURRENT_COPY); +} + +ReturnValue_t CoreController::performGracefulShutdown(xsc::Chip tgtChip, xsc::Copy tgtCopy) { + bool protOpPerformed = false; + // This function can not really fail + gracefulShutdownTasks(tgtChip, tgtCopy, protOpPerformed); + + switch (tgtChip) { + case (xsc::Chip::CHIP_0): { + switch (tgtCopy) { + case (xsc::Copy::COPY_0): { + xsc_boot_copy(XSC_LIBNOR_CHIP_0, XSC_LIBNOR_COPY_NOMINAL); + break; + } + case (xsc::Copy::COPY_1): { + xsc_boot_copy(XSC_LIBNOR_CHIP_0, XSC_LIBNOR_COPY_GOLD); + break; + } + default: { + break; + } + } + break; + } + case (xsc::Chip::CHIP_1): { + switch (tgtCopy) { + case (xsc::Copy::COPY_0): { + xsc_boot_copy(XSC_LIBNOR_CHIP_1, XSC_LIBNOR_COPY_NOMINAL); + break; + } + case (xsc::Copy::COPY_1): { + xsc_boot_copy(XSC_LIBNOR_CHIP_1, XSC_LIBNOR_COPY_GOLD); + break; + } + default: { + break; + } + } + break; + } + default: + break; + } + return returnvalue::OK; +} + +void CoreController::announceSdInfo(SdCardManager::SdStatePair sdStates) { + auto activeSdCard = sdcMan->getActiveSdCard(); + uint32_t p1 = sd::SdCard::NONE; + if (activeSdCard.has_value()) { + p1 = static_cast(activeSdCard.value()); + } + uint32_t p2 = + (static_cast(sdStates.first) << 16) | static_cast(sdStates.second); + triggerEvent(core::ACTIVE_SD_INFO, p1, p2); +} + +ReturnValue_t CoreController::handleSwitchingSdCardsOffNonBlocking() { + sdcMan->setBlocking(false); + SdCardManager::Operations op; + std::pair sdStatus; + ReturnValue_t result = sdcMan->getSdCardsStatus(sdStatus); + if (result != returnvalue::OK) { + return result; + } + Countdown maxWaitTimeCd(10000); + // Stopwatch watch; + auto waitingForFinish = [&]() { + auto currentState = sdcMan->checkCurrentOp(op); + if (currentState == SdCardManager::OpStatus::IDLE) { + return returnvalue::OK; + } + while (currentState == SdCardManager::OpStatus::ONGOING) { + if (maxWaitTimeCd.hasTimedOut()) { + return returnvalue::FAILED; + } + TaskFactory::delayTask(50); + currentState = sdcMan->checkCurrentOp(op); + } + return returnvalue::OK; + }; + if (sdStatus.first != sd::SdState::OFF) { + sdcMan->unmountSdCard(sd::SdCard::SLOT_0); + result = waitingForFinish(); + if (result != returnvalue::OK) { + return result; + } + sdcMan->switchOffSdCard(sd::SdCard::SLOT_0, sdStatus, false); + result = waitingForFinish(); + if (result != returnvalue::OK) { + return result; + } + } + if (sdStatus.second != sd::SdState::OFF) { + sdcMan->unmountSdCard(sd::SdCard::SLOT_1); + result = waitingForFinish(); + if (result != returnvalue::OK) { + return result; + } + sdcMan->switchOffSdCard(sd::SdCard::SLOT_1, sdStatus, false); + result = waitingForFinish(); + if (result != returnvalue::OK) { + return result; + } + } + return result; +} + +bool CoreController::isNumber(const std::string &s) { + return !s.empty() && std::find_if(s.begin(), s.end(), + [](unsigned char c) { return !std::isdigit(c); }) == s.end(); +} + +ReturnValue_t CoreController::getParameter(uint8_t domainId, uint8_t uniqueIdentifier, + ParameterWrapper *parameterWrapper, + const ParameterWrapper *newValues, + uint16_t startAtIndex) { + if (domainId != 0) { + return HasParametersIF::INVALID_DOMAIN_ID; + } + if (uniqueIdentifier >= ParamId::NUM_IDS) { + return HasParametersIF::INVALID_IDENTIFIER_ID; + } + uint8_t newPrefSd; + ReturnValue_t result = newValues->getElement(&newPrefSd); + if (result != returnvalue::OK) { + return result; + } + // Only SD card 0 (0) and 1 (1) are allowed values. + if (newPrefSd > 1) { + return HasParametersIF::INVALID_VALUE; + } + result = sdcMan->setPreferredSdCard(static_cast(newPrefSd)); + if (result != returnvalue::OK) { + return returnvalue::FAILED; + } + parameterWrapper->set(prefSdRaw); + return returnvalue::OK; +} diff --git a/bsp_q7s/core/CoreController.h b/bsp_q7s/core/CoreController.h new file mode 100644 index 0000000..6827a39 --- /dev/null +++ b/bsp_q7s/core/CoreController.h @@ -0,0 +1,401 @@ +#ifndef BSP_Q7S_CORE_CORECONTROLLER_H_ +#define BSP_Q7S_CORE_CORECONTROLLER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "OBSWConfig.h" +#include "bsp_q7s/fs/SdCardManager.h" +#include "events/subsystemIdRanges.h" +#include "fsfw/controller/ExtendedControllerBase.h" +#include "mission/sysDefs.h" + +class Timer; +class SdCardManager; + +struct RebootWatchdogFile { + static constexpr uint8_t DEFAULT_MAX_BOOT_CNT = 10; + + bool enabled = true; + size_t maxCount = DEFAULT_MAX_BOOT_CNT; + uint32_t img00Cnt = 0; + uint32_t img01Cnt = 0; + uint32_t img10Cnt = 0; + uint32_t img11Cnt = 0; + bool img00Lock = false; + bool img01Lock = false; + bool img10Lock = false; + bool img11Lock = false; + uint32_t* relevantBootCnt = &img00Cnt; + bool bootFlag = false; + xsc::Chip lastChip = xsc::Chip::CHIP_0; + xsc::Copy lastCopy = xsc::Copy::COPY_0; + xsc::Chip mechanismNextChip = xsc::Chip::NO_CHIP; + xsc::Copy mechanismNextCopy = xsc::Copy::NO_COPY; +}; + +class RebootWatchdogPacket : public SerialLinkedListAdapter { + public: + RebootWatchdogPacket(RebootWatchdogFile& rf) { + enabled = rf.enabled; + maxCount = rf.maxCount; + img00Count = rf.img00Cnt; + img01Count = rf.img01Cnt; + img10Count = rf.img10Cnt; + img11Count = rf.img11Cnt; + img00Lock = rf.img00Lock; + img01Lock = rf.img01Lock; + img10Lock = rf.img10Lock; + img11Lock = rf.img11Lock; + lastChip = static_cast(rf.lastChip); + lastCopy = static_cast(rf.lastCopy); + nextChip = static_cast(rf.mechanismNextChip); + nextCopy = static_cast(rf.mechanismNextCopy); + setLinks(); + } + + private: + void setLinks() { + setStart(&enabled); + enabled.setNext(&maxCount); + maxCount.setNext(&img00Count); + img00Count.setNext(&img01Count); + img01Count.setNext(&img10Count); + img10Count.setNext(&img11Count); + img11Count.setNext(&img00Lock); + img00Lock.setNext(&img01Lock); + img01Lock.setNext(&img10Lock); + img10Lock.setNext(&img11Lock); + img11Lock.setNext(&lastChip); + lastChip.setNext(&lastCopy); + lastCopy.setNext(&nextChip); + nextChip.setNext(&nextCopy); + setLast(&nextCopy); + } + + SerializeElement enabled = false; + SerializeElement maxCount = 0; + SerializeElement img00Count = 0; + SerializeElement img01Count = 0; + SerializeElement img10Count = 0; + SerializeElement img11Count = 0; + SerializeElement img00Lock = false; + SerializeElement img01Lock = false; + SerializeElement img10Lock = false; + SerializeElement img11Lock = false; + SerializeElement lastChip = 0; + SerializeElement lastCopy = 0; + SerializeElement nextChip = 0; + SerializeElement nextCopy = 0; +}; + +struct RebootCountersFile { + // 16 bit values so all boot counters fit into one event. + uint16_t img00Cnt = 0; + uint16_t img01Cnt = 0; + uint16_t img10Cnt = 0; + uint16_t img11Cnt = 0; +}; + +class RebootCountersPacket : public SerialLinkedListAdapter { + RebootCountersPacket(RebootCountersFile& rf) { + img00Count = rf.img00Cnt; + img01Count = rf.img01Cnt; + img10Count = rf.img10Cnt; + img11Count = rf.img11Cnt; + setLinks(); + } + + private: + void setLinks() { + setStart(&img00Count); + img00Count.setNext(&img01Count); + img01Count.setNext(&img10Count); + img10Count.setNext(&img11Count); + setLast(&img11Count); + } + + SerializeElement img00Count = 0; + SerializeElement img01Count = 0; + SerializeElement img10Count = 0; + SerializeElement img11Count = 0; +}; + +class CoreController : public ExtendedControllerBase, public ReceivesParameterMessagesIF { + public: + enum ParamId : uint8_t { PREF_SD = 0, NUM_IDS }; + + static xsc::Chip CURRENT_CHIP; + static xsc::Copy CURRENT_COPY; + + static constexpr char CHIP_PROT_SCRIPT[] = "get-chip-prot-status.sh"; + static constexpr char CHIP_STATE_FILE[] = "/tmp/chip_prot_status.txt"; + static constexpr char CURR_COPY_FILE[] = "/tmp/curr_copy.txt"; + + const std::string VERSION_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::VERSION_FILE_NAME); + const std::string LEGACY_REBOOT_WATCHDOG_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + + std::string(core::LEGACY_REBOOT_WATCHDOG_FILE_NAME); + const std::string REBOOT_WATCHDOG_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::REBOOT_WATCHDOG_FILE_NAME); + const std::string LEAP_SECONDS_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::LEAP_SECONDS_FILE_NAME); + const std::string BACKUP_TIME_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::TIME_FILE_NAME); + const std::string REBOOT_COUNTERS_FILE = + "/" + std::string(core::CONF_FOLDER) + "/" + std::string(core::REBOOT_COUNTER_FILE_NAME); + + static constexpr char CHIP_0_COPY_0_MOUNT_DIR[] = "/tmp/mntupdate-xdi-qspi0-nom-rootfs"; + static constexpr char CHIP_0_COPY_1_MOUNT_DIR[] = "/tmp/mntupdate-xdi-qspi0-gold-rootfs"; + static constexpr char CHIP_1_COPY_0_MOUNT_DIR[] = "/tmp/mntupdate-xdi-qspi1-nom-rootfs"; + static constexpr char CHIP_1_COPY_1_MOUNT_DIR[] = "/tmp/mntupdate-xdi-qspi1-gold-rootfs"; + static constexpr char LIST_DIR_DUMP_WORK_FILE[] = "/tmp/dir_listing.tmp"; + + static constexpr dur_millis_t INIT_SD_CARD_CHECK_TIMEOUT = 5000; + static constexpr dur_millis_t DEFAULT_SD_CARD_CHECK_TIMEOUT = 60000; + + CoreController(object_id_t objectId, bool enableHkSet); + virtual ~CoreController(); + + ReturnValue_t initialize() override; + + ReturnValue_t initializeAfterTaskCreation() override; + + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + void performControlOperation() override; + + /** + * Generate a file containing the chip lock/unlock states inside /tmp/chip_prot_status.txt + * @return + */ + static ReturnValue_t generateChipStateFile(); + static ReturnValue_t incrementAllocationFailureCount(); + static void getCurrentBootCopy(xsc::Chip& chip, xsc::Copy& copy); + static const char* getXscMountDir(xsc::Chip chip, xsc::Copy copy); + + ReturnValue_t updateProtInfo(bool regenerateChipStateFile = true); + + /** + * Checks whether the target chip and copy are write protected and protect set them to a target + * state where applicable. + * @param targetChip + * @param targetCopy + * @param protect Target state + * @param protOperationPerformed [out] Can be used to determine whether any operation + * was performed + * @param updateProtFile Specify whether the protection info file is updated + * @return + */ + ReturnValue_t setBootCopyProtectionAndUpdateFile(xsc::Chip targetChip, xsc::Copy targetCopy, + bool protect); + + bool sdInitFinished() const; + + private: + static constexpr uint32_t BOOT_OFFSET_SECONDS = 15; + static constexpr MutexIF::TimeoutType TIMEOUT_TYPE = MutexIF::TimeoutType::WAITING; + static constexpr uint32_t MUTEX_TIMEOUT = 20; + bool enableHkSet = false; + GpsHyperion::FixMode gpsFix = GpsHyperion::FixMode::NOT_SEEN; + + // States for SD state machine, which is used in non-blocking mode + enum class SdStates { + NONE, + START, + UPDATE_SD_INFO_START, + SKIP_TWO_CYCLES_IF_SD_LOCKED, + MOUNT_SELF, + // Determine operations for other SD card, depending on redundancy configuration + DETERMINE_OTHER, + SET_STATE_OTHER, + // Mount or unmount other + MOUNT_UNMOUNT_OTHER, + // Skip period because the shell command used to generate the info file sometimes is + // missing the last performed operation if executed too early + SKIP_CYCLE_BEFORE_INFO_UPDATE, + UPDATE_SD_INFO_END, + // SD initialization done + IDLE + }; + + enum class SwUpdateSources { SD_0, SD_1, TMP_DIR }; + + static constexpr bool BLOCKING_SD_INIT = false; + + uint32_t* mappedSysRomAddr = nullptr; + SdCardManager* sdcMan = nullptr; + MessageQueueIF* eventQueue = nullptr; + + uint8_t prefSdRaw = sd::SdCard::SLOT_0; + SdStates sdFsmState = SdStates::START; + SdStates fsmStateAfterDelay = SdStates::IDLE; + enum SdCfgMode { PASSIVE, COLD_REDUNDANT, HOT_REDUNDANT }; + + struct SdFsmParams { + SdCfgMode cfgMode = SdCfgMode::COLD_REDUNDANT; + sd::SdCard active = sd::SdCard::NONE; + sd::SdCard other = sd::SdCard::NONE; + std::string activeChar = "0"; + std::string otherChar = "1"; + sd::SdState activeState = sd::SdState::OFF; + sd::SdState otherState = sd::SdState::OFF; + std::pair mountSwitch = {true, true}; + // This flag denotes that the SD card usage is locked. This is relevant if SD cards go off + // to leave appliation using the SD cards some time to detect the SD card is not usable anymore. + // This is relevant if the active SD card is being switched. The SD card will also be locked + // when going from hot-redundant mode to cold-redundant mode. + bool lockSdCardUsage = false; + bool commandPending = true; + bool initFinished = false; + SdCardManager::SdStatePair currentState; + uint16_t cycleCount = 0; + uint16_t skippedCyclesCount = 0; + } sdInfo; + + struct SdCommanding { + bool cmdPending = false; + MessageQueueId_t commander = MessageQueueIF::NO_QUEUE; + DeviceCommandId_t actionId; + } sdCommandingInfo; + + struct DirListingDumpContext { + bool active; + bool firstDump; + size_t dumpedBytes; + size_t totalFileSize; + size_t listingDataOffset; + size_t maxDumpLen; + uint32_t segmentIdx; + MessageQueueId_t commander = MessageQueueIF::NO_QUEUE; + DeviceCommandId_t actionId; + }; + std::array dirListingBuf{}; + DirListingDumpContext dumpContext{}; + + RebootWatchdogFile rebootWatchdogFile = {}; + RebootCountersFile rebootCountersFile = {}; + + CommandExecutor cmdExecutor; + SimpleRingBuffer cmdReplyBuf; + DynamicFIFO cmdRepliesSizes; + bool shellCmdIsExecuting = false; + MessageQueueId_t successRecipient = MessageQueueIF::NO_QUEUE; + + std::string currMntPrefix; + bool timeFileInitDone = false; + bool leapSecondsInitDone = false; + bool performOneShotSdCardOpsSwitch = false; + uint8_t shortSdCardCdCounter = 0; +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter; +#endif + Countdown sdCardCheckCd = Countdown(INIT_SD_CARD_CHECK_TIMEOUT); + + /** + * First index: Chip. + * Second index: Copy. + */ + bool protArray[2][2]{}; + PeriodicOperationDivider opDivider5; + PeriodicOperationDivider opDivider10; + + PoolEntry tempPoolEntry = PoolEntry(0.0); + PoolEntry psVoltageEntry = PoolEntry(0.0); + PoolEntry plVoltageEntry = PoolEntry(0.0); + + core::HkSet hkSet; + + ParameterHelper paramHelper; + +#if OBSW_SD_CARD_MUST_BE_ON == 1 + bool remountAttemptFlag = true; +#endif + + MessageQueueId_t getCommandQueue() const override; + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueIdentifier, + ParameterWrapper* parameterWrapper, const ParameterWrapper* newValues, + uint16_t startAtIndex) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, uint32_t* msToReachTheMode); + void performMountedSdCardOperations(); + ReturnValue_t initVersionFile(); + + void initLeapSeconds(); + ReturnValue_t initLeapSecondsFromFile(); + ReturnValue_t initClockFromTimeFile(); + ReturnValue_t actionUpdateLeapSeconds(const uint8_t* data); + ReturnValue_t writeLeapSecondsToFile(const uint16_t leapSeconds); + ReturnValue_t performSdCardCheck(); + ReturnValue_t backupTimeFileHandler(); + ReturnValue_t initBootCopyFile(); + ReturnValue_t initSdCardBlocking(); + bool startSdStateMachine(sd::SdCard targetActiveSd, SdCfgMode mode, MessageQueueId_t commander, + DeviceCommandId_t actionId); + void initPrint(); + + ReturnValue_t sdStateMachine(); + void updateInternalSdInfo(); + ReturnValue_t sdCardSetup(sd::SdCard sdCard, sd::SdState targetState, std::string sdChar, + bool printOutput = true); + ReturnValue_t executeSwUpdate(SwUpdateSources sourceDir, const uint8_t* data, size_t size); + ReturnValue_t sdColdRedundantBlockingInit(); + + void currentStateSetter(sd::SdCard sdCard, sd::SdState newState); + void executeNextExternalSdCommand(); + void checkExternalSdCommandStatus(); + void performRebootWatchdogHandling(bool recreateFile); + void performRebootCountersHandling(bool recreateFile); + + ReturnValue_t actionListDirectoryIntoFile(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size); + ReturnValue_t actionListDirectoryDumpDirectly(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size); + ReturnValue_t performGracefulShutdown(xsc::Chip targetChip, xsc::Copy targetCopy); + + ReturnValue_t actionListDirectoryCommonCommandCreator(const uint8_t* data, size_t size, + std::ostringstream& oss); + + ReturnValue_t actionXscReboot(const uint8_t* data, size_t size); + ReturnValue_t actionReboot(const uint8_t* data, size_t size); + + ReturnValue_t gracefulShutdownTasks(xsc::Chip chip, xsc::Copy copy, bool& protOpPerformed); + + ReturnValue_t handleProtInfoUpdateLine(std::string nextLine); + ReturnValue_t handleSwitchingSdCardsOffNonBlocking(); + bool handleBootCopyProt(xsc::Chip targetChip, xsc::Copy targetCopy, bool protect); + void rebootWatchdogAlgorithm(RebootWatchdogFile& rf, bool& needsReboot, xsc::Chip& tgtChip, + xsc::Copy& tgtCopy); + void resetRebootWatchdogCounters(xsc::Chip tgtChip, xsc::Copy tgtCopy); + void setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy); + bool parseRebootWatchdogFile(std::string path, RebootWatchdogFile& file); + bool parseRebootCountersFile(std::string path, RebootCountersFile& file); + void rewriteRebootWatchdogFile(RebootWatchdogFile file); + void rewriteRebootCountersFile(RebootCountersFile file); + void announceBootCounts(); + void announceVersionInfo(); + void announceCurrentImageInfo(); + void announceSdInfo(SdCardManager::SdStatePair sdStates); + void readHkData(); + void dirListingDumpHandler(); + bool isNumber(const std::string& s); +}; + +#endif /* BSP_Q7S_CORE_CORECONTROLLER_H_ */ diff --git a/bsp_q7s/core/WatchdogHandler.cpp b/bsp_q7s/core/WatchdogHandler.cpp new file mode 100644 index 0000000..9e3a102 --- /dev/null +++ b/bsp_q7s/core/WatchdogHandler.cpp @@ -0,0 +1,87 @@ +#include "WatchdogHandler.h" + +#include +#include + +#include +#include +#include + +#include "fsfw/serviceinterface.h" +#include "watchdog/definitions.h" + +WatchdogHandler::WatchdogHandler() {} + +void WatchdogHandler::periodicOperation() { + if (watchdogFifoFd != 0) { + if (watchdogFifoFd == RETRY_FIFO_OPEN) { + // Open FIFO write only and non-blocking + watchdogFifoFd = open(watchdog::FIFO_NAME.c_str(), O_WRONLY | O_NONBLOCK); + if (watchdogFifoFd < 0) { + if (errno == ENXIO) { + watchdogFifoFd = RETRY_FIFO_OPEN; + // No printout for now, would be spam + return; + } else { + sif::error << "Opening pipe " << watchdog::FIFO_NAME << " write-only failed with " + << errno << ": " << strerror(errno) << std::endl; + return; + } + } + sif::info << "Opened " << watchdog::FIFO_NAME << " successfully" << std::endl; + performStartHandling(); + } else if (watchdogFifoFd > 0) { + // Write to OBSW watchdog FIFO here + const char writeChar = watchdog::first::IDLE_CHAR; + ssize_t writtenBytes = write(watchdogFifoFd, &writeChar, 1); + if (writtenBytes < 0) { + sif::error << "Errors writing to watchdog FIFO, code " << errno << ": " << strerror(errno) + << std::endl; + } + } + } +} + +ReturnValue_t WatchdogHandler::initialize(bool enableWatchdogFunction) { + using namespace std::filesystem; + this->enableWatchFunction = enableWatchdogFunction; + std::error_code e; + if (not std::filesystem::exists(watchdog::FIFO_NAME, e)) { + // Still return returnvalue::OK for now + sif::info << "Watchdog FIFO " << watchdog::FIFO_NAME << " does not exist, can't initiate" + << " watchdog" << std::endl; + return returnvalue::OK; + } + // Open FIFO write only and non-blocking to prevent SW from killing itself. + watchdogFifoFd = open(watchdog::FIFO_NAME.c_str(), O_WRONLY | O_NONBLOCK); + if (watchdogFifoFd < 0) { + if (errno == ENXIO) { + watchdogFifoFd = RETRY_FIFO_OPEN; + sif::info << "eive-watchdog not running. FIFO can not be opened" << std::endl; + } else { + sif::error << "Opening pipe " << watchdog::FIFO_NAME << " write-only failed with " << errno + << ": " << strerror(errno) << std::endl; + return returnvalue::FAILED; + } + } + return performStartHandling(); +} + +ReturnValue_t WatchdogHandler::performStartHandling() { + char startBuf[2]; + ssize_t writeLen = 1; + startBuf[0] = watchdog::first::START_CHAR; + if (enableWatchFunction) { + writeLen += 1; + startBuf[1] = watchdog::second::WATCH_FLAG; + } + ssize_t writtenBytes = write(watchdogFifoFd, &startBuf, writeLen); + if (writtenBytes < 0) { + sif::error << "WatchdogHandler: Errors writing to watchdog FIFO, code " << errno << ": " + << strerror(errno) << std::endl; + return returnvalue::FAILED; + } else if (writtenBytes != writeLen) { + sif::warning << "WatchdogHandler: Not all bytes were written, possible error" << std::endl; + } + return returnvalue::OK; +} diff --git a/bsp_q7s/core/WatchdogHandler.h b/bsp_q7s/core/WatchdogHandler.h new file mode 100644 index 0000000..5db4228 --- /dev/null +++ b/bsp_q7s/core/WatchdogHandler.h @@ -0,0 +1,23 @@ +#ifndef BSP_Q7S_CORE_WATCHDOGHANDLER_H_ +#define BSP_Q7S_CORE_WATCHDOGHANDLER_H_ + +#include "fsfw/returnvalues/returnvalue.h" + +class WatchdogHandler { + public: + WatchdogHandler(); + + ReturnValue_t initialize(bool enableWatchFunction); + void periodicOperation(); + + private: + // Designated value for rechecking FIFO open + static constexpr int RETRY_FIFO_OPEN = -2; + + int watchdogFifoFd = 0; + bool enableWatchFunction = false; + + ReturnValue_t performStartHandling(); +}; + +#endif /* BSP_Q7S_CORE_WATCHDOGHANDLER_H_ */ diff --git a/bsp_q7s/core/XiphosWdtHandler.cpp b/bsp_q7s/core/XiphosWdtHandler.cpp new file mode 100644 index 0000000..8444b65 --- /dev/null +++ b/bsp_q7s/core/XiphosWdtHandler.cpp @@ -0,0 +1,122 @@ +#include "XiphosWdtHandler.h" + +#include "fsfw/ipc/QueueFactory.h" + +XiphosWdtHandler::XiphosWdtHandler(object_id_t objectId) + : SystemObject(objectId), + requestQueue(QueueFactory::instance()->createMessageQueue()), + actionHelper(this, requestQueue) {} + +ReturnValue_t XiphosWdtHandler::initialize() { + ReturnValue_t result = actionHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + int retval = xsc_watchdog_init(&wdtHandle); + if (retval != 0) { + sif::error << "XiphosWdtHandler: Initiating watchdog failed with code " << retval << ": " + << strerror(retval) << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + } + if (wdtHandle == nullptr) { + sif::error << "XiphosWdtHandler: WDT handle is nullptr!" << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + } + retval = xsc_watchdog_set_timeout(wdtHandle, timeoutSeconds); + if (retval != 0) { + // This propably means that the default timeout is used. Still continue with task init. + sif::warning << "XiphosWdtHandler: Setting WDT timeout of " << timeoutSeconds + << " seconds failed with code " << result << ": " << strerror(retval) << std::endl; + } + return enableWdt(); +} + +ReturnValue_t XiphosWdtHandler::performOperation(uint8_t opCode) { + CommandMessage command; + ReturnValue_t result; + for (result = requestQueue->receiveMessage(&command); result == returnvalue::OK; + result = requestQueue->receiveMessage(&command)) { + result = actionHelper.handleActionMessage(&command); + if (result == returnvalue::OK) { + continue; + } + sif::warning << "Can not handle message with message type " << command.getMessageType() + << std::endl; + } + if (enabled) { + int retval = xsc_watchdog_keepalive(wdtHandle); + if (retval != 0) { + sif::warning << "XiphosWdtHandler: Feeding WDT failed with code " << retval << ": " + << strerror(retval) << std::endl; + return returnvalue::FAILED; + } + } + return returnvalue::OK; +} + +ReturnValue_t XiphosWdtHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t *data, size_t size) { + switch (actionId) { + case (ActionId::ENABLE): { + ReturnValue_t result = enableWdt(); + if (result != returnvalue::OK) { + return result; + } + return EXECUTION_FINISHED; + } + case (ActionId::DISABLE): { + ReturnValue_t result = disableWdt(); + if (result != returnvalue::OK) { + return result; + } + return EXECUTION_FINISHED; + } + } + return HasActionsIF::INVALID_ACTION_ID; +} + +ReturnValue_t XiphosWdtHandler::enableWdt() { + int nowayout = 0; + int status = 0; + int retval = xsc_watchdog_get_status(&nowayout, &status); + // If this fails for whatever reason, just try enabling in any case. + if (retval != 0) { + sif::warning << "XiphosWdtHandler: Getting WDT status failed" << std::endl; + } + // Of course the enable API will fail if the device is already on, just perfect, love me some + // good C API... :))) + if (retval != 0 or status == 0) { + retval = xsc_watchdog_enable(wdtHandle); + if (retval != 0) { + sif::error << "XiphosWdtHandler: Enabling WDT failed with code " << retval << ": " + << strerror(retval) << std::endl; + return returnvalue::FAILED; + } + } + enabled = true; + return returnvalue::OK; +} + +ReturnValue_t XiphosWdtHandler::disableWdt() { + int nowayout = 0; + int status = 0; + int retval = xsc_watchdog_get_status(&nowayout, &status); + // If this fails for whatever reason, just try disabling in any case. + if (retval != 0) { + sif::warning << "XiphosWdtHandler: Getting WDT status failed" << std::endl; + } + // Of course the disable API will fail if the device is already off, just perfect, love me some + // good C API... :))) + if (retval != 0 or status == 1) { + retval = xsc_watchdog_disable(wdtHandle); + if (retval != 0) { + sif::error << "XiphosWdtHandler: Disabling WDT failed with code " << retval << ": " + << strerror(retval) << std::endl; + return returnvalue::FAILED; + } + } + enabled = false; + return returnvalue::OK; +} + +MessageQueueId_t XiphosWdtHandler::getCommandQueue() const { return requestQueue->getId(); } diff --git a/bsp_q7s/core/XiphosWdtHandler.h b/bsp_q7s/core/XiphosWdtHandler.h new file mode 100644 index 0000000..a8d73f4 --- /dev/null +++ b/bsp_q7s/core/XiphosWdtHandler.h @@ -0,0 +1,36 @@ +#ifndef BSP_Q7S_CORE_XIPHOSWDTHANDLER_H_ +#define BSP_Q7S_CORE_XIPHOSWDTHANDLER_H_ + +#include +#include +#include +#include + +class XiphosWdtHandler : public SystemObject, public ExecutableObjectIF, public HasActionsIF { + public: + enum ActionId { ENABLE = 0, DISABLE = 1 }; + + XiphosWdtHandler(object_id_t objectId); + ReturnValue_t performOperation(uint8_t opCode) override; + ReturnValue_t initialize() override; + + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + [[nodiscard]] virtual MessageQueueId_t getCommandQueue() const override; + + private: + // Wrappers to ensure idempotency of trash C API. + ReturnValue_t enableWdt(); + ReturnValue_t disableWdt(); + // Timeout duration range specified by Xiphos: 0.001 seconds to 171 seconds. The libxiphos API + // expects an int, so I guess this translates to 1 to 171 seconds. + // WARNING: DO NOT SET THIS HIGHER THAN 80 SECONDS! + // Possible bug in Xiphos/Xilinx kernel driver for watchdog, related to overflow. + int timeoutSeconds = 80; + bool enabled = false; + struct watchdog_s* wdtHandle = nullptr; + MessageQueueIF* requestQueue = nullptr; + ActionHelper actionHelper; +}; + +#endif /* BSP_Q7S_CORE_XIPHOSWDTHANDLER_H_ */ diff --git a/bsp_q7s/core/defs.h b/bsp_q7s/core/defs.h new file mode 100644 index 0000000..a82c1c5 --- /dev/null +++ b/bsp_q7s/core/defs.h @@ -0,0 +1,45 @@ +#ifndef BSP_Q7S_CORE_DEFS_H_ +#define BSP_Q7S_CORE_DEFS_H_ + +#include + +namespace core { + +extern uint8_t FW_VERSION_MAJOR; +extern uint8_t FW_VERSION_MINOR; +extern uint8_t FW_VERSION_REVISION; +extern bool FW_VERSION_HAS_SHA; +extern char FW_VERSION_GIT_SHA[4]; + +static const uint8_t HK_SET_ENTRIES = 3; +static const uint32_t HK_SET_ID = 5; + +enum PoolIds { TEMPERATURE, PS_VOLTAGE, PL_VOLTAGE }; + +/** + * @brief Set storing OBC internal housekeeping data + */ +class HkSet : public StaticLocalDataSet { + public: + HkSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, HK_SET_ID) {} + + HkSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, HK_SET_ID)) {} + + // On-chip temperature + lp_var_t temperature = lp_var_t(sid.objectId, PoolIds::TEMPERATURE, this); + // Processing system VCC + lp_var_t psVoltage = lp_var_t(sid.objectId, PoolIds::PS_VOLTAGE, this); + // Programmable logic VCC + lp_var_t plVoltage = lp_var_t(sid.objectId, PoolIds::PL_VOLTAGE, this); + + void printSet() { + sif::info << "HkSet::printSet: On-chip temperature: " << this->temperature << " °C" + << std::endl; + sif::info << "HkSet::printSet: PS voltage: " << this->psVoltage << " mV" << std::endl; + sif::info << "HkSet::printSet: PL voltage: " << this->plVoltage << " mV" << std::endl; + } +}; + +} // namespace core + +#endif /* BSP_Q7S_CORE_DEFS_H_ */ diff --git a/bsp_q7s/em/CMakeLists.txt b/bsp_q7s/em/CMakeLists.txt new file mode 100644 index 0000000..5ac6819 --- /dev/null +++ b/bsp_q7s/em/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PRIVATE emObjectFactory.cpp) diff --git a/bsp_q7s/em/emObjectFactory.cpp b/bsp_q7s/em/emObjectFactory.cpp new file mode 100644 index 0000000..0c0c3fa --- /dev/null +++ b/bsp_q7s/em/emObjectFactory.cpp @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OBSWConfig.h" +#include "bsp_q7s/core/CoreController.h" +#include "busConf.h" +#include "common/config/devices/addresses.h" +#include "devConf.h" +#include "dummies/helperFactory.h" +#include "eive/objects.h" +#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h" +#include "linux/ObjectFactory.h" +#include "linux/callbacks/gpioCallbacks.h" +#include "mission/genericFactory.h" +#include "mission/system/com/comModeTree.h" + +void ObjectFactory::produce(void* args) { + ObjectFactory::setStatics(); + HealthTableIF* healthTable = nullptr; + PusTmFunnel* pusFunnel = nullptr; + CfdpTmFunnel* cfdpFunnel = nullptr; + StorageManagerIF* ipcStore = nullptr; + StorageManagerIF* tmStore = nullptr; + + bool enableHkSets = false; +#if OBSW_ENABLE_PERIODIC_HK == 1 + enableHkSets = true; +#endif + + PersistentTmStores stores; + readFirmwareVersion(); + new XiphosWdtHandler(objects::XIPHOS_WDT); + ObjectFactory::produceGenericObjects(&healthTable, &pusFunnel, &cfdpFunnel, + *SdCardManager::instance(), &ipcStore, &tmStore, stores, 200, + enableHkSets, true); + + LinuxLibgpioIF* gpioComIF = nullptr; + SerialComIF* uartComIF = nullptr; + SpiComIF* spiMainComIF = nullptr; + I2cComIF* i2cComIF = nullptr; + createCommunicationInterfaces(&gpioComIF, &uartComIF, &spiMainComIF, &i2cComIF); + // Adding GPIOs for chip select decoding and initializing them. + q7s::gpioCallbacks::initSpiCsDecoder(gpioComIF); + gpioCallbacks::disableAllDecoder(gpioComIF); + createPlI2cResetGpio(gpioComIF); + + // Hardware is usually not connected to EM, so we need to create dummies which replace lower + // level components. + dummy::DummyCfg dummyCfg; + dummyCfg.addCoreCtrlCfg = false; + dummyCfg.addCamSwitcherDummy = false; +#if OBSW_ADD_SYRLINKS == 1 + dummyCfg.addSyrlinksDummies = false; +#endif +#if OBSW_ADD_PLOC_SUPERVISOR == 1 || OBSW_ADD_PLOC_MPSOC == 1 + dummyCfg.addPlocDummies = false; +#endif +#if OBSW_ADD_TMP_DEVICES == 1 + std::vector> tmpDevsToAdd = {{ + {objects::TMP1075_HANDLER_PLPCDU_0, addresses::TMP1075_PLPCDU_0}, + {objects::TMP1075_HANDLER_PLPCDU_1, addresses::TMP1075_PLPCDU_1}, + {objects::TMP1075_HANDLER_IF_BOARD, addresses::TMP1075_IF_BOARD}, + }}; + createTmpComponents(tmpDevsToAdd); + dummy::Tmp1075Cfg tmpCfg{}; + tmpCfg.addTcsBrd0 = true; + tmpCfg.addTcsBrd1 = true; + tmpCfg.addPlPcdu0 = false; + tmpCfg.addPlPcdu1 = false; + tmpCfg.addIfBrd = false; + dummyCfg.tmp1075Cfg = tmpCfg; +#endif +#if OBSW_ADD_GOMSPACE_PCDU == 1 + dummyCfg.addPowerDummies = false; + // The ACU broke. + dummyCfg.addOnlyAcuDummy = true; +#endif +#if OBSW_ADD_STAR_TRACKER == 1 + dummyCfg.addStrDummy = false; +#endif +#if OBSW_ADD_SCEX_DEVICE == 0 + dummyCfg.addScexDummy = true; +#endif +#if OBSW_ADD_BPX_BATTERY_HANDLER == 1 + dummyCfg.addBpxBattDummy = false; +#endif +#if OBSW_ADD_ACS_BOARD == 1 + dummyCfg.addAcsBoardDummies = false; +#endif +#if OBSW_ADD_PL_PCDU == 0 + dummyCfg.addPlPcduDummy = true; +#endif + + PowerSwitchIF* pwrSwitcher = nullptr; +#if OBSW_ADD_GOMSPACE_PCDU == 0 + pwrSwitcher = new PcduHandlerDummy(objects::PCDU_HANDLER); +#else + createPcduComponents(gpioComIF, &pwrSwitcher, enableHkSets); +#endif + satsystem::EIVE_SYSTEM.setI2cRecoveryParams(pwrSwitcher); + + const char* battAndImtqI2cDev = q7s::I2C_PL_EIVE; + if (core::FW_VERSION_MAJOR >= 4) { + battAndImtqI2cDev = q7s::I2C_PS_EIVE; + } + static_cast(battAndImtqI2cDev); + +#if OBSW_ADD_BPX_BATTERY_HANDLER == 1 + createBpxBatteryComponent(enableHkSets, battAndImtqI2cDev); +#endif + + dummy::createDummies(dummyCfg, *pwrSwitcher, gpioComIF, enableHkSets); + + createPowerController(true, enableHkSets); + + new CoreController(objects::CORE_CONTROLLER, enableHkSets); + + auto* stackHandler = new Stack5VHandler(*pwrSwitcher); + static_cast(stackHandler); + + // Initialize chip select to avoid SPI bus issues. + createRadSensorChipSelect(gpioComIF); + +#if OBSW_ADD_ACS_BOARD == 1 + createAcsBoardComponents(*spiMainComIF, gpioComIF, uartComIF, *pwrSwitcher, true, + adis1650x::Type::ADIS16507); +#else + // Still add all GPIOs for EM. + GpioCookie* acsBoardGpios = new GpioCookie(); + createAcsBoardGpios(*acsBoardGpios); + gpioChecker(gpioComIF->addGpios(acsBoardGpios), "ACS Board"); +#endif + +#if OBSW_ADD_MGT == 1 + createImtqComponents(pwrSwitcher, enableHkSets, battAndImtqI2cDev); +#endif + +#if OBSW_ADD_RW == 1 + createReactionWheelComponents(gpioComIF, pwrSwitcher); +#endif + +#if OBSW_ADD_STAR_TRACKER == 1 + createStrComponents(pwrSwitcher, *SdCardManager::instance()); +#endif /* OBSW_ADD_STAR_TRACKER == 1 */ + +#if OBSW_ADD_SYRLINKS == 1 + createSyrlinksComponents(pwrSwitcher); +#endif + +#if OBSW_ADD_PL_PCDU == 1 + createPlPcduComponents(gpioComIF, spiMainComIF, pwrSwitcher, *stackHandler); +#endif + createPayloadComponents(gpioComIF, *pwrSwitcher); + +#if OBSW_ADD_CCSDS_IP_CORES == 1 + CcsdsIpCoreHandler* ipCoreHandler = nullptr; + CcsdsComponentArgs ccsdsArgs(*gpioComIF, *ipcStore, *tmStore, stores, *pusFunnel, *cfdpFunnel, + &ipCoreHandler, 0, 0); + createCcsdsIpComponentsWrapper(ccsdsArgs); +#endif /* OBSW_ADD_CCSDS_IP_CORES == 1 */ + + /* Test Task */ +#if OBSW_ADD_TEST_CODE == 1 + createTestComponents(gpioComIF); +#endif /* OBSW_ADD_TEST_CODE == 1 */ +#if OBSW_ADD_SCEX_DEVICE == 1 + createScexComponents(q7s::UART_SCEX_DEV, pwrSwitcher, *SdCardManager::instance(), false, + power::Switches::PDU1_CH5_SOLAR_CELL_EXP_5V); +#endif + createAcsController(true, enableHkSets, *SdCardManager::instance()); + HeaterHandler* heaterHandler; + createHeaterComponents(gpioComIF, pwrSwitcher, healthTable, heaterHandler); + createThermalController(*heaterHandler, true); + satsystem::init(true); +} diff --git a/bsp_q7s/fmObjectFactory.cpp b/bsp_q7s/fmObjectFactory.cpp new file mode 100644 index 0000000..71722e5 --- /dev/null +++ b/bsp_q7s/fmObjectFactory.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OBSWConfig.h" +#include "bsp_q7s/core/CoreController.h" +#include "busConf.h" +#include "devConf.h" +#include "devices/addresses.h" +#include "eive/objects.h" +#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h" +#include "linux/ObjectFactory.h" +#include "linux/callbacks/gpioCallbacks.h" +#include "mission/genericFactory.h" +#include "mission/system/systemTree.h" +#include "mission/tmtc/tmFilters.h" + +void ObjectFactory::produce(void* args) { + ObjectFactory::setStatics(); + HealthTableIF* healthTable = nullptr; + PusTmFunnel* pusFunnel = nullptr; + CfdpTmFunnel* cfdpFunnel = nullptr; + StorageManagerIF* ipcStore = nullptr; + StorageManagerIF* tmStore = nullptr; + + bool enableHkSets = false; +#if OBSW_ENABLE_PERIODIC_HK == 1 + enableHkSets = true; +#endif + + PersistentTmStores stores; + readFirmwareVersion(); + new XiphosWdtHandler(objects::XIPHOS_WDT); + ObjectFactory::produceGenericObjects(&healthTable, &pusFunnel, &cfdpFunnel, + *SdCardManager::instance(), &ipcStore, &tmStore, stores, 200, + true, true); + + LinuxLibgpioIF* gpioComIF = nullptr; + SerialComIF* uartComIF = nullptr; + SpiComIF* spiMainComIF = nullptr; + I2cComIF* i2cComIF = nullptr; + PowerSwitchIF* pwrSwitcher = nullptr; + createCommunicationInterfaces(&gpioComIF, &uartComIF, &spiMainComIF, &i2cComIF); + /* Adding gpios for chip select decoding to the gpioComIf */ + q7s::gpioCallbacks::initSpiCsDecoder(gpioComIF); + gpioCallbacks::disableAllDecoder(gpioComIF); + createPlI2cResetGpio(gpioComIF); + + new CoreController(objects::CORE_CONTROLLER, enableHkSets); + createPcduComponents(gpioComIF, &pwrSwitcher, enableHkSets); + satsystem::EIVE_SYSTEM.setI2cRecoveryParams(pwrSwitcher); + + auto* stackHandler = new Stack5VHandler(*pwrSwitcher); + +#if OBSW_ADD_RAD_SENSORS == 1 + createRadSensorComponent(gpioComIF, *stackHandler); +#endif +#if OBSW_ADD_SUN_SENSORS == 1 + createSunSensorComponents(gpioComIF, spiMainComIF, *pwrSwitcher, q7s::SPI_DEFAULT_DEV, true); +#endif + +#if OBSW_ADD_ACS_BOARD == 1 + createAcsBoardComponents(*spiMainComIF, gpioComIF, uartComIF, *pwrSwitcher, true, + adis1650x::Type::ADIS16505); +#endif + HeaterHandler* heaterHandler; + createHeaterComponents(gpioComIF, pwrSwitcher, healthTable, heaterHandler); +#if OBSW_ADD_TMP_DEVICES == 1 + std::vector> tmpDevsToAdd = {{ + {objects::TMP1075_HANDLER_TCS_0, addresses::TMP1075_TCS_0}, + {objects::TMP1075_HANDLER_TCS_1, addresses::TMP1075_TCS_1}, + {objects::TMP1075_HANDLER_PLPCDU_0, addresses::TMP1075_PLPCDU_0}, + // damaged + // {objects::TMP1075_HANDLER_PLPCDU_1, addresses::TMP1075_PLPCDU_1}, + {objects::TMP1075_HANDLER_IF_BOARD, addresses::TMP1075_IF_BOARD}, + }}; + + createTmpComponents(tmpDevsToAdd); +#endif + createSolarArrayDeploymentComponents(*pwrSwitcher, *gpioComIF); + + const char* battAndImtqI2cDev = q7s::I2C_PL_EIVE; + if (core::FW_VERSION_MAJOR >= 4) { + battAndImtqI2cDev = q7s::I2C_PS_EIVE; + } +#if OBSW_ADD_MGT == 1 + createImtqComponents(pwrSwitcher, enableHkSets, battAndImtqI2cDev); +#endif + createReactionWheelComponents(gpioComIF, pwrSwitcher); + +#if OBSW_ADD_BPX_BATTERY_HANDLER == 1 + createBpxBatteryComponent(enableHkSets, battAndImtqI2cDev); +#endif + createPowerController(true, enableHkSets); + +#if OBSW_ADD_PL_PCDU == 1 + createPlPcduComponents(gpioComIF, spiMainComIF, pwrSwitcher, *stackHandler); +#endif +#if OBSW_ADD_SYRLINKS == 1 + createSyrlinksComponents(pwrSwitcher); +#endif /* OBSW_ADD_SYRLINKS == 1 */ + + createRtdComponents(q7s::SPI_DEFAULT_DEV, gpioComIF, pwrSwitcher, spiMainComIF); + createPayloadComponents(gpioComIF, *pwrSwitcher); + +#if OBSW_ADD_STAR_TRACKER == 1 + createStrComponents(pwrSwitcher, *SdCardManager::instance()); +#endif /* OBSW_ADD_STAR_TRACKER == 1 */ + +#if OBSW_ADD_CCSDS_IP_CORES == 1 + CcsdsIpCoreHandler* ipCoreHandler = nullptr; + CcsdsComponentArgs ccsdsArgs(*gpioComIF, *ipcStore, *tmStore, stores, *pusFunnel, *cfdpFunnel, + &ipCoreHandler, 0, 0); + createCcsdsIpComponentsWrapper(ccsdsArgs); +#endif /* OBSW_ADD_CCSDS_IP_CORES == 1 */ + +#if OBSW_ADD_SCEX_DEVICE == 1 + createScexComponents(q7s::UART_SCEX_DEV, pwrSwitcher, *SdCardManager::instance(), false, + power::Switches::PDU1_CH5_SOLAR_CELL_EXP_5V); +#endif + /* Test Task */ +#if OBSW_ADD_TEST_CODE == 1 + createTestComponents(gpioComIF); +#endif /* OBSW_ADD_TEST_CODE == 1 */ + + createMiscComponents(); + createThermalController(*heaterHandler, false); + createAcsController(true, enableHkSets, *SdCardManager::instance()); + satsystem::init(false); +} diff --git a/bsp_q7s/fs/CMakeLists.txt b/bsp_q7s/fs/CMakeLists.txt new file mode 100644 index 0000000..0e51683 --- /dev/null +++ b/bsp_q7s/fs/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(${OBSW_NAME} PRIVATE helpers.cpp SdCardManager.cpp + FilesystemHelper.cpp) diff --git a/bsp_q7s/fs/FilesystemHelper.cpp b/bsp_q7s/fs/FilesystemHelper.cpp new file mode 100644 index 0000000..8f49d10 --- /dev/null +++ b/bsp_q7s/fs/FilesystemHelper.cpp @@ -0,0 +1,38 @@ +#include "FilesystemHelper.h" + +#include +#include + +#include "SdCardManager.h" +#include "eive/definitions.h" +#include "fsfw/serviceinterface.h" + +FilesystemHelper::FilesystemHelper() {} + +ReturnValue_t FilesystemHelper::checkPath(std::string path) { + SdCardManager* sdcMan = SdCardManager::instance(); + if (sdcMan == nullptr) { + sif::warning << "FilesystemHelper::checkPath: Invalid SD card manager" << std::endl; + return returnvalue::FAILED; + } + if (path.substr(0, sizeof(config::SD_0_MOUNT_POINT)) == std::string(config::SD_0_MOUNT_POINT)) { + if (!sdcMan->isSdCardUsable(sd::SLOT_0)) { + sif::warning << "FilesystemHelper::checkPath: SD card 0 not mounted" << std::endl; + return SD_NOT_MOUNTED; + } + } else if (path.substr(0, sizeof(config::SD_1_MOUNT_POINT)) == + std::string(config::SD_1_MOUNT_POINT)) { + if (!sdcMan->isSdCardUsable(sd::SLOT_1)) { + sif::warning << "FilesystemHelper::checkPath: SD card 1 not mounted" << std::endl; + return SD_NOT_MOUNTED; + } + } + return returnvalue::OK; +} + +ReturnValue_t FilesystemHelper::fileExists(std::string file) { + if (not std::filesystem::exists(file)) { + return FILE_NOT_EXISTS; + } + return returnvalue::OK; +} diff --git a/bsp_q7s/fs/FilesystemHelper.h b/bsp_q7s/fs/FilesystemHelper.h new file mode 100644 index 0000000..ea1b570 --- /dev/null +++ b/bsp_q7s/fs/FilesystemHelper.h @@ -0,0 +1,49 @@ +#ifndef BSP_Q7S_MEMORY_FILESYSTEMHELPER_H_ +#define BSP_Q7S_MEMORY_FILESYSTEMHELPER_H_ + +#include + +#include "eive/resultClassIds.h" +#include "fsfw/returnvalues/returnvalue.h" + +/** + * @brief This class implements often used functions related to the file system management. + * + * @author J. Meier + */ +class FilesystemHelper { + public: + static const uint8_t INTERFACE_ID = CLASS_ID::FILE_SYSTEM_HELPER; + + //! [EXPORT] : [COMMENT] SD card specified with path string not mounted + static const ReturnValue_t SD_NOT_MOUNTED = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] Specified file does not exist on filesystem + static const ReturnValue_t FILE_NOT_EXISTS = MAKE_RETURN_CODE(0xA1); + + /** + * @brief In case the path points to a directory on the sd card, the function checks if the + * appropriate SD card is mounted. + * + * @param path Path to check + * + * @return returnvalue::OK if path points to SD card and the appropriate SD card is mounted or if + * path does not point to SD card. + * Return error code if path points to SD card and the corresponding SD card is not + * mounted. + */ + static ReturnValue_t checkPath(std::string path); + + /** + * @brief Checks if the file exists on the filesystem. + * + * @param file File to check + * + * @return returnvalue::OK if file exists, otherwise return error code. + */ + static ReturnValue_t fileExists(std::string file); + + private: + FilesystemHelper(); +}; + +#endif /* BSP_Q7S_MEMORY_FILESYSTEMHELPER_H_ */ diff --git a/bsp_q7s/fs/SdCardManager.cpp b/bsp_q7s/fs/SdCardManager.cpp new file mode 100644 index 0000000..ffbed66 --- /dev/null +++ b/bsp_q7s/fs/SdCardManager.cpp @@ -0,0 +1,584 @@ +#include "SdCardManager.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "OBSWConfig.h" +#include "bsp_q7s/memory/scratchApi.h" +#include "eive/definitions.h" +#include "eive/objects.h" +#include "fsfw/ipc/MutexFactory.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "linux/utility/utility.h" + +SdCardManager* SdCardManager::INSTANCE = nullptr; + +SdCardManager::SdCardManager() : SystemObject(objects::SDC_MANAGER), cmdExecutor(256) { + sdLock = MutexFactory::instance()->createMutex(); + prefLock = MutexFactory::instance()->createMutex(); + defaultLock = MutexFactory::instance()->createMutex(); + + MutexGuard mg(prefLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + if (mg.getLockResult() != returnvalue::OK) { + sif::error << "SdCardManager::SdCardManager: Mutex lock failed" << std::endl; + } + uint8_t prefSdRaw = 0; + ReturnValue_t result = scratch::readNumber(scratch::PREFERED_SDC_KEY, prefSdRaw); + + if (result != returnvalue::OK) { + if (result == scratch::KEY_NOT_FOUND) { + sif::warning << "CoreController::sdCardInit: " + "Preferred SD card not set. Setting to 0" + << std::endl; + scratch::writeNumber(scratch::PREFERED_SDC_KEY, static_cast(sd::SdCard::SLOT_0)); + prefSdRaw = sd::SdCard::SLOT_0; + + } else { + // Should not happen. + // TODO: Maybe trigger event? + sif::error << "SdCardManager::SdCardManager: Reading preferred SD card from scratch" + "buffer failed" + << std::endl; + prefSdRaw = sd::SdCard::SLOT_0; + } + } + sdInfo.pref = static_cast(prefSdRaw); +} + +SdCardManager::~SdCardManager() {} + +void SdCardManager::create() { + if (INSTANCE == nullptr) { + INSTANCE = new SdCardManager(); + } +} + +SdCardManager* SdCardManager::instance() { + SdCardManager::create(); + return SdCardManager::INSTANCE; +} + +ReturnValue_t SdCardManager::switchOnSdCard(sd::SdCard sdCard, bool doMountSdCard, + SdStatePair* statusPair) { + ReturnValue_t result = returnvalue::OK; + if (doMountSdCard) { + if (not blocking) { + sif::warning << "SdCardManager::switchOnSdCard: Two-step command but manager is" + " not configured for blocking operation. " + "Forcing blocking mode.." + << std::endl; + blocking = true; + } + } + std::unique_ptr sdStatusPtr; + if (statusPair == nullptr) { + sdStatusPtr = std::make_unique(); + statusPair = sdStatusPtr.get(); + result = getSdCardsStatus(*statusPair); + if (result != returnvalue::OK) { + return result; + } + } + + // Not allowed, this function turns on one SD card + if (sdCard == sd::SdCard::BOTH) { + sif::warning << "SdCardManager::switchOffSdCard: API does not allow sd::SdStatus::BOTH" + << std::endl; + return returnvalue::FAILED; + } + + sd::SdState currentState; + if (sdCard == sd::SdCard::SLOT_0) { + currentState = statusPair->first; + } else if (sdCard == sd::SdCard::SLOT_1) { + currentState = statusPair->second; + } else { + // Should not happen + currentState = sd::SdState::OFF; + } + + if (currentState == sd::SdState::ON) { + if (not doMountSdCard) { + return ALREADY_ON; + } else { + return mountSdCard(sdCard); + } + } else if (currentState == sd::SdState::MOUNTED) { + result = ALREADY_MOUNTED; + } else if (currentState == sd::SdState::OFF) { + result = setSdCardState(sdCard, true); + } else { + result = returnvalue::FAILED; + } + + if (result != returnvalue::OK or not doMountSdCard) { + return result; + } + + return mountSdCard(sdCard); +} + +ReturnValue_t SdCardManager::switchOffSdCard(sd::SdCard sdCard, SdStatePair& sdStates, + bool doUnmountSdCard) { + if (doUnmountSdCard) { + if (not blocking) { + sif::warning << "SdCardManager::switchOffSdCard: Two-step command but manager is" + " not configured for blocking operation. Forcing blocking mode.." + << std::endl; + blocking = true; + } + } + // Not allowed, this function turns off one SD card + if (sdCard == sd::SdCard::BOTH) { + sif::warning << "SdCardManager::switchOffSdCard: API does not allow sd::SdStatus::BOTH" + << std::endl; + return returnvalue::FAILED; + } + if (sdCard == sd::SdCard::SLOT_0) { + if (sdStates.first == sd::SdState::OFF) { + return ALREADY_OFF; + } + } else if (sdCard == sd::SdCard::SLOT_1) { + if (sdStates.second == sd::SdState::OFF) { + return ALREADY_OFF; + } + } + + if (doUnmountSdCard) { + ReturnValue_t result = unmountSdCard(sdCard); + if (result != returnvalue::OK) { + return result; + } + } + + return setSdCardState(sdCard, false); +} + +ReturnValue_t SdCardManager::setSdCardState(sd::SdCard sdCard, bool on) { + using namespace std; + if (cmdExecutor.getCurrentState() == CommandExecutor::States::PENDING) { + return CommandExecutor::COMMAND_PENDING; + } + string sdstring = ""; + string statestring = ""; + if (sdCard == sd::SdCard::SLOT_0) { + sdstring = "0"; + } else if (sdCard == sd::SdCard::SLOT_1) { + sdstring = "1"; + } + if (on) { + currentOp = Operations::SWITCHING_ON; + statestring = "on"; + } else { + currentOp = Operations::SWITCHING_OFF; + statestring = "off"; + } + ostringstream command; + command << "q7hw sd set " << sdstring << " " << statestring; + cmdExecutor.load(command.str(), blocking, printCmdOutput); + ReturnValue_t result = cmdExecutor.execute(); + if (result != returnvalue::OK) { + utility::handleSystemError(cmdExecutor.getLastError(), "SdCardManager::setSdCardState"); + } + return result; +} + +ReturnValue_t SdCardManager::getSdCardsStatus(SdStatePair& sdStates) { + MutexGuard mg(sdLock, LOCK_TYPE, SD_LOCK_TIMEOUT, LOCK_CTX); + sdStates = this->sdStates; + return returnvalue::OK; +} + +ReturnValue_t SdCardManager::mountSdCard(sd::SdCard sdCard) { + using namespace std; + if (cmdExecutor.getCurrentState() == CommandExecutor::States::PENDING) { + sif::warning << "SdCardManager::mountSdCard: Command still pending" << std::endl; + return CommandExecutor::COMMAND_PENDING; + } + if (sdCard == sd::SdCard::BOTH) { + sif::warning << "SdCardManager::mountSdCard: API does not allow sd::SdStatus::BOTH" + << std::endl; + return returnvalue::FAILED; + } + string mountDev; + string mountPoint; + if (sdCard == sd::SdCard::SLOT_0) { + mountDev = SD_0_DEV_NAME; + mountPoint = config::SD_0_MOUNT_POINT; + } else if (sdCard == sd::SdCard::SLOT_1) { + mountDev = SD_1_DEV_NAME; + mountPoint = config::SD_1_MOUNT_POINT; + } + std::error_code e; + if (not filesystem::exists(mountDev, e)) { + sif::warning << "SdCardManager::mountSdCard: Device file does not exists. Make sure to" + " turn on the SD card" + << std::endl; + return MOUNT_ERROR; + } + + if (not blocking) { + currentOp = Operations::MOUNTING; + } + string sdMountCommand = "mount " + mountDev + " " + mountPoint; + cmdExecutor.load(sdMountCommand, blocking, printCmdOutput); + ReturnValue_t result = cmdExecutor.execute(); + if (blocking and result != returnvalue::OK) { + utility::handleSystemError(cmdExecutor.getLastError(), "SdCardManager::mountSdCard"); + } + return result; +} + +ReturnValue_t SdCardManager::unmountSdCard(sd::SdCard sdCard) { + if (cmdExecutor.getCurrentState() == CommandExecutor::States::PENDING) { + return CommandExecutor::COMMAND_PENDING; + } + using namespace std; + if (sdCard == sd::SdCard::BOTH) { + sif::warning << "SdCardManager::unmountSdCard: API does not allow sd::SdStatus::BOTH" + << std::endl; + return returnvalue::FAILED; + } + string mountPoint; + if (sdCard == sd::SdCard::SLOT_0) { + mountPoint = config::SD_0_MOUNT_POINT; + } else if (sdCard == sd::SdCard::SLOT_1) { + mountPoint = config::SD_1_MOUNT_POINT; + } + std::error_code e; + if (not filesystem::exists(mountPoint, e)) { + sif::error << "SdCardManager::unmountSdCard: Default mount point " << mountPoint + << "does not exist" << std::endl; + return UNMOUNT_ERROR; + } + if (filesystem::is_empty(mountPoint)) { + // The mount point will always exist, but if it is empty, that is strong hint that + // the SD card was not mounted properly. Still proceed with operation. + sif::warning << "SdCardManager::unmountSdCard: Mount point is empty!" << std::endl; + } + string sdUnmountCommand = "umount " + mountPoint; + if (not blocking) { + currentOp = Operations::UNMOUNTING; + } + cmdExecutor.load(sdUnmountCommand, blocking, printCmdOutput); + ReturnValue_t result = cmdExecutor.execute(); + if (blocking and result != returnvalue::OK) { + utility::handleSystemError(cmdExecutor.getLastError(), "SdCardManager::unmountSdCard"); + } + return result; +} + +ReturnValue_t SdCardManager::sanitizeState(SdStatePair* statusPair, sd::SdCard prefSdCard) { + std::unique_ptr sdStatusPtr; + ReturnValue_t result = returnvalue::OK; + // Enforce blocking operation for now. Be careful to reset it when returning prematurely! + bool resetNonBlockingState = false; + if (not this->blocking) { + blocking = true; + resetNonBlockingState = true; + } + if (statusPair == nullptr) { + return returnvalue::FAILED; + } + getSdCardsStatus(*statusPair); + + if (statusPair->first == sd::SdState::ON) { + result = mountSdCard(prefSdCard); + } + + result = switchOnSdCard(prefSdCard, true, statusPair); + if (resetNonBlockingState) { + blocking = false; + } + return result; +} + +void SdCardManager::resetState() { + cmdExecutor.reset(); + currentOp = Operations::IDLE; +} + +void SdCardManager::processSdStatusLine(std::string& line, uint8_t& idx, sd::SdCard& currentSd) { + using namespace std; + istringstream iss(line); + string word; + bool slotLine = false; + bool mountLine = false; + while (iss >> word) { + if (word == "Slot") { + slotLine = true; + } + if (word == "Mounted") { + mountLine = true; + } + + if (slotLine) { + if (word == "1:") { + currentSd = sd::SdCard::SLOT_1; + } + + if (word == "on") { + if (currentSd == sd::SdCard::SLOT_0) { + sdStates.first = sd::SdState::ON; + } else { + sdStates.second = sd::SdState::ON; + } + } else if (word == "off") { + MutexGuard mg(sdLock, LOCK_TYPE, SD_LOCK_TIMEOUT, LOCK_CTX); + if (currentSd == sd::SdCard::SLOT_0) { + sdStates.first = sd::SdState::OFF; + } else { + sdStates.second = sd::SdState::OFF; + } + } + } + + if (mountLine) { + MutexGuard mg(sdLock, LOCK_TYPE, SD_LOCK_TIMEOUT, LOCK_CTX); + if (currentSd == sd::SdCard::SLOT_0) { + sdStates.first = sd::SdState::MOUNTED; + } else { + sdStates.second = sd::SdState::MOUNTED; + } + } + + if (idx > 5) { + sif::warning << "SdCardManager::sdCardActive: /tmp/sd_status.txt has more than 6 " + "lines and might be invalid!" + << std::endl; + } + } + idx++; +} + +std::optional SdCardManager::getPreferredSdCard() const { + MutexGuard mg(prefLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + auto res = mg.getLockResult(); + if (res != returnvalue::OK) { + sif::error << "SdCardManager::getPreferredSdCard: Lock error" << std::endl; + } + return sdInfo.pref; +} + +ReturnValue_t SdCardManager::setPreferredSdCard(sd::SdCard sdCard) { + MutexGuard mg(prefLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + if (sdCard == sd::SdCard::BOTH) { + return returnvalue::FAILED; + } + sdInfo.pref = sdCard; + return scratch::writeNumber(scratch::PREFERED_SDC_KEY, static_cast(sdCard)); +} + +ReturnValue_t SdCardManager::updateSdCardStateFile() { + using namespace std; + if (cmdExecutor.getCurrentState() == CommandExecutor::States::PENDING) { + return CommandExecutor::COMMAND_PENDING; + } + // Use q7hw utility and pipe the command output into the state file + std::string updateCmd = "q7hw sd info all > " + std::string(SD_STATE_FILE); + cmdExecutor.load(updateCmd, true, printCmdOutput); + ReturnValue_t result = cmdExecutor.execute(); + if (result != returnvalue::OK) { + utility::handleSystemError(cmdExecutor.getLastError(), "SdCardManager::mountSdCard"); + } + + std::error_code e; + if (not filesystem::exists(SD_STATE_FILE, e)) { + return STATUS_FILE_NEXISTS; + } + + // Now the file should exist in any case. Still check whether it exists. + fstream sdStatus(SD_STATE_FILE); + if (not sdStatus.good()) { + return STATUS_FILE_NEXISTS; + } + string line; + uint8_t idx = 0; + sd::SdCard currentSd = sd::SdCard::SLOT_0; + // Process status file line by line + while (std::getline(sdStatus, line)) { + processSdStatusLine(line, idx, currentSd); + } + if (sdStates.first != sd::SdState::MOUNTED && sdStates.second != sd::SdState::MOUNTED) { + sdCardActive = false; + } + return returnvalue::OK; +} + +const char* SdCardManager::getCurrentMountPrefix() const { + MutexGuard mg(defaultLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + if (currentPrefix.has_value()) { + return currentPrefix.value().c_str(); + } + return nullptr; +} + +SdCardManager::OpStatus SdCardManager::checkCurrentOp(Operations& currentOp) { + CommandExecutor::States state = cmdExecutor.getCurrentState(); + if (state == CommandExecutor::States::IDLE or state == CommandExecutor::States::COMMAND_LOADED) { + return OpStatus::IDLE; + } + currentOp = this->currentOp; + bool bytesRead = false; + +#if OBSW_ENABLE_TIMERS == 1 + Countdown timer(1000); +#endif + while (true) { + ReturnValue_t result = cmdExecutor.check(bytesRead); + // This timer can prevent deadlocks due to missconfigurations +#if OBSW_ENABLE_TIMERS == 1 + if (timer.hasTimedOut()) { + sif::error << "SdCardManager::checkCurrentOp: Timeout!" << std::endl; + return OpStatus::FAIL; + } +#endif + switch (result) { + case (CommandExecutor::BYTES_READ): { + continue; + } + case (CommandExecutor::EXECUTION_FINISHED): { + return OpStatus::SUCCESS; + } + case (returnvalue::OK): { + return OpStatus::ONGOING; + } + case (returnvalue::FAILED): { + return OpStatus::FAIL; + } + default: { + sif::warning << "SdCardManager::checkCurrentOp: Unhandled case" << std::endl; + } + } + } +} + +void SdCardManager::setBlocking(bool blocking) { this->blocking = blocking; } + +void SdCardManager::setPrintCommandOutput(bool print) { this->printCmdOutput = print; } + +bool SdCardManager::isSdCardUsable(std::optional sdCard) { + { + MutexGuard mg(defaultLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + if (markedUnusable) { + return false; + } + } + + MutexGuard mg(sdLock, LOCK_TYPE, SD_LOCK_TIMEOUT, LOCK_CTX); + if (not sdCard) { + if (sdStates.first == sd::MOUNTED or sdStates.second == sd::MOUNTED) { + return true; + } + return false; + } + if (sdCard == sd::SLOT_0) { + if (sdStates.first == sd::MOUNTED) { + return true; + } else { + return false; + } + } + if (sdCard == sd::SLOT_1) { + if (sdStates.second == sd::MOUNTED) { + return true; + } else { + return false; + } + } + if (sdCard == sd::BOTH) { + if (sdStates.first == sd::MOUNTED && sdStates.second == sd::MOUNTED) { + return true; + } + } + return false; +} + +ReturnValue_t SdCardManager::isSdCardMountedReadOnly(sd::SdCard sdcard, bool& readOnly) { + std::ostringstream command; + if (sdcard == sd::SdCard::SLOT_0) { + command << "grep -q '" << config::SD_0_MOUNT_POINT << " ext4 rw,' /proc/mounts"; + } else if (sdcard == sd::SdCard::SLOT_1) { + command << "grep -q '" << config::SD_1_MOUNT_POINT << " ext4 rw,' /proc/mounts"; + } else { + return returnvalue::FAILED; + } + ReturnValue_t result = cmdExecutor.load(command.str(), true, false); + if (result != returnvalue::OK) { + return result; + } + result = cmdExecutor.execute(); + if (result == returnvalue::OK) { + readOnly = false; + return result; + } + readOnly = true; + return returnvalue::OK; +} + +ReturnValue_t SdCardManager::remountReadWrite(sd::SdCard sdcard) { + std::ostringstream command; + if (sdcard == sd::SdCard::SLOT_0) { + command << "mount -o remount,rw " << SD_0_DEV_NAME << " " << config::SD_0_MOUNT_POINT; + } else { + command << "mount -o remount,rw " << SD_1_DEV_NAME << " " << config::SD_1_MOUNT_POINT; + } + ReturnValue_t result = cmdExecutor.load(command.str(), true, false); + if (result != returnvalue::OK) { + return result; + } + return cmdExecutor.execute(); +} + +ReturnValue_t SdCardManager::performFsck(sd::SdCard sdcard, bool printOutput, int& linuxError) { + std::ostringstream command; + if (sdcard == sd::SdCard::SLOT_0) { + command << "fsck -y " << SD_0_DEV_NAME; + } else { + command << "fsck -y " << SD_1_DEV_NAME; + } + ReturnValue_t result = cmdExecutor.load(command.str(), true, printOutput); + if (result != returnvalue::OK) { + return result; + } + result = cmdExecutor.execute(); + if (result != returnvalue::OK) { + linuxError = cmdExecutor.getLastError(); + } + return result; +} + +void SdCardManager::setActiveSdCard(sd::SdCard sdCard) { + MutexGuard mg(defaultLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + sdInfo.active = sdCard; + if (sdInfo.active == sd::SdCard::SLOT_0) { + currentPrefix = config::SD_0_MOUNT_POINT; + } else { + currentPrefix = config::SD_1_MOUNT_POINT; + } +} + +std::optional SdCardManager::getActiveSdCard() const { + MutexGuard mg(defaultLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + if (markedUnusable) { + return std::nullopt; + } + return sdInfo.active; +} + +void SdCardManager::markUnusable() { + MutexGuard mg(defaultLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + markedUnusable = true; +} + +void SdCardManager::markUsable() { + MutexGuard mg(defaultLock, LOCK_TYPE, OTHER_TIMEOUT, LOCK_CTX); + markedUnusable = false; +} diff --git a/bsp_q7s/fs/SdCardManager.h b/bsp_q7s/fs/SdCardManager.h new file mode 100644 index 0000000..23a3d19 --- /dev/null +++ b/bsp_q7s/fs/SdCardManager.h @@ -0,0 +1,246 @@ +#ifndef BSP_Q7S_MEMORY_SDCARDACCESSMANAGER_H_ +#define BSP_Q7S_MEMORY_SDCARDACCESSMANAGER_H_ + +#include +#include + +#include +#include +#include +#include +#include + +#include "events/subsystemIdRanges.h" +#include "fsfw/events/Event.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw_hal/linux/CommandExecutor.h" +#include "mission/memory/SdCardMountedIF.h" +#include "mission/memory/definitions.h" +#include "returnvalues/classIds.h" + +class MutexIF; + +/** + * @brief Manages handling of SD cards like switching them on or off or getting the current + * state + */ +class SdCardManager : public SystemObject, public SdCardMountedIF { + friend class CoreController; + + public: + using mountInitCb = ReturnValue_t (*)(void* args); + + enum class Operations { SWITCHING_ON, SWITCHING_OFF, MOUNTING, UNMOUNTING, IDLE }; + + enum class OpStatus { IDLE, TIMEOUT, ONGOING, SUCCESS, FAIL }; + + using SdStatePair = std::pair; + + struct SdInfo { + sd::SdCard pref = sd::SdCard::NONE; + sd::SdCard other = sd::SdCard::NONE; + sd::SdCard active = sd::SdCard::NONE; + } sdInfo; + + static constexpr uint8_t INTERFACE_ID = CLASS_ID::SD_CARD_MANAGER; + + static constexpr ReturnValue_t OP_ONGOING = returnvalue::makeCode(INTERFACE_ID, 0); + static constexpr ReturnValue_t ALREADY_ON = returnvalue::makeCode(INTERFACE_ID, 1); + static constexpr ReturnValue_t ALREADY_MOUNTED = returnvalue::makeCode(INTERFACE_ID, 2); + static constexpr ReturnValue_t ALREADY_OFF = returnvalue::makeCode(INTERFACE_ID, 3); + static constexpr ReturnValue_t STATUS_FILE_NEXISTS = returnvalue::makeCode(INTERFACE_ID, 10); + static constexpr ReturnValue_t STATUS_FILE_FORMAT_INVALID = + returnvalue::makeCode(INTERFACE_ID, 11); + static constexpr ReturnValue_t MOUNT_ERROR = returnvalue::makeCode(INTERFACE_ID, 12); + static constexpr ReturnValue_t UNMOUNT_ERROR = returnvalue::makeCode(INTERFACE_ID, 13); + static constexpr ReturnValue_t SYSTEM_CALL_ERROR = returnvalue::makeCode(INTERFACE_ID, 14); + static constexpr ReturnValue_t POPEN_CALL_ERROR = returnvalue::makeCode(INTERFACE_ID, 15); + + static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::FILE_SYSTEM; + + static constexpr Event SANITIZATION_FAILED = event::makeEvent(SUBSYSTEM_ID, 0, severity::LOW); + static constexpr Event MOUNTED_SD_CARD = event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO); + + // C++17 does not support constexpr std::string yet + static constexpr char SD_0_DEV_NAME[] = "/dev/mmcblk0p1"; + static constexpr char SD_1_DEV_NAME[] = "/dev/mmcblk1p1"; + + static constexpr char SD_STATE_FILE[] = "/tmp/sd_status.txt"; + + virtual ~SdCardManager(); + + static void create(); + + /** + * Returns the single instance of the SD card manager. + */ + static SdCardManager* instance(); + + /** + * Set the preferred SD card which will determine which SD card will be used as the primary + * SD card in hot redundant and cold redundant mode. This function will not switch the + * SD cards which are currently on and mounted, this needs to be implemented by + * an upper layer by using #switchOffSdCard , #switchOnSdCard and #updateSdCardStateFile + * @param sdCard + * @return + */ + ReturnValue_t setPreferredSdCard(sd::SdCard sdCard); + + /** + * Get the currently configured preferred SD card + * @param sdCard + * @return + */ + std::optional getPreferredSdCard() const override; + + /** + * Switch on the specified SD card. + * @param sdCard + * @param doMountSdCard Mount the SD card after switching it on, which is necessary + * to use it + * @param statusPair If the status pair is already available, it can be passed here + * @return - returnvalue::OK on success, ALREADY_ON if it is already on, + * SYSTEM_CALL_ERROR on system error + */ + ReturnValue_t switchOnSdCard(sd::SdCard sdCard, bool doMountSdCard = true, + SdStatePair* statusPair = nullptr); + + /** + * Switch off the specified SD card. + * @param sdCard + * @param doUnmountSdCard Unmount the SD card before switching the card off, which makes + * the operation safer + * @param statusPair If the status pair is already available, it can be passed here + * @return - returnvalue::OK on success, ALREADY_ON if it is already on, + * SYSTEM_CALL_ERROR on system error + */ + ReturnValue_t switchOffSdCard(sd::SdCard sdCard, SdStatePair& sdStates, bool doUnmountSdCard); + + /** + * Get the state of the SD cards. If the state file does not exist, this function will + * take care of updating it. If it does not, the function will use the state file to get + * the status of the SD cards and set the field of the provided boolean pair. + * @param active Pair of booleans, where the first entry is the state of the first SD card + * and the second one the state of the second SD card + * @return - returnvalue::OK if the state was read successfully + * - STATUS_FILE_FORMAT_INVALID if there was an issue with the state file. The user + * should call #updateSdCardStateFile again in that case + * - STATUS_FILE_NEXISTS if the status file does not exist + */ + ReturnValue_t getSdCardsStatus(SdStatePair& active); + + /** + * Mount the specified SD card. This is necessary to use it. + * @param sdCard + * @return + */ + ReturnValue_t mountSdCard(sd::SdCard sdCard); + + /** + * Set the currently active SD card. This does not necessarily mean that the SD card is on or + * mounted + * @param sdCard + */ + void setActiveSdCard(sd::SdCard sdCard) override; + /** + * Get the currently active SD card. This does not necessarily mean that the SD card is on or + * mounted + * @return + */ + std::optional getActiveSdCard() const override; + + /** + * Unmount the specified SD card. This is recommended before switching it off. The SD card + * can't be used after it has been unmounted. + * @param sdCard + * @return + */ + ReturnValue_t unmountSdCard(sd::SdCard sdCard); + + /** + * In case that there is a discrepancy between the preferred SD card and the currently + * mounted one, this function will sanitize the state by attempting to mount the + * currently preferred SD card. If the caller already has state information, it can be + * passed into the function. For now, this operation will be enforced in blocking mode. + * @param statusPair Current SD card status capture with #getSdCardActiveStatus + * @param prefSdCard Preferred SD card captured with #getPreferredSdCard + * @throws std::bad_alloc if one of the two arguments was a nullptr and an allocation failed + * @return + */ + ReturnValue_t sanitizeState(SdStatePair* statusPair = nullptr, + sd::SdCard prefSdCard = sd::SdCard::NONE); + + /** + * If sd::SdCard::NONE is passed as an argument, this function will get the currently + * preferred SD card from the scratch buffer. + * @param prefSdCardPtr + * @return + */ + const char* getCurrentMountPrefix() const override; + + OpStatus checkCurrentOp(Operations& currentOp); + + /** + * If there are issues with the state machine, it can be reset with this function + */ + void resetState(); + + void setBlocking(bool blocking); + void setPrintCommandOutput(bool print); + + /** + * @brief Checks if an SD card is mounted. + * + * @param sdCard The SD card to check + * + * @return true if mounted, otherwise false + */ + bool isSdCardUsable(std::optional sdCard) override; + + ReturnValue_t isSdCardMountedReadOnly(sd::SdCard sdcard, bool& readOnly); + + ReturnValue_t remountReadWrite(sd::SdCard sdcard); + + ReturnValue_t performFsck(sd::SdCard sdcard, bool printOutput, int& linuxError); + + void markUnusable(); + void markUsable(); + + private: + CommandExecutor cmdExecutor; + SdStatePair sdStates; + Operations currentOp = Operations::IDLE; + bool blocking = false; + bool sdCardActive = true; + bool printCmdOutput = true; + bool markedUnusable = false; + MutexIF* sdLock = nullptr; + MutexIF* prefLock = nullptr; + MutexIF* defaultLock = nullptr; + static constexpr MutexIF::TimeoutType LOCK_TYPE = MutexIF::TimeoutType::WAITING; + static constexpr uint32_t SD_LOCK_TIMEOUT = 100; + static constexpr uint32_t OTHER_TIMEOUT = 20; + static constexpr char LOCK_CTX[] = "SdCardManager"; + + SdCardManager(); + + /** + * Update the state file or creates one if it does not exist. You need to call this + * function before calling #sdCardActive + * @return + * - returnvalue::OK if the state file was updated successfully + * - CommandExecutor::COMMAND_PENDING: Non-blocking command is pending + * - returnvalue::FAILED: blocking command failed + */ + ReturnValue_t updateSdCardStateFile(); + + ReturnValue_t setSdCardState(sd::SdCard sdCard, bool on); + + void processSdStatusLine(std::string& line, uint8_t& idx, sd::SdCard& currentSd); + + std::optional currentPrefix; + + static SdCardManager* INSTANCE; +}; + +#endif /* BSP_Q7S_MEMORY_SDCARDACCESSMANAGER_H_ */ diff --git a/bsp_q7s/fs/helpers.cpp b/bsp_q7s/fs/helpers.cpp new file mode 100644 index 0000000..19c2f67 --- /dev/null +++ b/bsp_q7s/fs/helpers.cpp @@ -0,0 +1,11 @@ +#include "helpers.h" + +std::filesystem::path fshelpers::getPrefixedPath(SdCardManager &man, + std::filesystem::path pathWihtoutPrefix) { + auto prefix = man.getCurrentMountPrefix(); + if (prefix == nullptr) { + return pathWihtoutPrefix; + } + auto resPath = prefix / pathWihtoutPrefix; + return resPath; +} diff --git a/bsp_q7s/fs/helpers.h b/bsp_q7s/fs/helpers.h new file mode 100644 index 0000000..b5dc736 --- /dev/null +++ b/bsp_q7s/fs/helpers.h @@ -0,0 +1,14 @@ +#ifndef BSP_Q7S_MEMORY_HELPERS_H_ +#define BSP_Q7S_MEMORY_HELPERS_H_ + +#include + +#include "SdCardManager.h" + +namespace fshelpers { + +std::filesystem::path getPrefixedPath(SdCardManager& man, std::filesystem::path pathWihtoutPrefix); + +} + +#endif /* BSP_Q7S_MEMORY_HELPERS_H_ */ diff --git a/bsp_q7s/main.cpp b/bsp_q7s/main.cpp new file mode 100644 index 0000000..d557cdb --- /dev/null +++ b/bsp_q7s/main.cpp @@ -0,0 +1,22 @@ +#include "q7sConfig.h" + +#if Q7S_SIMPLE_MODE == 0 +#include "obsw.h" +#else +#include "simple/simple.h" +#endif + +#include + +/** + * @brief This is the main program for the target hardware. + * @return + */ +int main(int argc, char* argv[]) { + using namespace std; +#if Q7S_SIMPLE_MODE == 0 + return obsw::obsw(argc, argv); +#else + return simple::simple(); +#endif +} diff --git a/bsp_q7s/memory/CMakeLists.txt b/bsp_q7s/memory/CMakeLists.txt new file mode 100644 index 0000000..4ff840c --- /dev/null +++ b/bsp_q7s/memory/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PRIVATE scratchApi.cpp LocalParameterHandler.cpp) diff --git a/bsp_q7s/memory/LocalParameterHandler.cpp b/bsp_q7s/memory/LocalParameterHandler.cpp new file mode 100644 index 0000000..d1fb635 --- /dev/null +++ b/bsp_q7s/memory/LocalParameterHandler.cpp @@ -0,0 +1,41 @@ +#include "LocalParameterHandler.h" + +#include + +LocalParameterHandler::LocalParameterHandler(std::string sdRelativeName, SdCardMountedIF* sdcMan) + : NVMParameterBase(), sdRelativeName(sdRelativeName), sdcMan(sdcMan) {} + +LocalParameterHandler::~LocalParameterHandler() {} + +ReturnValue_t LocalParameterHandler::initialize() { + ReturnValue_t result = updateFullName(); + if (result != returnvalue::OK) { + return result; + } + result = readJsonFile(); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t LocalParameterHandler::writeJsonFile() { + ReturnValue_t result = updateFullName(); + if (result != returnvalue::OK) { + return result; + } + return NVMParameterBase::writeJsonFile(); +} + +ReturnValue_t LocalParameterHandler::updateFullName() { + std::string mountPrefix; + auto activeSd = sdcMan->getActiveSdCard(); + if (activeSd and sdcMan->isSdCardUsable(activeSd.value())) { + mountPrefix = sdcMan->getCurrentMountPrefix(); + } else { + return SD_NOT_READY; + } + std::string fullname = mountPrefix + "/" + sdRelativeName; + NVMParameterBase::setFullName(fullname); + return returnvalue::OK; +} diff --git a/bsp_q7s/memory/LocalParameterHandler.h b/bsp_q7s/memory/LocalParameterHandler.h new file mode 100644 index 0000000..7462620 --- /dev/null +++ b/bsp_q7s/memory/LocalParameterHandler.h @@ -0,0 +1,106 @@ +#ifndef BSP_Q7S_MEMORY_LOCALPARAMETERHANDLER_H_ +#define BSP_Q7S_MEMORY_LOCALPARAMETERHANDLER_H_ + +#include +#include + +#include + +/** + * @brief Class to handle persistent parameters + * + */ +class LocalParameterHandler : public NVMParameterBase { + public: + static constexpr uint8_t INTERFACE_ID = CLASS_ID::LOCAL_PARAM_HANDLER; + + static constexpr ReturnValue_t SD_NOT_READY = returnvalue::makeCode(INTERFACE_ID, 0); + /** + * @brief Constructor + * + * @param sdRelativeName Absolute name of json file relative to mount + * directory + * of SD card. E.g. conf/example.json + * @param sdcMan Pointer to SD card manager + */ + LocalParameterHandler(std::string sdRelativeName, SdCardMountedIF* sdcMan); + virtual ~LocalParameterHandler(); + + /** + * @brief Will initialize the local parameter handler + * + * @return OK if successful, otherwise error return value + */ + ReturnValue_t initialize(); + + /** + * @brief Function to add parameter to json file. If the json file does + * not yet exist it will be created here. + * + * @param key The string to identify the parameter + * @param value The value to set for this parameter + * + * @return OK if successful, otherwise error return value + * + * @details The function will add the parameter only if it is not already + * present in the json file + */ + template + ReturnValue_t addParameter(std::string key, T value); + + /** + * @brief Function will update a parameter which already exists in the json + * file + * + * @param key The unique string to identify the parameter to update + * @param value The new new value to set + * + * @return OK if successful, otherwise error return value + */ + template + ReturnValue_t updateParameter(std::string key, T value); + + private: + // Name relative to mount point of SD card where parameters will be stored + std::string sdRelativeName; + + SdCardMountedIF* sdcMan; + + virtual ReturnValue_t writeJsonFile(); + + /** + * @brief This function sets the name of the json file dependent on the + * currently active SD card + * + * @return OK if successful, otherwise error return value + */ + ReturnValue_t updateFullName(); +}; + +template +inline ReturnValue_t LocalParameterHandler::addParameter(std::string key, T value) { + ReturnValue_t result = insertValue(key, value); + if (result != returnvalue::OK) { + return result; + } + result = writeJsonFile(); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +template +inline ReturnValue_t LocalParameterHandler::updateParameter(std::string key, T value) { + ReturnValue_t result = setValue(key, value); + if (result != returnvalue::OK) { + return result; + } + result = writeJsonFile(); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +#endif /* BSP_Q7S_MEMORY_LOCALPARAMETERHANDLER_H_ */ diff --git a/bsp_q7s/memory/scratchApi.cpp b/bsp_q7s/memory/scratchApi.cpp new file mode 100644 index 0000000..b5fa08f --- /dev/null +++ b/bsp_q7s/memory/scratchApi.cpp @@ -0,0 +1,50 @@ +#include "scratchApi.h" + +ReturnValue_t scratch::writeString(std::string name, std::string string) { + std::ostringstream oss("xsc_scratch write ", std::ostringstream::ate); + oss << name << " \"" << string << "\""; + int result = std::system(oss.str().c_str()); + if (result != 0) { + utility::handleSystemError(result, "scratch::writeString"); + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t scratch::readString(std::string key, std::string &string) { + std::ifstream file; + std::string filename; + ReturnValue_t result = readToFile(key, file, filename); + if (result != returnvalue::OK) { + return result; + } + + std::string line; + if (not std::getline(file, line)) { + std::remove(filename.c_str()); + return returnvalue::FAILED; + } + + size_t pos = line.find("="); + if (pos == std::string::npos) { + sif::warning << "scratch::readNumber: Output file format invalid, " + "no \"=\" found" + << std::endl; + // Could not find value + std::remove(filename.c_str()); + return KEY_NOT_FOUND; + } + string = line.substr(pos + 1); + return returnvalue::OK; +} + +ReturnValue_t scratch::clearValue(std::string key) { + std::ostringstream oss("xsc_scratch clear ", std::ostringstream::ate); + oss << key; + int result = std::system(oss.str().c_str()); + if (result != 0) { + utility::handleSystemError(result, "scratch::clearValue"); + return returnvalue::FAILED; + } + return returnvalue::OK; +} diff --git a/bsp_q7s/memory/scratchApi.h b/bsp_q7s/memory/scratchApi.h new file mode 100644 index 0000000..b3ea4a6 --- /dev/null +++ b/bsp_q7s/memory/scratchApi.h @@ -0,0 +1,146 @@ +#ifndef BSP_Q7S_MEMORY_SCRATCHAPI_H_ +#define BSP_Q7S_MEMORY_SCRATCHAPI_H_ + +#include +#include +#include +#include +#include + +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "linux/utility/utility.h" +#include "returnvalues/classIds.h" + +/** + * @brief API for the scratch buffer + */ +namespace scratch { + +static constexpr char PREFERED_SDC_KEY[] = "PREFSD"; +static constexpr char ALLOC_FAILURE_COUNT[] = "ALLOCERR"; + +static constexpr uint8_t INTERFACE_ID = CLASS_ID::SCRATCH_BUFFER; +static constexpr ReturnValue_t KEY_NOT_FOUND = returnvalue::makeCode(INTERFACE_ID, 0); + +ReturnValue_t clearValue(std::string key); + +/** + * Write a string to the scratch buffer + * @param key + * @param string String to write + * @return + */ +ReturnValue_t writeString(std::string key, std::string string); +/** + * Read a string from the scratch buffer + * @param key + * @param string Will be set to read string + * @return + */ +ReturnValue_t readString(std::string key, std::string& string); + +/** + * Write a number to the scratch buffer + * @tparam T + * @tparam + * @param key + * @param num Number. Template allows to set signed, unsigned and floating point numbers + * @return + */ +template ::value>::type> +inline ReturnValue_t writeNumber(std::string key, T num) noexcept; + +/** + * Read a number from the scratch buffer. + * @tparam T + * @tparam + * @param name + * @param num + * @return + */ +template ::value>::type> +inline ReturnValue_t readNumber(std::string key, T& num) noexcept; + +// Anonymous namespace +namespace { + +static uint8_t counter = 0; + +ReturnValue_t readToFile(std::string name, std::ifstream& file, std::string& filename) { + using namespace std; + filename = "/tmp/sro" + std::to_string(counter++); + ostringstream oss; + oss << "xsc_scratch read " << name << " > " << filename; + + int result = std::system(oss.str().c_str()); + if (result != 0) { + if (WEXITSTATUS(result) == 1) { + sif::warning << "scratch::readToFile: Key " << name << " does not exist" << std::endl; + // Could not find value + std::remove(filename.c_str()); + return KEY_NOT_FOUND; + } else { + utility::handleSystemError(result, "scratch::readToFile"); + std::remove(filename.c_str()); + return returnvalue::FAILED; + } + } + file.open(filename); + return returnvalue::OK; +} + +} // End of anonymous namespace + +template ::value>::type> +inline ReturnValue_t writeNumber(std::string key, T num) noexcept { + std::ostringstream oss("xsc_scratch write ", std::ostringstream::ate); + oss << key << " " << std::to_string(num); + int result = std::system(oss.str().c_str()); + if (result != 0) { + utility::handleSystemError(result, "scratch::writeNumber"); + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +template ::value>::type> +inline ReturnValue_t readNumber(std::string key, T& num) noexcept { + using namespace std; + ifstream file; + std::string filename; + ReturnValue_t result = readToFile(key, file, filename); + if (result != returnvalue::OK) { + std::remove(filename.c_str()); + return result; + } + + string line; + if (not std::getline(file, line)) { + std::remove(filename.c_str()); + return returnvalue::FAILED; + } + + size_t pos = line.find("="); + if (pos == string::npos) { + sif::warning << "scratch::readNumber: Output file format invalid, " + "no \"=\" found" + << std::endl; + // Could not find value + std::remove(filename.c_str()); + return KEY_NOT_FOUND; + } + std::string valueAsString = line.substr(pos + 1); + try { + num = std::stoi(valueAsString); + } catch (std::invalid_argument& e) { + sif::warning << "scratch::readNumber: stoi call failed with " << e.what() << std::endl; + } + + std::remove(filename.c_str()); + return returnvalue::OK; +} + +} // namespace scratch + +#endif /* BSP_Q7S_MEMORY_SCRATCHAPI_H_ */ diff --git a/bsp_q7s/objectFactory.cpp b/bsp_q7s/objectFactory.cpp new file mode 100644 index 0000000..bbd6363 --- /dev/null +++ b/bsp_q7s/objectFactory.cpp @@ -0,0 +1,1093 @@ +#include "objectFactory.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "OBSWConfig.h" +#include "bsp_q7s/acs/StrConfigPathGetter.h" +#include "bsp_q7s/boardtest/Q7STestTask.h" +#include "bsp_q7s/callbacks/gnssCallback.h" +#include "bsp_q7s/callbacks/q7sGpioCallbacks.h" +#include "busConf.h" +#include "ccsdsConfig.h" +#include "devConf.h" +#include "devices/addresses.h" +#include "devices/gpioIds.h" +#include "devices/powerSwitcherList.h" +#include "eive/definitions.h" +#include "eive/objects.h" +#include "fsfw/ipc/QueueFactory.h" +#include "linux/ObjectFactory.h" +#include "linux/boardtest/I2cTestClass.h" +#include "linux/boardtest/SpiTestClass.h" +#include "linux/boardtest/UartTestClass.h" +#include "linux/callbacks/gpioCallbacks.h" +#include "linux/ipcore/AxiPtmeConfig.h" +#include "linux/ipcore/PapbVcInterface.h" +#include "linux/ipcore/PdecHandler.h" +#include "linux/ipcore/Ptme.h" +#include "linux/ipcore/PtmeConfig.h" +#include "linux/payload/FreshSupvHandler.h" +#include "linux/payload/MpsocCommunication.h" +#include "linux/payload/PlocMpsocSpecialComHelper.h" +#include "linux/payload/SerialConfig.h" +#include "mission/config/configfile.h" +#include "mission/power/defs.h" +#include "mission/system/acs/AcsBoardFdir.h" +#include "mission/system/acs/AcsSubsystem.h" +#include "mission/system/acs/RwAssembly.h" +#include "mission/system/acs/SusFdir.h" +#include "mission/system/acs/acsModeTree.h" +#include "mission/system/com/SyrlinksFdir.h" +#include "mission/system/com/comModeTree.h" +#include "mission/system/payload/payloadModeTree.h" +#include "mission/system/power/GomspacePowerFdir.h" +#include "mission/system/tcs/RtdFdir.h" +#include "mission/system/tcs/TcsBoardAssembly.h" +#include "mission/system/tcs/tcsModeTree.h" +#include "mission/tmtc/tmFilters.h" +#include "mission/utility/GlobalConfigHandler.h" +#include "tmtc/pusIds.h" + +#if OBSW_TEST_LIBGPIOD == 1 +#include "linux/boardtest/LibgpiodTest.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "bsp_q7s/core/defs.h" +#include "fsfw/datapoollocal/LocalDataPoolManager.h" +#include "fsfw/tmtcpacket/pus/tm.h" +#include "fsfw/tmtcservices/CommandingServiceBase.h" +#include "fsfw/tmtcservices/PusServiceBase.h" +#include "fsfw_hal/common/gpio/GpioCookie.h" +#include "fsfw_hal/common/gpio/gpioDefinitions.h" +#include "fsfw_hal/devicehandlers/GyroL3GD20Handler.h" +#include "fsfw_hal/devicehandlers/MgmLIS3MDLHandler.h" +#include "fsfw_hal/devicehandlers/MgmRM3100Handler.h" +#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h" +#include "fsfw_hal/linux/i2c/I2cComIF.h" +#include "fsfw_hal/linux/i2c/I2cCookie.h" +#include "fsfw_hal/linux/serial/SerialComIF.h" +#include "fsfw_hal/linux/serial/SerialCookie.h" +#include "fsfw_hal/linux/spi/SpiComIF.h" +#include "fsfw_hal/linux/spi/SpiCookie.h" +#include "mission/acs/RwHandler.h" +#include "mission/com/CcsdsIpCoreHandler.h" +#include "mission/com/syrlinksDefs.h" +#include "mission/genericFactory.h" +#include "mission/system/acs/AcsBoardAssembly.h" +#include "mission/tmtc/TmFunnelHandler.h" + +using gpio::Direction; +using gpio::Levels; + +ResetArgs RESET_ARGS_GNSS; +std::atomic_bool LINK_STATE = CcsdsIpCoreHandler::LINK_DOWN; +std::atomic_bool PTME_LOCKED = false; +std::atomic_uint16_t signals::I2C_FATAL_ERRORS = 0; +uint8_t core::FW_VERSION_MAJOR = 0; +uint8_t core::FW_VERSION_MINOR = 0; +uint8_t core::FW_VERSION_REVISION = 0; +bool core::FW_VERSION_HAS_SHA = false; +char core::FW_VERSION_GIT_SHA[4] = {}; + +void Factory::setStaticFrameworkObjectIds() { + PusServiceBase::PUS_DISTRIBUTOR = objects::PUS_PACKET_DISTRIBUTOR; + PusServiceBase::PACKET_DESTINATION = objects::PUS_TM_FUNNEL; + + CommandingServiceBase::defaultPacketSource = objects::PUS_PACKET_DISTRIBUTOR; + CommandingServiceBase::defaultPacketDestination = objects::PUS_TM_FUNNEL; + +#if OBSW_Q7S_EM == 1 + DeviceHandlerBase::powerSwitcherId = objects::NO_OBJECT; +#else + DeviceHandlerBase::powerSwitcherId = objects::PCDU_HANDLER; +#endif /* OBSW_Q7S_EM == 1 */ + + LocalDataPoolManager::defaultHkDestination = objects::PUS_SERVICE_3_HOUSEKEEPING; + + VerificationReporter::DEFAULT_RECEIVER = objects::PUS_SERVICE_1_VERIFICATION; +} + +void ObjectFactory::setStatics() { Factory::setStaticFrameworkObjectIds(); } + +void ObjectFactory::createTmpComponents( + std::vector> tmpDevsToAdd) { + const char* tmpI2cDev = q7s::I2C_PS_EIVE; + if (core::FW_VERSION_MAJOR == 4) { + tmpI2cDev = q7s::I2C_PL_EIVE; + } else if (core::FW_VERSION_MAJOR >= 5) { + tmpI2cDev = q7s::I2C_Q7_EIVE; + } + std::vector tmpDevCookies; + + for (size_t idx = 0; idx < tmpDevsToAdd.size(); idx++) { + tmpDevCookies.push_back( + new I2cCookie(tmpDevsToAdd[idx].second, TMP1075::MAX_REPLY_LENGTH, tmpI2cDev)); + auto* tmpDevHandler = + new Tmp1075Handler(tmpDevsToAdd[idx].first, objects::I2C_COM_IF, tmpDevCookies[idx]); + tmpDevHandler->setCustomFdir(new TmpDevFdir(tmpDevsToAdd[idx].first)); + tmpDevHandler->connectModeTreeParent(satsystem::tcs::SUBSYSTEM); + } +} + +void ObjectFactory::createCommunicationInterfaces(LinuxLibgpioIF** gpioComIF, + SerialComIF** uartComIF, SpiComIF** spiMainComIF, + I2cComIF** i2cComIF) { + if (gpioComIF == nullptr or uartComIF == nullptr or spiMainComIF == nullptr) { + sif::error << "ObjectFactory::createCommunicationInterfaces: Invalid passed ComIF pointer" + << std::endl; + } + *gpioComIF = new LinuxLibgpioIF(objects::GPIO_IF); + + /* Communication interfaces */ + new CspComIF(objects::CSP_COM_IF, "CSP_ROUTER", 60); + *i2cComIF = new I2cComIF(objects::I2C_COM_IF); + *uartComIF = new SerialComIF(objects::UART_COM_IF); + *spiMainComIF = new SpiComIF(objects::SPI_MAIN_COM_IF, q7s::SPI_DEFAULT_DEV, **gpioComIF); +} + +void ObjectFactory::createPcduComponents(LinuxLibgpioIF* gpioComIF, PowerSwitchIF** pwrSwitcher, + bool enableHkSets) { + CspCookie* p60DockCspCookie = new CspCookie(P60Dock::MAX_REPLY_SIZE, addresses::P60DOCK, 500); + CspCookie* pdu1CspCookie = new CspCookie(PDU::MAX_REPLY_SIZE, addresses::PDU1, 500); + CspCookie* pdu2CspCookie = new CspCookie(PDU::MAX_REPLY_SIZE, addresses::PDU2, 500); + + auto p60Fdir = new GomspacePowerFdir(objects::P60DOCK_HANDLER); + P60DockHandler* p60dockhandler = new P60DockHandler(objects::P60DOCK_HANDLER, objects::CSP_COM_IF, + p60DockCspCookie, p60Fdir, enableHkSets); + + auto pdu1Fdir = new GomspacePowerFdir(objects::PDU1_HANDLER); + Pdu1Handler* pdu1handler = new Pdu1Handler(objects::PDU1_HANDLER, objects::CSP_COM_IF, + pdu1CspCookie, pdu1Fdir, enableHkSets); + + auto pdu2Fdir = new GomspacePowerFdir(objects::PDU2_HANDLER); + Pdu2Handler* pdu2handler = new Pdu2Handler(objects::PDU2_HANDLER, objects::CSP_COM_IF, + pdu2CspCookie, pdu2Fdir, enableHkSets); + +#if OBSW_ADD_GOMSPACE_ACU == 1 + CspCookie* acuCspCookie = new CspCookie(ACU::MAX_REPLY_SIZE, addresses::ACU, 500); + auto acuFdir = new GomspacePowerFdir(objects::ACU_HANDLER); + ACUHandler* acuhandler = new ACUHandler(objects::ACU_HANDLER, objects::CSP_COM_IF, acuCspCookie, + acuFdir, enableHkSets); +#endif + auto pcduHandler = new PcduHandler(objects::PCDU_HANDLER, 50); + + /** + * Setting PCDU devices to mode normal immediately after start up because PCDU is always + * running. + */ + p60dockhandler->setModeNormal(); + pdu1handler->setModeNormal(); + pdu2handler->setModeNormal(); +#if OBSW_ADD_GOMSPACE_ACU == 1 + acuhandler->setModeNormal(); +#endif + if (pwrSwitcher != nullptr) { + *pwrSwitcher = pcduHandler; + } +#if OBSW_DEBUG_P60DOCK == 1 + p60dockhandler->setDebugMode(true); +#endif +#if OBSW_DEBUG_ACU == 1 + acuhandler->setDebugMode(true); +#endif +} + +ReturnValue_t ObjectFactory::createRadSensorComponent(LinuxLibgpioIF* gpioComIF, + Stack5VHandler& stackHandler) { + createRadSensorChipSelect(gpioComIF); + + SpiCookie* spiCookieRadSensor = + new SpiCookie(addresses::RAD_SENSOR, gpioIds::CS_RAD_SENSOR, radSens::READ_SIZE, + spi::DEFAULT_MAX_1227_MODE, spi::DEFAULT_MAX_1227_SPEED); + spiCookieRadSensor->setMutexParams(MutexIF::TimeoutType::WAITING, spi::RAD_SENSOR_CS_TIMEOUT); + auto radSensor = new RadiationSensorHandler(objects::RAD_SENSOR, objects::SPI_MAIN_COM_IF, + spiCookieRadSensor, gpioComIF, stackHandler); + static_cast(radSensor); +#if OBSW_DEBUG_RAD_SENSOR == 1 + radSensor->enablePeriodicDataPrint(true); +#endif + return returnvalue::OK; +} + +void ObjectFactory::createAcsBoardGpios(GpioCookie& cookie) { + std::stringstream consumer; + GpiodRegularByLineName* gpio = nullptr; + consumer << "0x" << std::hex << objects::GYRO_0_ADIS_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_0_ADIS_CS, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::GYRO_0_ADIS_CS, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::GYRO_1_L3G_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_1_L3G_CS, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::GYRO_1_L3G_CS, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::GYRO_2_ADIS_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_2_ADIS_CS, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::GYRO_2_ADIS_CS, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::GYRO_3_L3G_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_3_L3G_CS, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::GYRO_3_L3G_CS, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::MGM_0_LIS3_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::MGM_0_CS, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::MGM_0_LIS3_CS, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::MGM_1_RM3100_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::MGM_1_CS, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::MGM_1_RM3100_CS, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::MGM_2_LIS3_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::MGM_2_CS, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::MGM_2_LIS3_CS, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::MGM_3_RM3100_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::MGM_3_CS, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::MGM_3_RM3100_CS, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::GPS_CONTROLLER; + // GNSS reset pins are active low + gpio = new GpiodRegularByLineName(q7s::gpioNames::RESET_GNSS_0, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::GNSS_0_NRESET, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::GPS_CONTROLLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::RESET_GNSS_1, consumer.str(), Direction::OUT, + Levels::HIGH); + cookie.addGpio(gpioIds::GNSS_1_NRESET, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::GYRO_0_ADIS_HANDLER; + // Enable pins must be pulled low for regular operations + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_0_ENABLE, consumer.str(), Direction::OUT, + Levels::LOW); + cookie.addGpio(gpioIds::GYRO_0_ENABLE, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::GYRO_2_ADIS_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_2_ENABLE, consumer.str(), Direction::OUT, + Levels::LOW); + cookie.addGpio(gpioIds::GYRO_2_ENABLE, gpio); + + // Enable pins for GNSS + consumer.str(""); + consumer << "0x" << std::hex << objects::GPS_CONTROLLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::GNSS_0_ENABLE, consumer.str(), Direction::OUT, + Levels::LOW); + cookie.addGpio(gpioIds::GNSS_0_ENABLE, gpio); + + consumer.str(""); + consumer << "0x" << std::hex << objects::GPS_CONTROLLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::GNSS_1_ENABLE, consumer.str(), Direction::OUT, + Levels::LOW); + cookie.addGpio(gpioIds::GNSS_1_ENABLE, gpio); + + // Select pin. 0 for GPS side A, 1 for GPS side B + consumer.str(""); + consumer << "0x" << std::hex << objects::GPS_CONTROLLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::GNSS_SELECT, consumer.str(), Direction::OUT, + Levels::LOW); + cookie.addGpio(gpioIds::GNSS_SELECT, gpio); +} + +void ObjectFactory::createAcsBoardComponents(SpiComIF& spiComIF, LinuxLibgpioIF* gpioComIF, + SerialComIF* uartComIF, PowerSwitchIF& pwrSwitcher, + bool enableHkSets, adis1650x::Type adisType) { + using namespace gpio; + GpioCookie* gpioCookieAcsBoard = new GpioCookie(); + createAcsBoardGpios(*gpioCookieAcsBoard); + gpioChecker(gpioComIF->addGpios(gpioCookieAcsBoard), "ACS Board"); + + AcsBoardFdir* fdir = nullptr; + static_cast(fdir); + +#if OBSW_ADD_ACS_BOARD == 1 + new AcsBoardPolling(objects::ACS_BOARD_POLLING_TASK, spiComIF, *gpioComIF); + std::string spiDev = q7s::SPI_DEFAULT_DEV; + std::array assemblyChildren; + SpiCookie* spiCookie = + new SpiCookie(addresses::MGM_0_LIS3, gpioIds::MGM_0_LIS3_CS, mgmLis3::MAX_BUFFER_SIZE, + spi::DEFAULT_LIS3_MODE, spi::DEFAULT_LIS3_SPEED); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::ACS_BOARD_CS_TIMEOUT); + auto mgmLis3Handler0 = + new MgmLis3CustomHandler(objects::MGM_0_LIS3_HANDLER, objects::ACS_BOARD_POLLING_TASK, + spiCookie, spi::LIS3_TRANSITION_DELAY); + fdir = new AcsBoardFdir(objects::MGM_0_LIS3_HANDLER); + mgmLis3Handler0->setCustomFdir(fdir); + assemblyChildren[0] = mgmLis3Handler0; +#if OBSW_TEST_ACS == 1 + mgmLis3Handler->setStartUpImmediately(); + mgmLis3Handler->setToGoToNormalMode(true); +#endif +#if OBSW_DEBUG_ACS == 1 + mgmLis3Handler->enablePeriodicPrintouts(true, 10); +#endif + spiCookie = + new SpiCookie(addresses::MGM_1_RM3100, gpioIds::MGM_1_RM3100_CS, mgmRm3100::MAX_BUFFER_SIZE, + spi::DEFAULT_RM3100_MODE, spi::DEFAULT_RM3100_SPEED); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::ACS_BOARD_CS_TIMEOUT); + auto mgmRm3100Handler1 = + new MgmRm3100CustomHandler(objects::MGM_1_RM3100_HANDLER, objects::ACS_BOARD_POLLING_TASK, + spiCookie, spi::RM3100_TRANSITION_DELAY); + fdir = new AcsBoardFdir(objects::MGM_1_RM3100_HANDLER); + mgmRm3100Handler1->setCustomFdir(fdir); + assemblyChildren[1] = mgmRm3100Handler1; +#if OBSW_TEST_ACS == 1 + mgmRm3100Handler->setStartUpImmediately(); + mgmRm3100Handler->setToGoToNormalMode(true); +#endif +#if OBSW_DEBUG_ACS == 1 + mgmRm3100Handler->enablePeriodicPrintouts(true, 10); +#endif + spiCookie = new SpiCookie(addresses::MGM_2_LIS3, gpioIds::MGM_2_LIS3_CS, mgmLis3::MAX_BUFFER_SIZE, + spi::DEFAULT_LIS3_MODE, spi::DEFAULT_LIS3_SPEED); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::ACS_BOARD_CS_TIMEOUT); + auto* mgmLis3Handler2 = + new MgmLis3CustomHandler(objects::MGM_2_LIS3_HANDLER, objects::ACS_BOARD_POLLING_TASK, + spiCookie, spi::LIS3_TRANSITION_DELAY); + fdir = new AcsBoardFdir(objects::MGM_2_LIS3_HANDLER); + mgmLis3Handler2->setCustomFdir(fdir); + assemblyChildren[2] = mgmLis3Handler2; +#if OBSW_TEST_ACS == 1 + mgmLis3Handler->setStartUpImmediately(); + mgmLis3Handler->setToGoToNormalMode(true); +#endif +#if OBSW_DEBUG_ACS == 1 + mgmLis3Handler->enablePeriodicPrintouts(true, 10); +#endif + spiCookie = + new SpiCookie(addresses::MGM_3_RM3100, gpioIds::MGM_3_RM3100_CS, mgmRm3100::MAX_BUFFER_SIZE, + spi::DEFAULT_RM3100_MODE, spi::DEFAULT_RM3100_SPEED); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::ACS_BOARD_CS_TIMEOUT); + auto* mgmRm3100Handler3 = + new MgmRm3100CustomHandler(objects::MGM_3_RM3100_HANDLER, objects::ACS_BOARD_POLLING_TASK, + spiCookie, spi::RM3100_TRANSITION_DELAY); + fdir = new AcsBoardFdir(objects::MGM_3_RM3100_HANDLER); + mgmRm3100Handler3->setCustomFdir(fdir); + assemblyChildren[3] = mgmRm3100Handler3; +#if OBSW_TEST_ACS == 1 + mgmRm3100Handler->setStartUpImmediately(); + mgmRm3100Handler->setToGoToNormalMode(true); +#endif +#if OBSW_DEBUG_ACS == 1 + mgmRm3100Handler->enablePeriodicPrintouts(true, 10); +#endif + // Commented until ACS board V2 in in clean room again + // Gyro 0 Side A + spiCookie = + new SpiCookie(addresses::GYRO_0_ADIS, gpioIds::GYRO_0_ADIS_CS, adis1650x::MAXIMUM_REPLY_SIZE, + spi::DEFAULT_ADIS16507_MODE, spi::DEFAULT_ADIS16507_SPEED); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::ACS_BOARD_CS_TIMEOUT); + auto adisHandler = new GyrAdis1650XHandler(objects::GYRO_0_ADIS_HANDLER, + objects::ACS_BOARD_POLLING_TASK, spiCookie, adisType); + fdir = new AcsBoardFdir(objects::GYRO_0_ADIS_HANDLER); + adisHandler->setCustomFdir(fdir); + assemblyChildren[4] = adisHandler; +#if OBSW_TEST_ACS == 1 + adisHandler->setStartUpImmediately(); + adisHandler->setToGoToNormalModeImmediately(); +#endif +#if OBSW_DEBUG_ACS == 1 + adisHandler->enablePeriodicPrintouts(true, 10); +#endif + // Gyro 1 Side A + spiCookie = new SpiCookie(addresses::GYRO_1_L3G, gpioIds::GYRO_1_L3G_CS, l3gd20h::MAX_BUFFER_SIZE, + spi::DEFAULT_L3G_MODE, spi::DEFAULT_L3G_SPEED); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::ACS_BOARD_CS_TIMEOUT); + auto gyroL3gHandler1 = + new GyrL3gCustomHandler(objects::GYRO_1_L3G_HANDLER, objects::ACS_BOARD_POLLING_TASK, + spiCookie, spi::L3G_TRANSITION_DELAY); + fdir = new AcsBoardFdir(objects::GYRO_1_L3G_HANDLER); + gyroL3gHandler1->setCustomFdir(fdir); + assemblyChildren[5] = gyroL3gHandler1; +#if OBSW_TEST_ACS == 1 + gyroL3gHandler->setStartUpImmediately(); + gyroL3gHandler->setToGoToNormalMode(true); +#endif +#if OBSW_DEBUG_ACS == 1 + gyroL3gHandler->enablePeriodicPrintouts(true, 10); +#endif + // Gyro 2 Side B + spiCookie = + new SpiCookie(addresses::GYRO_2_ADIS, gpioIds::GYRO_2_ADIS_CS, adis1650x::MAXIMUM_REPLY_SIZE, + spi::DEFAULT_ADIS16507_MODE, spi::DEFAULT_ADIS16507_SPEED); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::ACS_BOARD_CS_TIMEOUT); + adisHandler = new GyrAdis1650XHandler(objects::GYRO_2_ADIS_HANDLER, + objects::ACS_BOARD_POLLING_TASK, spiCookie, adisType); + fdir = new AcsBoardFdir(objects::GYRO_2_ADIS_HANDLER); + adisHandler->setCustomFdir(fdir); + assemblyChildren[6] = adisHandler; +#if OBSW_TEST_ACS == 1 + adisHandler->setStartUpImmediately(); + adisHandler->setToGoToNormalModeImmediately(); +#endif + // Gyro 3 Side B + spiCookie = new SpiCookie(addresses::GYRO_3_L3G, gpioIds::GYRO_3_L3G_CS, l3gd20h::MAX_BUFFER_SIZE, + spi::DEFAULT_L3G_MODE, spi::DEFAULT_L3G_SPEED); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::ACS_BOARD_CS_TIMEOUT); + auto gyroL3gHandler3 = + new GyrL3gCustomHandler(objects::GYRO_3_L3G_HANDLER, objects::ACS_BOARD_POLLING_TASK, + spiCookie, spi::L3G_TRANSITION_DELAY); + fdir = new AcsBoardFdir(objects::GYRO_3_L3G_HANDLER); + gyroL3gHandler3->setCustomFdir(fdir); + assemblyChildren[7] = gyroL3gHandler3; +#if OBSW_TEST_ACS == 1 + gyroL3gHandler->setStartUpImmediately(); + gyroL3gHandler->setToGoToNormalMode(true); +#endif +#if OBSW_DEBUG_ACS == 1 + gyroL3gHandler->enablePeriodicPrintouts(true, 10); +#endif + bool debugGps = false; +#if OBSW_DEBUG_GPS == 1 + debugGps = true; +#endif + RESET_ARGS_GNSS.gpioComIF = gpioComIF; + RESET_ARGS_GNSS.waitPeriodMs = 10 * 1e3; + auto gpsCtrl = new GpsHyperionLinuxController(objects::GPS_CONTROLLER, objects::NO_OBJECT, + enableHkSets, debugGps); + gpsCtrl->setResetPinTriggerFunction(gps::triggerGpioResetPin, &RESET_ARGS_GNSS); + + ObjectFactory::createAcsBoardAssy(pwrSwitcher, assemblyChildren, gpsCtrl, gpioComIF); +#endif /* OBSW_ADD_ACS_HANDLERS == 1 */ +} + +void ObjectFactory::createHeaterComponents(GpioIF* gpioIF, PowerSwitchIF* pwrSwitcher, + HealthTableIF* healthTable, + HeaterHandler*& heaterHandler) { + using namespace gpio; + GpioCookie* heaterGpiosCookie = new GpioCookie; + GpiodRegularByLineName* gpio = nullptr; + + std::stringstream consumer; + consumer << "0x" << std::hex << objects::HEATER_HANDLER; + /* Pin H2-11 on stack connector */ + gpio = new GpiodRegularByLineName(q7s::gpioNames::HEATER_0, consumer.str(), Direction::OUT, + Levels::LOW); + heaterGpiosCookie->addGpio(gpioIds::HEATER_0, gpio); + /* Pin H2-12 on stack connector */ + gpio = new GpiodRegularByLineName(q7s::gpioNames::HEATER_1, consumer.str(), Direction::OUT, + Levels::LOW); + heaterGpiosCookie->addGpio(gpioIds::HEATER_1, gpio); + + /* Pin H2-13 on stack connector */ + gpio = new GpiodRegularByLineName(q7s::gpioNames::HEATER_2, consumer.str(), Direction::OUT, + Levels::LOW); + heaterGpiosCookie->addGpio(gpioIds::HEATER_2, gpio); + + gpio = new GpiodRegularByLineName(q7s::gpioNames::HEATER_3, consumer.str(), Direction::OUT, + Levels::LOW); + heaterGpiosCookie->addGpio(gpioIds::HEATER_3, gpio); + + gpio = new GpiodRegularByLineName(q7s::gpioNames::HEATER_4, consumer.str(), Direction::OUT, + Levels::LOW); + heaterGpiosCookie->addGpio(gpioIds::HEATER_4, gpio); + + gpio = new GpiodRegularByLineName(q7s::gpioNames::HEATER_5, consumer.str(), Direction::OUT, + Levels::LOW); + heaterGpiosCookie->addGpio(gpioIds::HEATER_5, gpio); + + gpio = new GpiodRegularByLineName(q7s::gpioNames::HEATER_6, consumer.str(), Direction::OUT, + Levels::LOW); + heaterGpiosCookie->addGpio(gpioIds::HEATER_6, gpio); + + gpio = new GpiodRegularByLineName(q7s::gpioNames::HEATER_7, consumer.str(), Direction::OUT, + Levels::LOW); + heaterGpiosCookie->addGpio(gpioIds::HEATER_7, gpio); + + gpioIF->addGpios(heaterGpiosCookie); + + ObjectFactory::createGenericHeaterComponents(*gpioIF, *pwrSwitcher, heaterHandler); +} + +void ObjectFactory::createSolarArrayDeploymentComponents(PowerSwitchIF& pwrSwitcher, + GpioIF& gpioIF) { + using namespace gpio; + GpioCookie* solarArrayDeplCookie = new GpioCookie; + GpiodRegularByLineName* gpio = nullptr; + + std::stringstream consumer; + consumer << "0x" << std::hex << objects::SOLAR_ARRAY_DEPL_HANDLER; + gpio = new GpiodRegularByLineName(q7s::gpioNames::SA_DPL_PIN_0, consumer.str(), Direction::OUT, + Levels::LOW); + solarArrayDeplCookie->addGpio(gpioIds::DEPLSA1, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::SA_DPL_PIN_1, consumer.str(), Direction::OUT, + Levels::LOW); + solarArrayDeplCookie->addGpio(gpioIds::DEPLSA2, gpio); + ReturnValue_t result = gpioIF.addGpios(solarArrayDeplCookie); + if (result != returnvalue::OK) { + sif::error << "Adding Solar Array Deployment GPIO cookie failed" << std::endl; + } + + new SolarArrayDeploymentHandler(objects::SOLAR_ARRAY_DEPL_HANDLER, gpioIF, pwrSwitcher, + power::Switches::PDU2_CH5_DEPLOYMENT_MECHANISM_8V, + gpioIds::DEPLSA1, gpioIds::DEPLSA2, *SdCardManager::instance()); +} + +void ObjectFactory::createSyrlinksComponents(PowerSwitchIF* pwrSwitcher) { + new SyrlinksComHandler(objects::SYRLINKS_COM_HANDLER); + auto* syrlinksAssy = new SyrlinksAssembly(objects::SYRLINKS_ASSY); + syrlinksAssy->connectModeTreeParent(satsystem::com::SUBSYSTEM); + auto* syrlinksUartCookie = + new SerialCookie(objects::SYRLINKS_HANDLER, q7s::UART_SYRLINKS_DEV, serial::SYRLINKS_BAUD, + syrlinks::MAX_REPLY_SIZE, UartModes::NON_CANONICAL); + syrlinksUartCookie->setParityEven(); + auto syrlinksFdir = new SyrlinksFdir(objects::SYRLINKS_HANDLER); + auto syrlinksHandler = + new SyrlinksHandler(objects::SYRLINKS_HANDLER, objects::SYRLINKS_COM_HANDLER, + syrlinksUartCookie, power::PDU1_CH1_SYRLINKS_12V, syrlinksFdir); + syrlinksHandler->setPowerSwitcher(pwrSwitcher); + syrlinksHandler->connectModeTreeParent(*syrlinksAssy); +#if OBSW_DEBUG_SYRLINKS == 1 + syrlinksHandler->setDebugMode(true); +#endif +} + +void ObjectFactory::createPayloadComponents(LinuxLibgpioIF* gpioComIF, PowerSwitchIF& pwrSwitcher) { + using namespace gpio; + std::stringstream consumer; + auto* camSwitcher = + new CamSwitcher(objects::CAM_SWITCHER, pwrSwitcher, power::PDU2_CH8_PAYLOAD_CAMERA); + camSwitcher->connectModeTreeParent(satsystem::payload::SUBSYSTEM); +#if OBSW_ADD_PLOC_MPSOC == 1 + consumer << "0x" << std::hex << objects::PLOC_MPSOC_HANDLER; + auto gpioConfigMPSoC = new GpiodRegularByLineName(q7s::gpioNames::ENABLE_MPSOC_UART, + consumer.str(), Direction::OUT, Levels::HIGH); + auto mpsocGpioCookie = new GpioCookie; + mpsocGpioCookie->addGpio(gpioIds::ENABLE_MPSOC_UART, gpioConfigMPSoC); + gpioChecker(gpioComIF->addGpios(mpsocGpioCookie), "PLOC MPSoC"); + SerialConfig serialCfg(q7s::UART_PLOC_MPSOC_DEV, serial::PLOC_MPSOC_BAUD, mpsoc::MAX_REPLY_SIZE, + UartModes::NON_CANONICAL); + auto mpsocCommunication = new MpsocCommunication(objects::PLOC_MPSOC_COMMUNICATION, serialCfg); + auto specialComHelper = + new PlocMpsocSpecialComHelper(objects::PLOC_MPSOC_HELPER, *mpsocCommunication); + DhbConfig dhbConf(objects::PLOC_MPSOC_HANDLER); + auto* mpsocHandler = new FreshMpsocHandler( + dhbConf, *mpsocCommunication, *specialComHelper, Gpio(gpioIds::ENABLE_MPSOC_UART, gpioComIF), + objects::PLOC_SUPERVISOR_HANDLER, pwrSwitcher, power::PDU2_CH8_PAYLOAD_CAMERA); + mpsocHandler->connectModeTreeParent(satsystem::payload::SUBSYSTEM); +#endif /* OBSW_ADD_PLOC_MPSOC == 1 */ +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + consumer << "0x" << std::hex << objects::PLOC_SUPERVISOR_HANDLER; + auto gpioConfigSupv = new GpiodRegularByLineName(q7s::gpioNames::ENABLE_SUPV_UART, consumer.str(), + Direction::OUT, Levels::LOW); + auto supvGpioCookie = new GpioCookie; + supvGpioCookie->addGpio(gpioIds::ENABLE_SUPV_UART, gpioConfigSupv); + gpioComIF->addGpios(supvGpioCookie); + const char* plocSupvDev = q7s::UART_PLOC_SUPERVISOR_DEV; + if (not std::filesystem::exists(plocSupvDev)) { + plocSupvDev = q7s::UART_PLOC_SUPERVISOR_DEV_FALLBACK; + } + auto supervisorCookie = + new SerialCookie(objects::PLOC_SUPERVISOR_HANDLER, plocSupvDev, serial::PLOC_SUPV_BAUD, + supv::MAX_PACKET_SIZE * 20, UartModes::NON_CANONICAL); + supervisorCookie->setNoFixedSizeReply(); + new PlocSupvUartManager(objects::PLOC_SUPERVISOR_HELPER); + dhbConf = DhbConfig(objects::PLOC_SUPERVISOR_HANDLER); + auto* supvHandler = + new FreshSupvHandler(dhbConf, supervisorCookie, Gpio(gpioIds::ENABLE_SUPV_UART, gpioComIF), + pwrSwitcher, power::PDU1_CH6_PLOC_12V); + supvHandler->connectModeTreeParent(satsystem::payload::SUBSYSTEM); +#endif /* OBSW_ADD_PLOC_SUPERVISOR == 1 */ + static_cast(consumer); +} + +void ObjectFactory::createReactionWheelComponents(LinuxLibgpioIF* gpioComIF, + PowerSwitchIF* pwrSwitcher) { + using namespace gpio; + GpioCookie* gpioCookieRw = new GpioCookie; + GpioCallback* csRw1 = + new GpioCallback("Chip select reaction wheel 1", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieRw->addGpio(gpioIds::CS_RW1, csRw1); + GpioCallback* csRw2 = + new GpioCallback("Chip select reaction wheel 2", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieRw->addGpio(gpioIds::CS_RW2, csRw2); + GpioCallback* csRw3 = + new GpioCallback("Chip select reaction wheel 3", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieRw->addGpio(gpioIds::CS_RW3, csRw3); + GpioCallback* csRw4 = + new GpioCallback("Chip select reaction wheel 4", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieRw->addGpio(gpioIds::CS_RW4, csRw4); + + std::stringstream consumer; + GpiodRegularByLineName* gpio = nullptr; + consumer << "0x" << std::hex << objects::RW1; + gpio = new GpiodRegularByLineName(q7s::gpioNames::EN_RW_1, consumer.str(), Direction::OUT, + Levels::LOW); + gpioCookieRw->addGpio(gpioIds::EN_RW1, gpio); + consumer.str(""); + consumer << "0x" << std::hex << objects::RW2; + gpio = new GpiodRegularByLineName(q7s::gpioNames::EN_RW_2, consumer.str(), Direction::OUT, + Levels::LOW); + gpioCookieRw->addGpio(gpioIds::EN_RW2, gpio); + consumer.str(""); + consumer << "0x" << std::hex << objects::RW3; + gpio = new GpiodRegularByLineName(q7s::gpioNames::EN_RW_3, consumer.str(), Direction::OUT, + Levels::LOW); + gpioCookieRw->addGpio(gpioIds::EN_RW3, gpio); + consumer.str(""); + consumer << "0x" << std::hex << objects::RW4; + gpio = new GpiodRegularByLineName(q7s::gpioNames::EN_RW_4, consumer.str(), Direction::OUT, + Levels::LOW); + gpioCookieRw->addGpio(gpioIds::EN_RW4, gpio); + + gpioChecker(gpioComIF->addGpios(gpioCookieRw), "RWs"); + +#if OBSW_ADD_RW == 1 + std::array, 4> rwCookieParams = { + {{addresses::RW1, gpioIds::CS_RW1}, + {addresses::RW2, gpioIds::CS_RW2}, + {addresses::RW3, gpioIds::CS_RW3}, + {addresses::RW4, gpioIds::CS_RW4}}}; + std::array rwCookies = {}; + std::array rwIds = {objects::RW1, objects::RW2, objects::RW3, objects::RW4}; + std::array rwGpioIds = {gpioIds::EN_RW1, gpioIds::EN_RW2, gpioIds::EN_RW3, + gpioIds::EN_RW4}; + std::array rws = {}; + new RwPollingTask(objects::RW_POLLING_TASK, q7s::SPI_RW_DEV, *gpioComIF); + for (uint8_t idx = 0; idx < rwCookies.size(); idx++) { + rwCookies[idx] = new RwCookie(idx, rwCookieParams[idx].first, rwCookieParams[idx].second, + rws::MAX_REPLY_SIZE, spi::RW_MODE, spi::RW_SPEED); + auto* rwHandler = new RwHandler(rwIds[idx], objects::RW_POLLING_TASK, rwCookies[idx], gpioComIF, + rwGpioIds[idx], idx); +#if OBSW_TEST_RW == 1 + rws[idx]->setStartUpImmediately(); +#endif +#if OBSW_DEBUG_RW == 1 + rwHandler->setDebugMode(true); +#endif + rws[idx] = rwHandler; + } + + createRwAssy(*pwrSwitcher, power::Switches::PDU2_CH2_RW_5V, rws, rwIds); +#endif /* OBSW_ADD_RW == 1 */ +} + +ReturnValue_t ObjectFactory::createCcsdsComponents(CcsdsComponentArgs& args) { + using namespace gpio; + // GPIO definitions of signals connected to the virtual channel interfaces of the PTME IP Core + GpioCookie* gpioCookiePtmeIp = new GpioCookie; + GpiodRegularByLineName* gpio = nullptr; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC0, "PAPB VC0"); + gpioCookiePtmeIp->addGpio(gpioIds::VC0_PAPB_EMPTY, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC1, "PAPB VC1"); + gpioCookiePtmeIp->addGpio(gpioIds::VC1_PAPB_EMPTY, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC2, "PAPB VC2"); + gpioCookiePtmeIp->addGpio(gpioIds::VC2_PAPB_EMPTY, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::PAPB_EMPTY_SIGNAL_VC3, "PAPB VC3"); + gpioCookiePtmeIp->addGpio(gpioIds::VC3_PAPB_EMPTY, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::PTME_RESETN, "PTME RESETN", + gpio::Direction::OUT, gpio::Levels::HIGH); + gpioCookiePtmeIp->addGpio(gpioIds::PTME_RESETN, gpio); + gpioChecker(args.gpioComIF.addGpios(gpioCookiePtmeIp), "PTME PAPB VCs"); + + // Creating virtual channel interfaces + VirtualChannelIF* vc0 = + new PapbVcInterface(&args.gpioComIF, gpioIds::VC0_PAPB_EMPTY, q7s::UIO_PTME, + q7s::uiomapids::PTME_VC0, config::MAX_SPACEPACKET_TC_SIZE); + VirtualChannelIF* vc1 = + new PapbVcInterface(&args.gpioComIF, gpioIds::VC1_PAPB_EMPTY, q7s::UIO_PTME, + q7s::uiomapids::PTME_VC1, config::MAX_SPACEPACKET_TC_SIZE); + VirtualChannelIF* vc2 = + new PapbVcInterface(&args.gpioComIF, gpioIds::VC2_PAPB_EMPTY, q7s::UIO_PTME, + q7s::uiomapids::PTME_VC2, config::MAX_SPACEPACKET_TC_SIZE); + VirtualChannelIF* vc3 = + new PapbVcInterface(&args.gpioComIF, gpioIds::VC3_PAPB_EMPTY, q7s::UIO_PTME, + q7s::uiomapids::PTME_VC3, config::MAX_SPACEPACKET_TC_SIZE); + // Creating ptme object and adding virtual channel interfaces + Ptme* ptme = new Ptme(objects::PTME); + ptme->addVcInterface(ccsds::VC0, vc0); + ptme->addVcInterface(ccsds::VC1, vc1); + ptme->addVcInterface(ccsds::VC2, vc2); + ptme->addVcInterface(ccsds::VC3, vc3); + AxiPtmeConfig* axiPtmeConfig = + new AxiPtmeConfig(objects::AXI_PTME_CONFIG, q7s::UIO_PTME, q7s::uiomapids::PTME_CONFIG); + PtmeConfig* ptmeConfig = new PtmeConfig(objects::PTME_CONFIG, axiPtmeConfig); + + PtmeGpios gpios; + gpios.enableTxClock = gpioIds::RS485_EN_TX_CLOCK; + gpios.enableTxData = gpioIds::RS485_EN_TX_DATA; + gpios.ptmeResetn = gpioIds::PTME_RESETN; + + *args.ipCoreHandler = + new CcsdsIpCoreHandler(objects::CCSDS_HANDLER, objects::CCSDS_PACKET_DISTRIBUTOR, *ptmeConfig, + LINK_STATE, &args.gpioComIF, gpios, PTME_LOCKED); + // This VC will receive all live TM + auto* vcWithQueue = new VirtualChannel(objects::PTME_VC0_LIVE_TM, ccsds::VC0, "PTME VC0 LIVE TM", + *ptme, LINK_STATE); + auto* liveTask = new LiveTmTask(objects::LIVE_TM_TASK, args.pusFunnel, args.cfdpFunnel, + *vcWithQueue, PTME_LOCKED, config::LIVE_CHANNEL_NORMAL_QUEUE_SIZE, + config::LIVE_CHANNEL_CFDP_QUEUE_SIZE); + args.normalLiveTmDest = liveTask->getNormalLiveQueueId(); + args.cfdpLiveTmDest = liveTask->getCfdpLiveQueueId(); + liveTask->connectModeTreeParent(satsystem::com::SUBSYSTEM); + + // Set up log store. + auto* vc = new VirtualChannel(objects::PTME_VC1_LOG_TM, ccsds::VC1, "PTME VC1 LOG TM", *ptme, + LINK_STATE); + LogStores logStores(args.stores); + // Core task which handles the LOG store and takes care of dumping it as TM using a VC directly + auto* logStore = + new PersistentLogTmStoreTask(objects::LOG_STORE_AND_TM_TASK, args.ipcStore, logStores, *vc, + *SdCardManager::instance(), PTME_LOCKED); + logStore->connectModeTreeParent(satsystem::com::SUBSYSTEM); + + vc = new VirtualChannel(objects::PTME_VC2_HK_TM, ccsds::VC2, "PTME VC2 HK TM", *ptme, LINK_STATE); + // Core task which handles the HK store and takes care of dumping it as TM using a VC directly + auto* hkStore = new PersistentSingleTmStoreTask( + objects::HK_STORE_AND_TM_TASK, args.ipcStore, *args.stores.hkStore, *vc, + persTmStore::DUMP_HK_STORE_DONE, persTmStore::DUMP_HK_CANCELLED, *SdCardManager::instance(), + PTME_LOCKED); + hkStore->connectModeTreeParent(satsystem::com::SUBSYSTEM); + + vc = new VirtualChannel(objects::PTME_VC3_CFDP_TM, ccsds::VC3, "PTME VC3 CFDP TM", *ptme, + LINK_STATE); + // Core task which handles the CFDP store and takes care of dumping it as TM using a VC directly + auto* cfdpTask = new PersistentSingleTmStoreTask( + objects::CFDP_STORE_AND_TM_TASK, args.ipcStore, *args.stores.cfdpStore, *vc, + persTmStore::DUMP_CFDP_STORE_DONE, persTmStore::DUMP_CFDP_CANCELLED, + *SdCardManager::instance(), PTME_LOCKED); + cfdpTask->connectModeTreeParent(satsystem::com::SUBSYSTEM); + + ReturnValue_t result = (*args.ipCoreHandler)->connectModeTreeParent(satsystem::com::SUBSYSTEM); + if (result != returnvalue::OK) { + sif::error + << "ObjectFactory::createCcsdsComponents: Connecting COM subsystem to CCSDS handler failed" + << std::endl; + } + + GpioCookie* gpioCookiePdec = new GpioCookie; + // GPIO also low after linux boot (specified by device-tree) + gpio = new GpiodRegularByLineName(q7s::gpioNames::PDEC_RESET, "PDEC Handler", Direction::OUT, + Levels::LOW); + gpioCookiePdec->addGpio(gpioIds::PDEC_RESET, gpio); + gpioChecker(args.gpioComIF.addGpios(gpioCookiePdec), "PDEC"); + struct UioNames uioNames{}; + uioNames.configMemory = q7s::UIO_PDEC_CONFIG_MEMORY; + uioNames.ramMemory = q7s::UIO_PDEC_RAM; + uioNames.registers = q7s::UIO_PDEC_REGISTERS; + uioNames.irq = q7s::UIO_PDEC_IRQ; + new PdecHandler(objects::PDEC_HANDLER, objects::CCSDS_HANDLER, &args.gpioComIF, + gpioIds::PDEC_RESET, uioNames, args.pdecCfgMemBaseAddr, args.pdecRamBaseAddr); + GpioCookie* gpioRS485Chip = new GpioCookie; + gpio = new GpiodRegularByLineName(q7s::gpioNames::RS485_EN_TX_CLOCK, "RS485 Transceiver", + Direction::OUT, Levels::LOW); + gpioRS485Chip->addGpio(gpioIds::RS485_EN_TX_CLOCK, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::RS485_EN_TX_DATA, "RS485 Transceiver", + Direction::OUT, Levels::LOW); + gpioRS485Chip->addGpio(gpioIds::RS485_EN_TX_DATA, gpio); + // Default configuration enables RX channels (RXEN = LOW) + gpio = new GpiodRegularByLineName(q7s::gpioNames::RS485_EN_RX_CLOCK, "RS485 Transceiver", + Direction::OUT, Levels::LOW); + gpioRS485Chip->addGpio(gpioIds::RS485_EN_RX_CLOCK, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::RS485_EN_RX_DATA, "RS485 Transceiver", + Direction::OUT, Levels::LOW); + gpioRS485Chip->addGpio(gpioIds::RS485_EN_RX_DATA, gpio); + gpioChecker(args.gpioComIF.addGpios(gpioRS485Chip), "RS485 Transceiver"); + return returnvalue::OK; +} + +void ObjectFactory::createPlPcduComponents(LinuxLibgpioIF* gpioComIF, SpiComIF* spiComIF, + PowerSwitchIF* pwrSwitcher, + Stack5VHandler& stackHandler) { + using namespace gpio; + // Create all GPIO components first + GpioCookie* plPcduGpios = new GpioCookie; + GpiodRegularByLineName* gpio = nullptr; + std::string consumer; + // Switch pins are active high + consumer = "PLPCDU_ENB_VBAT_0"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_VBAT0, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_VBAT0, gpio); + consumer = "PLPCDU_ENB_VBAT_1"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_VBAT1, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_VBAT1, gpio); + consumer = "PLPCDU_ENB_DRO"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_DRO, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_DRO, gpio); + consumer = "PLPCDU_ENB_X8"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_X8, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_X8, gpio); + consumer = "PLPCDU_ENB_TX"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_TX, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_TX, gpio); + consumer = "PLPCDU_ENB_MPA"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_MPA, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_MPA, gpio); + consumer = "PLPCDU_ENB_HPA"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ENABLE_HPA, consumer, Direction::OUT, + gpio::Levels::LOW); + plPcduGpios->addGpio(gpioIds::PLPCDU_ENB_HPA, gpio); + + // Chip select pin is active low + consumer = "PLPCDU_ADC_CS"; + gpio = new GpiodRegularByLineName(q7s::gpioNames::PL_PCDU_ADC_CS, consumer, Direction::OUT, + gpio::Levels::HIGH); + plPcduGpios->addGpio(gpioIds::PLPCDU_ADC_CS, gpio); + gpioChecker(gpioComIF->addGpios(plPcduGpios), "PL PCDU"); + SpiCookie* spiCookie = + new SpiCookie(addresses::PLPCDU_ADC, gpioIds::PLPCDU_ADC_CS, plpcdu::MAX_ADC_REPLY_SIZE, + spi::DEFAULT_MAX_1227_MODE, spi::PL_PCDU_MAX_1227_SPEED); + // Create device handler components + auto plPcduHandler = + new PayloadPcduHandler(objects::PLPCDU_HANDLER, objects::SPI_MAIN_COM_IF, spiCookie, + gpioComIF, SdCardManager::instance(), stackHandler, false); + spiCookie->setCallbackMode(PayloadPcduHandler::extConvAsTwoCallback, plPcduHandler); +#if OBSW_TEST_PL_PCDU == 1 + plPcduHandler->setStartUpImmediately(); +#endif +#if OBSW_DEBUG_PL_PCDU == 1 + plPcduHandler->setToGoToNormalModeImmediately(true); + plPcduHandler->enablePeriodicPrintout(true, 10); +#endif + plPcduHandler->connectModeTreeParent(satsystem::payload::SUBSYSTEM); +} + +void ObjectFactory::createTestComponents(LinuxLibgpioIF* gpioComIF) { + new Q7STestTask(objects::TEST_TASK); +#if OBSW_ADD_SPI_TEST_CODE == 1 + new SpiTestClass(objects::SPI_TEST, gpioComIF); +#endif +#if OBSW_ADD_I2C_TEST_CODE == 1 + new I2cTestClass(objects::I2C_TEST, q7s::I2C_PL_EIVE); +#endif +#if OBSW_ADD_UART_TEST_CODE == 1 + // auto* reader= new ScexUartReader(objects::SCEX_UART_READER); + new UartTestClass(objects::UART_TEST); +#endif +} + +void ObjectFactory::createStrComponents(PowerSwitchIF* pwrSwitcher, SdCardManager& sdcMan) { + auto* strAssy = new StrAssembly(objects::STR_ASSY); + strAssy->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); + auto* starTrackerCookie = + new SerialCookie(objects::STAR_TRACKER, q7s::UART_STAR_TRACKER_DEV, serial::STAR_TRACKER_BAUD, + startracker::MAX_FRAME_SIZE * 2 + 2, UartModes::NON_CANONICAL); + starTrackerCookie->setNoFixedSizeReply(); + StrComHandler* strComIF = new StrComHandler(objects::STR_COM_IF); + + const char* paramJsonFile = "/mnt/sd0/startracker/flight-config.json"; + if (paramJsonFile == nullptr) { + sif::error << "No valid Star Tracker parameter JSON file" << std::endl; + } + auto strFdir = new StrFdir(objects::STAR_TRACKER); + auto cfgGetter = new StrConfigPathGetter(sdcMan); + auto starTracker = + new StarTrackerHandler(objects::STAR_TRACKER, objects::STR_COM_IF, starTrackerCookie, + strComIF, power::PDU1_CH2_STAR_TRACKER_5V, *cfgGetter, sdcMan); + starTracker->setPowerSwitcher(pwrSwitcher); + starTracker->connectModeTreeParent(*strAssy); + starTracker->setCustomFdir(strFdir); +} + +void ObjectFactory::createImtqComponents(PowerSwitchIF* pwrSwitcher, bool enableHkSets, + const char* i2cDev) { + auto* imtqAssy = new ImtqAssembly(objects::IMTQ_ASSY); + imtqAssy->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); + + new ImtqPollingTask(objects::IMTQ_POLLING, signals::I2C_FATAL_ERRORS); + I2cCookie* imtqI2cCookie = new I2cCookie(addresses::IMTQ, imtq::MAX_REPLY_SIZE, i2cDev); + auto imtqHandler = new ImtqHandler(objects::IMTQ_HANDLER, objects::IMTQ_POLLING, imtqI2cCookie, + power::Switches::PDU1_CH3_MGT_5V, enableHkSets); + imtqHandler->enableThermalModule(ThermalStateCfg()); + imtqHandler->setPowerSwitcher(pwrSwitcher); + imtqHandler->connectModeTreeParent(*imtqAssy); + static_cast(imtqHandler); +#if OBSW_TEST_IMTQ == 1 + imtqHandler->setStartUpImmediately(); + imtqHandler->setToGoToNormal(true); +#endif +#if OBSW_DEBUG_IMTQ == 1 + imtqHandler->setDebugMode(true); +#endif +} + +void ObjectFactory::createBpxBatteryComponent(bool enableHkSets, const char* i2cDev) { + I2cCookie* bpxI2cCookie = new I2cCookie(addresses::BPX_BATTERY, 100, i2cDev); + BpxBatteryHandler* bpxHandler = new BpxBatteryHandler( + objects::BPX_BATT_HANDLER, objects::I2C_COM_IF, bpxI2cCookie, enableHkSets); + bpxHandler->setStartUpImmediately(); + bpxHandler->setToGoToNormalMode(true); +#if OBSW_DEBUG_BPX_BATT == 1 + bpxHandler->setDebugMode(true); +#endif +} + +void ObjectFactory::createMiscComponents() { new PlocMemoryDumper(objects::PLOC_MEMORY_DUMPER); } + +void ObjectFactory::testAcsBrdAss(AcsBoardAssembly* acsAss) { + CommandMessage msg; + ModeMessage::setModeMessage(&msg, ModeMessage::CMD_MODE_COMMAND, DeviceHandlerIF::MODE_NORMAL, + duallane::A_SIDE); + ReturnValue_t result = MessageQueueSenderIF::sendMessage(acsAss->getCommandQueue(), &msg); + if (result != returnvalue::OK) { + sif::warning << "Sending mode command failed" << std::endl; + } +} + +void ObjectFactory::createRadSensorChipSelect(LinuxLibgpioIF* gpioIF) { + using namespace gpio; + if (gpioIF == nullptr) { + return; + } + GpioCookie* gpioCookieRadSensor = new GpioCookie; + std::stringstream consumer; + consumer << "0x" << std::hex << objects::RAD_SENSOR; + GpiodRegularByLineName* gpio = new GpiodRegularByLineName( + q7s::gpioNames::RAD_SENSOR_CHIP_SELECT, consumer.str(), Direction::OUT, Levels::HIGH); + gpioCookieRadSensor->addGpio(gpioIds::CS_RAD_SENSOR, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::ENABLE_RADFET, consumer.str(), Direction::OUT, + Levels::LOW); + gpioCookieRadSensor->addGpio(gpioIds::ENABLE_RADFET, gpio); + gpioChecker(gpioIF->addGpios(gpioCookieRadSensor), "RAD sensor"); +} + +void ObjectFactory::createPlI2cResetGpio(LinuxLibgpioIF* gpioIF) { + using namespace gpio; + if (common::OBSW_VERSION_MAJOR >= 6 or common::OBSW_VERSION_MAJOR == 4) { + if (gpioIF == nullptr) { + return; + } + GpioCookie* gpioI2cResetnCookie = new GpioCookie; + GpiodRegularByLineName* gpioI2cResetn = new GpiodRegularByLineName( + q7s::gpioNames::PL_I2C_ARESETN, "PL_I2C_ARESETN", Direction::OUT, Levels::HIGH); + gpioI2cResetnCookie->addGpio(gpioIds::PL_I2C_ARESETN, gpioI2cResetn); + gpioChecker(gpioIF->addGpios(gpioI2cResetnCookie), "PL I2C ARESETN"); + // Reset I2C explicitely again. + gpioIF->pullLow(gpioIds::PL_I2C_ARESETN); + TaskFactory::delayTask(1); + gpioIF->pullHigh(gpioIds::PL_I2C_ARESETN); + } +} + +ReturnValue_t ObjectFactory::readFirmwareVersion() { + uint32_t* mappedSysRomAddr = nullptr; + // The SYS ROM FPGA block is only available in those versions. + if (not(common::OBSW_VERSION_MAJOR >= 6) or (common::OBSW_VERSION_MAJOR == 4)) { + return returnvalue::OK; + } + // This has to come before the version announce because it might be required for retrieving + // the firmware version. + UioMapper sysRomMapper(q7s::UIO_SYS_ROM); + ReturnValue_t result = + sysRomMapper.getMappedAdress(&mappedSysRomAddr, UioMapper::Permissions::READ_ONLY); + if (result != returnvalue::OK) { + sif::error << "Getting mapped SYS ROM UIO address failed" << std::endl; + return returnvalue::FAILED; + } + if (mappedSysRomAddr != nullptr) { + uint32_t firstEntry = *(reinterpret_cast(mappedSysRomAddr)); + uint32_t secondEntry = *(reinterpret_cast(mappedSysRomAddr) + 1); + core::FW_VERSION_MAJOR = (firstEntry >> 24) & 0xff; + core::FW_VERSION_MINOR = (firstEntry >> 16) & 0xff; + core::FW_VERSION_REVISION = (firstEntry >> 8) & 0xff; + bool hasGitSha = (firstEntry & 0x0b1); + if (hasGitSha) { + std::memcpy(core::FW_VERSION_GIT_SHA, &secondEntry, 4); + } + } + return returnvalue::OK; +} + +ReturnValue_t ObjectFactory::createCcsdsIpComponentsWrapper(CcsdsComponentArgs& ccsdsArgs) { + ccsdsArgs.pdecCfgMemBaseAddr = config::pdec::PDEC_CONFIG_BASE_ADDR; + ccsdsArgs.pdecRamBaseAddr = config::pdec::PDEC_RAM_ADDR; + if (core::FW_VERSION_MAJOR < 6) { + ccsdsArgs.pdecCfgMemBaseAddr = config::pdec::PDEC_CONFIG_BASE_ADDR_LEGACY; + ccsdsArgs.pdecRamBaseAddr = config::pdec::PDEC_RAM_ADDR_LEGACY; + } + ReturnValue_t result = createCcsdsComponents(ccsdsArgs); +#if OBSW_TM_TO_PTME == 1 + if (ccsdsArgs.normalLiveTmDest != MessageQueueIF::NO_QUEUE) { + ccsdsArgs.pusFunnel.addLiveDestinationByRawId("VC0 NORMAL LIVE TM", ccsdsArgs.normalLiveTmDest, + 0); + } + if (ccsdsArgs.cfdpLiveTmDest != MessageQueueIF::NO_QUEUE) { + ccsdsArgs.cfdpFunnel.addLiveDestinationByRawId("VC0 CFDP LIVE TM", ccsdsArgs.cfdpLiveTmDest, 0); + } +#endif + return result; +} diff --git a/bsp_q7s/objectFactory.h b/bsp_q7s/objectFactory.h new file mode 100644 index 0000000..2e10dd7 --- /dev/null +++ b/bsp_q7s/objectFactory.h @@ -0,0 +1,97 @@ +#ifndef BSP_Q7S_OBJECTFACTORY_H_ +#define BSP_Q7S_OBJECTFACTORY_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "bsp_q7s/fs/SdCardManager.h" + +class LinuxLibgpioIF; +class SerialComIF; +class SpiComIF; +class I2cComIF; +class PowerSwitchIF; +class HealthTableIF; +class AcsBoardAssembly; +class GpioIF; + +extern std::atomic_bool PTME_LOCKED; + +namespace ObjectFactory { + +struct CcsdsComponentArgs { + CcsdsComponentArgs(LinuxLibgpioIF& gpioIF, StorageManagerIF& ipcStore, StorageManagerIF& tmStore, + PersistentTmStores& stores, PusTmFunnel& pusFunnel, CfdpTmFunnel& cfdpFunnel, + CcsdsIpCoreHandler** ipCoreHandler, uint32_t pdecCfgMemBaseAddr, + uint32_t pdecRamBaseAddr) + : gpioComIF(gpioIF), + ipcStore(ipcStore), + tmStore(tmStore), + stores(stores), + pusFunnel(pusFunnel), + cfdpFunnel(cfdpFunnel), + ipCoreHandler(ipCoreHandler), + pdecCfgMemBaseAddr(pdecCfgMemBaseAddr), + pdecRamBaseAddr(pdecRamBaseAddr) {} + LinuxLibgpioIF& gpioComIF; + StorageManagerIF& ipcStore; + StorageManagerIF& tmStore; + PersistentTmStores& stores; + PusTmFunnel& pusFunnel; + CfdpTmFunnel& cfdpFunnel; + CcsdsIpCoreHandler** ipCoreHandler; + uint32_t pdecCfgMemBaseAddr; + uint32_t pdecRamBaseAddr; + MessageQueueId_t normalLiveTmDest = MessageQueueIF::NO_QUEUE; + MessageQueueId_t cfdpLiveTmDest = MessageQueueIF::NO_QUEUE; +}; + +void setStatics(); +void produce(void* args); + +void createCommunicationInterfaces(LinuxLibgpioIF** gpioComIF, SerialComIF** uartComIF, + SpiComIF** spiMainComIF, I2cComIF** i2cComIF); +void createPcduComponents(LinuxLibgpioIF* gpioComIF, PowerSwitchIF** pwrSwitcher, + bool enableHkSets); +void createPlPcduComponents(LinuxLibgpioIF* gpioComIF, SpiComIF* spiComIF, + PowerSwitchIF* pwrSwitcher, Stack5VHandler& stackHandler); +void createTmpComponents(std::vector> tmpDevsToAdd); +void createRadSensorChipSelect(LinuxLibgpioIF* gpioIF); +ReturnValue_t createRadSensorComponent(LinuxLibgpioIF* gpioComIF, Stack5VHandler& handler); +void createAcsBoardGpios(GpioCookie& cookie); +void createAcsBoardComponents(SpiComIF& spiComIF, LinuxLibgpioIF* gpioComIF, SerialComIF* uartComIF, + PowerSwitchIF& pwrSwitcher, bool enableHkSets, + adis1650x::Type adisType); +void createHeaterComponents(GpioIF* gpioIF, PowerSwitchIF* pwrSwitcher, HealthTableIF* healthTable, + HeaterHandler*& heaterHandler); +void createImtqComponents(PowerSwitchIF* pwrSwitcher, bool enableHkSets, const char* i2cDev); +void createBpxBatteryComponent(bool enableHkSets, const char* i2cDev); +void createStrComponents(PowerSwitchIF* pwrSwitcher, SdCardManager& sdcMan); +void createSolarArrayDeploymentComponents(PowerSwitchIF& pwrSwitcher, GpioIF& gpioIF); +void createSyrlinksComponents(PowerSwitchIF* pwrSwitcher); +void createPayloadComponents(LinuxLibgpioIF* gpioComIF, PowerSwitchIF& pwrSwitcher); +void createReactionWheelComponents(LinuxLibgpioIF* gpioComIF, PowerSwitchIF* pwrSwitcher); +ReturnValue_t createCcsdsIpComponentsWrapper(CcsdsComponentArgs& args); +ReturnValue_t createCcsdsComponents(CcsdsComponentArgs& args); +ReturnValue_t readFirmwareVersion(); +void createMiscComponents(); + +void createTestComponents(LinuxLibgpioIF* gpioComIF); +void createPlI2cResetGpio(LinuxLibgpioIF* gpioComIF); + +void testAcsBrdAss(AcsBoardAssembly* assAss); + +}; // namespace ObjectFactory + +#endif /* BSP_Q7S_OBJECTFACTORY_H_ */ diff --git a/bsp_q7s/obsw.cpp b/bsp_q7s/obsw.cpp new file mode 100644 index 0000000..fc49c36 --- /dev/null +++ b/bsp_q7s/obsw.cpp @@ -0,0 +1,151 @@ +#include "obsw.h" + +#include +#include +#include + +#include +#include +#include + +#include "OBSWConfig.h" +#include "bsp_q7s/core/WatchdogHandler.h" +#include "commonConfig.h" +#include "fsfw/tasks/TaskFactory.h" +#include "fsfw/version.h" +#include "mission/acs/defs.h" +#include "mission/com/defs.h" +#include "mission/system/systemTree.h" +#include "q7sConfig.h" +#include "scheduling.h" +#include "watchdog/definitions.h" + +static constexpr int OBSW_ALREADY_RUNNING = -2; +#if OBSW_Q7S_EM == 0 +static const char* DEV_STRING = "Xiphos Q7S FM"; +#else +static const char* DEV_STRING = "Xiphos Q7S EM"; +#endif + +WatchdogHandler WATCHDOG_HANDLER; + +int obsw::obsw(int argc, char* argv[]) { + using namespace fsfw; + std::cout << "-- EIVE OBSW --" << std::endl; + std::cout << "-- Compiled for Linux (" << DEV_STRING << ") --" << std::endl; + std::cout << "-- OBSW v" << common::OBSW_VERSION << " | FSFW v" << fsfw::FSFW_VERSION << " --" + << std::endl; + std::cout << "-- " << __DATE__ << " " << __TIME__ << " --" << std::endl; + +#if Q7S_CHECK_FOR_ALREADY_RUNNING_IMG == 1 + std::error_code e; + // Check special file here. This file is created or deleted by the eive-watchdog application + // or systemd service! + if (std::filesystem::exists(watchdog::RUNNING_FILE_NAME, e)) { + sif::warning << "File " << watchdog::RUNNING_FILE_NAME + << " exists so the software might " + "already be running. Check if obsw systemd service has been stopped." + << std::endl; + return OBSW_ALREADY_RUNNING; + } +#endif + + // Delay the boot if applicable. + bootDelayHandling(); + + bool initWatchFunction = false; + std::string fullExecPath = argv[0]; + if (fullExecPath.find("/usr/bin") != std::string::npos) { + initWatchFunction = true; + } + ReturnValue_t result = WATCHDOG_HANDLER.initialize(initWatchFunction); + if (result != returnvalue::OK) { + std::cerr << "Initiating EIVE watchdog handler failed" << std::endl; + } + + scheduling::initMission(); + + // Command the EIVE system to safe mode +#if OBSW_COMMAND_SAFE_MODE_AT_STARTUP == 1 + // This ensures that the PCDU switches were updated. + TaskFactory::delayTask(1000); + commandComSubsystemRxOnly(); + commandEiveSystemToSafe(); +#else + announceAllModes(); +#endif + + for (;;) { + WATCHDOG_HANDLER.periodicOperation(); + TaskFactory::delayTask(2000); + } + return 0; +} + +void obsw::bootDelayHandling() { + const char* homedir = nullptr; + homedir = getenv("HOME"); + if (homedir == nullptr) { + homedir = getpwuid(getuid())->pw_dir; + } + std::filesystem::path bootDelayFile = std::filesystem::path(homedir) / "boot_delay_secs.txt"; + std::error_code e; + // Init delay handling. + if (std::filesystem::exists(bootDelayFile, e)) { + std::ifstream ifile(bootDelayFile); + std::string lineStr; + unsigned int bootDelaySecs = 0; + unsigned int line = 0; + // Try to reas delay seconds from file. + while (std::getline(ifile, lineStr)) { + std::istringstream iss(lineStr); + if (!(iss >> bootDelaySecs)) { + break; + } + line++; + } + if (line == 0) { + // If the file is empty, assume default of 6 seconds + bootDelaySecs = 6; + } + std::cout << "Delaying OBSW start for " << bootDelaySecs << " seconds" << std::endl; + TaskFactory::delayTask(bootDelaySecs * 1000); + } +} + +void obsw::commandEiveSystemToSafe() { + auto sysQueueId = satsystem::EIVE_SYSTEM.getCommandQueue(); + CommandMessage msg; + ModeMessage::setCmdModeMessage(msg, acs::AcsMode::SAFE, 0); + ReturnValue_t result = + MessageQueueSenderIF::sendMessage(sysQueueId, &msg, MessageQueueIF::NO_QUEUE, false); + if (result != returnvalue::OK) { + sif::error << "obsw: Sending safe mode command to EIVE system failed" << std::endl; + } +} + +void obsw::commandComSubsystemRxOnly() { + auto* comSs = ObjectManager::instance()->get(objects::COM_SUBSYSTEM); + if (comSs == nullptr) { + sif::error << "obsw: Could not retrieve COM subsystem object" << std::endl; + return; + } + CommandMessage msg; + ModeMessage::setCmdModeMessage(msg, com::RX_ONLY, 0); + ReturnValue_t result = MessageQueueSenderIF::sendMessage(comSs->getCommandQueue(), &msg, + MessageQueueIF::NO_QUEUE, false); + if (result != returnvalue::OK) { + sif::error << "obsw: Sending RX_ONLY mode command to COM subsystem failed" << std::endl; + } +} + +void obsw::announceAllModes() { + auto sysQueueId = satsystem::EIVE_SYSTEM.getCommandQueue(); + CommandMessage msg; + ModeMessage::setModeAnnounceMessage(msg, true); + ReturnValue_t result = + MessageQueueSenderIF::sendMessage(sysQueueId, &msg, MessageQueueIF::NO_QUEUE, false); + if (result != returnvalue::OK) { + sif::error << "obsw: Sending safe mode command to EIVE system failed" << std::endl; + } +} diff --git a/bsp_q7s/obsw.h b/bsp_q7s/obsw.h new file mode 100644 index 0000000..8260a60 --- /dev/null +++ b/bsp_q7s/obsw.h @@ -0,0 +1,15 @@ +#ifndef BSP_Q7S_CORE_OBSW_H_ +#define BSP_Q7S_CORE_OBSW_H_ + +namespace obsw { + +int obsw(int argc, char* argv[]); + +void bootDelayHandling(); +void commandEiveSystemToSafe(); +void commandComSubsystemRxOnly(); +void announceAllModes(); + +}; // namespace obsw + +#endif /* BSP_Q7S_CORE_OBSW_H_ */ diff --git a/bsp_q7s/scheduling.cpp b/bsp_q7s/scheduling.cpp new file mode 100644 index 0000000..fd99ab3 --- /dev/null +++ b/bsp_q7s/scheduling.cpp @@ -0,0 +1,702 @@ +#include "scheduling.h" + +#include +#include +#include +#include + +#include +#include + +#include "OBSWConfig.h" +#include "fsfw/objectmanager/ObjectManager.h" +#include "fsfw/objectmanager/ObjectManagerIF.h" +#include "fsfw/platform.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/serviceinterface/ServiceInterfaceStream.h" +#include "fsfw/tasks/FixedTimeslotTaskIF.h" +#include "fsfw/tasks/PeriodicTaskIF.h" +#include "fsfw/tasks/TaskFactory.h" +#include "mission/pollingSeqTables.h" +#include "mission/scheduling.h" +#include "mission/utility/InitMission.h" +#include "objectFactory.h" +#include "q7sConfig.h" + +/* This is configured for linux without CR */ +#ifdef PLATFORM_UNIX +ServiceInterfaceStream sif::debug("DEBUG"); +ServiceInterfaceStream sif::info("INFO"); +ServiceInterfaceStream sif::warning("WARNING"); +ServiceInterfaceStream sif::error("ERROR"); +#else +ServiceInterfaceStream sif::debug("DEBUG", true); +ServiceInterfaceStream sif::info("INFO", true); +ServiceInterfaceStream sif::warning("WARNING", true); +ServiceInterfaceStream sif::error("ERROR", true, false, true); +#endif + +ObjectManagerIF* objectManager = nullptr; + +void scheduling::initMission() { + sif::info << "Building global objects.." << std::endl; + try { + /* Instantiate global object manager and also create all objects */ + ObjectManager::instance()->setObjectFactoryFunction(ObjectFactory::produce, nullptr); + } catch (const std::invalid_argument& e) { + sif::error << "scheduling::initMission: Object Construction failed with an " + "invalid argument: " + << e.what(); + std::exit(1); + } + + sif::info << "Initializing all objects.." << std::endl; + ObjectManager::instance()->initialize(); + + /* This function creates and starts all tasks */ + initTasks(); +} + +void scheduling::initTasks() { + TaskFactory* factory = TaskFactory::instance(); + ReturnValue_t result = returnvalue::OK; + if (factory == nullptr) { + /* Should never happen ! */ + return; + } +#if OBSW_PRINT_MISSED_DEADLINES == 1 + void (*missedDeadlineFunc)(void) = TaskFactory::printMissedDeadline; +#else + void (*missedDeadlineFunc)(void) = nullptr; +#endif + +#if OBSW_ADD_SA_DEPL == 1 + // Could add this to the core controller but the core controller does so many thing that I would + // prefer to have the solar array deployment in a seprate task. + PeriodicTaskIF* solarArrayDeplTask = + factory->createPeriodicTask("SOLAR_ARRAY_DEPL", 65, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.4, + missedDeadlineFunc, &RR_SCHEDULING); + result = solarArrayDeplTask->addComponent(objects::SOLAR_ARRAY_DEPL_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("SOLAR_ARRAY_DEPL", objects::SOLAR_ARRAY_DEPL_HANDLER); + } +#endif + + // Medium priority, higher than something like payload, but not the highest priority to also + // detect tasks which choke other tasks. + PeriodicTaskIF* xiphosWdtTask = + factory->createPeriodicTask("XIPHOS_WDT", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.4, + missedDeadlineFunc, &RR_SCHEDULING); + result = xiphosWdtTask->addComponent(objects::XIPHOS_WDT); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("XIPHOS_WDT", objects::XIPHOS_WDT); + } + + PeriodicTaskIF* coreCtrlTask = factory->createPeriodicTask( + "CORE_CTRL", 55, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.4, missedDeadlineFunc, &RR_SCHEDULING); + result = coreCtrlTask->addComponent(objects::CORE_CONTROLLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CORE_CTRL", objects::CORE_CONTROLLER); + } + + /* TMTC Distribution */ + PeriodicTaskIF* tmTcDistributor = factory->createPeriodicTask( + "TC_DIST", 45, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc, &RR_SCHEDULING); +#if OBSW_ADD_TCPIP_SERVERS == 1 +#if OBSW_ADD_TMTC_UDP_SERVER == 1 + result = tmTcDistributor->addComponent(objects::UDP_TMTC_SERVER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("UDP_TMTC_SERVER", objects::UDP_TMTC_SERVER); + } +#endif +#if OBSW_ADD_TMTC_TCP_SERVER == 1 + result = tmTcDistributor->addComponent(objects::TCP_TMTC_SERVER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("TCP_TMTC_SERVER", objects::TCP_TMTC_SERVER); + } +#endif +#endif + result = tmTcDistributor->addComponent(objects::CCSDS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CCSDS_DISTRIB", objects::CCSDS_PACKET_DISTRIBUTOR); + } + result = tmTcDistributor->addComponent(objects::PUS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_PACKET_DISTRIB", objects::PUS_PACKET_DISTRIBUTOR); + } + result = tmTcDistributor->addComponent(objects::CFDP_DISTRIBUTOR); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CFDP_DISTRIBUTOR", objects::CFDP_DISTRIBUTOR); + } + +#if OBSW_ADD_TCPIP_SERVERS == 1 +#if OBSW_ADD_TMTC_UDP_SERVER == 1 + PeriodicTaskIF* udpPollingTask = factory->createPeriodicTask( + "UDP_TMTC_POLLING", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = udpPollingTask->addComponent(objects::UDP_TMTC_POLLING_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("UDP_POLLING", objects::UDP_TMTC_POLLING_TASK); + } +#endif +#if OBSW_ADD_TMTC_TCP_SERVER == 1 + PeriodicTaskIF* tcpPollingTask = factory->createPeriodicTask( + "TCP_TMTC_POLLING", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = tcpPollingTask->addComponent(objects::TCP_TMTC_POLLING_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("UDP_POLLING", objects::TCP_TMTC_POLLING_TASK); + } +#endif +#endif + + PeriodicTaskIF* genericSysTask = + factory->createPeriodicTask("SYSTEM_TASK", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.5, + missedDeadlineFunc, &RR_SCHEDULING); + result = genericSysTask->addComponent(objects::EIVE_SYSTEM); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("EIVE_SYSTEM", objects::EIVE_SYSTEM); + } + result = genericSysTask->addComponent(objects::COM_SUBSYSTEM); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("COM_SUBSYSTEM", objects::COM_SUBSYSTEM); + } + result = genericSysTask->addComponent(objects::SYRLINKS_ASSY); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("SYRLINKS_ASSY", objects::SYRLINKS_ASSY); + } + result = genericSysTask->addComponent(objects::PL_SUBSYSTEM); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PL_SUBSYSTEM", objects::PL_SUBSYSTEM); + } + result = genericSysTask->addComponent(objects::EPS_SUBSYSTEM); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("EPS_SUBSYSTEM", objects::EPS_SUBSYSTEM); + } + result = genericSysTask->addComponent(objects::INTERNAL_ERROR_REPORTER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("ERROR_REPORTER", objects::INTERNAL_ERROR_REPORTER); + } + result = genericSysTask->addComponent(objects::PUS_SERVICE_17_TEST); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_17", objects::PUS_SERVICE_17_TEST); + } + +#if OBSW_ADD_CCSDS_IP_CORES == 1 + result = genericSysTask->addComponent(objects::CCSDS_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CCSDS Handler", objects::CCSDS_HANDLER); + } + + // Runs in IRQ mode, frequency does not really matter + PeriodicTaskIF* pdecHandlerTask = factory->createPeriodicTask( + "PDEC_HANDLER", 75, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr, &RR_SCHEDULING); + result = pdecHandlerTask->addComponent(objects::PDEC_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PDEC Handler", objects::PDEC_HANDLER); + } + +#endif /* OBSW_ADD_CCSDS_IP_CORE == 1 */ + // All the TM store tasks run in permanent loops, frequency does not matter + PeriodicTaskIF* liveTmTask = factory->createPeriodicTask( + "LIVE_TM", 55, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr, &RR_SCHEDULING); + result = liveTmTask->addComponent(objects::LIVE_TM_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("LIVE_TM", objects::LIVE_TM_TASK); + } + PeriodicTaskIF* logTmTask = factory->createPeriodicTask( + "LOG_PSTORE", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + result = logTmTask->addComponent(objects::LOG_STORE_AND_TM_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("LOG_STORE_AND_TM", objects::LOG_STORE_AND_TM_TASK); + } + PeriodicTaskIF* hkTmTask = + factory->createPeriodicTask("HK_PSTORE", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + result = hkTmTask->addComponent(objects::HK_STORE_AND_TM_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("HK_STORE_AND_TM", objects::HK_STORE_AND_TM_TASK); + } + PeriodicTaskIF* cfdpTmTask = factory->createPeriodicTask( + "CFDP_PSTORE", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + result = cfdpTmTask->addComponent(objects::CFDP_STORE_AND_TM_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CFDP_STORE_AND_TM", objects::CFDP_STORE_AND_TM_TASK); + } + + // TODO: Use user priorities for this task. +#if OBSW_ADD_CFDP_COMPONENTS == 1 + PeriodicTaskIF* cfdpTask = + factory->createPeriodicTask("CFDP_HANDLER", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.4, + missedDeadlineFunc, &RR_SCHEDULING); + result = cfdpTask->addComponent(objects::CFDP_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("CFDP", objects::CFDP_HANDLER); + } +#endif + + PeriodicTaskIF* gpsTask = + factory->createPeriodicTask("GPS_TASK", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, 0.4, + missedDeadlineFunc, &RR_SCHEDULING); + result = gpsTask->addComponent(objects::GPS_CONTROLLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("GPS_CTRL", objects::GPS_CONTROLLER); + } + +#if OBSW_ADD_ACS_BOARD == 1 + PeriodicTaskIF* acsBrdPolling = + factory->createPeriodicTask("ACS_BOARD_POLLING", 85, PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, + 0.4, missedDeadlineFunc, &RR_SCHEDULING); + result = acsBrdPolling->addComponent(objects::ACS_BOARD_POLLING_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("ACS_BOARD_POLLING", objects::ACS_BOARD_POLLING_TASK); + } +#endif + +#if OBSW_ADD_RW == 1 + PeriodicTaskIF* rwPolling = + factory->createPeriodicTask("RW_POLLING_TASK", 75, PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, + 0.4, missedDeadlineFunc, &RR_SCHEDULING); + result = rwPolling->addComponent(objects::RW_POLLING_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("RW_POLLING_TASK", objects::RW_POLLING_TASK); + } +#endif +#if OBSW_ADD_MGT == 1 + PeriodicTaskIF* imtqPolling = + factory->createPeriodicTask("IMTQ_POLLING_TASK", 85, PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, + 0.4, missedDeadlineFunc, &RR_SCHEDULING); + result = imtqPolling->addComponent(objects::IMTQ_POLLING); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("IMTQ_POLLING_TASK", objects::IMTQ_POLLING); + } +#endif + +#if OBSW_ADD_SUN_SENSORS == 1 + PeriodicTaskIF* susPolling = + factory->createPeriodicTask("SUS_POLLING_TASK", 85, PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, + 0.4, missedDeadlineFunc, &RR_SCHEDULING); + result = susPolling->addComponent(objects::SUS_POLLING_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("SUS_POLLING_TASK", objects::SUS_POLLING_TASK); + } +#endif + + PeriodicTaskIF* acsSysTask = + factory->createPeriodicTask("ACS_SYS_TASK", 55, PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, 0.4, + missedDeadlineFunc, &RR_SCHEDULING); + result = acsSysTask->addComponent(objects::ACS_SUBSYSTEM); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("ACS_SUBSYSTEM", objects::ACS_SUBSYSTEM); + } + result = acsSysTask->addComponent(objects::IMTQ_ASSY); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("IMTQ_ASSY", objects::IMTQ_ASSY); + } + result = acsSysTask->addComponent(objects::ACS_BOARD_ASS); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("ACS_BOARD_ASS", objects::ACS_BOARD_ASS); + } + result = acsSysTask->addComponent(objects::RW_ASSY); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("RW_ASS", objects::RW_ASSY); + } + result = acsSysTask->addComponent(objects::SUS_BOARD_ASS); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("SUS_BOARD_ASS", objects::SUS_BOARD_ASS); + } + result = acsSysTask->addComponent(objects::STR_ASSY); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("STR_ASSY", objects::STR_ASSY); + } + result = acsSysTask->addComponent(objects::GPS_0_HEALTH_DEV); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("GPS_0_HEALTH_DEV", objects::GPS_0_HEALTH_DEV); + } + result = acsSysTask->addComponent(objects::GPS_1_HEALTH_DEV); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("GPS_1_HEALTH_DEV", objects::GPS_1_HEALTH_DEV); + } + + PeriodicTaskIF* tcsSystemTask = factory->createPeriodicTask( + "TCS_TASK", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.5, missedDeadlineFunc, &RR_SCHEDULING); +#if OBSW_ADD_THERMAL_TEMP_INSERTER == 1 + tcsSystemTask->addComponent(objects::THERMAL_TEMP_INSERTER); +#endif + scheduling::scheduleRtdSensors(tcsSystemTask); + result = tcsSystemTask->addComponent(objects::TCS_SUBSYSTEM); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("TCS_SUBSYSTEM", objects::TCS_SUBSYSTEM); + } + result = tcsSystemTask->addComponent(objects::TCS_BOARD_ASS); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("TCS_BOARD_ASS", objects::TCS_BOARD_ASS); + } +#if OBSW_ADD_TCS_CTRL == 1 + result = tcsSystemTask->addComponent(objects::THERMAL_CONTROLLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("THERMAL_CONTROLLER", objects::THERMAL_CONTROLLER); + } +#endif + result = tcsSystemTask->addComponent(objects::HEATER_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("HEATER_HANDLER", objects::HEATER_HANDLER); + } + result = tcsSystemTask->addComponent(objects::HEATER_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("HEATER_HANDLER", objects::HEATER_HANDLER); + } + +#if OBSW_ADD_SYRLINKS == 1 + PeriodicTaskIF* syrlinksCom = factory->createPeriodicTask( + "SYRLINKS_COM", 65, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, missedDeadlineFunc); + result = syrlinksCom->addComponent(objects::SYRLINKS_COM_HANDLER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("SYRLINKS_COM", objects::SYRLINKS_COM_HANDLER); + } +#endif + +#if OBSW_ADD_STAR_TRACKER == 1 + // Relatively high priority to make sure STR COM works well. + PeriodicTaskIF* strHelperTask = + factory->createPeriodicTask("STR_HELPER", 30, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, + missedDeadlineFunc, &RR_SCHEDULING); + result = strHelperTask->addComponent(objects::STR_COM_IF); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("STR_HELPER", objects::STR_COM_IF); + } +#endif /* OBSW_ADD_STAR_TRACKER == 1 */ + +#if OBSW_ADD_PLOC_MPSOC == 1 + PeriodicTaskIF* mpsocHelperTask = factory->createPeriodicTask( + "PLOC_MPSOC_HELPER", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = mpsocHelperTask->addComponent(objects::PLOC_MPSOC_HELPER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PLOC_MPSOC_HELPER", objects::PLOC_MPSOC_HELPER); + } +#endif /* OBSW_ADD_PLOC_MPSOC */ + + // TODO: Use regular scheduler for this task +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + PeriodicTaskIF* supvHelperTask = factory->createPeriodicTask( + "PLOC_SUPV_HELPER", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, missedDeadlineFunc); + result = supvHelperTask->addComponent(objects::PLOC_SUPERVISOR_HELPER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PLOC_SUPV_HELPER", objects::PLOC_SUPERVISOR_HELPER); + } +#endif /* OBSW_ADD_PLOC_SUPERVISOR */ + + FixedTimeslotTaskIF* plTask = factory->createFixedTimeslotTask( + "PL_TASK", 25, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.5, missedDeadlineFunc); + pst::pstPayload(plTask); + +#if OBSW_ADD_SCEX_DEVICE == 1 + PeriodicTaskIF* scexReaderTask; + scheduling::scheduleScexReader(*factory, scexReaderTask); +#endif + + std::vector pusTasks; + createPusTasks(*factory, missedDeadlineFunc, pusTasks); + std::vector pstTasks; + AcsPstCfg cfg; + createPstTasks(*factory, missedDeadlineFunc, pstTasks, cfg); + +#if OBSW_ADD_TEST_CODE == 1 +#if OBSW_TEST_CCSDS_BRIDGE == 1 + PeriodicTaskIF* ptmeTestTask = factory->createPeriodicTask( + "PTME_TEST", 80, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = ptmeTestTask->addComponent(objects::CCSDS_IP_CORE_BRIDGE); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PTME_TEST", objects::CCSDS_IP_CORE_BRIDGE); + } +#endif + std::vector testTasks; + createTestTasks(*factory, missedDeadlineFunc, testTasks); +#endif + + auto taskStarter = [](std::vector& taskVector, std::string name) { + for (const auto& task : taskVector) { + if (task != nullptr) { + task->startTask(); + } else { + sif::error << "Task in vector " << name << " is invalid!" << std::endl; + } + } + }; + + sif::info << "Starting tasks.." << std::endl; + xiphosWdtTask->startTask(); + tmTcDistributor->startTask(); + +#if OBSW_ADD_TCPIP_SERVERS == 1 +#if OBSW_ADD_TMTC_UDP_SERVER == 1 + udpPollingTask->startTask(); +#endif +#if OBSW_ADD_TMTC_TCP_SERVER == 1 + tcpPollingTask->startTask(); +#endif +#endif + + genericSysTask->startTask(); +#if OBSW_ADD_CCSDS_IP_CORES == 1 + pdecHandlerTask->startTask(); +#endif /* OBSW_ADD_CCSDS_IP_CORES == 1 */ + liveTmTask->startTask(); + logTmTask->startTask(); + hkTmTask->startTask(); + cfdpTmTask->startTask(); + + coreCtrlTask->startTask(); +#if OBSW_ADD_SA_DEPL == 1 + solarArrayDeplTask->startTask(); +#endif +#if OBSW_ADD_ACS_BOARD == 1 + acsBrdPolling->startTask(); +#endif +#if OBSW_ADD_SYRLINKS == 1 + syrlinksCom->startTask(); +#endif +#if OBSW_ADD_MGT == 1 + imtqPolling->startTask(); +#endif +#if OBSW_ADD_SUN_SENSORS == 1 + susPolling->startTask(); +#endif + + taskStarter(pstTasks, "PST task vector"); + taskStarter(pusTasks, "PUS task vector"); +#if OBSW_ADD_SCEX_DEVICE == 1 + scexReaderTask->startTask(); +#endif + +#if OBSW_TEST_CCSDS_BRIDGE == 1 + ptmeTestTask->startTask(); +#endif + +#if OBSW_ADD_CFDP_COMPONENTS == 1 + cfdpTask->startTask(); +#endif + +#if OBSW_ADD_STAR_TRACKER == 1 + strHelperTask->startTask(); +#endif /* OBSW_ADD_STAR_TRACKER == 1 */ + +#if OBSW_ADD_RW == 1 + rwPolling->startTask(); +#endif + gpsTask->startTask(); + acsSysTask->startTask(); + if (not tcsSystemTask->isEmpty()) { + tcsSystemTask->startTask(); + } +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + supvHelperTask->startTask(); +#endif /* OBSW_ADD_PLOC_SUPERVISOR == 1 */ +#if OBSW_ADD_PLOC_MPSOC == 1 + mpsocHelperTask->startTask(); +#endif + plTask->startTask(); + +#if OBSW_ADD_TEST_CODE == 1 + taskStarter(testTasks, "Test task vector"); +#endif + + sif::info << "Tasks started.." << std::endl; +} + +void scheduling::createPstTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec, AcsPstCfg cfg) { + ReturnValue_t result = returnvalue::OK; + +#ifdef RELEASE_BUILD + static constexpr float acsPstPeriod = 0.4; +#else + static constexpr float acsPstPeriod = 0.4; +#endif + FixedTimeslotTaskIF* acsTcsPst = + factory.createFixedTimeslotTask("ACS_TCS_PST", 85, PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, + acsPstPeriod, missedDeadlineFunc, &RR_SCHEDULING); + result = pst::pstTcsAndAcs(acsTcsPst, cfg); + if (result != returnvalue::OK) { + if (result == FixedTimeslotTaskIF::SLOT_LIST_EMPTY) { + sif::warning << "scheduling::initTasks: ACS PST is empty" << std::endl; + } else { + sif::error << "scheduling::initTasks: Creating ACS PST failed!" << std::endl; + } + } else { + taskVec.push_back(acsTcsPst); + } + + /* Polling Sequence Table Default */ +#if OBSW_ADD_SPI_TEST_CODE == 0 + FixedTimeslotTaskIF* syrlinksPst = + factory.createFixedTimeslotTask("SYRLINKS", 65, PeriodicTaskIF::MINIMUM_STACK_SIZE * 4, 0.5, + missedDeadlineFunc, &RR_SCHEDULING); + result = pst::pstSyrlinks(syrlinksPst); + if (result != returnvalue::OK) { + if (result == FixedTimeslotTaskIF::SLOT_LIST_EMPTY) { + sif::warning << "scheduling::initTasks: SPI PST is empty" << std::endl; + } else { + sif::error << "scheduling::initTasks: Creating SPI PST failed!" << std::endl; + } + } else { + taskVec.push_back(syrlinksPst); + } +#endif + +#if OBSW_ADD_I2C_TEST_CODE == 0 + FixedTimeslotTaskIF* i2cPst = + factory.createFixedTimeslotTask("I2C_PS_PST", 60, PeriodicTaskIF::MINIMUM_STACK_SIZE * 4, 0.6, + missedDeadlineFunc, &RR_SCHEDULING); + pst::TmpSchedConfig tmpSchedConf; +#if OBSW_Q7S_EM == 1 + tmpSchedConf.scheduleTmpDev0 = true; + tmpSchedConf.scheduleTmpDev1 = true; + tmpSchedConf.schedulePlPcduDev0 = true; + tmpSchedConf.schedulePlPcduDev1 = true; + tmpSchedConf.scheduleIfBoardDev = true; +#endif + result = pst::pstI2c(tmpSchedConf, i2cPst); + if (result != returnvalue::OK) { + if (result == FixedTimeslotTaskIF::SLOT_LIST_EMPTY) { + sif::warning << "scheduling::initTasks: I2C PST is empty" << std::endl; + } else { + sif::error << "scheduling::initTasks: Creating I2C PST failed!" << std::endl; + } + } else { + taskVec.push_back(i2cPst); + } +#endif + + FixedTimeslotTaskIF* gomSpacePstTask = + factory.createFixedTimeslotTask("GS_PST_TASK", 65, PeriodicTaskIF::MINIMUM_STACK_SIZE * 4, + 0.25, missedDeadlineFunc, &RR_SCHEDULING); + result = pst::pstGompaceCan(gomSpacePstTask); + if (result != returnvalue::OK) { + if (result != FixedTimeslotTaskIF::SLOT_LIST_EMPTY) { + sif::error << "scheduling::initTasks: GomSpace PST initialization failed!" << std::endl; + } + } + taskVec.push_back(gomSpacePstTask); +} + +void scheduling::createPusTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec) { + ReturnValue_t result = returnvalue::OK; + /* PUS Services */ + PeriodicTaskIF* pusHighPrio = + factory.createPeriodicTask("PUS_HIGH_PRIO", 60, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, + missedDeadlineFunc, &RR_SCHEDULING); + result = pusHighPrio->addComponent(objects::PUS_SERVICE_1_VERIFICATION); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_VERIF", objects::PUS_SERVICE_1_VERIFICATION); + } + result = pusHighPrio->addComponent(objects::PUS_SERVICE_5_EVENT_REPORTING); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_EVENTS", objects::PUS_SERVICE_5_EVENT_REPORTING); + } + result = pusHighPrio->addComponent(objects::EVENT_MANAGER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("EVENT_MGMT", objects::EVENT_MANAGER); + } + result = pusHighPrio->addComponent(objects::PUS_SERVICE_9_TIME_MGMT); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_TIME", objects::PUS_SERVICE_9_TIME_MGMT); + } + taskVec.push_back(pusHighPrio); + + PeriodicTaskIF* pusMedPrio = + factory.createPeriodicTask("PUS_MED_PRIO", 45, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.8, + missedDeadlineFunc, &RR_SCHEDULING); + result = pusMedPrio->addComponent(objects::PUS_SERVICE_3_HOUSEKEEPING); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_3", objects::PUS_SERVICE_3_HOUSEKEEPING); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_8_FUNCTION_MGMT); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_8", objects::PUS_SERVICE_8_FUNCTION_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_15_TM_STORAGE); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_15", objects::PUS_SERVICE_15_TM_STORAGE); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_11_TC_SCHEDULER); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_11", objects::PUS_SERVICE_11_TC_SCHEDULER); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_20_PARAMETERS); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_20", objects::PUS_SERVICE_20_PARAMETERS); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_200_MODE_MGMT); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_200", objects::PUS_SERVICE_200_MODE_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_201_HEALTH); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_201", objects::PUS_SERVICE_201_HEALTH); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_2_DEVICE_ACCESS); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("PUS_2", objects::PUS_SERVICE_2_DEVICE_ACCESS); + } + taskVec.push_back(pusMedPrio); +} + +void scheduling::createTestTasks(TaskFactory& factory, + TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec) { +#if OBSW_ADD_TEST_TASK == 1 && OBSW_ADD_TEST_CODE == 1 + ReturnValue_t result = returnvalue::OK; + static_cast(result); // supress warning in case it is not used + + PeriodicTaskIF* testTask = factory.createPeriodicTask( + "TEST_TASK", 60, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1, missedDeadlineFunc); + + result = testTask->addComponent(objects::TEST_TASK); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("TEST_TASK", objects::TEST_TASK); + } + +#if OBSW_ADD_SPI_TEST_CODE == 1 + result = testTask->addComponent(objects::SPI_TEST); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("SPI_TEST", objects::SPI_TEST); + } +#endif +#if OBSW_ADD_I2C_TEST_CODE == 1 + result = testTask->addComponent(objects::I2C_TEST); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("I2C_TEST", objects::I2C_TEST); + } +#endif +#if OBSW_ADD_UART_TEST_CODE == 1 + result = testTask->addComponent(objects::UART_TEST); + if (result != returnvalue::OK) { + scheduling::printAddObjectError("UART_TEST", objects::UART_TEST); + } +#endif + + taskVec.push_back(testTask); + +#endif // OBSW_ADD_TEST_TASK == 1 && OBSW_ADD_TEST_CODE == 1 +} + +/** + ▄ ▄ + ▌▒█ ▄▀▒▌ + ▌▒▒█ ▄▀▒▒▒▐ + ▐▄▀▒▒▀▀▀▀▄▄▄▀▒▒▒▒▒▐ + ▄▄▀▒░▒▒▒▒▒▒▒▒▒█▒▒▄█▒▐ + ▄▀▒▒▒░░░▒▒▒░░░▒▒▒▀██▀▒▌ + ▐▒▒▒▄▄▒▒▒▒░░░▒▒▒▒▒▒▒▀▄▒▒▌ + ▌░░▌█▀▒▒▒▒▒▄▀█▄▒▒▒▒▒▒▒█▒▐ + ▐░░░▒▒▒▒▒▒▒▒▌██▀▒▒░░░▒▒▒▀▄▌ + ▌░▒▄██▄▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▌ + ▌▒▀▐▄█▄█▌▄░▀▒▒░░░░░░░░░░▒▒▒▐ + ▐▒▒▐▀▐▀▒░▄▄▒▄▒▒▒▒▒▒░▒░▒░▒▒▒▒▌ + ▐▒▒▒▀▀▄▄▒▒▒▄▒▒▒▒▒▒▒▒░▒░▒░▒▒▐ + ▌▒▒▒▒▒▒▀▀▀▒▒▒▒▒▒░▒░▒░▒░▒▒▒▌ + ▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▒▄▒▒▐ + ▀▄▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▄▒▒▒▒▌ + ▀▄▒▒▒▒▒▒▒▒▒▒▄▄▄▀▒▒▒▒▄▀ + ▀▄▄▄▄▄▄▀▀▀▒▒▒▒▒▄▄▀ + ▒▒▒▒▒▒▒▒▒▒▀▀ + **/ diff --git a/bsp_q7s/scheduling.h b/bsp_q7s/scheduling.h new file mode 100644 index 0000000..4a49374 --- /dev/null +++ b/bsp_q7s/scheduling.h @@ -0,0 +1,26 @@ +#ifndef BSP_Q7S_INITMISSION_H_ +#define BSP_Q7S_INITMISSION_H_ + +#include + +#include "fsfw/tasks/definitions.h" +#include "mission/pollingSeqTables.h" + +using pst::AcsPstCfg; + +class PeriodicTaskIF; +class TaskFactory; + +namespace scheduling { +void initMission(); +void initTasks(); + +void createPstTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec, AcsPstCfg cfg); +void createPusTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +void createTestTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +}; // namespace scheduling + +#endif /* BSP_Q7S_INITMISSION_H_ */ diff --git a/bsp_q7s/simple/CMakeLists.txt b/bsp_q7s/simple/CMakeLists.txt new file mode 100644 index 0000000..b5c4443 --- /dev/null +++ b/bsp_q7s/simple/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${SIMPLE_OBSW_NAME} PRIVATE simple.cpp) diff --git a/bsp_q7s/simple/simple.cpp b/bsp_q7s/simple/simple.cpp new file mode 100644 index 0000000..ac39c7a --- /dev/null +++ b/bsp_q7s/simple/simple.cpp @@ -0,0 +1,22 @@ +#include "simple.h" + +#include "iostream" +#include "q7sConfig.h" + +#if Q7S_SIMPLE_ADD_FILE_SYSTEM_TEST == 1 +#include "../boardtest/FileSystemTest.h" +#endif + +int simple::simple() { + std::cout << "-- Q7S Simple Application --" << std::endl; +#if Q7S_SIMPLE_ADD_FILE_SYSTEM_TEST == 1 + { + FileSystemTest fileSystemTest; + } +#endif + +#if TE0720_GPIO_TEST + +#endif + return 0; +} diff --git a/bsp_q7s/simple/simple.h b/bsp_q7s/simple/simple.h new file mode 100644 index 0000000..096a7b7 --- /dev/null +++ b/bsp_q7s/simple/simple.h @@ -0,0 +1,10 @@ +#ifndef BSP_Q7S_SIMPLE_SIMPLE_H_ +#define BSP_Q7S_SIMPLE_SIMPLE_H_ + +namespace simple { + +int simple(); + +} + +#endif /* BSP_Q7S_SIMPLE_SIMPLE_H_ */ diff --git a/bsp_q7s/spi/Q7sSpiComIF.cpp b/bsp_q7s/spi/Q7sSpiComIF.cpp new file mode 100644 index 0000000..8455250 --- /dev/null +++ b/bsp_q7s/spi/Q7sSpiComIF.cpp @@ -0,0 +1,5 @@ +#include + +Q7sSpiComIF::Q7sSpiComIF(object_id_t objectId, GpioIF* gpioComIF) : SpiComIF(objectId, gpioComIF) {} + +Q7sSpiComIF::~Q7sSpiComIF() {} diff --git a/bsp_q7s/spi/Q7sSpiComIF.h b/bsp_q7s/spi/Q7sSpiComIF.h new file mode 100644 index 0000000..def754a --- /dev/null +++ b/bsp_q7s/spi/Q7sSpiComIF.h @@ -0,0 +1,32 @@ +#ifndef BSP_Q7S_SPI_Q7SSPICOMIF_H_ +#define BSP_Q7S_SPI_Q7SSPICOMIF_H_ + +#include + +/** + * @brief This additional communication interface is required because the SPI busses behind the + * devices "/dev/spi2.0" and "dev/spidev3.0" are multiplexed to one SPI interface. + * This was necessary because the processing system spi (/dev/spi2.0) does not support + * frequencies lower than 650 kHz. To reach lower frequencies also the CPU frequency must + * be reduced which leads to other effects compromising kernel drivers. + * The nano avionics reaction wheels require a spi frequency between 150 kHz and 300 kHz + * why an additional AXI SPI core has been implemented in the programmable logic. However, + * the spi frequency of the AXI SPI core is not configurable during runtime. Therefore, + * this communication interface multiplexes either the hard-wired SPI or the AXI SPI to + * the SPI interface. The multiplexing is performed via a GPIO connected to a VHDL + * module responsible for switching between the to SPI peripherals. + */ +class Q7sSpiComIF : public SpiComIF { + public: + /** + * @brief Constructor + * + * @param objectId + * @param gpioComIF + * @param gpioSwitchId The gpio ID of the GPIO connected to the SPI mux module in the PL. + */ + Q7sSpiComIF(object_id_t objectId, GpioIF* gpioComIF, gpioId_t gpioSwitchId); + virtual ~Q7sSpiComIF(); +}; + +#endif /* BSP_Q7S_SPI_Q7SSPICOMIF_H_ */ diff --git a/bsp_q7s/xadc/CMakeLists.txt b/bsp_q7s/xadc/CMakeLists.txt new file mode 100644 index 0000000..a8d6181 --- /dev/null +++ b/bsp_q7s/xadc/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PRIVATE Xadc.cpp) diff --git a/bsp_q7s/xadc/Xadc.cpp b/bsp_q7s/xadc/Xadc.cpp new file mode 100644 index 0000000..da3fbf7 --- /dev/null +++ b/bsp_q7s/xadc/Xadc.cpp @@ -0,0 +1,149 @@ +#include "Xadc.h" + +#include +#include + +#include + +#include "fsfw/serviceinterface/ServiceInterfaceStream.h" + +Xadc::Xadc() {} + +Xadc::~Xadc() {} + +ReturnValue_t Xadc::getTemperature(float& temperature) { + ReturnValue_t result = returnvalue::OK; + int raw = 0; + int offset = 0; + float scale = 0; + result = readValFromFile(xadc::file::tempRaw.c_str(), raw); + if (result != returnvalue::OK) { + return result; + } + result = readValFromFile(xadc::file::tempOffset.c_str(), offset); + if (result != returnvalue::OK) { + return result; + } + result = readValFromFile(xadc::file::tempScale.c_str(), scale); + if (result != returnvalue::OK) { + return result; + } + temperature = (raw + offset) * scale / 1000; + return result; +} + +ReturnValue_t Xadc::getVccPint(float& vccPint) { + ReturnValue_t result = + readVoltageFromSysfs(xadc::file::vccpintRaw, xadc::file::vccpintScale, vccPint); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t Xadc::getVccPaux(float& vccPaux) { + ReturnValue_t result = + readVoltageFromSysfs(xadc::file::vccpauxRaw, xadc::file::vccpauxScale, vccPaux); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t Xadc::getVccInt(float& vccInt) { + ReturnValue_t result = + readVoltageFromSysfs(xadc::file::vccintRaw, xadc::file::vccintScale, vccInt); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t Xadc::getVccAux(float& vccAux) { + ReturnValue_t result = + readVoltageFromSysfs(xadc::file::vccauxRaw, xadc::file::vccauxScale, vccAux); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t Xadc::getVccBram(float& vccBram) { + ReturnValue_t result = + readVoltageFromSysfs(xadc::file::vccbramRaw, xadc::file::vccbramScale, vccBram); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t Xadc::getVccOddr(float& vccOddr) { + ReturnValue_t result = + readVoltageFromSysfs(xadc::file::vccoddrRaw, xadc::file::vccoddrScale, vccOddr); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t Xadc::getVrefp(float& vrefp) { + ReturnValue_t result = readVoltageFromSysfs(xadc::file::vrefpRaw, xadc::file::vrefpScale, vrefp); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t Xadc::getVrefn(float& vrefn) { + ReturnValue_t result = readVoltageFromSysfs(xadc::file::vrefnRaw, xadc::file::vrefnScale, vrefn); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t Xadc::readVoltageFromSysfs(std::string rawFile, std::string scaleFile, + float& voltage) { + ReturnValue_t result = returnvalue::OK; + float raw = 0; + float scale = 0; + result = readValFromFile(rawFile.c_str(), raw); + if (result != returnvalue::OK) { + return result; + } + result = readValFromFile(scaleFile.c_str(), scale); + if (result != returnvalue::OK) { + return result; + } + voltage = calculateVoltage(raw, scale); + return result; +} + +float Xadc::calculateVoltage(int raw, float scale) { return static_cast(raw * scale); } + +template +ReturnValue_t Xadc::readValFromFile(const char* filename, T& val) { + FILE* fp; + fp = fopen(filename, "r"); + if (fp == nullptr) { + sif::warning << "Xadc::readValFromFile: Failed to open file " << filename << std::endl; + return returnvalue::FAILED; + } + char valstring[MAX_STR_LENGTH]{}; + char* returnVal = fgets(valstring, MAX_STR_LENGTH, fp); + if (returnVal == nullptr) { + sif::warning << "Xadc::readValFromFile: Failed to read string from file " << filename + << std::endl; + fclose(fp); + return returnvalue::FAILED; + } + std::istringstream valSstream(valstring); + valSstream >> val; + if (valSstream.bad()) { + sif::warning << "Xadc: Conversion of value to target type failed" << std::endl; + fclose(fp); + return returnvalue::FAILED; + } + fclose(fp); + return returnvalue::OK; +} diff --git a/bsp_q7s/xadc/Xadc.h b/bsp_q7s/xadc/Xadc.h new file mode 100644 index 0000000..be6ab70 --- /dev/null +++ b/bsp_q7s/xadc/Xadc.h @@ -0,0 +1,108 @@ +#ifndef BSP_Q7S_XADC_XADC_H_ +#define BSP_Q7S_XADC_XADC_H_ + +#include + +#include "fsfw/returnvalues/returnvalue.h" + +namespace xadc { +using namespace std; +static const string iioPath = "/sys/bus/iio/devices/iio:device1"; +namespace file { +static const string tempOffset = iioPath + "/in_temp0_offset"; +static const string tempRaw = iioPath + "/in_temp0_raw"; +static const string tempScale = iioPath + "/in_temp0_scale"; +static const string vccintRaw = iioPath + "/in_voltage0_vccint_raw"; +static const string vccintScale = iioPath + "/in_voltage0_vccint_scale"; +static const string vccauxRaw = iioPath + "/in_voltage1_vccaux_raw"; +static const string vccauxScale = iioPath + "/in_voltage1_vccaux_scale"; +static const string vccbramRaw = iioPath + "/in_voltage2_vccbram_raw"; +static const string vccbramScale = iioPath + "/in_voltage2_vccbram_scale"; +static const string vccpintRaw = iioPath + "/in_voltage3_vccpint_raw"; +static const string vccpintScale = iioPath + "/in_voltage3_vccpint_scale"; +static const string vccpauxRaw = iioPath + "/in_voltage4_vccpaux_raw"; +static const string vccpauxScale = iioPath + "/in_voltage4_vccpaux_scale"; +static const string vccoddrRaw = iioPath + "/in_voltage5_vccoddr_raw"; +static const string vccoddrScale = iioPath + "/in_voltage5_vccoddr_scale"; +static const string vrefpRaw = iioPath + "/in_voltage6_vrefp_raw"; +static const string vrefpScale = iioPath + "/in_voltage6_vrefp_scale"; +static const string vrefnRaw = iioPath + "/in_voltage7_vrefn_raw"; +static const string vrefnScale = iioPath + "/in_voltage7_vrefn_scale"; +} // namespace file +} // namespace xadc + +/** + * @brief Class providing access to the data generated by the analog mixed signal module (XADC). + * + * @details Details about the XADC peripheral of the Zynq-7020 can be found in the UG480 "7-Series + * FPGAs and Zynq-7000 SoC XADC Dual 12-Bit 1 MSPS Analog-to-Digital Converter" user guide + * from Xilinx. + * + * @author J. Meier + */ +class Xadc { + public: + /** + * @brief Constructor + */ + Xadc(); + virtual ~Xadc(); + + /** + * @brief Returns on-chip temperature degree celcius + */ + ReturnValue_t getTemperature(float& temperature); + + /** + * @brief Returns PS internal logic supply voltage in millivolts + */ + ReturnValue_t getVccPint(float& vccPint); + + /** + * @brief Returns PS auxiliary supply voltage in millivolts + */ + ReturnValue_t getVccPaux(float& vccPaux); + + /** + * @brief Returns PL internal supply voltage in millivolts + */ + ReturnValue_t getVccInt(float& vccInt); + + /** + * @brief Returns PL auxiliary supply voltage in millivolts + */ + ReturnValue_t getVccAux(float& vccAux); + + /** + * @brief Returns PL block RAM supply voltage in millivolts + */ + ReturnValue_t getVccBram(float& vccBram); + + /** + * @brief Returns the PS DDR I/O supply voltage + */ + ReturnValue_t getVccOddr(float& vcOddr); + + /** + * @brief Returns XADC reference input voltage relative to GND in millivolts + */ + ReturnValue_t getVrefp(float& vrefp); + + /** + * @brief Returns negative reference input voltage. Should normally be 0 V. + */ + ReturnValue_t getVrefn(float& vrefn); + + private: + // Maximum length of the string representation of a value in a xadc sysfs file + static const uint8_t MAX_STR_LENGTH = 15; + + ReturnValue_t readVoltageFromSysfs(std::string rawFile, std::string scaleFile, float& voltage); + + float calculateVoltage(int raw, float scale); + + template + ReturnValue_t readValFromFile(const char* filename, T& val); +}; + +#endif /* BSP_Q7S_XADC_XADC_H_ */ diff --git a/bsp_te0720_1cfa/CMakeLists.txt b/bsp_te0720_1cfa/CMakeLists.txt new file mode 100644 index 0000000..cb02f93 --- /dev/null +++ b/bsp_te0720_1cfa/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(${OBSW_NAME} PUBLIC + InitMission.cpp + main.cpp + ObjectFactory.cpp +) + +add_subdirectory(boardconfig) diff --git a/bsp_te0720_1cfa/InitMission.cpp b/bsp_te0720_1cfa/InitMission.cpp new file mode 100644 index 0000000..a3e3a00 --- /dev/null +++ b/bsp_te0720_1cfa/InitMission.cpp @@ -0,0 +1,227 @@ +#include "InitMission.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "OBSWConfig.h" +#include "ObjectFactory.h" +#include "objects/systemObjectList.h" +#include "pollingsequence/pollingSequenceFactory.h" + +ServiceInterfaceStream sif::debug("DEBUG"); +ServiceInterfaceStream sif::info("INFO"); +ServiceInterfaceStream sif::warning("WARNING"); +ServiceInterfaceStream sif::error("ERROR"); + +ObjectManagerIF* objectManager = nullptr; + +void initmission::initMission() { + sif::info << "Building global objects.." << std::endl; + /* Instantiate global object manager and also create all objects */ + ObjectManager::instance()->setObjectFactoryFunction(ObjectFactory::produce, nullptr); + sif::info << "Initializing all objects.." << std::endl; + ObjectManager::instance()->initialize(); + + /* This function creates and starts all tasks */ + initTasks(); +} + +void initmission::initTasks() { + TaskFactory* factory = TaskFactory::instance(); + ReturnValue_t result = returnvalue::OK; + if (factory == nullptr) { + /* Should never happen ! */ + return; + } +#if OBSW_PRINT_MISSED_DEADLINES == 1 + void (*missedDeadlineFunc)(void) = TaskFactory::printMissedDeadline; +#else + void (*missedDeadlineFunc)(void) = nullptr; +#endif + + /* TMTC Distribution */ + PeriodicTaskIF* tmtcDistributor = factory->createPeriodicTask( + "DIST", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = tmtcDistributor->addComponent(objects::CCSDS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + result = tmtcDistributor->addComponent(objects::PUS_PACKET_DISTRIBUTOR); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + result = tmtcDistributor->addComponent(objects::TM_FUNNEL); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + + PeriodicTaskIF* tmtcBridgeTask = factory->createPeriodicTask( + "TMTC_BRIDGE", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = tmtcBridgeTask->addComponent(objects::TMTC_BRIDGE); + if (result != returnvalue::OK) { + sif::error << "Add component TMTC Bridge failed" << std::endl; + } + PeriodicTaskIF* tmtcPollingTask = factory->createPeriodicTask( + "TMTC_POLLING", 80, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = tmtcPollingTask->addComponent(objects::TMTC_POLLING_TASK); + if (result != returnvalue::OK) { + sif::error << "Add component TMTC Polling failed" << std::endl; + } + + /* PUS Services */ + std::vector pusTasks; + createPusTasks(*factory, missedDeadlineFunc, pusTasks); + + std::vector pstTasks; + FixedTimeslotTaskIF* pst = factory->createFixedTimeslotTask( + "UART_PST", 70, PeriodicTaskIF::MINIMUM_STACK_SIZE * 4, 1.0, missedDeadlineFunc); + result = pst::pstUart(pst); + if (result != returnvalue::OK) { + sif::error << "InitMission::initTasks: Creating PST failed!" << std::endl; + } + pstTasks.push_back(pst); + +#if OBSW_ADD_PLOC_MPSOC == 1 + PeriodicTaskIF* mpsocHelperTask = factory->createPeriodicTask( + "PLOC_MPSOC_HELPER", 20, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = mpsocHelperTask->addComponent(objects::PLOC_MPSOC_HELPER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PLOC_MPSOC_HELPER", objects::PLOC_MPSOC_HELPER); + } +#endif /* OBSW_ADD_PLOC_MPSOC == 1*/ + +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + PeriodicTaskIF* supvHelperTask = factory->createPeriodicTask( + "PLOC_SUPV_HELPER", 20, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.2, missedDeadlineFunc); + result = supvHelperTask->addComponent(objects::PLOC_SUPERVISOR_HELPER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PLOC_SUPV_HELPER", objects::PLOC_SUPERVISOR_HELPER); + } +#endif /* OBSW_ADD_PLOC_SUPERVISOR == 1 */ + +#if OBSW_ADD_CCSDS_IP_CORES == 1 + PeriodicTaskIF* ccsdsHandlerTask = factory->createPeriodicTask( + "CCSDS_HANDLER", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, missedDeadlineFunc); + result = ccsdsHandlerTask->addComponent(objects::CCSDS_HANDLER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("CCSDS Handler", objects::CCSDS_HANDLER); + } + + // Minimal distance between two received TCs amounts to 0.6 seconds + // If a command has not been read before the next one arrives, the old command will be + // overwritten by the PDEC. + PeriodicTaskIF* pdecHandlerTask = factory->createPeriodicTask( + "PDEC_HANDLER", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, missedDeadlineFunc); + result = pdecHandlerTask->addComponent(objects::PDEC_HANDLER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PDEC Handler", objects::PDEC_HANDLER); + } +#endif /* OBSW_ADD_CCSDS_IP_CORES == 1 */ + + auto taskStarter = [](std::vector& taskVector, std::string name) { + for (const auto& task : taskVector) { + if (task != nullptr) { + task->startTask(); + } else { + sif::error << "Task in vector " << name << " is invalid!" << std::endl; + } + } + }; + + sif::info << "Starting tasks.." << std::endl; + tmtcDistributor->startTask(); + tmtcBridgeTask->startTask(); + tmtcPollingTask->startTask(); +#if OBSW_ADD_CCSDS_IP_CORE == 1 + pdecHandlerTask->startTask(); + ccsdsHandlerTask->startTask(); +#endif /* #if OBSW_ADD_CCSDS_IP_CORE == 1 */ +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + supvHelperTask->startTask(); +#endif /* OBSW_ADD_PLOC_SUPERVISOR == 1 */ +#if OBSW_ADD_PLOC_MPSOC == 1 + mpsocHelperTask->startTask(); +#endif /* OBSW_ADD_PLOC_MPSOC == 1 */ + + taskStarter(pstTasks, "PST Tasks"); + taskStarter(pusTasks, "PUS Tasks"); + + sif::info << "Tasks started.." << std::endl; +} + +void initmission::createPusTasks(TaskFactory& factory, + TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec) { + ReturnValue_t result = returnvalue::OK; + PeriodicTaskIF* pusVerification = factory.createPeriodicTask( + "PUS_VERIF", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusVerification->addComponent(objects::PUS_SERVICE_1_VERIFICATION); + if (result != returnvalue::OK) { + sif::error << "Object add component failed" << std::endl; + } + taskVec.push_back(pusVerification); + + PeriodicTaskIF* pusEvents = factory.createPeriodicTask( + "PUS_EVENTS", 60, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusEvents->addComponent(objects::PUS_SERVICE_5_EVENT_REPORTING); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS_EVENTS", objects::PUS_SERVICE_5_EVENT_REPORTING); + } + result = pusEvents->addComponent(objects::EVENT_MANAGER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS_MGMT", objects::EVENT_MANAGER); + } + taskVec.push_back(pusEvents); + + PeriodicTaskIF* pusHighPrio = factory.createPeriodicTask( + "PUS_HIGH_PRIO", 50, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.200, missedDeadlineFunc); + result = pusHighPrio->addComponent(objects::PUS_SERVICE_2_DEVICE_ACCESS); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS2", objects::PUS_SERVICE_2_DEVICE_ACCESS); + } + result = pusHighPrio->addComponent(objects::PUS_SERVICE_9_TIME_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS9", objects::PUS_SERVICE_9_TIME_MGMT); + } + taskVec.push_back(pusHighPrio); + + PeriodicTaskIF* pusMedPrio = factory.createPeriodicTask( + "PUS_MED_PRIO", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.8, missedDeadlineFunc); + result = pusMedPrio->addComponent(objects::PUS_SERVICE_8_FUNCTION_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS8", objects::PUS_SERVICE_8_FUNCTION_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_200_MODE_MGMT); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS200", objects::PUS_SERVICE_200_MODE_MGMT); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_20_PARAMETERS); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS20", objects::PUS_SERVICE_20_PARAMETERS); + } + result = pusMedPrio->addComponent(objects::PUS_SERVICE_3_HOUSEKEEPING); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS3", objects::PUS_SERVICE_3_HOUSEKEEPING); + } + taskVec.push_back(pusMedPrio); + + PeriodicTaskIF* pusLowPrio = factory.createPeriodicTask( + "PUS_LOW_PRIO", 30, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.6, missedDeadlineFunc); + result = pusLowPrio->addComponent(objects::PUS_SERVICE_17_TEST); + if (result != returnvalue::OK) { + initmission::printAddObjectError("PUS17", objects::PUS_SERVICE_17_TEST); + } + result = pusLowPrio->addComponent(objects::INTERNAL_ERROR_REPORTER); + if (result != returnvalue::OK) { + initmission::printAddObjectError("INT_ERR_RPRT", objects::INTERNAL_ERROR_REPORTER); + } + taskVec.push_back(pusLowPrio); +} diff --git a/bsp_te0720_1cfa/InitMission.h b/bsp_te0720_1cfa/InitMission.h new file mode 100644 index 0000000..a939987 --- /dev/null +++ b/bsp_te0720_1cfa/InitMission.h @@ -0,0 +1,21 @@ +#ifndef BSP_LINUX_INITMISSION_H_ +#define BSP_LINUX_INITMISSION_H_ + +#include + +#include "fsfw/tasks/definitions.h" + +class PeriodicTaskIF; +class TaskFactory; + +namespace initmission { +void initMission(); +void initTasks(); + +void createPstTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +void createPusTasks(TaskFactory& factory, TaskDeadlineMissedFunction missedDeadlineFunc, + std::vector& taskVec); +}; // namespace initmission + +#endif /* BSP_LINUX_INITMISSION_H_ */ diff --git a/bsp_te0720_1cfa/OBSWConfig.h.in b/bsp_te0720_1cfa/OBSWConfig.h.in new file mode 100644 index 0000000..d2efda3 --- /dev/null +++ b/bsp_te0720_1cfa/OBSWConfig.h.in @@ -0,0 +1,126 @@ +/** + * @brief This file can be used to add preprocessor define for conditional + * code inclusion exclusion or various other project constants and + * properties in one place. + */ +#ifndef FSFWCONFIG_OBSWCONFIG_H_ +#define FSFWCONFIG_OBSWCONFIG_H_ + +#include "commonConfig.h" +#include "OBSWVersion.h" + +/*******************************************************************/ +/** All of the following flags should be enabled for mission code */ +/*******************************************************************/ + +#define OBSW_ADD_CCSDS_IP_CORE 0 +// Set to 1 if all telemetry should be sent to the PTME IP Core +#define OBSW_TM_TO_PTME 0 +// Set to 1 if telecommands are received via the PDEC IP Core +#define OBSW_TC_FROM_PDEC 0 + +#define OBSW_ENABLE_TIMERS 1 +#define OBSW_ADD_MGT 0 +#define OBSW_ADD_BPX_BATTERY_HANDLER 0 +#define OBSW_ADD_STAR_TRACKER 0 +#define OBSW_ADD_PLOC_SUPERVISOR 0 +#define OBSW_ADD_PLOC_MPSOC 1 +#define OBSW_ADD_SUN_SENSORS 0 +#define OBSW_ADD_ACS_BOARD 1 +#define OBSW_ADD_ACS_HANDLERS 0 +#define OBSW_ADD_RW 0 +#define OBSW_ADD_RTD_DEVICES 0 +#define OBSW_ADD_TMP_DEVICES 0 +#define OBSW_ADD_RAD_SENSORS 0 +#define OBSW_ADD_PL_PCDU 0 +#define OBSW_ADD_SYRLINKS 0 +#define OBSW_ENABLE_SYRLINKS_TRANSMIT_TIMEOUT 0 +#define OBSW_SYRLINKS_SIMULATED 1 +#define OBSW_STAR_TRACKER_GROUND_CONFIG 1 +#define OBSW_PRINT_CORE_HK 0 +#define OBSW_INITIALIZE_SWITCHES 0 + +// This is a really tricky switch.. It initializes the PCDU switches to their default states +// at powerup. I think it would be better +// to leave it off for now. It makes testing a lot more difficult and it might mess with +// something the operators might want to do by giving the software too much intelligence +// at the wrong place. The system component might command all the Switches accordingly anyway +#define OBSW_INITIALIZE_SWITCHES 0 +#define OBSW_ENABLE_PERIODIC_HK 0 + +/*******************************************************************/ +/** All of the following flags should be disabled for mission code */ +/*******************************************************************/ + +// Can be used to switch device to NORMAL mode immediately +#define OBSW_SWITCH_TO_NORMAL_MODE_AFTER_STARTUP 1 +#define OBSW_PRINT_MISSED_DEADLINES 1 + +#define OBSW_SYRLINKS_SIMULATED 1 +#define OBSW_ADD_TEST_CODE 0 +#define OBSW_ADD_TEST_TASK 0 +#define OBSW_ADD_TEST_PST 0 +// If this is enabled, all other SPI code should be disabled +#define OBSW_ADD_SPI_TEST_CODE 0 +// If this is enabled, all other I2C code should be disabled +#define OBSW_ADD_I2C_TEST_CODE 0 +#define OBSW_ADD_UART_TEST_CODE 0 + +#define OBSW_TEST_ACS 0 +#define OBSW_DEBUG_ACS 0 +#define OBSW_TEST_SUS 0 +#define OBSW_DEBUG_SUS 0 +#define OBSW_TEST_RTD 0 +#define OBSW_DEBUG_RTD 0 +#define OBSW_TEST_RAD_SENSOR 0 +#define OBSW_DEBUG_RAD_SENSOR 0 +#define OBSW_TEST_PL_PCDU 0 +#define OBSW_DEBUG_PL_PCDU 0 +#define OBSW_TEST_BPX_BATT 0 +#define OBSW_DEBUG_BPX_BATT 0 +#define OBSW_TEST_IMTQ 0 +#define OBSW_DEBUG_IMTQ 0 +#define OBSW_TEST_RW 0 +#define OBSW_DEBUG_RW 0 + +#define OBSW_TEST_LIBGPIOD 0 +#define OBSW_TEST_PLOC_HANDLER 0 +#define OBSW_TEST_CCSDS_BRIDGE 0 +#define OBSW_TEST_CCSDS_PTME 0 +#define OBSW_TEST_TE7020_HEATER 0 +#define OBSW_TEST_GPIO_OPEN_BY_LABEL 0 +#define OBSW_TEST_GPIO_OPEN_BY_LINE_NAME 0 +#define OBSW_DEBUG_P60DOCK 0 + +#define OBSW_PRINT_CORE_HK 0 +#define OBSW_DEBUG_PDU1 0 +#define OBSW_DEBUG_PDU2 0 +#define OBSW_DEBUG_GPS 0 +#define OBSW_DEBUG_ACU 0 +#define OBSW_DEBUG_SYRLINKS 0 + +#define OBSW_DEBUG_PDEC_HANDLER 0 + +#define OBSW_DEBUG_PLOC_SUPERVISOR 1 +#define OBSW_DEBUG_PLOC_MPSOC 1 + +#define OBSW_DEBUG_STARTRACKER 0 +#define OBSW_TCP_SERVER_WIRETAPPING 0 + +/*******************************************************************/ +/** CMake Defines */ +/*******************************************************************/ +#cmakedefine EIVE_BUILD_GPSD_GPS_HANDLER + +#cmakedefine LIBGPS_VERSION_MAJOR @LIBGPS_VERSION_MAJOR@ +#cmakedefine LIBGPS_VERSION_MINOR @LIBGPS_VERSION_MINOR@ + +#ifdef __cplusplus + +#include "objects/systemObjectList.h" +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +#endif + +#endif /* FSFWCONFIG_OBSWCONFIG_H_ */ diff --git a/bsp_te0720_1cfa/ObjectFactory.cpp b/bsp_te0720_1cfa/ObjectFactory.cpp new file mode 100644 index 0000000..2877c4e --- /dev/null +++ b/bsp_te0720_1cfa/ObjectFactory.cpp @@ -0,0 +1,159 @@ +#include "ObjectFactory.h" + +#include "OBSWConfig.h" +#include "busConf.h" +#include "devConf.h" +#include "ccsdsConfig.h" +#include "devices/addresses.h" +#include "devices/gpioIds.h" +#include "fsfw/datapoollocal/LocalDataPoolManager.h" +#include "fsfw/tmtcpacket/pus/tm.h" +#include "fsfw/tmtcservices/CommandingServiceBase.h" +#include "fsfw/tmtcservices/PusServiceBase.h" +#include "fsfw_hal/linux/i2c/I2cComIF.h" +#include "fsfw_hal/linux/i2c/I2cCookie.h" +#include "fsfw_hal/linux/serial/SerialComIF.h" +#include "fsfw_hal/linux/serial/SerialCookie.h" +#include "fsfw_hal/common/gpio/GpioCookie.h" +#include "linux/ObjectFactory.h" +#include "linux/devices/ploc/PlocMPSoCHandler.h" +#include "linux/devices/ploc/PlocMPSoCHelper.h" +#include "linux/devices/ploc/PlocMemoryDumper.h" +#include "linux/devices/ploc/PlocSupervisorHandler.h" +#include "linux/devices/ploc/PlocSupvHelper.h" +#include "linux/obc/AxiPtmeConfig.h" +#include "linux/obc/PapbVcInterface.h" +#include "linux/obc/PdecHandler.h" +#include "linux/obc/Ptme.h" +#include "linux/obc/PtmeConfig.h" +#include "mission/core/GenericFactory.h" +#include "mission/devices/Tmp1075Handler.h" +#include "mission/tmtc/TmFunnel.h" +#include "mission/tmtc/CCSDSHandler.h" +#include "mission/tmtc/VirtualChannel.h" +#include "objects/systemObjectList.h" +#include "test/gpio/DummyGpioIF.h" +#include "tmtc/apid.h" +#include "tmtc/pusIds.h" + +void Factory::setStaticFrameworkObjectIds() { + PusServiceBase::packetSource = objects::PUS_PACKET_DISTRIBUTOR; + PusServiceBase::packetDestination = objects::TM_FUNNEL; + + CommandingServiceBase::defaultPacketSource = objects::PUS_PACKET_DISTRIBUTOR; + CommandingServiceBase::defaultPacketDestination = objects::TM_FUNNEL; + +#if OBSW_TM_TO_PTME == 1 + TmFunnel::downlinkDestination = objects::CCSDS_HANDLER; +#else + TmFunnel::downlinkDestination = objects::TMTC_BRIDGE; +#endif + TmFunnel::storageDestination = objects::NO_OBJECT; + + VerificationReporter::messageReceiver = objects::PUS_SERVICE_1_VERIFICATION; + TmPacketBase::timeStamperId = objects::TIME_STAMPER; +} + +void ObjectFactory::produce(void* args) { + Factory::setStaticFrameworkObjectIds(); + ObjectFactory::produceGenericObjects(); + + LinuxLibgpioIF* gpioComIF = new LinuxLibgpioIF(objects::GPIO_IF);; + newSerialComIF(objects::UART_COM_IF); + +#if OBSW_ADD_PLOC_MPSOC == 1 + UartCookie* mpsocUartCookie = new UartCookie(objects::PLOC_MPSOC_HANDLER, te0720_1cfa::MPSOC_UART, + uart::PLOC_MPSOC_BAUD, mpsoc::MAX_REPLY_SIZE); + mpsocUartCookie->setNoFixedSizeReply(); + PlocMPSoCHelper* plocMpsocHelper = new PlocMPSoCHelper(objects::PLOC_MPSOC_HELPER); + auto dummyGpioIF = new DummyGpioIF(); + PlocMPSoCHandler* plocMPSoCHandler = new PlocMPSoCHandler( + objects::PLOC_MPSOC_HANDLER, objects::UART_COM_IF, mpsocUartCookie, plocMpsocHelper, + Gpio(gpioIds::ENABLE_MPSOC_UART, dummyGpioIF), objects::PLOC_SUPERVISOR_HANDLER); + plocMPSoCHandler->setStartUpImmediately(); +#endif /* OBSW_ADD_PLOC_MPSOC == 1 */ + +#if OBSW_ADD_PLOC_SUPERVISOR == 1 + UartCookie* supervisorCookie = + new UartCookie(objects::PLOC_SUPERVISOR_HANDLER, std::string("/dev/ttyPS1"), + uart::PLOC_SUPV_BAUD, supv::MAX_PACKET_SIZE * 20); + supervisorCookie->setNoFixedSizeReply(); + auto supvGpioIF = new DummyGpioIF(); + auto supvHelper = new PlocSupvHelper(objects::PLOC_SUPERVISOR_HELPER); + new PlocSupervisorHandler(objects::PLOC_SUPERVISOR_HANDLER, objects::UART_COM_IF, + supervisorCookie, Gpio(gpioIds::ENABLE_SUPV_UART, supvGpioIF), + pcdu::PDU1_CH6_PLOC_12V, supvHelper); + +#endif + + new PlocMemoryDumper(objects::PLOC_MEMORY_DUMPER); + +#if OBSW_TEST_LIBGPIOD == 1 +#if OBSW_TEST_GPIO_OPEN_BYLABEL == 1 + /* Configure MIO0 as input */ + GpiodRegular* testGpio = new GpiodRegular("MIO0", Direction::OUT, 0, "/amba_pl/gpio@41200000", 0); +#elif OBSW_TEST_GPIO_OPEN_BY_LINE_NAME + GpiodRegularByLineName* testGpio = + new GpiodRegularByLineName("test-name", "gpio-test", Direction::OUT, 0); +#else + /* Configure MIO0 as input */ + GpiodRegular* testGpio = new GpiodRegular("gpiochip0", 0, "MIO0", gpio::IN, 0); +#endif /* OBSW_TEST_GPIO_LABEL == 1 */ + GpioCookie* gpioCookie = new GpioCookie; + gpioCookie->addGpio(gpioIds::TEST_ID_0, testGpio); + new LibgpiodTest(objects::LIBGPIOD_TEST, objects::GPIO_IF, gpioCookie); +#endif + +#if OBSW_TEST_SUS == 1 + GpioCookie* gpioCookieSus = new GpioCookie; + GpiodRegular* chipSelectSus = new GpiodRegular( + std::string("gpiochip1"), 9, std::string("Chip Select Sus Sensor"), Direction::OUT, 1); + gpioCookieSus->addGpio(gpioIds::CS_SUS_0, chipSelectSus); + gpioComIF->addGpios(gpioCookieSus); + + SpiCookie* spiCookieSus = + new SpiCookie(addresses::SUS_0, std::string("/dev/spidev1.0"), SUS::MAX_CMD_SIZE, + spi::DEFAULT_MAX_1227_MODE, spi::DEFAULT_MAX_1227_SPEED); + + new SusHandler(objects::SUS_0, objects::SPI_COM_IF, spiCookieSus, gpioComIF, gpioIds::CS_SUS_0); +#endif + +#if OBSW_TEST_RAD_SENSOR == 1 + GpioCookie* gpioCookieRadSensor = new GpioCookie; + GpiodRegular* chipSelectRadSensor = new GpiodRegular( + std::string("gpiochip1"), 0, std::string("Chip select radiation sensor"), Direction::OUT, 1); + gpioCookieRadSensor->addGpio(gpioIds::CS_RAD_SENSOR, chipSelectRadSensor); + gpioComIF->addGpios(gpioCookieRadSensor); + + SpiCookie* spiCookieRadSensor = + new SpiCookie(addresses::RAD_SENSOR, gpioIds::CS_RAD_SENSOR, std::string("/dev/spidev1.0"), + SUS::MAX_CMD_SIZE, spi::DEFAULT_MAX_1227_MODE, spi::DEFAULT_MAX_1227_SPEED); + + RadiationSensorHandler* radSensor = + new RadiationSensorHandler(objects::RAD_SENSOR, objects::SPI_COM_IF, spiCookieRadSensor); + radSensor->setStartUpImmediately(); +#endif + +#if OBSW_TEST_TE7020_HEATER == 1 + /* Configuration for MIO0 on TE0720-03-1CFA */ + GpiodRegular* heaterGpio = + new GpiodRegular(std::string("gpiochip0"), 0, std::string("MIO0"), gpio::IN, 0); + GpioCookie* gpioCookie = new GpioCookie; + gpioCookie->addGpio(gpioIds::HEATER_0, heaterGpio); + new HeaterHandler(objects::HEATER_HANDLER, objects::GPIO_IF, gpioCookie, objects::PCDU_HANDLER, + pcdu::TCS_BOARD_8V_HEATER_IN); +#endif + + new I2cComIF(objects::I2C_COM_IF); + + I2cCookie* i2cCookieTmp1075tcs1 = + new I2cCookie(addresses::TMP1075_TCS_1, TMP1075::MAX_REPLY_LENGTH, std::string("/dev/i2c-0")); + I2cCookie* i2cCookieTmp1075tcs2 = + new I2cCookie(addresses::TMP1075_TCS_2, TMP1075::MAX_REPLY_LENGTH, std::string("/dev/i2c-0")); + + /* Temperature sensors */ + new Tmp1075Handler(objects::TMP1075_HANDLER_1, objects::I2C_COM_IF, i2cCookieTmp1075tcs1); + new Tmp1075Handler(objects::TMP1075_HANDLER_2, objects::I2C_COM_IF, i2cCookieTmp1075tcs2); + + static_cast(gpioComIF); +} diff --git a/bsp_te0720_1cfa/ObjectFactory.h b/bsp_te0720_1cfa/ObjectFactory.h new file mode 100644 index 0000000..828f5d3 --- /dev/null +++ b/bsp_te0720_1cfa/ObjectFactory.h @@ -0,0 +1,12 @@ +#ifndef BSP_LINUX_OBJECTFACTORY_H_ +#define BSP_LINUX_OBJECTFACTORY_H_ + +#include +#include + +namespace ObjectFactory { +static const uint32_t TRANSMITTER_TIMEOUT = 86400000; // 1 day +void produce(void* args); +}; // namespace ObjectFactory + +#endif /* BSP_LINUX_OBJECTFACTORY_H_ */ diff --git a/bsp_te0720_1cfa/boardconfig/CMakeLists.txt b/bsp_te0720_1cfa/boardconfig/CMakeLists.txt new file mode 100644 index 0000000..f9136e3 --- /dev/null +++ b/bsp_te0720_1cfa/boardconfig/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(${OBSW_NAME} PRIVATE + print.c +) + +target_include_directories(${OBSW_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/bsp_te0720_1cfa/boardconfig/busConf.h b/bsp_te0720_1cfa/boardconfig/busConf.h new file mode 100644 index 0000000..7b51ab3 --- /dev/null +++ b/bsp_te0720_1cfa/boardconfig/busConf.h @@ -0,0 +1,39 @@ +#ifndef BSP_EGSE_BOARDCONFIG_BUSCONF_H_ +#define BSP_EGSE_BOARDCONFIG_BUSCONF_H_ + +namespace te0720_1cfa { +static constexpr char MPSOC_UART[] = "/dev/ttyPS1"; + +static constexpr char UIO_PDEC_REGISTERS[] = "/dev/uio0"; +static constexpr char UIO_PTME[] = "/dev/uio1"; +static constexpr char UIO_PDEC_CONFIG_MEMORY[] = "/dev/uio2"; +static constexpr char UIO_PDEC_RAM[] = "/dev/uio3"; +static constexpr int MAP_ID_PTME_CONFIG = 3; + +namespace uiomapids { +static const int PTME_VC0 = 0; +static const int PTME_VC1 = 1; +static const int PTME_VC2 = 2; +static const int PTME_VC3 = 3; +static const int PTME_CONFIG = 4; +} // namespace uiomapids + +namespace gpioNames { + static constexpr char PAPB_BUSY_SIGNAL_VC0[] = "papb_busy_signal_vc0"; + static constexpr char PAPB_EMPTY_SIGNAL_VC0[] = "papb_empty_signal_vc0"; + static constexpr char PAPB_BUSY_SIGNAL_VC1[] = "papb_busy_signal_vc1"; + static constexpr char PAPB_EMPTY_SIGNAL_VC1[] = "papb_empty_signal_vc1"; + static constexpr char PAPB_BUSY_SIGNAL_VC2[] = "papb_busy_signal_vc2"; + static constexpr char PAPB_EMPTY_SIGNAL_VC2[] = "papb_empty_signal_vc2"; + static constexpr char PAPB_BUSY_SIGNAL_VC3[] = "papb_busy_signal_vc3"; + static constexpr char PAPB_EMPTY_SIGNAL_VC3[] = "papb_empty_signal_vc3"; + static constexpr char RS485_EN_TX_CLOCK[] = "tx_clock_enable_ltc2872"; + static constexpr char RS485_EN_TX_DATA[] = "tx_data_enable_ltc2872"; + static constexpr char RS485_EN_RX_CLOCK[] = "rx_clock_enable_ltc2872"; + static constexpr char RS485_EN_RX_DATA[] = "rx_data_enable_ltc2872"; + static constexpr char PDEC_RESET[] = "pdec_reset"; +} + +} + +#endif /* BSP_EGSE_BOARDCONFIG_BUSCONF_H_ */ diff --git a/bsp_te0720_1cfa/boardconfig/etl_profile.h b/bsp_te0720_1cfa/boardconfig/etl_profile.h new file mode 100644 index 0000000..54aca34 --- /dev/null +++ b/bsp_te0720_1cfa/boardconfig/etl_profile.h @@ -0,0 +1,38 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2019 jwellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ +#ifndef __ETL_PROFILE_H__ +#define __ETL_PROFILE_H__ + +#define ETL_CHECK_PUSH_POP + +#define ETL_CPP11_SUPPORTED 1 +#define ETL_NO_NULLPTR_SUPPORT 0 + +#endif diff --git a/bsp_te0720_1cfa/boardconfig/gcov.h b/bsp_te0720_1cfa/boardconfig/gcov.h new file mode 100644 index 0000000..80acdd8 --- /dev/null +++ b/bsp_te0720_1cfa/boardconfig/gcov.h @@ -0,0 +1,15 @@ +#ifndef LINUX_GCOV_H_ +#define LINUX_GCOV_H_ +#include + +#ifdef GCOV +extern "C" void __gcov_flush(); +#else +void __gcov_flush() { + sif::info << "GCC GCOV: Please supply GCOV=1 in Makefile if " + "coverage information is desired.\n" + << std::flush; +} +#endif + +#endif /* LINUX_GCOV_H_ */ diff --git a/bsp_te0720_1cfa/boardconfig/print.c b/bsp_te0720_1cfa/boardconfig/print.c new file mode 100644 index 0000000..1739e90 --- /dev/null +++ b/bsp_te0720_1cfa/boardconfig/print.c @@ -0,0 +1,10 @@ +#include +#include + +void printChar(const char* character, bool errStream) { + if (errStream) { + putc(*character, stderr); + return; + } + putc(*character, stdout); +} diff --git a/bsp_te0720_1cfa/boardconfig/print.h b/bsp_te0720_1cfa/boardconfig/print.h new file mode 100644 index 0000000..8e7e2e5 --- /dev/null +++ b/bsp_te0720_1cfa/boardconfig/print.h @@ -0,0 +1,8 @@ +#ifndef HOSTED_BOARDCONFIG_PRINT_H_ +#define HOSTED_BOARDCONFIG_PRINT_H_ + +#include + +void printChar(const char* character, bool errStream); + +#endif /* HOSTED_BOARDCONFIG_PRINT_H_ */ diff --git a/bsp_te0720_1cfa/main.cpp b/bsp_te0720_1cfa/main.cpp new file mode 100644 index 0000000..cb7d987 --- /dev/null +++ b/bsp_te0720_1cfa/main.cpp @@ -0,0 +1,29 @@ +#include + +#include "InitMission.h" +#include "OBSWConfig.h" +#include "OBSWVersion.h" +#include "fsfw/version.h" +#include "fsfw/tasks/TaskFactory.h" + +/** + * @brief This is the main program entry point for the obsw running on the trenz electronic + * te0720-1cfa. + * @return + */ +int main(void) { + using namespace fsfw; + std::cout << "-- EIVE OBSW --" << std::endl; + std::cout << "-- Compiled for Trenz TE0720-1CFA" + << " --" << std::endl; + std::cout << "-- OBSW v" << SW_VERSION << "." << SW_SUBVERSION << "." << SW_REVISION << ", FSFW v" + << FSFW_VERSION << "--" << std::endl; + std::cout << "-- " << __DATE__ << " " << __TIME__ << " --" << std::endl; + + initmission::initMission(); + + for (;;) { + /* Suspend main thread by sleeping it. */ + TaskFactory::delayTask(5000); + } +} diff --git a/clone-submodules-no-privlibs.sh b/clone-submodules-no-privlibs.sh new file mode 100755 index 0000000..48d34bc --- /dev/null +++ b/clone-submodules-no-privlibs.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +root="$(pwd)" +ln -s "$root/hooks" "$root/.git/hooks" + +git submodule update --init fsfw thirdparty/rapidcsv thirdparty/lwgps thirdparty/json diff --git a/cmake/BBBCrossCompileConfig.cmake b/cmake/BBBCrossCompileConfig.cmake new file mode 100644 index 0000000..fcbab88 --- /dev/null +++ b/cmake/BBBCrossCompileConfig.cmake @@ -0,0 +1,103 @@ +# LINUX_ROOTFS should point to the local directory which contains all the +# libraries and includes from the target raspi. +# The following command can be used to do this, replace and the +# local accordingly: +# rsync -vR --progress -rl --delete-after --safe-links pi@:/{lib,usr,opt/vc/lib} +# LINUX_ROOTFS needs to be passed to the CMake command or defined in the +# application CMakeLists.txt before loading the toolchain file. + +# CROSS_COMPILE also needs to be set accordingly or passed to the CMake command +if(NOT DEFINED ENV{LINUX_ROOTFS}) + # Sysroot has not been cached yet and was not set in environment either + if(NOT SYSROOT_PATH) + message(FATAL_ERROR + "Define the LINUX_ROOTFS variable to point to the Raspberry Pi rootfs." + ) + endif() +else() + set(SYSROOT_PATH "$ENV{LINUX_ROOTFS}" CACHE PATH "Local linux root filesystem path") + message(STATUS "Raspberry Pi sysroot: ${SYSROOT_PATH}") +endif() + +if(NOT DEFINED ENV{CROSS_COMPILE}) + set(CROSS_COMPILE "arm-linux-gnueabihf") + message(STATUS + "No CROSS_COMPILE environmental variable set, using default ARM linux " + "cross compiler name ${CROSS_COMPILE}" + ) +else() + set(CROSS_COMPILE "$ENV{CROSS_COMPILE}") + message(STATUS + "Using environmental variable CROSS_COMPILE as cross-compiler: " + "$ENV{CROSS_COMPILE}" + ) +endif() + +# Generally, the debian roots will be a multiarch rootfs where some libraries are put +# into a folder named "arm-linux-gnueabihf". The user can override the folder name if this is +# not the case +if(NOT ENV{MULTIARCH_FOLDER_NAME}) + set(MULTIARCH_FOLDER_NAME "arm-linux-gnueabihf") +else() + set(MUTLIARCH_FOLDER_NAME $ENV{MULTIARCH_FOLDER_NAME}) +endif() + +message(STATUS "Using sysroot path: ${SYSROOT_PATH}") + +set(CROSS_COMPILE_CC "${CROSS_COMPILE}-gcc") +set(CROSS_COMPILE_CXX "${CROSS_COMPILE}-g++") +set(CROSS_COMPILE_LD "${CROSS_COMPILE}-ld") +set(CROSS_COMPILE_AR "${CROSS_COMPILE}-ar") +set(CROSS_COMPILE_RANLIB "${CROSS_COMPILE}-ranlib") +set(CROSS_COMPILE_STRIP "${CROSS_COMPILE}-strip") +set(CROSS_COMPILE_NM "${CROSS_COMPILE}-nm") +set(CROSS_COMPILE_OBJCOPY "${CROSS_COMPILE}-objcopy") +set(CROSS_COMPILE_SIZE "${CROSS_COMPILE}-size") + +# At the very least, cross compile gcc and g++ have to be set! +find_program (CMAKE_C_COMPILER ${CROSS_COMPILE_CC} REQUIRED) +find_program (CMAKE_CXX_COMPILER ${CROSS_COMPILE_CXX} REQUIRED) +# Useful utilities, not strictly necessary +find_program(CMAKE_SIZE ${CROSS_COMPILE_SIZE}) +find_program(CMAKE_OBJCOPY ${CROSS_COMPILE_OBJCOPY}) + +set(CMAKE_CROSSCOMPILING TRUE) +set(CMAKE_SYSROOT "${SYSROOT_PATH}") + +# Define name of the target system +set(CMAKE_SYSTEM_NAME "Linux") +set(CMAKE_SYSTEM_PROCESSOR "arm") + +# List of library dirs where LD has to look. Pass them directly through gcc. +# LD_LIBRARY_PATH is not evaluated by arm-*-ld +set(LIB_DIRS + "${SYSROOT_PATH}/lib/${MUTLIARCH_FOLDER_NAME}" + "${SYSROOT_PATH}/usr/local/lib" + "${SYSROOT_PATH}/usr/lib/${MUTLIARCH_FOLDER_NAME}" + "${SYSROOT_PATH}/usr/lib" +) +# You can additionally check the linker paths if you add the +# flags ' -Xlinker --verbose' +set(COMMON_FLAGS "-I${SYSROOT_PATH}/usr/include") +foreach(LIB ${LIB_DIRS}) + set(COMMON_FLAGS "${COMMON_FLAGS} -L${LIB} -Wl,-rpath-link,${LIB}") +endforeach() + +set(CMAKE_C_FLAGS + "-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard ${COMMON_FLAGS}" + CACHE STRING "Flags for Beagle Bone Black" +) +set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}" + CACHE STRING "Flags for Beagle Bone Black" +) + +set(CMAKE_FIND_ROOT_PATH + "${CMAKE_INSTALL_PREFIX};${CMAKE_PREFIX_PATH};${CMAKE_SYSROOT}" +) + +# search for programs in the build host directories +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# for libraries and headers in the target directories +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/cmake/BuildType.cmake b/cmake/BuildType.cmake new file mode 100644 index 0000000..ee027d1 --- /dev/null +++ b/cmake/BuildType.cmake @@ -0,0 +1,48 @@ +function(set_build_type) + +message(STATUS "Used build generator: ${CMAKE_GENERATOR}") + +# Set a default build type if none was specified +set(DEFAULT_BUILD_TYPE "RelWithDebInfo") +if(EXISTS "${CMAKE_SOURCE_DIR}/.git") + set(DEFAULT_BUILD_TYPE "Debug") +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS + "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified." + ) + set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE + STRING "Choose the type of build." FORCE + ) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo" + ) +endif() + +set(RELEASE_BUILD 1 PARENT_SCOPE) + +if(${CMAKE_BUILD_TYPE} MATCHES "Debug") + message(STATUS + "Building Debug application with flags: ${CMAKE_C_FLAGS_DEBUG}" + ) + set(RELEASE_BUILD 0 PARENT_SCOPE) +elseif(${CMAKE_BUILD_TYPE} MATCHES "RelWithDebInfo") + message(STATUS + "Building Release (Debug) application with " + "flags: ${CMAKE_C_FLAGS_RELWITHDEBINFO}" + ) +elseif(${CMAKE_BUILD_TYPE} MATCHES "MinSizeRel") + message(STATUS + "Building Release (Size) application with " + "flags: ${CMAKE_C_FLAGS_MINSIZEREL}" + ) +else() + message(STATUS + "Building Release (Speed) application with " + "flags: ${CMAKE_C_FLAGS_RELEASE}" + ) +endif() + +endfunction() diff --git a/cmake/EiveHelpers.cmake b/cmake/EiveHelpers.cmake new file mode 100644 index 0000000..b210739 --- /dev/null +++ b/cmake/EiveHelpers.cmake @@ -0,0 +1,30 @@ +# Determines the git version with git describe and returns it by setting +# the GIT_INFO list in the parent scope. The list has the following entries +# 1. Full version string +# 2. Major version +# 3. Minor version +# 4. Revision +# 5. (Optional) git SHA hash and commits since tag when applicable +function(determine_version_with_git) + include(GetGitRevisionDescription) + git_describe(VERSION ${ARGN}) + string(FIND ${VERSION} "." VALID_VERSION) + if(VALID_VERSION EQUAL -1) + message(WARNING "Version string ${VERSION} retrieved with git describe is invalid") + return() + endif() + # Parse the version information into pieces. + string(REGEX REPLACE "^v([0-9]+)\\..*" "\\1" _VERSION_MAJOR "${VERSION}") + string(REGEX REPLACE "^v[0-9]+\\.([0-9]+).*" "\\1" _VERSION_MINOR "${VERSION}") + string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" _VERSION_PATCH "${VERSION}") + string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.[0-9]+-(.*)" "\\1" VERSION_SHA1 "${VERSION}") + set(GIT_INFO ${VERSION}) + list(APPEND GIT_INFO ${_VERSION_MAJOR}) + list(APPEND GIT_INFO ${_VERSION_MINOR}) + list(APPEND GIT_INFO ${_VERSION_PATCH}) + if(NOT VERSION_SHA1 STREQUAL VERSION) + list(APPEND GIT_INFO ${VERSION_SHA1}) + endif() + set(GIT_INFO ${GIT_INFO} PARENT_SCOPE) + message(STATUS "eive | Set git version info into GIT_INFO from the git tag ${VERSION}") +endfunction() diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake new file mode 100644 index 0000000..69ef78b --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake @@ -0,0 +1,284 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_describe_working_tree( [ ...]) +# +# Returns the results of git describe on the working tree (--dirty option), +# and adjusting the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# git_local_changes() +# +# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. +# Uses the return code of "git diff-index --quiet HEAD --". +# Does not regard untracked files. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2020 Ryan Pavlik +# http://academic.cleardefinition.com +# +# Copyright 2009-2013, Iowa State University. +# Copyright 2013-2020, Ryan Pavlik +# Copyright 2013-2020, Contributors +# SPDX-License-Identifier: BSL-1.0 +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +# Function _git_find_closest_git_dir finds the next closest .git directory +# that is part of any directory in the path defined by _start_dir. +# The result is returned in the parent scope variable whose name is passed +# as variable _git_dir_var. If no .git directory can be found, the +# function returns an empty string via _git_dir_var. +# +# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and +# neither foo nor bar contain a file/directory .git. This wil return +# C:/bla/.git +# +function(_git_find_closest_git_dir _start_dir _git_dir_var) + set(cur_dir "${_start_dir}") + set(git_dir "${_start_dir}/.git") + while(NOT EXISTS "${git_dir}") + # .git dir not found, search parent directories + set(git_previous_parent "${cur_dir}") + get_filename_component(cur_dir "${cur_dir}" DIRECTORY) + if(cur_dir STREQUAL git_previous_parent) + # We have reached the root directory, we are not in git + set(${_git_dir_var} + "" + PARENT_SCOPE) + return() + endif() + set(git_dir "${cur_dir}/.git") + endwhile() + set(${_git_dir_var} + "${git_dir}" + PARENT_SCOPE) +endfunction() + +function(get_git_head_revision _refspecvar _hashvar) + _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR) + + if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") + set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE) + else() + set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE) + endif() + if(NOT "${GIT_DIR}" STREQUAL "") + file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}" + "${GIT_DIR}") + if("${_relative_to_source_dir}" MATCHES "[.][.]" AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) + # We've gone above the CMake root dir. + set(GIT_DIR "") + endif() + endif() + if("${GIT_DIR}" STREQUAL "") + set(${_refspecvar} + "GITDIR-NOTFOUND" + PARENT_SCOPE) + set(${_hashvar} + "GITDIR-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + # Check if the current source dir is a git submodule or a worktree. + # In both cases .git is a file instead of a directory. + # + if(NOT IS_DIRECTORY ${GIT_DIR}) + # The following git command will return a non empty string that + # points to the super project working tree if the current + # source dir is inside a git submodule. + # Otherwise the command will return an empty string. + # + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse + --show-superproject-working-tree + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT "${out}" STREQUAL "") + # If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE + ${submodule}) + string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} + ABSOLUTE) + set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") + else() + # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree + file(READ ${GIT_DIR} worktree_ref) + # The .git directory contains a path to the worktree information directory + # inside the parent git repo of the worktree. + # + string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir + ${worktree_ref}) + string(STRIP ${git_worktree_dir} git_worktree_dir) + _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR) + set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") + endif() + else() + set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${HEAD_SOURCE_FILE}") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} + "${HEAD_REF}" + PARENT_SCOPE) + set(${_hashvar} + "${HEAD_HASH}" + PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} + "HEAD-HASH-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_describe_working_tree _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_local_changes _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} + "HEAD-HASH-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(res EQUAL 0) + set(${_var} + "CLEAN" + PARENT_SCOPE) + else() + set(${_var} + "DIRTY" + PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in new file mode 100644 index 0000000..66eee63 --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake.in @@ -0,0 +1,43 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright 2009-2012, Iowa State University +# Copyright 2011-2015, Contributors +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# SPDX-License-Identifier: BSL-1.0 + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + else() + configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) + file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) + if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") + set(HEAD_HASH "${CMAKE_MATCH_1}") + endif() + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/cmake/HardwareOsPostConfig.cmake b/cmake/HardwareOsPostConfig.cmake new file mode 100644 index 0000000..111e859 --- /dev/null +++ b/cmake/HardwareOsPostConfig.cmake @@ -0,0 +1,66 @@ +function(post_source_hw_os_config) + +if(LINKER_SCRIPT) + add_link_options( + -T${LINKER_SCRIPT} + ) +endif() + +set(C_FLAGS "" CACHE INTERNAL "C flags") + +set(C_DEFS "" + CACHE INTERNAL + "C Defines" +) + +set(CXX_FLAGS ${C_FLAGS}) +set(CXX_DEFS ${C_DEFS}) + +if(CMAKE_VERBOSE) + message(STATUS "C Flags: ${C_FLAGS}") + message(STATUS "CXX Flags: ${CXX_FLAGS}") + message(STATUS "C Defs: ${C_DEFS}") + message(STATUS "CXX Defs: ${CXX_DEFS}") +endif() + +# Generator expression. Can be used to set different C, CXX and ASM flags. +add_compile_options( + $<$:${C_DEFS} ${C_FLAGS}> + $<$:${CXX_DEFS} ${CXX_FLAGS}> + $<$:${ASM_FLAGS}> +) + +set(STRIPPED_OBSW_NAME ${OBSW_BIN_NAME}-stripped) +set(STRIPPED_WATCHDOG_NAME eive-watchdog-stripped) + +if(EIVE_CREATE_UNIQUE_OBSW_BIN) + set(UNIQUE_OBSW_BIN_NAME ${OBSW_BIN_NAME}-$ENV{USERNAME}) +endif() + +add_custom_command( + TARGET ${OBSW_NAME} + POST_BUILD + COMMAND ${CMAKE_STRIP} --strip-all ${OBSW_BIN_NAME} -o ${STRIPPED_OBSW_NAME} + BYPRODUCTS ${STRIPPED_OBSW_NAME} + COMMENT "Generating stripped executable ${STRIPPED_OBSW_NAME}.." +) + +if(UNIQUE_OBSW_BIN_NAME) + add_custom_command( + TARGET ${OBSW_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/${OBSW_BIN_NAME} + ${CMAKE_CURRENT_BINARY_DIR}/${UNIQUE_OBSW_BIN_NAME} + COMMENT "Generating unique EIVE OBSW binary ${UNIQUE_OBSW_BIN_NAME}") +endif() + +add_custom_command( + TARGET ${WATCHDOG_NAME} + POST_BUILD + COMMAND ${CMAKE_STRIP} --strip-all eive-watchdog -o ${STRIPPED_WATCHDOG_NAME} + BYPRODUCTS ${STRIPPED_WATCHDOG_NAME} + COMMENT "Generating stripped executable ${STRIPPED_WATCHDOG_NAME}.." +) + +endfunction() \ No newline at end of file diff --git a/cmake/PreProjectConfig.cmake b/cmake/PreProjectConfig.cmake new file mode 100644 index 0000000..1c998e8 --- /dev/null +++ b/cmake/PreProjectConfig.cmake @@ -0,0 +1,144 @@ +function(obsw_module_config) +endfunction() + +function(pre_source_hw_os_config) + +# FreeRTOS +if(FSFW_OSAL MATCHES freertos) + message(FATAL_ERROR "No FreeRTOS support implemented yet.") +# RTEMS +elseif(FSFW_OSAL STREQUAL rtems) + add_definitions(-DRTEMS) + message(FATAL_ERROR "No RTEMS support implemented yet.") +elseif(FSFW_OSAL STREQUAL linux) + add_definitions(-DUNIX -DLINUX) + find_package(Threads REQUIRED) +# Hosted +else() + set(BSP_PATH "bsp_hosted") + if(WIN32) + add_definitions(-DWIN32) + elseif(UNIX) + find_package(Threads REQUIRED) + add_definitions(-DUNIX -DLINUX) + endif() +endif() + +# Cross-compile information +if(CMAKE_CROSSCOMPILING) + # set(CMAKE_VERBOSE TRUE) + + message(STATUS "Cross-compiling for ${TGT_BSP} target") + message(STATUS "Cross-compile gcc: ${CMAKE_C_COMPILER}") + message(STATUS "Cross-compile g++: ${CMAKE_CXX_COMPILER}") + + if(CMAKE_VERBOSE) + message(STATUS "Cross-compile linker: ${CMAKE_LINKER}") + message(STATUS "Cross-compile size utility: ${CMAKE_SIZE}") + message(STATUS "Cross-compile objcopy utility: ${CMAKE_OBJCOPY}") + message(STATUS "Cross-compile ranlib utility: ${CMAKE_RANLIB}") + message(STATUS "Cross-compile ar utility: ${CMAKE_AR}") + message(STATUS "Cross-compile nm utility: ${CMAKE_NM}") + message(STATUS "Cross-compile strip utility: ${CMAKE_STRIP}") + message(STATUS + "Cross-compile assembler: ${CMAKE_ASM_COMPILER} " + "-x assembler-with-cpp" + ) + message(STATUS "ABI flags: ${ABI_FLAGS}") + message(STATUS "Custom linker script: ${LINKER_SCRIPT}") + endif() + + set_property(CACHE TGT_BSP + PROPERTY STRINGS + "arm/q7s" "arm/raspberrypi" "arm/egse" + ) +endif() + + +if(TGT_BSP) + if (TGT_BSP MATCHES "arm/raspberrypi" OR TGT_BSP MATCHES "arm/beagleboneblack") + set(BSP_PATH "bsp_linux_board") + elseif(TGT_BSP MATCHES "arm/q7s") + set(BSP_PATH "bsp_q7s") + elseif(TGT_BSP MATCHES "arm/egse") + set(BSP_PATH "bsp_egse") + elseif(TGT_BSP MATCHES "arm/te0720-1cfa") + set(BSP_PATH "bsp_te0720_1cfa") + else() + message(WARNING "CMake not configured for this target!") + message(FATAL_ERROR "Target: ${TGT_BSP}!") + endif() +else() + set(BSP_PATH "bsp_hosted") +endif() + +set(BSP_PATH ${BSP_PATH} PARENT_SCOPE) + +endfunction() + +function(pre_project_config) + +# Basic input sanitization +if(DEFINED TGT_BSP) + if(${TGT_BSP} MATCHES "arm/raspberrypi" AND NOT FSFW_OSAL MATCHES linux) + message(STATUS "FSFW OSAL invalid for specified target BSP ${TGT_BSP}!") + message(STATUS "Setting valid FSFW_OSAL: linux") + set(FSFW_OSAL "linux") + endif() +endif() + + +# Disable compiler checks for cross-compiling. +if(FSFW_OSAL MATCHES linux AND TGT_BSP AND EIVE_HARDCODED_TOOLCHAIN_FILE) + if(TGT_BSP MATCHES "arm/q7s" OR TGT_BSP MATCHES "arm/te0720-1cfa") + set(CMAKE_TOOLCHAIN_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Zynq7020CrossCompileConfig.cmake" + PARENT_SCOPE + ) + elseif(TGT_BSP MATCHES "arm/raspberrypi" OR TGT_BSP MATCHES "arm/egse") + if(NOT DEFINED ENV{LINUX_ROOTFS}) + if(NOT DEFINED LINUX_ROOTFS) + message(WARNING "No LINUX_ROOTFS environmental or CMake variable set!") + set(ENV{LINUX_ROOTFS} "$ENV{HOME}/raspberrypi/rootfs") + else() + set(ENV{LINUX_ROOTFS} "${LINUX_ROOTFS}") + endif() + else() + message(STATUS + "LINUX_ROOTFS from environmental variables used: $ENV{LINUX_ROOTFS}" + ) + endif() + + if(NOT DEFINED ENV{RASPBERRY_VERSION}) + if(NOT RASPBERRY_VERSION) + message(STATUS "No RASPBERRY_VERSION specified, setting to 4") + set(RASPBERRY_VERSION "4" CACHE STRING "Raspberry Pi version") + else() + message(STATUS "Setting RASPBERRY_VERSION to ${RASPBERRY_VERSION}") + set(RASPBERRY_VERSION ${RASPBERRY_VERSION} CACHE STRING "Raspberry Pi version") + set(ENV{RASPBERRY_VERSION} ${RASPBERRY_VERSION}) + endif() + else() + message(STATUS + "RASPBERRY_VERSION from environmental variables used: " + "$ENV{RASPBERRY_VERSION}" + ) + endif() + + set(CMAKE_TOOLCHAIN_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/RPiCrossCompileConfig.cmake" + PARENT_SCOPE + ) + elseif(${TGT_BSP} MATCHES "arm/beagleboneblack") + if(LINUX_CROSS_COMPILE) + set(CMAKE_TOOLCHAIN_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/BBBCrossCompileConfig.cmake" + PARENT_SCOPE + ) + endif() + else() + message(WARNING "Target BSP (TGT_BSP) ${TGT_BSP} unknown!") + endif() +endif() + +endfunction() \ No newline at end of file diff --git a/cmake/RPiCrossCompileConfig.cmake b/cmake/RPiCrossCompileConfig.cmake new file mode 100644 index 0000000..5806af5 --- /dev/null +++ b/cmake/RPiCrossCompileConfig.cmake @@ -0,0 +1,148 @@ +# Based on https://github.com/Pro/raspi-toolchain but rewritten completely. + +# Adapted for the FSFW Example +if(NOT DEFINED ENV{RASPBERRY_VERSION}) + message(STATUS "Raspberry Pi version not specified, setting version 4!") + set(RASPBERRY_VERSION 4) +else() + set(RASPBERRY_VERSION $ENV{RASPBERRY_VERSION}) +endif() + + +# LINUX_ROOTFS should point to the local directory which contains all the +# libraries and includes from the target raspi. +# The following command can be used to do this, replace and the +# local accordingly: +# rsync -vR --progress -rl --delete-after --safe-links pi@:/{lib,usr,opt/vc/lib} +# LINUX_ROOTFS needs to be passed to the CMake command or defined in the +# application CMakeLists.txt before loading the toolchain file. + +# CROSS_COMPILE also needs to be set accordingly or passed to the CMake command +if(NOT DEFINED ENV{LINUX_ROOTFS}) + # Sysroot has not been cached yet and was not set in environment either + if(NOT SYSROOT_PATH) + message(FATAL_ERROR + "Define the LINUX_ROOTFS variable to point to the Raspberry Pi rootfs." + ) + endif() +else() + set(SYSROOT_PATH "$ENV{LINUX_ROOTFS}" CACHE PATH "Local linux root filesystem path") + message(STATUS "Raspberry Pi sysroot: ${SYSROOT_PATH}") +endif() + +if(NOT DEFINED ENV{CROSS_COMPILE}) + set(CROSS_COMPILE "arm-linux-gnueabihf") + message(STATUS + "No CROSS_COMPILE environmental variable set, using default ARM linux " + "cross compiler name ${CROSS_COMPILE}" + ) +else() + set(CROSS_COMPILE "$ENV{CROSS_COMPILE}") + message(STATUS + "Using environmental variable CROSS_COMPILE as cross-compiler: " + "$ENV{CROSS_COMPILE}" + ) +endif() + +# Generally, the debian roots will be a multiarch rootfs where some libraries are put +# into a folder named "arm-linux-gnueabihf". The user can override the folder name if this is +# not the case +if(NOT ENV{MULTIARCH_FOLDER_NAME}) + set(MULTIARCH_FOLDER_NAME "arm-linux-gnueabihf") +else() + set(MUTLIARCH_FOLDER_NAME $ENV{MULTIARCH_FOLDER_NAME}) +endif() + +message(STATUS "Using sysroot path: ${SYSROOT_PATH}") + +set(CROSS_COMPILE_CC "${CROSS_COMPILE}-gcc") +set(CROSS_COMPILE_CXX "${CROSS_COMPILE}-g++") +set(CROSS_COMPILE_LD "${CROSS_COMPILE}-ld") +set(CROSS_COMPILE_AR "${CROSS_COMPILE}-ar") +set(CROSS_COMPILE_RANLIB "${CROSS_COMPILE}-ranlib") +set(CROSS_COMPILE_STRIP "${CROSS_COMPILE}-strip") +set(CROSS_COMPILE_NM "${CROSS_COMPILE}-nm") +set(CROSS_COMPILE_OBJCOPY "${CROSS_COMPILE}-objcopy") +set(CROSS_COMPILE_SIZE "${CROSS_COMPILE}-size") + +# At the very least, cross compile gcc and g++ have to be set! +find_program (CMAKE_C_COMPILER ${CROSS_COMPILE_CC} REQUIRED) +find_program (CMAKE_CXX_COMPILER ${CROSS_COMPILE_CXX} REQUIRED) +# Useful utilities, not strictly necessary +find_program(CMAKE_SIZE ${CROSS_COMPILE_SIZE}) +find_program(CMAKE_OBJCOPY ${CROSS_COMPILE_OBJCOPY}) + +set(CMAKE_CROSSCOMPILING TRUE) +set(CMAKE_SYSROOT "${SYSROOT_PATH}") + +# Define name of the target system +set(CMAKE_SYSTEM_NAME "Linux") +if(RASPBERRY_VERSION VERSION_GREATER 1) + set(CMAKE_SYSTEM_PROCESSOR "armv7") +else() + set(CMAKE_SYSTEM_PROCESSOR "arm") +endif() + +# List of library dirs where LD has to look. Pass them directly through gcc. +# LD_LIBRARY_PATH is not evaluated by arm-*-ld +set(LIB_DIRS + "${SYSROOT_PATH}/opt/vc/lib" + "${SYSROOT_PATH}/lib/${MULTIARCH_FOLDER_NAME}" + "${SYSROOT_PATH}/usr/local/lib" + "${SYSROOT_PATH}/usr/lib/${MULTIARCH_FOLDER_NAME}" + "${SYSROOT_PATH}/usr/lib" + "${SYSROOT_PATH}/usr/lib/${MULTIARCH_FOLDER_NAME}/blas" + "${SYSROOT_PATH}/usr/lib/${MULTIARCH_FOLDER_NAME}/lapack" +) +# You can additionally check the linker paths if you add the +# flags ' -Xlinker --verbose' +set(COMMON_FLAGS "-I${SYSROOT_PATH}/usr/include") +foreach(LIB ${LIB_DIRS}) + set(COMMON_FLAGS "${COMMON_FLAGS} -L${LIB} -Wl,-rpath-link,${LIB}") +endforeach() + +if(RASPBERRY_VERSION VERSION_GREATER 3) + set(CMAKE_C_FLAGS + "-mcpu=cortex-a72 -mfpu=neon-vfpv4 -mfloat-abi=hard ${COMMON_FLAGS}" + CACHE STRING "Flags for Raspberry Pi 4" + ) + set(CMAKE_CXX_FLAGS + "${CMAKE_C_FLAGS}" + CACHE STRING "Flags for Raspberry Pi 4" + ) +elseif(RASPBERRY_VERSION VERSION_GREATER 2) + set(CMAKE_C_FLAGS + "-mcpu=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard ${COMMON_FLAGS}" + CACHE STRING "Flags for Raspberry Pi 3" + ) + set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}" + CACHE STRING "Flags for Raspberry Pi 3" + ) +elseif(RASPBERRY_VERSION VERSION_GREATER 1) + set(CMAKE_C_FLAGS + "-mcpu=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard ${COMMON_FLAGS}" + CACHE STRING "Flags for Raspberry Pi 2" + ) + set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}" + CACHE STRING "Flags for Raspberry Pi 2" + ) +else() + set(CMAKE_C_FLAGS + "-mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard ${COMMON_FLAGS}" + CACHE STRING "Flags for Raspberry Pi 1 B+ Zero" + ) + set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}" + CACHE STRING "Flags for Raspberry PI 1 B+ Zero" + ) +endif() + +set(CMAKE_FIND_ROOT_PATH + "${CMAKE_INSTALL_PREFIX};${CMAKE_PREFIX_PATH};${CMAKE_SYSROOT}" +) + +# search for programs in the build host directories +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# for libraries and headers in the target directories +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/cmake/Zynq7020CrossCompileConfig.cmake b/cmake/Zynq7020CrossCompileConfig.cmake new file mode 100644 index 0000000..5e269f1 --- /dev/null +++ b/cmake/Zynq7020CrossCompileConfig.cmake @@ -0,0 +1,112 @@ +if(DEFINED ENV{ZYNQ_7020_SYSROOT}) + set(ENV{ZYNQ_7020_ROOTFS} $ENV{ZYNQ_7020_SYSROOT}) +endif() +# CROSS_COMPILE also needs to be set accordingly or passed to the CMake command +if(NOT DEFINED ENV{ZYNQ_7020_ROOTFS}) + # Sysroot has not been cached yet and was not set in environment either + if(NOT DEFINED SYSROOT_PATH) + message(FATAL_ERROR + "Define the ZYNQ_7020_ROOTFS variable to point to the Zynq-7020 rootfs." + ) + endif() +else() + set(SYSROOT_PATH "$ENV{ZYNQ_7020_ROOTFS}" CACHE PATH "Zynq-7020 root filesystem path") +endif() + +if(NOT DEFINED ENV{CROSS_COMPILE}) + set(CROSS_COMPILE "arm-linux-gnueabihf") + message(STATUS + "No CROSS_COMPILE environmental variable set, using default ARM linux " + "cross compiler name ${CROSS_COMPILE}" + ) +else() + set(CROSS_COMPILE "$ENV{CROSS_COMPILE}") + message(STATUS + "Using environmental variable CROSS_COMPILE as cross-compiler: " + "$ENV{CROSS_COMPILE}" + ) +endif() + +message(STATUS "Using sysroot path: ${SYSROOT_PATH}") + +set(CROSS_COMPILE_CC "${CROSS_COMPILE}-gcc") +set(CROSS_COMPILE_CXX "${CROSS_COMPILE}-g++") +set(CROSS_COMPILE_LD "${CROSS_COMPILE}-ld") +set(CROSS_COMPILE_AR "${CROSS_COMPILE}-ar") +set(CROSS_COMPILE_RANLIB "${CROSS_COMPILE}-ranlib") +set(CROSS_COMPILE_STRIP "${CROSS_COMPILE}-strip") +set(CROSS_COMPILE_NM "${CROSS_COMPILE}-nm") +set(CROSS_COMPILE_OBJCOPY "${CROSS_COMPILE}-objcopy") +set(CROSS_COMPILE_SIZE "${CROSS_COMPILE}-size") + +# At the very least, cross compile gcc and g++ have to be set! +find_program (CMAKE_C_COMPILER ${CROSS_COMPILE_CC} HINTS $ENV{CROSS_COMPILE_BIN_PATH} REQUIRED) +find_program (CMAKE_CXX_COMPILER ${CROSS_COMPILE_CXX} HINTS $ENV{CROSS_COMPILE_BIN_PATH} REQUIRED) +# Useful utilities, not strictly necessary +find_program(CMAKE_SIZE ${CROSS_COMPILE_SIZE}) +find_program(CMAKE_OBJCOPY ${CROSS_COMPILE_OBJCOPY}) +find_program(CMAKE_STRIP ${CROSS_COMPILE_STRIP}) + +set(CMAKE_CROSSCOMPILING TRUE) +set(CMAKE_SYSROOT "${SYSROOT_PATH}") + +# Define name of the target system +set(CMAKE_SYSTEM_NAME "Linux") +set(CMAKE_SYSTEM_PROCESSOR "armv7") + +# Define the compiler +set(CMAKE_C_COMPILER ${CROSS_COMPILE_CC}) +set(CMAKE_CXX_COMPILER ${CROSS_COMPILE_CXX}) + +if(EIVE_SYSROOT_MAGIC) + # List of library dirs where LD has to look. Pass them directly through gcc. + set(LIB_DIRS + "${SYSROOT_PATH}/usr/include" + "${SYSROOT_PATH}/usr/include/linux" + "${SYSROOT_PATH}/usr/lib" + "${SYSROOT_PATH}/lib" + "${SYSROOT_PATH}" + "${SYSROOT_PATH}/usr/lib/arm-xiphos-linux-gnueabi" + ) + # You can additionally check the linker paths if you add the + # flags ' -Xlinker --verbose' + set(COMMON_FLAGS "-I${SYSROOT_PATH}/usr/lib") + foreach(LIB ${LIB_DIRS}) + set(COMMON_FLAGS "${COMMON_FLAGS} -L${LIB} -Wl,-rpath-link,${LIB}") + endforeach() +endif() + +set(CMAKE_PREFIX_PATH + "${CMAKE_PREFIX_PATH}" + # "${SYSROOT_PATH}/usr/lib/${CROSS_COMPILE}" +) + +set(C_FLAGS + -mcpu=cortex-a9 + -mfpu=neon-vfpv3 + -mfloat-abi=hard + ${COMMON_FLAGS} + -lgpiod +) + +if (TGT_BSP MATCHES "arm/q7s") + set(C_FLAGS ${C_FLAGS} -lxiphos) +endif() + +string (REPLACE ";" " " C_FLAGS "${C_FLAGS}") + +set(CMAKE_C_FLAGS + ${C_FLAGS} + CACHE STRING "C flags for Zynq-7020" +) +set(CMAKE_CXX_FLAGS + "${CMAKE_C_FLAGS}" + CACHE STRING "CPP flags for Zynq-7020" +) + +# search for programs in the build host directories +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# for libraries and headers in the target directories +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/cmake/scripts/beagleboneb/crosscompile/bbb_path_helper.sh b/cmake/scripts/beagleboneb/crosscompile/bbb_path_helper.sh new file mode 100644 index 0000000..340713e --- /dev/null +++ b/cmake/scripts/beagleboneb/crosscompile/bbb_path_helper.sh @@ -0,0 +1,3 @@ +export PATH=$PATH:"$HOME/beaglebone//bin" +export CROSS_COMPILE="arm-linux-gnueabihf" +export BBB_ROOTFS="${HOME}/raspberrypi/rootfs" diff --git a/cmake/scripts/beagleboneb/crosscompile/make-debug-cfg.sh b/cmake/scripts/beagleboneb/crosscompile/make-debug-cfg.sh new file mode 100755 index 0000000..ba8d94c --- /dev/null +++ b/cmake/scripts/beagleboneb/crosscompile/make-debug-cfg.sh @@ -0,0 +1,35 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/beagleboneblack" +build_generator="" +builddir="build-Debug-BBB" +defines="LINUX_CROSS_COMPILE=ON" +if [ "${OS}" = "Windows_NT" ]; then + build_generator="MinGW Makefiles" +# Could be other OS but this works for now. +else + build_generator="Unix Makefiles" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l "${builddir}" -d "${defines}" +# Use this if commands are added which should not be printed +# set +x diff --git a/cmake/scripts/beagleboneb/crosscompile/make-release-cfg.sh b/cmake/scripts/beagleboneb/crosscompile/make-release-cfg.sh new file mode 100755 index 0000000..59d548c --- /dev/null +++ b/cmake/scripts/beagleboneb/crosscompile/make-release-cfg.sh @@ -0,0 +1,35 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/beagleboneblack" +build_generator="" +builddir="build-Release-BBB" +defines="LINUX_CROSS_COMPILE=ON" +if [ "${OS}" = "Windows_NT" ]; then + build_generator="MinGW Makefiles" +# Could be other OS but this works for now. +else + build_generator="Unix Makefiles" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l "${builddir}" -d "${defines}" +# Use this if commands are added which should not be printed +# set +x diff --git a/cmake/scripts/beagleboneb/make-debug-cfg.sh b/cmake/scripts/beagleboneb/make-debug-cfg.sh new file mode 100755 index 0000000..ede76ac --- /dev/null +++ b/cmake/scripts/beagleboneb/make-debug-cfg.sh @@ -0,0 +1,35 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/beagleboneblack" +build_generator="" +builddir="build-Debug-BBB" +defines="LINUX_CROSS_COMPILE=OFF" +if [ "${OS}" = "Windows_NT" ]; then + build_generator="MinGW Makefiles" +# Could be other OS but this works for now. +else + build_generator="Unix Makefiles" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l "${builddir}" -d "${defines}" +# Use this if commands are added which should not be printed +# set +x diff --git a/cmake/scripts/cmake-build-cfg.py b/cmake/scripts/cmake-build-cfg.py new file mode 100755 index 0000000..ea21fbd --- /dev/null +++ b/cmake/scripts/cmake-build-cfg.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +""" +@brief CMake configuration helper +@details +This script was written to have a portable way to perform the CMake configuration with various parameters on +different OSes. It was first written for the FSFW Example, but could be adapted to be more generic +in the future. + +Run cmake_build_config.py --help to get more information. +""" +import os +import sys +import argparse +import shutil +import stat + + +def main(): + print("-- Python CMake build configurator utility --") + + print("Parsing command line arguments..") + parser = argparse.ArgumentParser( + description="Processing arguments for CMake build configuration." + ) + parser.add_argument( + "-o", + "--osal", + type=str, + choices=["freertos", "linux", "rtems", "host"], + help="FSFW OSAL. Valid arguments: host, linux, rtems, freertos", + ) + parser.add_argument( + "-b", + "--buildtype", + type=str, + choices=["debug", "release", "size", "reldeb"], + help="CMake build type. Valid arguments: debug, release, size, reldeb (Release with Debug " + "Information)", + default="debug", + ) + parser.add_argument("-l", "--builddir", type=str, help="Specify build directory.") + parser.add_argument( + "-g", "--generator", type=str, help="CMake Generator", choices=["make", "ninja"] + ) + parser.add_argument( + "-d", + "--defines", + help="Additional custom defines passed to CMake (supply without -D prefix!)", + nargs="*", + type=str, + ) + parser.add_argument( + "-t", + "--target-bsp", + type=str, + help="Target BSP, combination of architecture and machine", + ) + + args = parser.parse_args() + + print("Determining source location..") + source_location = determine_source_location() + print(f"Determined source location: {source_location}") + + print("Building cmake configuration command..") + + if args.osal is None: + print("No FSFW OSAL specified, setting to default (host)..") + osal = "host" + else: + osal = args.osal + + if args.generator is None: + generator_cmake_arg = "" + else: + if args.generator == "make": + if os.name == "nt": + generator_cmake_arg = '-G "MinGW Makefiles"' + else: + generator_cmake_arg = '-G "Unix Makefiles"' + elif args.generator == "ninja": + generator_cmake_arg = "-G Ninja" + else: + generator_cmake_arg = args.generator + + if args.buildtype == "debug": + cmake_build_type = "Debug" + elif args.buildtype == "release": + cmake_build_type = "Release" + elif args.buildtype == "size": + cmake_build_type = "MinSizeRel" + else: + cmake_build_type = "RelWithDebInfo" + + if args.target_bsp is not None: + cmake_target_cfg_cmd = f'-DTGT_BSP="{args.target_bsp}"' + else: + cmake_target_cfg_cmd = "" + + define_string = "" + if args.defines is not None: + define_list = args.defines[0].split() + for define in define_list: + define_string += f"-D{define} " + + build_folder = cmake_build_type + if args.builddir is not None: + build_folder = args.builddir + + build_path = source_location + os.path.sep + build_folder + if os.path.isdir(build_path): + remove_old_dir = input( + f"{build_folder} folder already exists. Remove old directory? [y/n]: " + ) + if str(remove_old_dir).lower() in ["yes", "y", 1]: + remove_old_dir = True + else: + build_folder = determine_new_folder() + build_path = source_location + os.path.sep + build_folder + remove_old_dir = False + if remove_old_dir: + rm_build_dir(build_path) + os.chdir(source_location) + os.mkdir(build_folder) + print(f"Navigating into build directory: {build_path}") + os.chdir(build_folder) + + cmake_command = ( + f'cmake {generator_cmake_arg} -DFSFW_OSAL="{osal}" ' + f'-DCMAKE_BUILD_TYPE="{cmake_build_type}" {cmake_target_cfg_cmd} ' + f"{define_string} {source_location}" + ) + # Remove redundant spaces + cmake_command = " ".join(cmake_command.split()) + print("Running CMake command: ") + print(f'" {cmake_command} "') + os.system(cmake_command) + print("-- CMake configuration done. --") + + +def rm_build_dir(path: str): + # On windows the permissions of the build directory may have been set to read-only. If this + # is the case the permissions are changed before trying to delete the directory. + if not os.access(path, os.W_OK): + os.chmod(path, stat.S_IWUSR) + shutil.rmtree(path) + + +def determine_source_location() -> str: + index = 0 + while not os.path.isdir("fsfw"): + index += 1 + os.chdir("..") + if index >= 5: + print( + "Error: Could not find source directory (determined by looking for fsfw folder!)" + ) + sys.exit(1) + return os.getcwd() + + +def determine_new_folder() -> str: + new_folder = input(f"Use different folder name? [y/n]: ") + if str(new_folder).lower() in ["yes", "y", 1]: + new_folder_name = input("New folder name: ") + return new_folder_name + else: + print("Aborting configuration.") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/cmake/scripts/egse/egse_path_helper_win.sh b/cmake/scripts/egse/egse_path_helper_win.sh new file mode 100644 index 0000000..4bda17b --- /dev/null +++ b/cmake/scripts/egse/egse_path_helper_win.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Script to set path to raspberry pi toolchain +# Run script with: source egse_path_helper_win.sh +TOOLCHAIN_PATH="/c/SysGCC/raspberry/bin" +if [ $# -eq 1 ];then + export PATH=$PATH:"$1" +else + export PATH=$PATH:$TOOLCHAIN_PATH +fi + +echo "Path of toolchain set to $TOOLCHAIN_PATH" +export CROSS_COMPILE="arm-linux-gnueabihf" +export RASPBERRY_VERSION="4" \ No newline at end of file diff --git a/cmake/scripts/egse/make-debug-cfg.sh b/cmake/scripts/egse/make-debug-cfg.sh new file mode 100644 index 0000000..9a61137 --- /dev/null +++ b/cmake/scripts/egse/make-debug-cfg.sh @@ -0,0 +1,34 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + + +os_fsfw="linux" +tgt_bsp="arm/egse" +build_generator="make" +build_dir="build-Debug-egse" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l"${build_dir}" +# set +x diff --git a/cmake/scripts/host/host-make-debug.sh b/cmake/scripts/host/host-make-debug.sh new file mode 100755 index 0000000..cb7a3fb --- /dev/null +++ b/cmake/scripts/host/host-make-debug.sh @@ -0,0 +1,41 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +root_dir="" +if [ -z "${EIVE_OBSW_ROOT}" ]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + root_dir=$(realpath "../..") + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" + root_dir=${EIVE_OBSW_ROOT} +fi + +build_generator="make" +os_fsfw="host" +builddir="cmake-build-debug" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -l "${builddir}" +# Use this if commands are added which should not be printed +set +x +cd ${root_dir}/${builddir} diff --git a/cmake/scripts/host/host-make-release.sh b/cmake/scripts/host/host-make-release.sh new file mode 100755 index 0000000..5aee761 --- /dev/null +++ b/cmake/scripts/host/host-make-release.sh @@ -0,0 +1,39 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [ -z "${EIVE_OBSW_ROOT}" ]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + root_dir=$(realpath "../..") + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +build_generator="make" +os_fsfw="host" +builddir="cmake-build-release" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "release" -l "${builddir}" +# Use this if commands are added which should not be printed +set +x +cd ${root_dir}/${builddir} diff --git a/cmake/scripts/host/host-ninja-debug.sh b/cmake/scripts/host/host-ninja-debug.sh new file mode 100755 index 0000000..5b5c68f --- /dev/null +++ b/cmake/scripts/host/host-ninja-debug.sh @@ -0,0 +1,38 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [ -z "${EIVE_OBSW_ROOT}" ]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +build_generator="ninja" +os_fsfw="host" +builddir="cmake-build-debug" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -l "${builddir}" +# Use this if commands are added which should not be printed +# set +x + diff --git a/cmake/scripts/linux/host-make-debug.sh b/cmake/scripts/linux/host-make-debug.sh new file mode 100755 index 0000000..0ea1d76 --- /dev/null +++ b/cmake/scripts/linux/host-make-debug.sh @@ -0,0 +1,37 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [ -z "${EIVE_OBSW_ROOT}" ]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +build_generator="make" +os_fsfw="linux" +builddir="cmake-build-debug" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -l "${builddir}" +# Use this if commands are added which should not be printed +# set +x diff --git a/cmake/scripts/linux/host-make-release.sh b/cmake/scripts/linux/host-make-release.sh new file mode 100755 index 0000000..89cb0f4 --- /dev/null +++ b/cmake/scripts/linux/host-make-release.sh @@ -0,0 +1,37 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [ -z "${EIVE_OBSW_ROOT}" ]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +build_generator="Unix Makefiles" +os_fsfw="linux" +builddir="cmake-build-release" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "release" -l "${builddir}" +# Use this if commands are added which should not be printed +# set +x diff --git a/cmake/scripts/linux/host-ninja-debug.sh b/cmake/scripts/linux/host-ninja-debug.sh new file mode 100755 index 0000000..2514635 --- /dev/null +++ b/cmake/scripts/linux/host-ninja-debug.sh @@ -0,0 +1,38 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [ -z "${EIVE_OBSW_ROOT}" ]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +build_generator="ninja" +os_fsfw="linux" +builddir="cmake-build-debug" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -l "${builddir}" +# Use this if commands are added which should not be printed +# set +x + diff --git a/cmake/scripts/q7s/q7s-make-debug.sh b/cmake/scripts/q7s/q7s-make-debug.sh new file mode 100755 index 0000000..a9536c9 --- /dev/null +++ b/cmake/scripts/q7s/q7s-make-debug.sh @@ -0,0 +1,50 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [ -z "${EIVE_OBSW_ROOT}" ]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +if [ ! -z "${EIVE_Q7S_EM}" ]; then + build_defs="EIVE_Q7S_EM=ON" +fi + +build_defs="${build_defs} CMAKE_EXPORT_COMPILE_COMMANDS=ON" + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="cmake-build-debug-q7s" +if [ ! -z "${EIVE_Q7S_EM}" ]; then + build_dir="${build_dir}-em" +fi + +build_generator="make" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l"${build_dir}" -d "${build_defs}" +set +x + +cd ${init_dir} diff --git a/cmake/scripts/q7s/q7s-make-release.sh b/cmake/scripts/q7s/q7s-make-release.sh new file mode 100755 index 0000000..f5e970d --- /dev/null +++ b/cmake/scripts/q7s/q7s-make-release.sh @@ -0,0 +1,50 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [[ -z ${EIVE_OBSW_ROOT} ]]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +if [ ! -z "${EIVE_Q7S_EM}" ]; then + build_defs="EIVE_Q7S_EM=ON" +fi + +build_defs="${build_defs} CMAKE_EXPORT_COMPILE_COMMANDS=ON" + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="cmake-build-release-q7s" +if [ ! -z "${EIVE_Q7S_EM}" ]; then + build_dir="${build_dir}-em" +fi +build_generator="make" + +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "release" -t "${tgt_bsp}" \ + -l"${build_dir}" -d "${build_defs}" +set +x + +cd ${init_dir} diff --git a/cmake/scripts/q7s/q7s-make-size.sh b/cmake/scripts/q7s/q7s-make-size.sh new file mode 100755 index 0000000..f75edb7 --- /dev/null +++ b/cmake/scripts/q7s/q7s-make-size.sh @@ -0,0 +1,35 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="build-Release-Q7S" +build_generator="" +if [ "${OS}" = "Windows_NT" ]; then + build_generator="MinGW Makefiles" + python="py" +# Could be other OS but this works for now. +else + build_generator="Unix Makefiles" + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "size" -t "${tgt_bsp}" \ + -l"${build_dir}" +# set +x diff --git a/cmake/scripts/q7s/q7s-ninja-debug.sh b/cmake/scripts/q7s/q7s-ninja-debug.sh new file mode 100755 index 0000000..ad50b6a --- /dev/null +++ b/cmake/scripts/q7s/q7s-ninja-debug.sh @@ -0,0 +1,48 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [[ -z ${EIVE_OBSW_ROOT} ]]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +if [ ! -z "${EIVE_Q7S_EM}" ]; then + build_defs="EIVE_Q7S_EM=ON" +fi + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="cmake-build-debug-q7s" +if [ ! -z "${EIVE_Q7S_EM}" ]; then + build_dir="${build_dir}-em" +fi + +build_generator="ninja" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l "${build_dir}" -d "${build_defs}" +set +x + +cd ${init_dir} diff --git a/cmake/scripts/q7s/q7s-ninja-release.sh b/cmake/scripts/q7s/q7s-ninja-release.sh new file mode 100755 index 0000000..f0587f5 --- /dev/null +++ b/cmake/scripts/q7s/q7s-ninja-release.sh @@ -0,0 +1,48 @@ +#!/bin/bash +cfg_script_name="cmake-build-cfg.py" +init_dir=$(pwd) +if [[ -z ${EIVE_OBSW_ROOT} ]]; then + counter=0 + while [ ${counter} -lt 5 ] + do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) + done + + if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 + fi +else + cfg_script_name="${EIVE_OBSW_ROOT}/cmake/scripts/${cfg_script_name}" +fi + +if [ ! -z "${EIVE_Q7S_EM}" ]; then + build_defs="EIVE_Q7S_EM=ON" +fi + +os_fsfw="linux" +tgt_bsp="arm/q7s" +build_dir="cmake-build-release-q7s" +if [ ! -z "${EIVE_Q7S_EM}" ]; then + build_dir="${build_dir}-em" +fi + +build_generator="ninja" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "release" -t "${tgt_bsp}" \ + -l"${build_dir}" -d "${build_defs}" +set +x + +cd ${init_dir} diff --git a/cmake/scripts/rpi/make-debug-cfg.sh b/cmake/scripts/rpi/make-debug-cfg.sh new file mode 100755 index 0000000..f4d006c --- /dev/null +++ b/cmake/scripts/rpi/make-debug-cfg.sh @@ -0,0 +1,34 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + + +os_fsfw="linux" +tgt_bsp="arm/raspberrypi" +build_generator="make" +build_dir="build-Debug-RPi" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l"${build_dir}" +# set +x diff --git a/cmake/scripts/rpi/make-release-cfg.sh b/cmake/scripts/rpi/make-release-cfg.sh new file mode 100755 index 0000000..56b4873 --- /dev/null +++ b/cmake/scripts/rpi/make-release-cfg.sh @@ -0,0 +1,33 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/raspberrypi" +build_generator="" +build_dir="build-Release-RPi" +if [ "${OS}" = "Windows_NT" ]; then + build_generator="MinGW Makefiles" +# Could be other OS but this works for now. +else + build_generator="Unix Makefiles" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "release" -t "${tgt_bsp}" \ + -l"${build_dir}" +# set +x diff --git a/cmake/scripts/rpi/ninja-debug-cfg.sh b/cmake/scripts/rpi/ninja-debug-cfg.sh new file mode 100755 index 0000000..13096fd --- /dev/null +++ b/cmake/scripts/rpi/ninja-debug-cfg.sh @@ -0,0 +1,34 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + +os_fsfw="linux" +tgt_bsp="arm/raspberrypi" +build_generator="ninja" +build_dir="build-Debug-RPi" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l"${build_dir}" +# set +x + diff --git a/cmake/scripts/rpi/rpi_path_helper.sh b/cmake/scripts/rpi/rpi_path_helper.sh new file mode 100644 index 0000000..9504428 --- /dev/null +++ b/cmake/scripts/rpi/rpi_path_helper.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# This script can be used to set the path to the cross-compile toolchain +# A default path is set if the path is not supplied via command line +if [ $# -eq 1 ];then + export PATH=$PATH:"$1" +else + # TODO: make version configurable via shell argument + export PATH=$PATH:"/opt/cross-pi-gcc/bin" + export CROSS_COMPILE="arm-linux-gnueabihf" + export RASPBERRY_VERSION="4" + export RASPBIAN_ROOTFS="${HOME}/raspberrypi/rootfs" +fi + +# It is also recommended to set up a custom shell script to perform the +# sysroot synchronization so that any software is built with the library and +# headers of the Raspberry Pi. This can for example be dome with the rsync +# command. +# The following command can be used, and the local +# need to be set accordingly. + +# rsync -vR --progress -rl --delete-after --safe-links pi@:/{lib,usr,opt/vc/lib} + +# It is recommended to use $HOME/raspberrypi/rootfs as the rootfs path, +# so the default RASPBIAN_ROOTFS variable set in the CMakeLists.txt is correct. diff --git a/cmake/scripts/rpi/rpi_path_helper_win.sh b/cmake/scripts/rpi/rpi_path_helper_win.sh new file mode 100644 index 0000000..2b590e9 --- /dev/null +++ b/cmake/scripts/rpi/rpi_path_helper_win.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# This script can be used to set the path to the cross-compile toolchain +# A default path is set if the path is not supplied via command line +if [ $# -eq 1 ];then + export PATH=$PATH:"$1" +else + # TODO: make version configurable via shell argument + export PATH=$PATH:"/c/SysGCC/raspberry/bin" + export CROSS_COMPILE="arm-linux-gnueabihf" + export RASPBERRY_VERSION="4" + export RASPBIAN_ROOTFS="/c/Users//raspberrypi/rootfs" +fi + +# It is also recommended to set up a custom shell script to perform the +# sysroot synchronization so that any software is built with the library and +# headers of the Raspberry Pi. This can for example be dome with the rsync +# command. +# The following command can be used, and the local +# need to be set accordingly. + +# rsync -vR --progress -rl --delete-after --safe-links pi@:/{lib,usr,opt/vc/lib} + diff --git a/cmake/scripts/te0720-1cfa/make-debug-cfg.sh b/cmake/scripts/te0720-1cfa/make-debug-cfg.sh new file mode 100644 index 0000000..46008c4 --- /dev/null +++ b/cmake/scripts/te0720-1cfa/make-debug-cfg.sh @@ -0,0 +1,34 @@ +#!/bin/sh +counter=0 +cfg_script_name="cmake-build-cfg.py" +while [ ${counter} -lt 5 ] +do + cd .. + if [ -f ${cfg_script_name} ];then + break + fi + counter=$((counter=counter + 1)) +done + +if [ "${counter}" -ge 5 ];then + echo "${cfg_script_name} not found in upper directories!" + exit 1 +fi + + +os_fsfw="linux" +tgt_bsp="arm/te0720-1cfa" +build_generator="make" +build_dir="build-Debug-te0720-1cfa" +if [ "${OS}" = "Windows_NT" ]; then + python="py" +# Could be other OS but this works for now. +else + python="python3" +fi + +echo "Running command (without the leading +):" +set -x # Print command +${python} ${cfg_script_name} -o "${os_fsfw}" -g "${build_generator}" -b "debug" -t "${tgt_bsp}" \ + -l"${build_dir}" +# set +x diff --git a/cmake/scripts/te0720-1cfa/win-env-te0720-1cfa.sh b/cmake/scripts/te0720-1cfa/win-env-te0720-1cfa.sh new file mode 100644 index 0000000..e77de4c --- /dev/null +++ b/cmake/scripts/te0720-1cfa/win-env-te0720-1cfa.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# Run with: source q7s-env-win-sh [OPTIONS] +function help () { + echo "source q7s-env-win-sh [options] -t|--toolchain= -s|--sysroot=" +} + +TOOLCHAIN_PATH="/c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" +SYSROOT="/c/Users/${USER}/eive-software/sysroots-petalinux-2019-2/cortexa9t2hf-neon-xilinx-linux-gnueabi" + +for i in "$@"; do + case $i in + -t=*|--toolchain=*) + TOOLCHAIN_PATH="${i#*=}" + shift + ;; + -s=*|--sysroot=*) + SYSROOT="${i#*=}" + shift + ;; + -h|--help) + help + shift + ;; + -*|--*) + echo "Unknown option $i" + help + return + ;; + *) + ;; + esac +done + +if [ -d "$TOOLCHAIN_PATH" ]; then + export PATH=$PATH:"/c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" + export CROSS_COMPILE="arm-linux-gnueabihf" + echo "Set toolchain path to /c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" +else + echo "Toolchain path $TOOLCHAIN_PATH does not exist" + return +fi + +if [ -d "$SYSROOT" ]; then + export ZYNQ_7020_SYSROOT=$SYSROOT + echo "Set sysroot path to $SYSROOT" +else + echo "Sysroot path $SYSROOT does not exist" + return +fi \ No newline at end of file diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 0000000..9040988 --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(config) diff --git a/common/config/CMakeLists.txt b/common/config/CMakeLists.txt new file mode 100644 index 0000000..ca29622 --- /dev/null +++ b/common/config/CMakeLists.txt @@ -0,0 +1,3 @@ +target_include_directories(${OBSW_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +target_sources(${OBSW_NAME} PRIVATE commonConfig.cpp) diff --git a/common/config/ccsdsConfig.h b/common/config/ccsdsConfig.h new file mode 100644 index 0000000..9736ae5 --- /dev/null +++ b/common/config/ccsdsConfig.h @@ -0,0 +1,8 @@ +#ifndef COMMON_CONFIG_CCSDSCONFIG_H_ +#define COMMON_CONFIG_CCSDSCONFIG_H_ + +namespace ccsds { +enum { VC0, VC1, VC2, VC3 }; +} + +#endif /* COMMON_CONFIG_CCSDSCONFIG_H_ */ diff --git a/common/config/commonConfig.cpp b/common/config/commonConfig.cpp new file mode 100644 index 0000000..cdc4e23 --- /dev/null +++ b/common/config/commonConfig.cpp @@ -0,0 +1,11 @@ +#include "commonConfig.h" + +#include "eive/definitions.h" +#include "fsfw/tmtcpacket/ccsds/defs.h" + +const fsfw::Version common::OBSW_VERSION{OBSW_VERSION_MAJOR, OBSW_VERSION_MINOR, + OBSW_VERSION_REVISION, OBSW_VERSION_CST_GIT_SHA1}; +const uint16_t common::PUS_PACKET_ID = + ccsds::getTcSpacePacketIdFromApid(config::EIVE_PUS_APID, true); +const uint16_t common::CFDP_PACKET_ID = + ccsds::getTcSpacePacketIdFromApid(config::EIVE_CFDP_APID, false); diff --git a/common/config/commonConfig.h.in b/common/config/commonConfig.h.in new file mode 100644 index 0000000..96dc948 --- /dev/null +++ b/common/config/commonConfig.h.in @@ -0,0 +1,43 @@ +#ifndef COMMON_CONFIG_COMMONCONFIG_H_ +#define COMMON_CONFIG_COMMONCONFIG_H_ + +#include +#include "fsfw/version.h" + +#cmakedefine RELEASE_BUILD + +#cmakedefine RASPBERRY_PI +#cmakedefine XIPHOS_Q7S +#cmakedefine BEAGLEBONEBLACK +#cmakedefine EGSE +#cmakedefine TE0720_1CFA + +/* These defines should be disabled for mission code but are useful for +debugging. */ +#define OBSW_VERBOSE_LEVEL 1 + +#define OBSW_ADD_LWGPS_TEST 0 + +// Disable this for mission code. It allows exchanging TMTC packets via the Ethernet port +#define OBSW_ADD_TCPIP_SERVERS 1 + +#define OBSW_ADD_CFDP_COMPONENTS 1 + +namespace common { + +static constexpr uint8_t OBSW_VERSION_MAJOR = @OBSW_VERSION_MAJOR@; +static constexpr uint8_t OBSW_VERSION_MINOR = @OBSW_VERSION_MINOR@; +static constexpr uint8_t OBSW_VERSION_REVISION = @OBSW_VERSION_REVISION@; +// CST: Commits since tag +static const char OBSW_VERSION_CST_GIT_SHA1[] = "@OBSW_VERSION_CST_GIT_SHA1@"; + + +static constexpr uint32_t OBSW_MAX_SCHEDULED_TCS = @OBSW_MAX_SCHEDULED_TCS@; + +extern const fsfw::Version OBSW_VERSION; + +extern const uint16_t PUS_PACKET_ID; +extern const uint16_t CFDP_PACKET_ID; +} + +#endif /* COMMON_CONFIG_COMMONCONFIG_H_ */ diff --git a/common/config/devConf.h b/common/config/devConf.h new file mode 100644 index 0000000..de33cf4 --- /dev/null +++ b/common/config/devConf.h @@ -0,0 +1,78 @@ +#ifndef COMMON_CONFIG_DEVCONF_H_ +#define COMMON_CONFIG_DEVCONF_H_ + +#include + +#include + +#include "commonConfig.h" +#include "fsfw/timemanager/clockDefinitions.h" +#include "fsfw_hal/linux/serial/SerialCookie.h" +#include "fsfw_hal/linux/spi/spiDefinitions.h" + +/** + * SPI configuration will be contained here to let the device handlers remain independent + * of SPI specific properties. + */ +namespace spi { + +// Default values, changing them is not supported for now +static constexpr uint32_t DEFAULT_LIS3_SPEED = 976'000; +static constexpr uint32_t LIS3_TRANSITION_DELAY = 5000; +static constexpr spi::SpiModes DEFAULT_LIS3_MODE = spi::SpiModes::MODE_3; + +static constexpr uint32_t DEFAULT_RM3100_SPEED = 976'000; +static constexpr uint32_t RM3100_TRANSITION_DELAY = 5000; +static constexpr spi::SpiModes DEFAULT_RM3100_MODE = spi::SpiModes::MODE_3; + +static constexpr uint32_t DEFAULT_L3G_SPEED = 976'000; +static constexpr uint32_t L3G_TRANSITION_DELAY = 5000; +static constexpr spi::SpiModes DEFAULT_L3G_MODE = spi::SpiModes::MODE_3; + +/** + * Some MAX1227 could not be reached with frequencies around 4 MHz. Maybe this is caused by + * the decoder and buffer circuits. Thus frequency is here defined to 1 MHz. + */ +static const uint32_t SUS_MAX1227_SPI_FREQ = 976'000; +static constexpr spi::SpiModes SUS_MAX_1227_MODE = spi::SpiModes::MODE_3; + +static constexpr dur_millis_t RAD_SENSOR_CS_TIMEOUT = 120; +static constexpr uint32_t DEFAULT_MAX_1227_SPEED = 976'000; +static constexpr spi::SpiModes DEFAULT_MAX_1227_MODE = spi::SpiModes::MODE_3; + +static constexpr uint32_t PL_PCDU_MAX_1227_SPEED = 976'000; + +static constexpr uint32_t DEFAULT_ADIS16507_SPEED = 976'000; +static constexpr spi::SpiModes DEFAULT_ADIS16507_MODE = spi::SpiModes::MODE_3; + +static constexpr uint32_t RW_SPEED = 300'000; +static constexpr spi::SpiModes RW_MODE = spi::SpiModes::MODE_0; + +#ifdef RELEASE_BUILD +static constexpr uint8_t CS_FACTOR = 1; +#else +static constexpr uint8_t CS_FACTOR = 3; +#endif + +static constexpr dur_millis_t RTD_CS_TIMEOUT = 50 * CS_FACTOR; +static constexpr uint32_t RTD_SPEED = 2'000'000; +static constexpr spi::SpiModes RTD_MODE = spi::SpiModes::MODE_3; + +static constexpr dur_millis_t SUS_CS_TIMEOUT = 50 * CS_FACTOR; +static constexpr dur_millis_t ACS_BOARD_CS_TIMEOUT = 50 * CS_FACTOR; + +} // namespace spi + +namespace serial { + +static constexpr size_t HYPERION_GPS_REPLY_MAX_BUFFER = 1024; +static constexpr UartBaudRate SYRLINKS_BAUD = UartBaudRate::RATE_38400; +static constexpr UartBaudRate SCEX_BAUD = UartBaudRate::RATE_115200; +static constexpr UartBaudRate GNSS_BAUD = UartBaudRate::RATE_9600; +static constexpr UartBaudRate PLOC_MPSOC_BAUD = UartBaudRate::RATE_115200; +static constexpr UartBaudRate PLOC_SUPV_BAUD = UartBaudRate::RATE_921600; +static constexpr UartBaudRate STAR_TRACKER_BAUD = UartBaudRate::RATE_921600; + +} // namespace serial + +#endif /* COMMON_CONFIG_DEVCONF_H_ */ diff --git a/common/config/devices/addresses.h b/common/config/devices/addresses.h new file mode 100644 index 0000000..e159857 --- /dev/null +++ b/common/config/devices/addresses.h @@ -0,0 +1,90 @@ +#ifndef FSFWCONFIG_DEVICES_ADDRESSES_H_ +#define FSFWCONFIG_DEVICES_ADDRESSES_H_ + +#include + +#include + +#include "objects/systemObjectList.h" + +namespace addresses { +/* Logical addresses have uint32_t datatype */ +enum LogicAddress : address_t { + PCDU, + + MGM_0_LIS3 = objects::MGM_0_LIS3_HANDLER, + MGM_1_RM3100 = objects::MGM_1_RM3100_HANDLER, + MGM_2_LIS3 = objects::MGM_2_LIS3_HANDLER, + MGM_3_RM3100 = objects::MGM_3_RM3100_HANDLER, + + GYRO_0_ADIS = objects::GYRO_0_ADIS_HANDLER, + GYRO_1_L3G = objects::GYRO_1_L3G_HANDLER, + GYRO_2_ADIS = objects::GYRO_2_ADIS_HANDLER, + GYRO_3_L3G = objects::GYRO_3_L3G_HANDLER, + + RAD_SENSOR = objects::RAD_SENSOR, + + SUS_0 = objects::SUS_0_N_LOC_XFYFZM_PT_XF, + SUS_1 = objects::SUS_1_N_LOC_XBYFZM_PT_XB, + SUS_2 = objects::SUS_2_N_LOC_XFYBZB_PT_YB, + SUS_3 = objects::SUS_3_N_LOC_XFYBZF_PT_YF, + SUS_4 = objects::SUS_4_N_LOC_XMYFZF_PT_ZF, + SUS_5 = objects::SUS_5_N_LOC_XFYMZB_PT_ZB, + SUS_6 = objects::SUS_6_R_LOC_XFYBZM_PT_XF, + SUS_7 = objects::SUS_7_R_LOC_XBYBZM_PT_XB, + SUS_8 = objects::SUS_8_R_LOC_XBYBZB_PT_YB, + SUS_9 = objects::SUS_9_R_LOC_XBYBZB_PT_YF, + SUS_10 = objects::SUS_10_N_LOC_XMYBZF_PT_ZF, + SUS_11 = objects::SUS_11_R_LOC_XBYMZB_PT_ZB, + + /* Dummy and Test Addresses */ + DUMMY_ECHO = 129, + DUMMY_GPS0 = 130, + DUMMY_GPS1 = 131, +}; + +enum I2cAddress : address_t { + BPX_BATTERY = 0x07, + IMTQ = 0x10, + TMP1075_TCS_0 = 0x48, + TMP1075_TCS_1 = 0x49, + TMP1075_PLPCDU_0 = 0x4A, + TMP1075_PLPCDU_1 = 0x4B, + TMP1075_IF_BOARD = 0x4C, +}; + +enum spiAddresses : address_t { + RTD_IC_0, + RTD_IC_1, + RTD_IC_2, + RTD_IC_3, + RTD_IC_4, + RTD_IC_5, + RTD_IC_6, + RTD_IC_7, + RTD_IC_8, + RTD_IC_9, + RTD_IC_10, + RTD_IC_11, + RTD_IC_12, + RTD_IC_13, + RTD_IC_14, + RTD_IC_15, + RW1, + RW2, + RW3, + RW4, + PLPCDU_ADC +}; + +/* Addresses of devices supporting the CSP protocol */ +enum cspAddresses : uint8_t { + P60DOCK = 4, + ACU = 2, + PDU1 = 3, + /* PDU2 occupies X4 slot of P60Dock */ + PDU2 = 6 +}; +} // namespace addresses + +#endif /* FSFWCONFIG_DEVICES_ADDRESSES_H_ */ diff --git a/common/config/devices/gpioIds.h b/common/config/devices/gpioIds.h new file mode 100644 index 0000000..2cab2c3 --- /dev/null +++ b/common/config/devices/gpioIds.h @@ -0,0 +1,128 @@ +#ifndef FSFWCONFIG_DEVICES_GPIOIDS_H_ +#define FSFWCONFIG_DEVICES_GPIOIDS_H_ + +#include + +namespace gpioIds { +enum gpioId_t { + HEATER_0, + HEATER_1, + HEATER_2, + HEATER_3, + HEATER_4, + HEATER_5, + HEATER_6, + HEATER_7, + DEPLSA1, + DEPLSA2, + + MGM_0_LIS3_CS, + MGM_1_RM3100_CS, + GYRO_0_ADIS_CS, + GYRO_1_L3G_CS, + GYRO_2_ADIS_CS, + GYRO_3_L3G_CS, + MGM_2_LIS3_CS, + MGM_3_RM3100_CS, + + GNSS_0_NRESET, + GNSS_1_NRESET, + GNSS_0_ENABLE, + GNSS_1_ENABLE, + GNSS_SELECT, + + GYRO_0_ENABLE, + GYRO_2_ENABLE, + + TEST_ID_0, + TEST_ID_1, + + RTD_IC_0, + RTD_IC_1, + RTD_IC_2, + RTD_IC_3, + RTD_IC_4, + RTD_IC_5, + RTD_IC_6, + RTD_IC_7, + RTD_IC_8, + RTD_IC_9, + RTD_IC_10, + RTD_IC_11, + RTD_IC_12, + RTD_IC_13, + RTD_IC_14, + RTD_IC_15, + + CS_SUS_0, + CS_SUS_1, + CS_SUS_2, + CS_SUS_3, + CS_SUS_4, + CS_SUS_5, + CS_SUS_6, + CS_SUS_7, + CS_SUS_8, + CS_SUS_9, + CS_SUS_10, + CS_SUS_11, + + SPI_MUX_BIT_0, + SPI_MUX_BIT_1, + SPI_MUX_BIT_2, + SPI_MUX_BIT_3, + SPI_MUX_BIT_4, + SPI_MUX_BIT_5, + + CS_RAD_SENSOR, + ENABLE_RADFET, + + PL_I2C_ARESETN, + + PAPB_BUSY_N, + PAPB_EMPTY, + + EN_RW1, + EN_RW2, + EN_RW3, + EN_RW4, + + CS_RW1, + CS_RW2, + CS_RW3, + CS_RW4, + + EN_RW_CS, + + SPI_MUX, + VC0_PAPB_EMPTY, + VC1_PAPB_EMPTY, + VC2_PAPB_EMPTY, + VC3_PAPB_EMPTY, + PTME_RESETN, + + PDEC_RESET, + + RS485_EN_TX_DATA, + RS485_EN_TX_CLOCK, + RS485_EN_RX_DATA, + RS485_EN_RX_CLOCK, + + BIT_RATE_SEL, + + PLPCDU_ENB_VBAT0, + PLPCDU_ENB_VBAT1, + PLPCDU_ENB_DRO, + PLPCDU_ENB_X8, + PLPCDU_ENB_TX, + PLPCDU_ENB_HPA, + PLPCDU_ENB_MPA, + PLPCDU_ADC_CS, + + ENABLE_MPSOC_UART, + ENABLE_SUPV_UART + +}; +} + +#endif /* FSFWCONFIG_DEVICES_GPIOIDS_H_ */ diff --git a/common/config/devices/powerSwitcherList.h b/common/config/devices/powerSwitcherList.h new file mode 100644 index 0000000..940d558 --- /dev/null +++ b/common/config/devices/powerSwitcherList.h @@ -0,0 +1,6 @@ +#ifndef FSFWCONFIG_DEVICES_POWERSWITCHERLIST_H_ +#define FSFWCONFIG_DEVICES_POWERSWITCHERLIST_H_ + +#include + +#endif /* FSFWCONFIG_DEVICES_POWERSWITCHERLIST_H_ */ diff --git a/common/config/eive/definitions.h b/common/config/eive/definitions.h new file mode 100644 index 0000000..4c96c3a --- /dev/null +++ b/common/config/eive/definitions.h @@ -0,0 +1,135 @@ +#ifndef COMMON_CONFIG_DEFINITIONS_H_ +#define COMMON_CONFIG_DEFINITIONS_H_ + +#include + +namespace config { + +static constexpr char SD_0_MOUNT_POINT[] = "/mnt/sd0"; +static constexpr char SD_1_MOUNT_POINT[] = "/mnt/sd1"; + +static constexpr char OBSW_UPDATE_ARCHIVE_FILE_NAME[] = "eive-sw-update.tar.xz"; +static constexpr char STRIPPED_OBSW_BINARY_FILE_NAME[] = "eive-obsw-stripped"; +static constexpr char OBSW_VERSION_FILE_NAME[] = "obsw_version.txt"; +static constexpr char PUS_SEQUENCE_COUNT_FILE[] = "pus-sequence-count.txt"; +static constexpr char CFDP_SEQUENCE_COUNT_FILE[] = "cfdp-sequence-count.txt"; + +static constexpr char OBSW_PATH[] = "/usr/bin/eive-obsw"; +static constexpr char OBSW_VERSION_FILE_PATH[] = "/usr/share/eive-obsw/obsw_version.txt"; + +// ISO8601 timestamp. +static constexpr char FILE_DATE_FORMAT[] = "%FT%H%M%SZ"; + +// Leap Seconds as of 2024-03-04 +static constexpr uint16_t LEAP_SECONDS = 37; + +static constexpr uint16_t EIVE_PUS_APID = 0x65; +static constexpr uint16_t EIVE_CFDP_APID = 0x66; +static constexpr uint16_t EIVE_LOCAL_CFDP_ENTITY_ID = EIVE_CFDP_APID; +static constexpr uint16_t EIVE_GROUND_CFDP_ENTITY_ID = 1; + +static constexpr uint32_t PL_PCDU_TRANSITION_TIMEOUT_MS = 20 * 60 * 1000; +static constexpr uint32_t LONGEST_MODE_TIMEOUT_SECONDS = PL_PCDU_TRANSITION_TIMEOUT_MS / 1000; + +/* Add mission configuration flags here */ +static constexpr uint32_t OBSW_FILESYSTEM_HANDLER_QUEUE_SIZE = 50; +static constexpr uint32_t PLOC_UPDATER_QUEUE_SIZE = 50; +static constexpr uint32_t STR_IMG_HELPER_QUEUE_SIZE = 50; + +static constexpr uint8_t LIVE_TM = 0; + +static constexpr size_t MAX_SPACEPACKET_TC_SIZE = 2048; + +/* Limits for filename and path checks */ +static constexpr uint32_t MAX_PATH_SIZE = 200; +static constexpr uint32_t MAX_FILENAME_SIZE = 100; + +static constexpr uint32_t SA_DEPL_INIT_BUFFER_SECS = 120; +// Burn time for autonomous deployment +static constexpr uint32_t SA_DEPL_BURN_TIME_SECS = 180; +static constexpr uint32_t SA_DEPL_WAIT_TIME_SECS = 45 * 60; +// HW constraints (current limit) mean that the GPIO channels need to be switched on in alternation +static constexpr uint32_t LEGACY_SA_DEPL_CHANNEL_ALTERNATION_INTERVAL_SECS = 5; +// Maximum allowed burn time allowed by the software. +static constexpr uint32_t SA_DEPL_MAX_BURN_TIME = 180; + +static constexpr size_t CFDP_MAX_FILE_SEGMENT_LEN = 900; + +static constexpr uint32_t CCSDS_HANDLER_QUEUE_SIZE = 50; +static constexpr uint8_t NUMBER_OF_VIRTUAL_CHANNELS = 4; +static constexpr uint32_t VC0_LIVE_TM_QUEUE_SIZE = 300; +// There are three individual log stores! +static constexpr uint32_t MISC_STORE_QUEUE_SIZE = 200; +static constexpr uint32_t OK_STORE_QUEUE_SIZE = 350; +static constexpr uint32_t NOK_STORE_QUEUE_SIZE = 350; +static constexpr uint32_t HK_STORE_QUEUE_SIZE = 300; +static constexpr uint32_t CFDP_STORE_QUEUE_SIZE = 300; + +static constexpr uint32_t LIVE_CHANNEL_NORMAL_QUEUE_SIZE = 250; +static constexpr uint32_t LIVE_CHANNEL_CFDP_QUEUE_SIZE = 350; + +static constexpr uint32_t CFDP_MAX_FSM_CALL_COUNT_SRC_HANDLER = 10; +static constexpr uint32_t CFDP_MAX_FSM_CALL_COUNT_DEST_HANDLER = 300; +static constexpr uint32_t CFDP_SHORT_DELAY_MS = 40; +static constexpr uint32_t CFDP_REGULAR_DELAY_MS = 200; + +static constexpr uint32_t MAX_PUS_FUNNEL_QUEUE_DEPTH = 100; +static constexpr uint32_t MAX_CFDP_FUNNEL_QUEUE_DEPTH = LIVE_CHANNEL_CFDP_QUEUE_SIZE; +static constexpr uint32_t VERIFICATION_SERVICE_QUEUE_DEPTH = 120; +static constexpr uint32_t HK_SERVICE_QUEUE_DEPTH = 60; +static constexpr uint32_t ACTION_SERVICE_QUEUE_DEPTH = 60; + +static constexpr uint32_t UDP_MAX_STORED_CMDS = 200; +static constexpr uint32_t UDP_MSG_QUEUE_DEPTH = UDP_MAX_STORED_CMDS; +static constexpr uint32_t TCP_MAX_STORED_CMDS = 350; +static constexpr uint32_t TCP_MSG_QUEUE_DEPTH = TCP_MAX_STORED_CMDS; +static constexpr uint32_t TCP_MAX_NUMBER_TMS_SENT_PER_CYCLE = TCP_MSG_QUEUE_DEPTH; + +namespace spiSched { + +static constexpr uint32_t SCHED_BLOCK_1_SUS_READ_MS = 15; +static constexpr uint32_t SCHED_BLOCK_2_SENSOR_READ_MS = 30; +static constexpr uint32_t SCHED_BLOCK_3_READ_IMTQ_MGM_MS = 43; +static constexpr uint32_t SCHED_BLOCK_4_ACS_CTRL_MS = 45; +static constexpr uint32_t SCHED_BLOCK_5_ACTUATOR_MS = 55; +static constexpr uint32_t SCHED_BLOCK_6_IMTQ_BLOCK_2_MS = 105; +static constexpr uint32_t SCHED_BLOCK_RTD = 150; +static constexpr uint32_t SCHED_BLOCK_7_RW_READ_MS = 300; +static constexpr uint32_t SCHED_BLOCK_8_PLPCDU_MS = 320; +static constexpr uint32_t SCHED_BLOCK_9_RAD_SENS_MS = 340; +static constexpr uint32_t SCHED_BLOCK_10_PWR_CTRL_MS = 350; + +// 15 ms for FM +static constexpr float SCHED_BLOCK_1_PERIOD = static_cast(SCHED_BLOCK_1_SUS_READ_MS) / 400.0; +static constexpr float SCHED_BLOCK_2_PERIOD = + static_cast(SCHED_BLOCK_2_SENSOR_READ_MS) / 400.0; +static constexpr float SCHED_BLOCK_3_PERIOD = + static_cast(SCHED_BLOCK_3_READ_IMTQ_MGM_MS) / 400.0; +static constexpr float SCHED_BLOCK_4_PERIOD = static_cast(SCHED_BLOCK_4_ACS_CTRL_MS) / 400.0; +static constexpr float SCHED_BLOCK_5_PERIOD = static_cast(SCHED_BLOCK_5_ACTUATOR_MS) / 400.0; +static constexpr float SCHED_BLOCK_6_PERIOD = + static_cast(SCHED_BLOCK_6_IMTQ_BLOCK_2_MS) / 400.0; +static constexpr float SCHED_BLOCK_RTD_PERIOD = static_cast(SCHED_BLOCK_RTD) / 400.0; +static constexpr float SCHED_BLOCK_7_PERIOD = static_cast(SCHED_BLOCK_7_RW_READ_MS) / 400.0; +static constexpr float SCHED_BLOCK_8_PERIOD = static_cast(SCHED_BLOCK_8_PLPCDU_MS) / 400.0; +static constexpr float SCHED_BLOCK_9_PERIOD = static_cast(SCHED_BLOCK_9_RAD_SENS_MS) / 400.0; +static constexpr float SCHED_BLOCK_10_PERIOD = + static_cast(SCHED_BLOCK_10_PWR_CTRL_MS) / 400.0; + +} // namespace spiSched + +namespace pdec { + +// Pre FW v6.0.0 +static constexpr uint32_t PDEC_CONFIG_BASE_ADDR_LEGACY = 0x24000000; +static constexpr uint32_t PDEC_RAM_ADDR_LEGACY = 0x26000000; + +// Post FW v6.0.0 +static constexpr uint32_t PDEC_CONFIG_BASE_ADDR = 0x4000000; +static constexpr uint32_t PDEC_RAM_ADDR = 0x7000000; + +} // namespace pdec + +} // namespace config + +#endif /* COMMON_CONFIG_DEFINITIONS_H_ */ diff --git a/common/config/eive/eventSubsystemIds.h b/common/config/eive/eventSubsystemIds.h new file mode 100644 index 0000000..370a00b --- /dev/null +++ b/common/config/eive/eventSubsystemIds.h @@ -0,0 +1,50 @@ +#ifndef COMMON_CONFIG_COMMONSUBSYSTEMIDS_H_ +#define COMMON_CONFIG_COMMONSUBSYSTEMIDS_H_ + +#include + +namespace SUBSYSTEM_ID { + +enum : uint8_t { + COMMON_SUBSYSTEM_ID_START = FW_SUBSYSTEM_ID_RANGE, + ACS_SUBSYSTEM = 112, + PCDU_HANDLER = 113, + HEATER_HANDLER = 114, + SA_DEPL_HANDLER = 115, + PLOC_MPSOC_HANDLER = 116, + IMTQ_HANDLER = 117, + RW_HANDLER = 118, + STR_HANDLER = 119, + PLOC_SUPERVISOR_HANDLER = 120, + FILE_SYSTEM = 121, + PLOC_UPDATER = 122, + PLOC_MEMORY_DUMPER = 123, + PDEC_HANDLER = 124, + STR_HELPER = 125, + PLOC_MPSOC_HELPER = 126, + PL_PCDU_HANDLER = 127, + ACS_BOARD_ASS = 128, + SUS_BOARD_ASS = 129, + TCS_BOARD_ASS = 130, + GPS_HANDLER = 131, + P60_DOCK_HANDLER = 132, + PDU1_HANDLER = 133, + PDU2_HANDLER = 134, + ACU_HANDLER = 135, + PLOC_SUPV_HELPER = 136, + SYRLINKS = 137, + SCEX_HANDLER = 138, + CONFIGHANDLER = 139, + CORE = 140, + TCS_CONTROLLER = 141, + COM_SUBSYSTEM = 142, + PERSISTENT_TM_STORE = 143, + SYRLINKS_COM = 144, + SUS_HANDLER = 145, + CFDP_APP = 146, + COMMON_SUBSYSTEM_ID_END + +}; +} + +#endif /* COMMON_CONFIG_COMMONSUBSYSTEMIDS_H_ */ diff --git a/common/config/eive/objects.h b/common/config/eive/objects.h new file mode 100644 index 0000000..797bea1 --- /dev/null +++ b/common/config/eive/objects.h @@ -0,0 +1,188 @@ +#ifndef COMMON_CONFIG_COMMONOBJECTS_H_ +#define COMMON_CONFIG_COMMONOBJECTS_H_ + +#include + +namespace objects { +enum commonObjects : uint32_t { + /* First Byte 0x50-0x52 reserved for PUS Services **/ + CCSDS_PACKET_DISTRIBUTOR = 0x50000100, + PUS_PACKET_DISTRIBUTOR = 0x50000200, + TCP_TMTC_SERVER = 0x50000300, + UDP_TMTC_SERVER = 0x50000301, + TCP_TMTC_POLLING_TASK = 0x50000400, + UDP_TMTC_POLLING_TASK = 0x50000401, + FILE_SYSTEM_HANDLER = 0x50000500, + SDC_MANAGER = 0x50000550, + PTME = 0x50000600, + PDEC_HANDLER = 0x50000700, + CCSDS_HANDLER = 0x50000800, + + /* 0x49 ('I') for Communication Interfaces **/ + UART_COM_IF = 0x49030003, + SCEX_UART_READER = 0x49010006, + + /* 0x43 ('C') for Controllers */ + THERMAL_CONTROLLER = 0x43400001, + ACS_CONTROLLER = 0x43000002, + CORE_CONTROLLER = 0x43000003, + POWER_CONTROLLER = 0x43000004, + GLOBAL_JSON_CFG = 0x43000006, + XIPHOS_WDT = 0x43000007, + + /* 0x44 ('D') for device handlers */ + MGM_0_LIS3_HANDLER = 0x44120006, + MGM_1_RM3100_HANDLER = 0x44120107, + MGM_2_LIS3_HANDLER = 0x44120208, + MGM_3_RM3100_HANDLER = 0x44120309, + GYRO_0_ADIS_HANDLER = 0x44120010, + GYRO_1_L3G_HANDLER = 0x44120111, + GYRO_2_ADIS_HANDLER = 0x44120212, + GYRO_3_L3G_HANDLER = 0x44120313, + RW1 = 0x44120047, + RW2 = 0x44120148, + RW3 = 0x44120249, + RW4 = 0x44120350, + STAR_TRACKER = 0x44130001, + GPS_CONTROLLER = 0x44130045, + GPS_0_HEALTH_DEV = 0x44130046, + GPS_1_HEALTH_DEV = 0x44130047, + + IMTQ_POLLING = 0x44140013, + IMTQ_HANDLER = 0x44140014, + TMP1075_HANDLER_TCS_0 = 0x44420004, + TMP1075_HANDLER_TCS_1 = 0x44420005, + TMP1075_HANDLER_PLPCDU_0 = 0x44420006, + TMP1075_HANDLER_PLPCDU_1 = 0x44420007, + TMP1075_HANDLER_IF_BOARD = 0x44420008, + + PCDU_HANDLER = 0x442000A1, + P60DOCK_HANDLER = 0x44250000, + PDU1_HANDLER = 0x44250001, + PDU2_HANDLER = 0x44250002, + ACU_HANDLER = 0x44250003, + BPX_BATT_HANDLER = 0x44260000, + PLPCDU_HANDLER = 0x44300000, + RAD_SENSOR = 0x443200A5, + PLOC_UPDATER = 0x44330000, + PLOC_MEMORY_DUMPER = 0x44330001, + STR_COM_IF = 0x44330002, + PLOC_MPSOC_HELPER = 0x44330003, + AXI_PTME_CONFIG = 0x44330004, + PTME_CONFIG = 0x44330005, + PTME_VC0_LIVE_TM = 0x44330006, + PTME_VC1_LOG_TM = 0x44330007, + PTME_VC2_HK_TM = 0x44330008, + PTME_VC3_CFDP_TM = 0x44330009, + PLOC_MPSOC_HANDLER = 0x44330015, + PLOC_SUPERVISOR_HANDLER = 0x44330016, + PLOC_SUPERVISOR_HELPER = 0x44330017, + PLOC_MPSOC_COMMUNICATION = 0x44330018, + SCEX = 0x44330032, + SOLAR_ARRAY_DEPL_HANDLER = 0x444100A2, + HEATER_HANDLER = 0x444100A4, + + /** + * Not yet specified which pt1000 will measure which device/location in the satellite. + * Therefore object ids are named according to the IC naming of the RTDs in the schematic. + */ + RTD_0_IC3_PLOC_HEATSPREADER = 0x44420016, + RTD_1_IC4_PLOC_MISSIONBOARD = 0x44420017, + RTD_2_IC5_4K_CAMERA = 0x44420018, + RTD_3_IC6_DAC_HEATSPREADER = 0x44420019, + RTD_4_IC7_STARTRACKER = 0x44420020, + RTD_5_IC8_RW1_MX_MY = 0x44420021, + RTD_6_IC9_DRO = 0x44420022, + RTD_7_IC10_SCEX = 0x44420023, + RTD_8_IC11_X8 = 0x44420024, + RTD_9_IC12_HPA = 0x44420025, + RTD_10_IC13_PL_TX = 0x44420026, + RTD_11_IC14_MPA = 0x44420027, + RTD_12_IC15_ACU = 0x44420028, + RTD_13_IC16_PLPCDU_HEATSPREADER = 0x44420029, + RTD_14_IC17_TCS_BOARD = 0x44420030, + RTD_15_IC18_IMTQ = 0x44420031, + + // Name convention for SUS devices + // SUS___LOC_XYZ_PT_ + // LOC: Location + // PT: Pointing + // N/R: Nominal/Redundant + // F/M/B: Forward/Middle/Backwards + SUS_0_N_LOC_XFYFZM_PT_XF = 0x44120032, + SUS_6_R_LOC_XFYBZM_PT_XF = 0x44120038, + + SUS_1_N_LOC_XBYFZM_PT_XB = 0x44120033, + SUS_7_R_LOC_XBYBZM_PT_XB = 0x44120039, + + SUS_2_N_LOC_XFYBZB_PT_YB = 0x44120034, + SUS_8_R_LOC_XBYBZB_PT_YB = 0x44120040, + + SUS_3_N_LOC_XFYBZF_PT_YF = 0x44120035, + SUS_9_R_LOC_XBYBZB_PT_YF = 0x44120041, + + SUS_4_N_LOC_XMYFZF_PT_ZF = 0x44120036, + SUS_10_N_LOC_XMYBZF_PT_ZF = 0x44120042, + + SUS_5_N_LOC_XFYMZB_PT_ZB = 0x44120037, + SUS_11_R_LOC_XBYMZB_PT_ZB = 0x44120043, + + SYRLINKS_HANDLER = 0x445300A3, + SYRLINKS_COM_HANDLER = 0x445300A4, + + /* 0x49 ('I') for Communication Interfaces */ + ACS_BOARD_POLLING_TASK = 0x49060004, + RW_POLLING_TASK = 0x49060005, + SPI_RTD_COM_IF = 0x49060006, + SUS_POLLING_TASK = 0x49060007, + + // 0x60 for other stuff + HEATER_0_PLOC_PROC_BRD = 0x60000000, + HEATER_1_PCDU_BRD = 0x60000001, + HEATER_2_ACS_BRD = 0x60000002, + HEATER_3_OBC_BRD = 0x60000003, + HEATER_4_CAMERA = 0x60000004, + HEATER_5_STR = 0x60000005, + HEATER_6_DRO = 0x60000006, + HEATER_7_SYRLINKS = 0x60000007, + + // 0x73 ('s') for assemblies and system/subsystem components + ACS_BOARD_ASS = 0x73000001, + SUS_BOARD_ASS = 0x73000002, + TCS_BOARD_ASS = 0x73000003, + RW_ASSY = 0x73000004, + CAM_SWITCHER = 0x73000006, + SYRLINKS_ASSY = 0x73000007, + IMTQ_ASSY = 0x73000008, + STR_ASSY = 0x73000009, + EIVE_SYSTEM = 0x73010000, + ACS_SUBSYSTEM = 0x73010001, + PL_SUBSYSTEM = 0x73010002, + TCS_SUBSYSTEM = 0x73010003, + COM_SUBSYSTEM = 0x73010004, + EPS_SUBSYSTEM = 0x73010005, + + TM_FUNNEL = 0x73000100, + PUS_TM_FUNNEL = 0x73000101, + CFDP_TM_FUNNEL = 0x73000102, + CFDP_HANDLER = 0x73000205, + CFDP_DISTRIBUTOR = 0x73000206, + CFDP_FAULT_HANDLER = 0x73000207, + MISC_TM_STORE = 0x73020001, + OK_TM_STORE = 0x73020002, + NOT_OK_TM_STORE = 0x73020003, + HK_TM_STORE = 0x73020004, + CFDP_TM_STORE = 0x73030000, + + LIVE_TM_TASK = 0x73040000, + LOG_STORE_AND_TM_TASK = 0x73040001, + HK_STORE_AND_TM_TASK = 0x73040002, + CFDP_STORE_AND_TM_TASK = 0x73040003, + DOWNLINK_RAM_STORE = 0x73040004, + + // Other stuff + THERMAL_TEMP_INSERTER = 0x90000003, +}; +} + +#endif /* COMMON_CONFIG_COMMONOBJECTS_H_ */ diff --git a/common/config/eive/resultClassIds.h b/common/config/eive/resultClassIds.h new file mode 100644 index 0000000..8c30dd1 --- /dev/null +++ b/common/config/eive/resultClassIds.h @@ -0,0 +1,50 @@ +#ifndef COMMON_CONFIG_COMMONCLASSIDS_H_ +#define COMMON_CONFIG_COMMONCLASSIDS_H_ + +#include + +#include + +namespace CLASS_ID { +enum commonClassIds : uint8_t { + COMMON_CLASS_ID_START = FW_CLASS_ID_COUNT, + PCDU_HANDLER, // PCDU + HEATER_HANDLER, // HEATER + SYRLINKS_HANDLER, // SYRLINKS + IMTQ_HANDLER, // IMTQ + RW_HANDLER, // RWHA + STR_HANDLER, // STRH + DWLPWRON_CMD, // DWLPWRON + MPSOC_TM, // MPTM + PLOC_SUPERVISOR_HANDLER, // PLSV + PLOC_SUPV_HELPER, // PLSPVhLP + SUS_HANDLER, // SUSS + CCSDS_IP_CORE_BRIDGE, // IPCI + PTME, // PTME + PLOC_UPDATER, // PLUD + STR_HELPER, // STRHLP + GOM_SPACE_HANDLER, // GOMS + PLOC_MEMORY_DUMPER, // PLMEMDUMP + PDEC_HANDLER, // PDEC + CCSDS_HANDLER, // CCSDS + RATE_SETTER, // RS + ARCSEC_JSON_BASE, // JSONBASE + NVM_PARAM_BASE, // NVMB + FILE_SYSTEM_HELPER, // FSHLP + PLOC_MPSOC_HELPER, // PLMPHLP + SA_DEPL_HANDLER, // SADPL + MPSOC_RETURN_VALUES_IF, // MPSOCRTVIF + SUPV_RETURN_VALUES_IF, // SPVRTVIF + ACS_CTRL, // ACSCTRL + ACS_MEKF, // ACSMEKF + SD_CARD_MANAGER, // SDMA + LOCAL_PARAM_HANDLER, // LPH + PERSISTENT_TM_STORE, // PTM + TM_SINK, // TMS + VIRTUAL_CHANNEL, // VCS + PLOC_MPSOC_COM, // PLMPCOM + COMMON_CLASS_ID_END // [EXPORT] : [END] +}; +} + +#endif /* COMMON_CONFIG_COMMONCLASSIDS_H_ */ diff --git a/common/config/lwgps_opts.h b/common/config/lwgps_opts.h new file mode 100644 index 0000000..2be39f1 --- /dev/null +++ b/common/config/lwgps_opts.h @@ -0,0 +1,48 @@ +/** + * \file lwgps_opts_template.h + * \brief LwGPS configuration file + */ + +/* + * Copyright (c) 2020 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwGPS - Lightweight GPS NMEA parser library. + * + * Author: Tilen MAJERLE + * Version: v2.1.0 + */ +#ifndef LWGPS_HDR_OPTS_H +#define LWGPS_HDR_OPTS_H + +/* Rename this file to "lwgps_opts.h" for your application */ + +/* + * Open "include/lwgps/lwgps_opt.h" and + * copy & replace here settings you want to change values + */ + +#ifndef __DOXYGEN__ +#define __DOXYGEN__ 0 +#endif + +#endif /* LWGPS_HDR_OPTS_H */ diff --git a/common/config/tmtc/pusIds.h b/common/config/tmtc/pusIds.h new file mode 100644 index 0000000..b44d7e2 --- /dev/null +++ b/common/config/tmtc/pusIds.h @@ -0,0 +1,25 @@ +#ifndef COMMON_CONFIG_TMTC_PUSIDS_H_ +#define COMMON_CONFIG_TMTC_PUSIDS_H_ + +namespace pus { +enum Ids { + PUS_SERVICE_1 = 1, + PUS_SERVICE_2 = 2, + PUS_SERVICE_3 = 3, + PUS_SERVICE_3_PSB = 3, + PUS_SERVICE_5 = 5, + PUS_SERVICE_6 = 6, + PUS_SERVICE_8 = 8, + PUS_SERVICE_9 = 9, + PUS_SERVICE_11 = 11, + PUS_SERVICE_15 = 15, + PUS_SERVICE_17 = 17, + PUS_SERVICE_19 = 19, + PUS_SERVICE_20 = 20, + PUS_SERVICE_23 = 23, + PUS_SERVICE_200 = 200, + PUS_SERVICE_201 = 201, +}; +}; + +#endif /* COMMON_CONFIG_TMTC_PUSIDS_H_ */ diff --git a/doc/XSC-1542-6025-i_Q7RevB_User_Manual.pdf b/doc/XSC-1542-6025-i_Q7RevB_User_Manual.pdf new file mode 100644 index 0000000..0ab8798 Binary files /dev/null and b/doc/XSC-1542-6025-i_Q7RevB_User_Manual.pdf differ diff --git a/doc/deprecated-Q7S-user-manual14042020.pdf b/doc/deprecated-Q7S-user-manual14042020.pdf new file mode 100644 index 0000000..70ab7bb Binary files /dev/null and b/doc/deprecated-Q7S-user-manual14042020.pdf differ diff --git a/doc/img/ProcessSettings.png b/doc/img/ProcessSettings.png new file mode 100644 index 0000000..5a8c3c9 Binary files /dev/null and b/doc/img/ProcessSettings.png differ diff --git a/doc/img/eive-logo.png b/doc/img/eive-logo.png new file mode 100644 index 0000000..0ce27fe Binary files /dev/null and b/doc/img/eive-logo.png differ diff --git a/doc/img/vivado-edition.png b/doc/img/vivado-edition.png new file mode 100644 index 0000000..c34051f Binary files /dev/null and b/doc/img/vivado-edition.png differ diff --git a/doc/img/vivado-hl-design.png b/doc/img/vivado-hl-design.png new file mode 100644 index 0000000..3603f8c Binary files /dev/null and b/doc/img/vivado-hl-design.png differ diff --git a/doc/img/xilinx-install.PNG b/doc/img/xilinx-install.PNG new file mode 100644 index 0000000..eff68ac Binary files /dev/null and b/doc/img/xilinx-install.PNG differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..48acea6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.3" + +services: + build-host: + build: + context: . + dockerfile: bsp_hosted/Dockerfile + + build-obsw: + build: + context: . + dockerfile: bsp_q7s/Dockerfile + + build-rpi: + build: + context: . + dockerfile: bsp_linux_board/Dockerfile + diff --git a/dummies/AcuDummy.cpp b/dummies/AcuDummy.cpp new file mode 100644 index 0000000..c9844eb --- /dev/null +++ b/dummies/AcuDummy.cpp @@ -0,0 +1,88 @@ +#include "AcuDummy.h" + +#include + +AcuDummy::AcuDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, bool enableHkSets) + : DeviceHandlerBase(objectId, comif, comCookie), + coreHk(this), + auxHk(this), + enableHkSets(enableHkSets) {} + +AcuDummy::~AcuDummy() {} + +void AcuDummy::doStartUp() { setMode(MODE_NORMAL); } + +void AcuDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t AcuDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { return NOTHING_TO_SEND; } + +ReturnValue_t AcuDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t AcuDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t AcuDummy::scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t AcuDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void AcuDummy::fillCommandAndReplyMap() {} + +uint32_t AcuDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t AcuDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + using namespace ACU; + localDataPoolMap.emplace(pool::ACU_CURRENT_IN_CHANNELS, new PoolEntry(6)); + localDataPoolMap.emplace(pool::ACU_VOLTAGE_IN_CHANNELS, new PoolEntry(6)); + + localDataPoolMap.emplace(pool::ACU_VCC, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_VBAT, new PoolEntry({0})); + + localDataPoolMap.emplace(ACU::pool::ACU_TEMPERATURES, + new PoolEntry({10.0, 10.0, 10.0}, true)); + + localDataPoolMap.emplace(pool::ACU_MPPT_MODE, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::ACU_VBOOST_IN_CHANNELS, new PoolEntry(6)); + localDataPoolMap.emplace(pool::ACU_POWER_IN_CHANNELS, new PoolEntry(6)); + + localDataPoolMap.emplace(pool::ACU_DAC_ENABLES, new PoolEntry(3)); + localDataPoolMap.emplace(pool::ACU_DAC_RAW_CHANNELS, new PoolEntry(6)); + + localDataPoolMap.emplace(pool::ACU_BOOTCAUSE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_BOOTCNT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_UPTIME, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_RESET_CAUSE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_MPPT_TIME, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_MPPT_PERIOD, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::ACU_DEVICES, new PoolEntry(8)); + localDataPoolMap.emplace(pool::ACU_DEVICES_STATUS, new PoolEntry(8)); + + localDataPoolMap.emplace(pool::ACU_WDT_CNT_GND, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_WDT_GND_LEFT, new PoolEntry({0})); + + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(coreHk.getSid(), enableHkSets, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(auxHk.getSid(), enableHkSets, 6000.0)); + return returnvalue::OK; +} + +LocalPoolDataSetBase *AcuDummy::getDataSetHandle(sid_t sid) { + if (sid == coreHk.getSid()) { + return &coreHk; + } else if (sid == auxHk.getSid()) { + return &auxHk; + } + return nullptr; +} diff --git a/dummies/AcuDummy.h b/dummies/AcuDummy.h new file mode 100644 index 0000000..8d85528 --- /dev/null +++ b/dummies/AcuDummy.h @@ -0,0 +1,39 @@ +#ifndef DUMMIES_ACUDUMMY_H_ +#define DUMMIES_ACUDUMMY_H_ + +#include +#include + +class AcuDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + AcuDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, bool enableHkSets); + virtual ~AcuDummy(); + + protected: + ACU::CoreHk coreHk; + ACU::AuxHk auxHk; + bool enableHkSets; + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; +}; + +#endif /* DUMMIES_ACUDUMMY_H_ */ diff --git a/dummies/BatteryDummy.cpp b/dummies/BatteryDummy.cpp new file mode 100644 index 0000000..ca35581 --- /dev/null +++ b/dummies/BatteryDummy.cpp @@ -0,0 +1,54 @@ +#include "BatteryDummy.h" + +BatteryDummy::BatteryDummy(DhbConfig cfg) + : FreshDeviceHandlerBase(cfg), cfgSet(this), hkSet(this) {} + +void BatteryDummy::performDeviceOperation(uint8_t opCode) {} + +LocalPoolDataSetBase* BatteryDummy::getDataSetHandle(sid_t sid) { + if (sid == hkSet.getSid()) { + return &hkSet; + } + if (sid == cfgSet.getSid()) { + return &cfgSet; + } + return nullptr; +} + +ReturnValue_t BatteryDummy::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(bpxBat::BATT_TEMP_1, &battTemp1); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_2, &battTemp2); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_3, &battTemp3); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_4, &battTemp4); + localDataPoolMap.emplace(bpxBat::CHARGE_CURRENT, &chargeCurrent); + localDataPoolMap.emplace(bpxBat::DISCHARGE_CURRENT, &dischargeCurrent); + localDataPoolMap.emplace(bpxBat::HEATER_CURRENT, &heaterCurrent); + localDataPoolMap.emplace(bpxBat::BATT_VOLTAGE, &battVolt); + localDataPoolMap.emplace(bpxBat::REBOOT_COUNTER, &rebootCounter); + localDataPoolMap.emplace(bpxBat::BOOTCAUSE, &bootCause); + + localDataPoolMap.emplace(bpxBat::BATTERY_HEATER_MODE, &battheatMode); + localDataPoolMap.emplace(bpxBat::BATTHEAT_LOW_LIMIT, &battheatLow); + localDataPoolMap.emplace(bpxBat::BATTHEAT_HIGH_LIMIT, &battheatHigh); + + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(hkSet.getSid(), true, 20.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(cfgSet.getSid(), false, 30.0)); + return returnvalue::OK; +} + +ReturnValue_t BatteryDummy::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) { + return returnvalue::OK; +} + +ReturnValue_t BatteryDummy::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + return returnvalue::OK; +} + +ReturnValue_t BatteryDummy::handleCommandMessage(CommandMessage* message) { + return returnvalue::FAILED; +} \ No newline at end of file diff --git a/dummies/BatteryDummy.h b/dummies/BatteryDummy.h new file mode 100644 index 0000000..84b8fe0 --- /dev/null +++ b/dummies/BatteryDummy.h @@ -0,0 +1,52 @@ +#pragma once + +#include "fsfw/devicehandlers/FreshDeviceHandlerBase.h" +#include "mission/power/bpxBattDefs.h" + +/// @brief +class BatteryDummy : public FreshDeviceHandlerBase { + public: + BatteryDummy(DhbConfig cfg); + + private: + /** + * Periodic helper executed function, implemented by child class. + */ + void performDeviceOperation(uint8_t opCode) override; + + /** + * Implemented by child class. Handle all command messages which are + * not health, mode, action or housekeeping messages. + * @param message + * @return + */ + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + + // HK manager abstract functions. + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + // Mode abstract functions + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + // Action override. Forward to user. + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + + BpxBatteryCfg cfgSet; + BpxBatteryHk hkSet; + PoolEntry chargeCurrent = PoolEntry({0}); + PoolEntry dischargeCurrent = PoolEntry({0}); + PoolEntry heaterCurrent = PoolEntry({0}); + PoolEntry battVolt = PoolEntry({16'000}); + PoolEntry battTemp1 = PoolEntry({10}, true); + PoolEntry battTemp2 = PoolEntry({10}, true); + PoolEntry battTemp3 = PoolEntry({10}, true); + PoolEntry battTemp4 = PoolEntry({10}, true); + PoolEntry rebootCounter = PoolEntry({0}); + PoolEntry bootCause = PoolEntry({0}); + PoolEntry battheatMode = PoolEntry({0}); + PoolEntry battheatLow = PoolEntry({0}); + PoolEntry battheatHigh = PoolEntry({0}); +}; \ No newline at end of file diff --git a/dummies/BpxDummy.cpp b/dummies/BpxDummy.cpp new file mode 100644 index 0000000..085bd84 --- /dev/null +++ b/dummies/BpxDummy.cpp @@ -0,0 +1,55 @@ +#include "BpxDummy.h" + +#include + +BpxDummy::BpxDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie) {} + +BpxDummy::~BpxDummy() {} + +void BpxDummy::doStartUp() { setMode(MODE_NORMAL); } + +void BpxDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t BpxDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { return NOTHING_TO_SEND; } + +ReturnValue_t BpxDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t BpxDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t BpxDummy::scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t BpxDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void BpxDummy::fillCommandAndReplyMap() {} + +uint32_t BpxDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t BpxDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(bpxBat::BATT_TEMP_1, &battTemp1); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_2, &battTemp2); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_3, &battTemp3); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_4, &battTemp4); + localDataPoolMap.emplace(bpxBat::CHARGE_CURRENT, &chargeCurrent); + localDataPoolMap.emplace(bpxBat::DISCHARGE_CURRENT, &dischargeCurrent); + localDataPoolMap.emplace(bpxBat::HEATER_CURRENT, &heaterCurrent); + localDataPoolMap.emplace(bpxBat::BATT_VOLTAGE, &battVolt); + localDataPoolMap.emplace(bpxBat::REBOOT_COUNTER, &rebootCounter); + localDataPoolMap.emplace(bpxBat::BOOTCAUSE, &bootCause); + + localDataPoolMap.emplace(bpxBat::BATTERY_HEATER_MODE, &battheatMode); + localDataPoolMap.emplace(bpxBat::BATTHEAT_LOW_LIMIT, &battheatLow); + localDataPoolMap.emplace(bpxBat::BATTHEAT_HIGH_LIMIT, &battheatHigh); + return returnvalue::OK; +} diff --git a/dummies/BpxDummy.h b/dummies/BpxDummy.h new file mode 100644 index 0000000..3c75cab --- /dev/null +++ b/dummies/BpxDummy.h @@ -0,0 +1,48 @@ +#ifndef DUMMIES_BPXDUMMY_H_ +#define DUMMIES_BPXDUMMY_H_ + +#include + +class BpxDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + BpxDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~BpxDummy(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + + private: + PoolEntry chargeCurrent = PoolEntry({0}); + PoolEntry dischargeCurrent = PoolEntry({0}); + PoolEntry heaterCurrent = PoolEntry({0}); + PoolEntry battVolt = PoolEntry({0}); + PoolEntry battTemp1 = PoolEntry({10}, true); + PoolEntry battTemp2 = PoolEntry({10}, true); + PoolEntry battTemp3 = PoolEntry({10}, true); + PoolEntry battTemp4 = PoolEntry({10}, true); + PoolEntry rebootCounter = PoolEntry({0}); + PoolEntry bootCause = PoolEntry({0}); + PoolEntry battheatMode = PoolEntry({0}); + PoolEntry battheatLow = PoolEntry({0}); + PoolEntry battheatHigh = PoolEntry({0}); +}; + +#endif /* DUMMIES_BPXDUMMY_H_ */ diff --git a/dummies/CMakeLists.txt b/dummies/CMakeLists.txt new file mode 100644 index 0000000..ed1b198 --- /dev/null +++ b/dummies/CMakeLists.txt @@ -0,0 +1,33 @@ +target_sources( + ${LIB_DUMMIES} + PUBLIC TemperatureSensorInserter.cpp + SusDummy.cpp + BpxDummy.cpp + ComIFDummy.cpp + ComCookieDummy.cpp + RwDummy.cpp + Max31865Dummy.cpp + PcduHandlerDummy.cpp + StarTrackerDummy.cpp + SyrlinksDummy.cpp + ImtqDummy.cpp + AcuDummy.cpp + PduDummy.cpp + P60DockDummy.cpp + SaDeploymentDummy.cpp + GpsDhbDummy.cpp + GpsCtrlDummy.cpp + GyroAdisDummy.cpp + GyroL3GD20Dummy.cpp + RadSensorDummy.cpp + MgmLIS3MDLDummy.cpp + PlPcduDummy.cpp + ExecutableComIfDummy.cpp + ScexDummy.cpp + CoreControllerDummy.cpp + PlocMpsocDummy.cpp + PlocSupervisorDummy.cpp + helperFactory.cpp + MgmRm3100Dummy.cpp + BatteryDummy.cpp + Tmp1075Dummy.cpp) diff --git a/dummies/ComCookieDummy.cpp b/dummies/ComCookieDummy.cpp new file mode 100644 index 0000000..2e2edf9 --- /dev/null +++ b/dummies/ComCookieDummy.cpp @@ -0,0 +1,5 @@ +#include "ComCookieDummy.h" + +ComCookieDummy::ComCookieDummy() {} + +ComCookieDummy::~ComCookieDummy() {} diff --git a/dummies/ComCookieDummy.h b/dummies/ComCookieDummy.h new file mode 100644 index 0000000..e4e2086 --- /dev/null +++ b/dummies/ComCookieDummy.h @@ -0,0 +1,12 @@ +#ifndef TESTS_SRC_FSFW_TESTS_UNIT_DEVICEHANDLER_COMCOOKIEDUMMY_H_ +#define TESTS_SRC_FSFW_TESTS_UNIT_DEVICEHANDLER_COMCOOKIEDUMMY_H_ + +#include "fsfw/devicehandlers/CookieIF.h" + +class ComCookieDummy : public CookieIF { + public: + ComCookieDummy(); + virtual ~ComCookieDummy(); +}; + +#endif /* TESTS_SRC_FSFW_TESTS_UNIT_DEVICEHANDLER_COMCOOKIEDUMMY_H_ */ diff --git a/dummies/ComIFDummy.cpp b/dummies/ComIFDummy.cpp new file mode 100644 index 0000000..af40fd5 --- /dev/null +++ b/dummies/ComIFDummy.cpp @@ -0,0 +1,21 @@ +#include "ComIFDummy.h" + +ComIFDummy::ComIFDummy(object_id_t objectId) : SystemObject(objectId) {} + +ComIFDummy::~ComIFDummy() {} + +ReturnValue_t ComIFDummy::initializeInterface(CookieIF *cookie) { return returnvalue::OK; } + +ReturnValue_t ComIFDummy::sendMessage(CookieIF *cookie, const uint8_t *sendData, size_t sendLen) { + return returnvalue::OK; +} + +ReturnValue_t ComIFDummy::getSendSuccess(CookieIF *cookie) { return returnvalue::OK; } + +ReturnValue_t ComIFDummy::requestReceiveMessage(CookieIF *cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t ComIFDummy::readReceivedMessage(CookieIF *cookie, uint8_t **buffer, size_t *size) { + return returnvalue::OK; +} diff --git a/dummies/ComIFDummy.h b/dummies/ComIFDummy.h new file mode 100644 index 0000000..499c2c4 --- /dev/null +++ b/dummies/ComIFDummy.h @@ -0,0 +1,26 @@ +#ifndef DUMMIES_COMIFDUMMY_H_ +#define DUMMIES_COMIFDUMMY_H_ + +#include +#include + +/** + * @brief The ComIFMock supports the simulation of various device communication error cases + * like incomplete or wrong replies and can be used to test the + * DeviceHandlerBase. + */ +class ComIFDummy : public DeviceCommunicationIF, public SystemObject { + public: + ComIFDummy(object_id_t objectId); + virtual ~ComIFDummy(); + + virtual ReturnValue_t initializeInterface(CookieIF *cookie) override; + virtual ReturnValue_t sendMessage(CookieIF *cookie, const uint8_t *sendData, + size_t sendLen) override; + virtual ReturnValue_t getSendSuccess(CookieIF *cookie) override; + virtual ReturnValue_t requestReceiveMessage(CookieIF *cookie, size_t requestLen) override; + virtual ReturnValue_t readReceivedMessage(CookieIF *cookie, uint8_t **buffer, + size_t *size) override; +}; + +#endif /* DUMMIES_COMIFDUMMY_H_ */ diff --git a/dummies/CoreControllerDummy.cpp b/dummies/CoreControllerDummy.cpp new file mode 100644 index 0000000..df2bef0 --- /dev/null +++ b/dummies/CoreControllerDummy.cpp @@ -0,0 +1,54 @@ +#include "CoreControllerDummy.h" + +#include +#include + +#include +#include + +CoreControllerDummy::CoreControllerDummy(object_id_t objectId) : ExtendedControllerBase(objectId) {} + +ReturnValue_t CoreControllerDummy::initialize() { + static bool done = false; + if (not done) { + done = true; + ReturnValue_t result = ExtendedControllerBase::initialize(); + if (result != returnvalue::OK) { + return result; + } + } + + return returnvalue::OK; +} + +ReturnValue_t CoreControllerDummy::handleCommandMessage(CommandMessage* message) { + return returnvalue::FAILED; +} + +void CoreControllerDummy::performControlOperation() { return; } + +ReturnValue_t CoreControllerDummy::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(core::TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(core::PS_VOLTAGE, new PoolEntry({0})); + localDataPoolMap.emplace(core::PL_VOLTAGE, new PoolEntry({0})); + return returnvalue::OK; +} + +LocalPoolDataSetBase* CoreControllerDummy::getDataSetHandle(sid_t sid) { + switch (sid.ownerSetId) { + default: + return nullptr; + } +} + +ReturnValue_t CoreControllerDummy::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) { + if (submode != SUBMODE_NONE) { + return INVALID_SUBMODE; + } + if ((mode != MODE_OFF) && (mode != MODE_ON) && (mode != MODE_NORMAL)) { + return INVALID_MODE; + } + return returnvalue::OK; +} diff --git a/dummies/CoreControllerDummy.h b/dummies/CoreControllerDummy.h new file mode 100644 index 0000000..8fd5076 --- /dev/null +++ b/dummies/CoreControllerDummy.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class CoreControllerDummy : public ExtendedControllerBase { + public: + CoreControllerDummy(object_id_t objectId); + + ReturnValue_t initialize() override; + + protected: + virtual ReturnValue_t handleCommandMessage(CommandMessage* message) override; + virtual void performControlOperation() override; + virtual ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + virtual LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + virtual ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; +}; diff --git a/dummies/ExecutableComIfDummy.cpp b/dummies/ExecutableComIfDummy.cpp new file mode 100644 index 0000000..becb4a7 --- /dev/null +++ b/dummies/ExecutableComIfDummy.cpp @@ -0,0 +1,27 @@ +#include + +ExecutableComIfDummy::ExecutableComIfDummy(object_id_t objectId) : SystemObject(objectId) {} + +ReturnValue_t ExecutableComIfDummy::initializeInterface(CookieIF *cookie) { + return returnvalue::OK; +} + +ReturnValue_t ExecutableComIfDummy::sendMessage(CookieIF *cookie, const uint8_t *sendData, + size_t sendLen) { + return returnvalue::OK; +} + +ReturnValue_t ExecutableComIfDummy::getSendSuccess(CookieIF *cookie) { return returnvalue::OK; } + +ReturnValue_t ExecutableComIfDummy::requestReceiveMessage(CookieIF *cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t ExecutableComIfDummy::performOperation(uint8_t operationCode) { + return returnvalue::OK; +} + +ReturnValue_t ExecutableComIfDummy::readReceivedMessage(CookieIF *cookie, uint8_t **buffer, + size_t *size) { + return returnvalue::OK; +} diff --git a/dummies/ExecutableComIfDummy.h b/dummies/ExecutableComIfDummy.h new file mode 100644 index 0000000..80e667d --- /dev/null +++ b/dummies/ExecutableComIfDummy.h @@ -0,0 +1,21 @@ +#ifndef DUMMIES_EXECUTABLECOMIFDUMMY_H_ +#define DUMMIES_EXECUTABLECOMIFDUMMY_H_ + +#include +#include +#include + +class ExecutableComIfDummy : public ExecutableObjectIF, + public DeviceCommunicationIF, + public SystemObject { + public: + ExecutableComIfDummy(object_id_t objectId); + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + ReturnValue_t initializeInterface(CookieIF *cookie) override; + ReturnValue_t sendMessage(CookieIF *cookie, const uint8_t *sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF *cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF *cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF *cookie, uint8_t **buffer, size_t *size) override; +}; + +#endif /* DUMMIES_EXECUTABLECOMIFDUMMY_H_ */ diff --git a/dummies/GpsCtrlDummy.cpp b/dummies/GpsCtrlDummy.cpp new file mode 100644 index 0000000..4ae8dc6 --- /dev/null +++ b/dummies/GpsCtrlDummy.cpp @@ -0,0 +1,36 @@ +#include "GpsCtrlDummy.h" + +GpsCtrlDummy::GpsCtrlDummy(object_id_t objectId) + : ExtendedControllerBase(objectId, 20), gpsSet(this) {} + +ReturnValue_t GpsCtrlDummy::handleCommandMessage(CommandMessage* message) { + return returnvalue::OK; +} + +void GpsCtrlDummy::performControlOperation() {} + +ReturnValue_t GpsCtrlDummy::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) { + return returnvalue::OK; +} + +LocalPoolDataSetBase* GpsCtrlDummy::getDataSetHandle(sid_t sid) { return &gpsSet; } + +ReturnValue_t GpsCtrlDummy::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(GpsHyperion::ALTITUDE, new PoolEntry({537222.3469}, true)); + localDataPoolMap.emplace(GpsHyperion::LONGITUDE, new PoolEntry({-8.8579}, true)); + localDataPoolMap.emplace(GpsHyperion::LATITUDE, new PoolEntry({49.5952}, true)); + localDataPoolMap.emplace(GpsHyperion::SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::YEAR, new PoolEntry({2023}, true)); + localDataPoolMap.emplace(GpsHyperion::MONTH, new PoolEntry({5}, true)); + localDataPoolMap.emplace(GpsHyperion::DAY, new PoolEntry({16}, true)); + localDataPoolMap.emplace(GpsHyperion::HOURS, new PoolEntry({1}, true)); + localDataPoolMap.emplace(GpsHyperion::MINUTES, new PoolEntry({0}, true)); + localDataPoolMap.emplace(GpsHyperion::SECONDS, new PoolEntry({0}, true)); + localDataPoolMap.emplace(GpsHyperion::UNIX_SECONDS, new PoolEntry({1684191600}, true)); + localDataPoolMap.emplace(GpsHyperion::SATS_IN_USE, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::SATS_IN_VIEW, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::FIX_MODE, new PoolEntry()); + return returnvalue::OK; +} diff --git a/dummies/GpsCtrlDummy.h b/dummies/GpsCtrlDummy.h new file mode 100644 index 0000000..b64647b --- /dev/null +++ b/dummies/GpsCtrlDummy.h @@ -0,0 +1,23 @@ +#ifndef DUMMIES_GPSCTRLDUMMY_H_ +#define DUMMIES_GPSCTRLDUMMY_H_ + +#include +#include + +class GpsCtrlDummy : public ExtendedControllerBase { + public: + GpsCtrlDummy(object_id_t objectId); + + private: + GpsPrimaryDataset gpsSet; + + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + void performControlOperation() override; + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; +}; + +#endif /* DUMMIES_GPSCTRLDUMMY_H_ */ diff --git a/dummies/GpsDhbDummy.cpp b/dummies/GpsDhbDummy.cpp new file mode 100644 index 0000000..73addb3 --- /dev/null +++ b/dummies/GpsDhbDummy.cpp @@ -0,0 +1,58 @@ +#include +#include + +GpsDhbDummy::GpsDhbDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie) {} + +GpsDhbDummy::~GpsDhbDummy() {} + +void GpsDhbDummy::doStartUp() {} + +void GpsDhbDummy::doShutDown() {} + +ReturnValue_t GpsDhbDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t GpsDhbDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t GpsDhbDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t GpsDhbDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t GpsDhbDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void GpsDhbDummy::fillCommandAndReplyMap() {} + +uint32_t GpsDhbDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t GpsDhbDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(GpsHyperion::LATITUDE, new PoolEntry({0.0}, 1)); + localDataPoolMap.emplace(GpsHyperion::LONGITUDE, new PoolEntry({0.0}, 1)); + localDataPoolMap.emplace(GpsHyperion::ALTITUDE, new PoolEntry({0.0})); + localDataPoolMap.emplace(GpsHyperion::SPEED, new PoolEntry({7684.2})); + localDataPoolMap.emplace(GpsHyperion::FIX_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::SATS_IN_USE, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::SATS_IN_VIEW, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::YEAR, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::MONTH, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::DAY, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::HOURS, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::MINUTES, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::SECONDS, new PoolEntry({0})); + localDataPoolMap.emplace(GpsHyperion::UNIX_SECONDS, new PoolEntry({0})); + + return returnvalue::OK; +} diff --git a/dummies/GpsDhbDummy.h b/dummies/GpsDhbDummy.h new file mode 100644 index 0000000..a1df7e4 --- /dev/null +++ b/dummies/GpsDhbDummy.h @@ -0,0 +1,33 @@ +#ifndef DUMMIES_GPSDHBDUMMY_H_ +#define DUMMIES_GPSDHBDUMMY_H_ + +#include + +class GpsDhbDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + GpsDhbDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~GpsDhbDummy(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_GPSDHBDUMMY_H_ */ diff --git a/dummies/GyroAdisDummy.cpp b/dummies/GyroAdisDummy.cpp new file mode 100644 index 0000000..6f2e8e7 --- /dev/null +++ b/dummies/GyroAdisDummy.cpp @@ -0,0 +1,52 @@ +#include "GyroAdisDummy.h" + +#include + +GyroAdisDummy::GyroAdisDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), dataset(this) {} + +GyroAdisDummy::~GyroAdisDummy() {} + +void GyroAdisDummy::doStartUp() { setMode(MODE_NORMAL); } + +void GyroAdisDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t GyroAdisDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t GyroAdisDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t GyroAdisDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t GyroAdisDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t GyroAdisDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void GyroAdisDummy::fillCommandAndReplyMap() {} + +uint32_t GyroAdisDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t GyroAdisDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(adis1650x::ANG_VELOC_X, new PoolEntry({-0.5}, true)); + localDataPoolMap.emplace(adis1650x::ANG_VELOC_Y, new PoolEntry({0.2}, true)); + localDataPoolMap.emplace(adis1650x::ANG_VELOC_Z, new PoolEntry({-1.2}, true)); + localDataPoolMap.emplace(adis1650x::ACCELERATION_X, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::ACCELERATION_Y, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::ACCELERATION_Z, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::TEMPERATURE, new PoolEntry({10.0}, true)); + + return returnvalue::OK; +} diff --git a/dummies/GyroAdisDummy.h b/dummies/GyroAdisDummy.h new file mode 100644 index 0000000..03c69ba --- /dev/null +++ b/dummies/GyroAdisDummy.h @@ -0,0 +1,35 @@ +#ifndef DUMMIES_GYROADISDUMMY_H_ +#define DUMMIES_GYROADISDUMMY_H_ + +#include +#include + +class GyroAdisDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + GyroAdisDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~GyroAdisDummy(); + + protected: + AdisGyroPrimaryDataset dataset; + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_GYROADISDUMMY_H_ */ diff --git a/dummies/GyroL3GD20Dummy.cpp b/dummies/GyroL3GD20Dummy.cpp new file mode 100644 index 0000000..ef2a7e5 --- /dev/null +++ b/dummies/GyroL3GD20Dummy.cpp @@ -0,0 +1,48 @@ +#include "GyroL3GD20Dummy.h" + +#include + +GyroL3GD20Dummy::GyroL3GD20Dummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie) {} + +GyroL3GD20Dummy::~GyroL3GD20Dummy() {} + +void GyroL3GD20Dummy::doStartUp() { setMode(MODE_NORMAL); } + +void GyroL3GD20Dummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t GyroL3GD20Dummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t GyroL3GD20Dummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t GyroL3GD20Dummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t GyroL3GD20Dummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t GyroL3GD20Dummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void GyroL3GD20Dummy::fillCommandAndReplyMap() {} + +uint32_t GyroL3GD20Dummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t GyroL3GD20Dummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(l3gd20h::ANG_VELOC_X, new PoolEntry({1.2}, true)); + localDataPoolMap.emplace(l3gd20h::ANG_VELOC_Y, new PoolEntry({-0.1}, true)); + localDataPoolMap.emplace(l3gd20h::ANG_VELOC_Z, new PoolEntry({0.7}, true)); + localDataPoolMap.emplace(l3gd20h::TEMPERATURE, new PoolEntry({0.0})); + return returnvalue::OK; +} diff --git a/dummies/GyroL3GD20Dummy.h b/dummies/GyroL3GD20Dummy.h new file mode 100644 index 0000000..7af69f5 --- /dev/null +++ b/dummies/GyroL3GD20Dummy.h @@ -0,0 +1,33 @@ +#ifndef DUMMIES_GYROL3GD20DUMMY_H_ +#define DUMMIES_GYROL3GD20DUMMY_H_ + +#include + +class GyroL3GD20Dummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + GyroL3GD20Dummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~GyroL3GD20Dummy(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_GYROL3GD20DUMMY_H_ */ diff --git a/dummies/ImtqDummy.cpp b/dummies/ImtqDummy.cpp new file mode 100644 index 0000000..8570a9b --- /dev/null +++ b/dummies/ImtqDummy.cpp @@ -0,0 +1,120 @@ +#include "ImtqDummy.h" + +#include + +ImtqDummy::ImtqDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, + power::Switch_t pwrSwitcher, bool enableHkSets) + : DeviceHandlerBase(objectId, comif, comCookie), + enableHkSets(enableHkSets), + statusSet(this), + dipoleSet(*this), + rawMtmNoTorque(this), + hkDatasetNoTorque(this), + rawMtmWithTorque(this), + hkDatasetWithTorque(this), + calMtmMeasurementSet(this), + switcher(pwrSwitcher) {} + +ImtqDummy::~ImtqDummy() = default; + +void ImtqDummy::doStartUp() { setMode(MODE_ON); } + +void ImtqDummy::doShutDown() { setMode(_MODE_POWER_DOWN); } + +ReturnValue_t ImtqDummy::getSwitches(const uint8_t **switches, uint8_t *numberOfSwitches) { + if (switcher != power::NO_SWITCH) { + *numberOfSwitches = 1; + *switches = &switcher; + return returnvalue::OK; + } + return DeviceHandlerBase::NO_SWITCH; +} + +ReturnValue_t ImtqDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { return NOTHING_TO_SEND; } + +ReturnValue_t ImtqDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t ImtqDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t ImtqDummy::scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t ImtqDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void ImtqDummy::fillCommandAndReplyMap() {} + +uint32_t ImtqDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t ImtqDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(imtq::MCU_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::MGM_CAL_NT, new PoolEntry({0.0, 0.0, 0.0})); + localDataPoolMap.emplace(imtq::ACTUATION_CAL_STATUS, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::MTM_RAW, new PoolEntry({0.12, 0.76, -0.45}, true)); + localDataPoolMap.emplace(imtq::ACTUATION_RAW_STATUS, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::DIPOLES_ID, new PoolEntry({0, 0, 0})); + localDataPoolMap.emplace(imtq::CURRENT_TORQUE_DURATION, new PoolEntry({0})); + + // ENG HK No Torque + localDataPoolMap.emplace(imtq::DIGITAL_VOLTAGE_MV, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::ANALOG_VOLTAGE_MV, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::DIGITAL_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::ANALOG_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::COIL_CURRENTS, &coilCurrentsMilliampsNoTorque); + localDataPoolMap.emplace(imtq::COIL_TEMPERATURES, &coilTempsNoTorque); + localDataPoolMap.emplace(imtq::MCU_TEMPERATURE, new PoolEntry({0})); + + // ENG HK With Torque + localDataPoolMap.emplace(imtq::DIGITAL_VOLTAGE_MV_WT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::ANALOG_VOLTAGE_MV_WT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::DIGITAL_CURRENT_WT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::ANALOG_CURRENT_WT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::COIL_CURRENTS_WT, &coilCurrentsMilliampsWithTorque); + localDataPoolMap.emplace(imtq::COIL_TEMPERATURES_WT, &coilTempsWithTorque); + localDataPoolMap.emplace(imtq::MCU_TEMPERATURE_WT, new PoolEntry({0})); + + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(hkDatasetNoTorque.getSid(), enableHkSets, 30.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(hkDatasetWithTorque.getSid(), enableHkSets, 30.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(rawMtmNoTorque.getSid(), false, 10.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(rawMtmWithTorque.getSid(), false, 10.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(calMtmMeasurementSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(statusSet.getSid(), false, 10.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(dipoleSet.getSid(), false, 10.0)); + return DeviceHandlerBase::initializeLocalDataPool(localDataPoolMap, poolManager); +} + +LocalPoolDataSetBase *ImtqDummy::getDataSetHandle(sid_t sid) { + if (sid == hkDatasetNoTorque.getSid()) { + return &hkDatasetNoTorque; + } else if (sid == dipoleSet.getSid()) { + return &dipoleSet; + } else if (sid == statusSet.getSid()) { + return &statusSet; + } else if (sid == hkDatasetWithTorque.getSid()) { + return &hkDatasetWithTorque; + } else if (sid == rawMtmWithTorque.getSid()) { + return &rawMtmWithTorque; + } else if (sid == calMtmMeasurementSet.getSid()) { + return &calMtmMeasurementSet; + } else if (sid == rawMtmNoTorque.getSid()) { + return &rawMtmNoTorque; + } + return nullptr; +} diff --git a/dummies/ImtqDummy.h b/dummies/ImtqDummy.h new file mode 100644 index 0000000..8c76aff --- /dev/null +++ b/dummies/ImtqDummy.h @@ -0,0 +1,70 @@ +#ifndef DUMMIES_IMTQDUMMY_H_ +#define DUMMIES_IMTQDUMMY_H_ + +#include + +#include "mission/acs/imtqHelpers.h" + +class ImtqDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + ImtqDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, + power::Switch_t pwrSwitcher, bool enableHkSets); + ~ImtqDummy() override; + + protected: + bool enableHkSets; + + imtq::StatusDataset statusSet; + imtq::DipoleActuationSet dipoleSet; + imtq::RawMtmMeasurementNoTorque rawMtmNoTorque; + imtq::HkDatasetNoTorque hkDatasetNoTorque; + + imtq::RawMtmMeasurementWithTorque rawMtmWithTorque; + imtq::HkDatasetWithTorque hkDatasetWithTorque; + + imtq::CalibratedMtmMeasurementSet calMtmMeasurementSet; + + PoolEntry statusMode = PoolEntry({0}); + PoolEntry statusError = PoolEntry({0}); + PoolEntry statusConfig = PoolEntry({0}); + PoolEntry statusUptime = PoolEntry({0}); + + PoolEntry mgmCalEntry = PoolEntry(3); + PoolEntry dipolesPoolEntry = PoolEntry({0, 0, 0}, false); + PoolEntry torqueDurationEntry = PoolEntry({0}, false); + PoolEntry coilCurrentsMilliampsNoTorque = PoolEntry(3); + PoolEntry coilCurrentsMilliampsWithTorque = PoolEntry(3); + PoolEntry coilTempsNoTorque = PoolEntry(3); + PoolEntry coilTempsWithTorque = PoolEntry(3); + PoolEntry mtmRawNoTorque = PoolEntry(3); + PoolEntry actStatusNoTorque = PoolEntry(1); + PoolEntry mtmRawWithTorque = PoolEntry(3); + PoolEntry actStatusWithTorque = PoolEntry(1); + + power::Switch_t switcher = power::NO_SWITCH; + + ReturnValue_t getSwitches(const uint8_t **switches, uint8_t *numberOfSwitches) override; + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; +}; + +#endif /* DUMMIES_IMTQDUMMY_H_ */ diff --git a/dummies/Max31865Dummy.cpp b/dummies/Max31865Dummy.cpp new file mode 100644 index 0000000..99fc336 --- /dev/null +++ b/dummies/Max31865Dummy.cpp @@ -0,0 +1,58 @@ +#include "Max31865Dummy.h" + +#include "fsfw/datapool/PoolReadGuard.h" + +using namespace returnvalue; + +Max31865Dummy::Max31865Dummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), set(this, EiveMax31855::EXCHANGE_SET_ID) {} +void Max31865Dummy::doStartUp() { setMode(MODE_ON); } +void Max31865Dummy::doShutDown() { + PoolReadGuard pg(&set); + set.setValidity(false, true); + setMode(MODE_OFF); +} +ReturnValue_t Max31865Dummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} +ReturnValue_t Max31865Dummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} +ReturnValue_t Max31865Dummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return NOTHING_TO_SEND; +} +ReturnValue_t Max31865Dummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return 0; +} +ReturnValue_t Max31865Dummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return 0; +} +void Max31865Dummy::fillCommandAndReplyMap() {} +uint32_t Max31865Dummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 0; } +ReturnValue_t Max31865Dummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + using namespace MAX31865; + localDataPoolMap.emplace(static_cast(PoolIds::RTD_VALUE), new PoolEntry({0})); + localDataPoolMap.emplace(static_cast(PoolIds::TEMPERATURE_C), + new PoolEntry({10.0}, true)); + localDataPoolMap.emplace(static_cast(PoolIds::LAST_FAULT_BYTE), + new PoolEntry({0})); + localDataPoolMap.emplace(static_cast(PoolIds::FAULT_BYTE), new PoolEntry({0})); + return OK; +} + +void Max31865Dummy::setTemperature(float temperature, bool valid) { + PoolReadGuard pg(&set); + if (pg.getReadResult() == returnvalue::OK) { + set.temperatureCelcius.value = temperature; + set.setValidity(valid, true); + } +} + +LocalPoolDataSetBase *Max31865Dummy::getDataSetHandle(sid_t sid) { return &set; } +Max31865Dummy::Max31865Dummy(object_id_t objectId, CookieIF *cookie) + : DeviceHandlerBase(objectId, objects::DUMMY_COM_IF, cookie), + set(this, EiveMax31855::EXCHANGE_SET_ID) {} diff --git a/dummies/Max31865Dummy.h b/dummies/Max31865Dummy.h new file mode 100644 index 0000000..56b13a4 --- /dev/null +++ b/dummies/Max31865Dummy.h @@ -0,0 +1,34 @@ +#ifndef EIVE_OBSW_MAX31865DUMMY_H +#define EIVE_OBSW_MAX31865DUMMY_H + +#include + +#include "fsfw/devicehandlers/DeviceHandlerBase.h" + +class Max31865Dummy : public DeviceHandlerBase { + public: + Max31865Dummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + Max31865Dummy(object_id_t objectId, CookieIF *comCookie); + + void setTemperature(float temperature, bool setValid); + + private: + MAX31865::PrimarySet set; + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; +}; + +#endif // EIVE_OBSW_MAX31865DUMMY_H diff --git a/dummies/MgmLIS3MDLDummy.cpp b/dummies/MgmLIS3MDLDummy.cpp new file mode 100644 index 0000000..03b163f --- /dev/null +++ b/dummies/MgmLIS3MDLDummy.cpp @@ -0,0 +1,47 @@ +#include "MgmLIS3MDLDummy.h" + +#include + +MgmLIS3MDLDummy::MgmLIS3MDLDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), dataset(this) {} + +MgmLIS3MDLDummy::~MgmLIS3MDLDummy() {} + +void MgmLIS3MDLDummy::doStartUp() { setMode(MODE_NORMAL); } + +void MgmLIS3MDLDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t MgmLIS3MDLDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmLIS3MDLDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmLIS3MDLDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t MgmLIS3MDLDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t MgmLIS3MDLDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void MgmLIS3MDLDummy::fillCommandAndReplyMap() {} + +uint32_t MgmLIS3MDLDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t MgmLIS3MDLDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(mgmLis3::TEMPERATURE_CELCIUS, new PoolEntry({10.0}, true)); + localDataPoolMap.emplace(mgmLis3::FIELD_STRENGTHS, + new PoolEntry({1.02, 0.56, -0.78}, true)); + return returnvalue::OK; +} diff --git a/dummies/MgmLIS3MDLDummy.h b/dummies/MgmLIS3MDLDummy.h new file mode 100644 index 0000000..214f2f9 --- /dev/null +++ b/dummies/MgmLIS3MDLDummy.h @@ -0,0 +1,35 @@ +#ifndef DUMMIES_MGMLIS3MDLDUMMY_H_ +#define DUMMIES_MGMLIS3MDLDUMMY_H_ + +#include +#include + +class MgmLIS3MDLDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + MgmLIS3MDLDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~MgmLIS3MDLDummy(); + + protected: + mgmLis3::MgmPrimaryDataset dataset; + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_MGMLIS3MDLDUMMY_H_ */ diff --git a/dummies/MgmRm3100Dummy.cpp b/dummies/MgmRm3100Dummy.cpp new file mode 100644 index 0000000..2c0c3f7 --- /dev/null +++ b/dummies/MgmRm3100Dummy.cpp @@ -0,0 +1,46 @@ +#include "MgmRm3100Dummy.h" + +using namespace returnvalue; + +MgmRm3100Dummy::MgmRm3100Dummy(object_id_t objectId, object_id_t comif, CookieIF* comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), dataset(this) {} + +MgmRm3100Dummy::~MgmRm3100Dummy() = default; + +void MgmRm3100Dummy::doStartUp() { setMode(MODE_NORMAL); } + +void MgmRm3100Dummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t MgmRm3100Dummy::buildNormalDeviceCommand(DeviceCommandId_t* id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmRm3100Dummy::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmRm3100Dummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmRm3100Dummy::scanForReply(const uint8_t* start, size_t len, + DeviceCommandId_t* foundId, size_t* foundLen) { + return OK; +} + +ReturnValue_t MgmRm3100Dummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) { + return OK; +} + +void MgmRm3100Dummy::fillCommandAndReplyMap() {} + +uint32_t MgmRm3100Dummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 0; } + +ReturnValue_t MgmRm3100Dummy::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(mgmRm3100::FIELD_STRENGTHS, + new PoolEntry({0.87, -0.95, 0.11}, true)); + return returnvalue::OK; +} diff --git a/dummies/MgmRm3100Dummy.h b/dummies/MgmRm3100Dummy.h new file mode 100644 index 0000000..0fa1004 --- /dev/null +++ b/dummies/MgmRm3100Dummy.h @@ -0,0 +1,30 @@ +#ifndef DUMMIES_MGMRM3100DUMMY_H_ +#define DUMMIES_MGMRM3100DUMMY_H_ + +#include + +#include "fsfw/devicehandlers/DeviceHandlerBase.h" + +class MgmRm3100Dummy : public DeviceHandlerBase { + public: + MgmRm3100Dummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~MgmRm3100Dummy(); + + protected: + mgmRm3100::Rm3100PrimaryDataset dataset; + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_MGMRM3100DUMMY_H_ */ diff --git a/dummies/P60DockDummy.cpp b/dummies/P60DockDummy.cpp new file mode 100644 index 0000000..79ad8ca --- /dev/null +++ b/dummies/P60DockDummy.cpp @@ -0,0 +1,50 @@ +#include "P60DockDummy.h" + +#include + +#include "fsfw/datapool/PoolReadGuard.h" + +P60DockDummy::P60DockDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie) {} + +P60DockDummy::~P60DockDummy() {} + +void P60DockDummy::doStartUp() { setMode(MODE_NORMAL); } + +void P60DockDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t P60DockDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t P60DockDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t P60DockDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t P60DockDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t P60DockDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void P60DockDummy::fillCommandAndReplyMap() {} + +uint32_t P60DockDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t P60DockDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(P60Dock::pool::P60DOCK_TEMPERATURE_1, + new PoolEntry({10.0}, true)); + localDataPoolMap.emplace(P60Dock::pool::P60DOCK_TEMPERATURE_2, + new PoolEntry({10.0}, true)); + return returnvalue::OK; +} diff --git a/dummies/P60DockDummy.h b/dummies/P60DockDummy.h new file mode 100644 index 0000000..46e6303 --- /dev/null +++ b/dummies/P60DockDummy.h @@ -0,0 +1,37 @@ +#ifndef DUMMIES_P60DOCKDUMMY_H_ +#define DUMMIES_P60DOCKDUMMY_H_ + +#include + +#include "mission/power/gsDefs.h" + +class P60DockDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + P60DockDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~P60DockDummy(); + + protected: + lp_var_t temp1 = lp_var_t(this, P60Dock::pool::P60DOCK_TEMPERATURE_1); + lp_var_t temp2 = lp_var_t(this, P60Dock::pool::P60DOCK_TEMPERATURE_2); + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_P60DOCKDUMMY_H_ */ diff --git a/dummies/PcduHandlerDummy.cpp b/dummies/PcduHandlerDummy.cpp new file mode 100644 index 0000000..c91de74 --- /dev/null +++ b/dummies/PcduHandlerDummy.cpp @@ -0,0 +1,82 @@ +#include "PcduHandlerDummy.h" + +#include +#include + +#include "mission/power/defs.h" + +PcduHandlerDummy::PcduHandlerDummy(object_id_t objectId) + : SystemObject(objectId), manager(this, nullptr), dummySwitcher(objectId, 18, 18, false) { + switcherLock = MutexFactory::instance()->createMutex(); + queue = QueueFactory::instance()->createMessageQueue(20); +} + +PcduHandlerDummy::~PcduHandlerDummy() {} + +ReturnValue_t PcduHandlerDummy::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + return returnvalue::OK; +} + +ReturnValue_t PcduHandlerDummy::sendSwitchCommand(power::Switch_t switchNr, ReturnValue_t onOff) { + if (onOff == SWITCH_ON) { + triggerEvent(power::SWITCH_CMD_SENT, true, switchNr); + } else { + triggerEvent(power::SWITCH_CMD_SENT, false, switchNr); + } + { + MutexGuard mg(switcherLock); + // To simulate a real PCDU, remember the switch change to trigger a SWITCH_HAS_CHANGED event + // at a later stage. + switchChangeArray[switchNr] = true; + } + return dummySwitcher.sendSwitchCommand(switchNr, onOff); +} + +ReturnValue_t PcduHandlerDummy::sendFuseOnCommand(uint8_t fuseNr) { + return dummySwitcher.sendFuseOnCommand(fuseNr); +} + +ReturnValue_t PcduHandlerDummy::getSwitchState(power::Switch_t switchNr) const { + return dummySwitcher.getSwitchState(switchNr); +} + +ReturnValue_t PcduHandlerDummy::getFuseState(uint8_t fuseNr) const { + return dummySwitcher.getFuseState(fuseNr); +} + +uint32_t PcduHandlerDummy::getSwitchDelayMs(void) const { return dummySwitcher.getSwitchDelayMs(); } + +ReturnValue_t PcduHandlerDummy::performOperation(uint8_t opCode) { + // TODO: Handle HK messages in queue. + SwitcherBoolArray switcherChangeCopy{}; + { + MutexGuard mg(switcherLock); + std::memcpy(switcherChangeCopy.data(), switchChangeArray.data(), switchChangeArray.size()); + } + for (uint8_t idx = 0; idx < switcherChangeCopy.size(); idx++) { + if (switcherChangeCopy[idx]) { + if (dummySwitcher.getSwitchState(idx) == PowerSwitchIF::SWITCH_ON) { + triggerEvent(power::SWITCH_HAS_CHANGED, true, idx); + } else { + triggerEvent(power::SWITCH_HAS_CHANGED, false, idx); + } + MutexGuard mg(switcherLock); + switchChangeArray[idx] = false; + } + } + return returnvalue::OK; +} + +object_id_t PcduHandlerDummy::getObjectId() const { return SystemObject::getObjectId(); } + +MessageQueueId_t PcduHandlerDummy::getCommandQueue() const { return queue->getId(); } + +dur_millis_t PcduHandlerDummy::getPeriodicOperationFrequency() const { + // TODO: dummy value. Retrieve from intiitalize after task creation.. + return 400; +} + +LocalDataPoolManager* PcduHandlerDummy::getHkManagerHandle() { return &manager; } + +LocalPoolDataSetBase* PcduHandlerDummy::getDataSetHandle(sid_t sid) { return nullptr; } diff --git a/dummies/PcduHandlerDummy.h b/dummies/PcduHandlerDummy.h new file mode 100644 index 0000000..c41846f --- /dev/null +++ b/dummies/PcduHandlerDummy.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +class PcduHandlerDummy : public PowerSwitchIF, + public HasLocalDataPoolIF, + public SystemObject, + public ExecutableObjectIF { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + PcduHandlerDummy(object_id_t objectId); + virtual ~PcduHandlerDummy(); + + protected: + MessageQueueIF* queue; + LocalDataPoolManager manager; + MutexIF* switcherLock; + DummyPowerSwitcher dummySwitcher; + using SwitcherBoolArray = std::array; + + ReturnValue_t performOperation(uint8_t opCode) override; + SwitcherBoolArray switchChangeArray{}; + + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + ReturnValue_t sendSwitchCommand(power::Switch_t switchNr, ReturnValue_t onOff) override; + ReturnValue_t sendFuseOnCommand(uint8_t fuseNr) override; + ReturnValue_t getSwitchState(power::Switch_t switchNr) const override; + ReturnValue_t getFuseState(uint8_t fuseNr) const override; + uint32_t getSwitchDelayMs(void) const override; + + object_id_t getObjectId() const override; + + /** Command queue for housekeeping messages. */ + MessageQueueId_t getCommandQueue() const override; + + dur_millis_t getPeriodicOperationFrequency() const override; + + /** + * Every class implementing this interface should have a local data pool manager. This + * function will return a reference to the manager. + * @return + */ + LocalDataPoolManager* getHkManagerHandle() override; + + /** + * This function is used by the pool manager to get a valid dataset + * from a SID. This function is protected to prevent users from + * using raw data set pointers which could not be thread-safe. Users + * should use the #ProvidesDataPoolSubscriptionIF. + * @param sid Corresponding structure ID + * @return + */ + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; +}; diff --git a/dummies/PduDummy.cpp b/dummies/PduDummy.cpp new file mode 100644 index 0000000..2e3079e --- /dev/null +++ b/dummies/PduDummy.cpp @@ -0,0 +1,45 @@ +#include "PduDummy.h" + +#include + +PduDummy::PduDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), + coreHk(this, static_cast(P60System::SetIds::CORE)) {} + +PduDummy::~PduDummy() {} + +void PduDummy::doStartUp() { setMode(MODE_NORMAL); } + +void PduDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t PduDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { return NOTHING_TO_SEND; } + +ReturnValue_t PduDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t PduDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t PduDummy::scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t PduDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void PduDummy::fillCommandAndReplyMap() {} + +uint32_t PduDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t PduDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(PDU::pool::PDU_TEMPERATURE, new PoolEntry({10.0}, true)); + localDataPoolMap.emplace(PDU::pool::PDU_VOLTAGES, &pduVoltages); + localDataPoolMap.emplace(PDU::pool::PDU_CURRENTS, &pduCurrents); + return returnvalue::OK; +} diff --git a/dummies/PduDummy.h b/dummies/PduDummy.h new file mode 100644 index 0000000..5d62978 --- /dev/null +++ b/dummies/PduDummy.h @@ -0,0 +1,38 @@ +#ifndef DUMMIES_PDUDUMMY_H_ +#define DUMMIES_PDUDUMMY_H_ + +#include +#include + +class PduDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + PduDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~PduDummy(); + + protected: + PDU::PduCoreHk coreHk; + PoolEntry pduVoltages = PoolEntry(9); + PoolEntry pduCurrents = PoolEntry(9); + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_PDUDUMMY_H_ */ diff --git a/dummies/PlPcduDummy.cpp b/dummies/PlPcduDummy.cpp new file mode 100644 index 0000000..cf65251 --- /dev/null +++ b/dummies/PlPcduDummy.cpp @@ -0,0 +1,58 @@ +#include "PlPcduDummy.h" + +#include + +PlPcduDummy::PlPcduDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie) {} + +PlPcduDummy::~PlPcduDummy() {} + +void PlPcduDummy::doStartUp() { setMode(MODE_ON); } + +void PlPcduDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t PlPcduDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t PlPcduDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t PlPcduDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t PlPcduDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t PlPcduDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void PlPcduDummy::fillCommandAndReplyMap() {} + +uint32_t PlPcduDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t PlPcduDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(plpcdu::PlPcduPoolIds::TEMP, new PoolEntry({0.0}, true)); + return returnvalue::OK; +} + +ReturnValue_t PlPcduDummy::checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t *msToReachTheMode) { + if (commandedMode != MODE_OFF) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) { + return NON_OP_STATE_OF_CHARGE; + } + } + } + return DeviceHandlerBase::checkModeCommand(commandedMode, commandedSubmode, msToReachTheMode); +} diff --git a/dummies/PlPcduDummy.h b/dummies/PlPcduDummy.h new file mode 100644 index 0000000..70ee5c4 --- /dev/null +++ b/dummies/PlPcduDummy.h @@ -0,0 +1,40 @@ +#ifndef DUMMIES_PLPCDUDUMMY_H_ +#define DUMMIES_PLPCDUDUMMY_H_ + +#include +#include +#include +#include + +class PlPcduDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + PlPcduDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~PlPcduDummy(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + + ReturnValue_t checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t *msToReachTheMode) override; + pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER); +}; + +#endif /* DUMMIES_PLPCDUDUMMY_H_ */ diff --git a/dummies/PlocMpsocDummy.cpp b/dummies/PlocMpsocDummy.cpp new file mode 100644 index 0000000..9959dd0 --- /dev/null +++ b/dummies/PlocMpsocDummy.cpp @@ -0,0 +1,55 @@ +#include "PlocMpsocDummy.h" + +PlocMpsocDummy::PlocMpsocDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie) {} + +PlocMpsocDummy::~PlocMpsocDummy() {} + +void PlocMpsocDummy::doStartUp() { setMode(MODE_ON); } + +void PlocMpsocDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t PlocMpsocDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t PlocMpsocDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t PlocMpsocDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocDummy::checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t *msToReachTheMode) { + if (commandedMode != MODE_OFF) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) { + return NON_OP_STATE_OF_CHARGE; + } + } + } + return DeviceHandlerBase::checkModeCommand(commandedMode, commandedSubmode, msToReachTheMode); +} + +ReturnValue_t PlocMpsocDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void PlocMpsocDummy::fillCommandAndReplyMap() {} + +uint32_t PlocMpsocDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t PlocMpsocDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + return returnvalue::OK; +} diff --git a/dummies/PlocMpsocDummy.h b/dummies/PlocMpsocDummy.h new file mode 100644 index 0000000..28960fe --- /dev/null +++ b/dummies/PlocMpsocDummy.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +#include "mission/power/defs.h" + +class PlocMpsocDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + PlocMpsocDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~PlocMpsocDummy(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t *msToReachTheMode) override; + pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER); + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; diff --git a/dummies/PlocSupervisorDummy.cpp b/dummies/PlocSupervisorDummy.cpp new file mode 100644 index 0000000..c560c91 --- /dev/null +++ b/dummies/PlocSupervisorDummy.cpp @@ -0,0 +1,67 @@ +#include "PlocSupervisorDummy.h" + +PlocSupervisorDummy::PlocSupervisorDummy(object_id_t objectId, object_id_t comif, + CookieIF *comCookie, PowerSwitchIF &pwrSwitcher) + : DeviceHandlerBase(objectId, comif, comCookie) { + setPowerSwitcher(&pwrSwitcher); +} + +PlocSupervisorDummy::~PlocSupervisorDummy() {} + +void PlocSupervisorDummy::doStartUp() { setMode(MODE_ON); } + +void PlocSupervisorDummy::doShutDown() { setMode(_MODE_POWER_DOWN); } + +ReturnValue_t PlocSupervisorDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t PlocSupervisorDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t PlocSupervisorDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorDummy::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + return returnvalue::OK; +} + +void PlocSupervisorDummy::fillCommandAndReplyMap() {} + +uint32_t PlocSupervisorDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t PlocSupervisorDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorDummy::getSwitches(const uint8_t **switches, + uint8_t *numberOfSwitches) { + *numberOfSwitches = 1; + *switches = reinterpret_cast(&switchId); + return returnvalue::OK; +} + +ReturnValue_t PlocSupervisorDummy::checkModeCommand(Mode_t commandedMode, + Submode_t commandedSubmode, + uint32_t *msToReachTheMode) { + if (commandedMode != MODE_OFF) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) { + return NON_OP_STATE_OF_CHARGE; + } + } + } + return DeviceHandlerBase::checkModeCommand(commandedMode, commandedSubmode, msToReachTheMode); +} diff --git a/dummies/PlocSupervisorDummy.h b/dummies/PlocSupervisorDummy.h new file mode 100644 index 0000000..c5334e5 --- /dev/null +++ b/dummies/PlocSupervisorDummy.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +class PlocSupervisorDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + PlocSupervisorDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, + PowerSwitchIF &pwrSwitcher); + virtual ~PlocSupervisorDummy(); + + protected: + const power::Switches switchId = power::Switches::PDU1_CH6_PLOC_12V; + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + ReturnValue_t getSwitches(const uint8_t **switches, uint8_t *numberOfSwitches) override; + ReturnValue_t checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t *msToReachTheMode) override; + pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER); +}; diff --git a/dummies/RadSensorDummy.cpp b/dummies/RadSensorDummy.cpp new file mode 100644 index 0000000..5c42c59 --- /dev/null +++ b/dummies/RadSensorDummy.cpp @@ -0,0 +1,55 @@ +#include "RadSensorDummy.h" + +RadSensorDummy::RadSensorDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), sensorSet(this) {} + +RadSensorDummy::~RadSensorDummy() {} + +void RadSensorDummy::doStartUp() { setMode(MODE_ON); } + +void RadSensorDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t RadSensorDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t RadSensorDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t RadSensorDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t RadSensorDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t RadSensorDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void RadSensorDummy::fillCommandAndReplyMap() {} + +uint32_t RadSensorDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t RadSensorDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(radSens::TEMPERATURE_C, new PoolEntry({0.0})); + localDataPoolMap.emplace(radSens::AIN0, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN1, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN4, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN5, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN6, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN7, new PoolEntry({0})); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(sensorSet.getSid(), false, 20.0)); + return returnvalue::OK; + + return returnvalue::OK; +} + +LocalPoolDataSetBase *RadSensorDummy::getDataSetHandle(sid_t sid) { return &sensorSet; } diff --git a/dummies/RadSensorDummy.h b/dummies/RadSensorDummy.h new file mode 100644 index 0000000..a3edf4f --- /dev/null +++ b/dummies/RadSensorDummy.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "mission/payload/radSensorDefinitions.h" + +class RadSensorDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + RadSensorDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~RadSensorDummy(); + + protected: + radSens::RadSensorDataset sensorSet; + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; +}; diff --git a/dummies/RwDummy.cpp b/dummies/RwDummy.cpp new file mode 100644 index 0000000..05e1439 --- /dev/null +++ b/dummies/RwDummy.cpp @@ -0,0 +1,113 @@ +#include "RwDummy.h" + +#include + +RwDummy::RwDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), + + statusSet(this), + lastResetStatusSet(this), + tmDataset(this), + rwSpeedActuationSet(*this) {} + +RwDummy::~RwDummy() {} + +void RwDummy::doStartUp() { + statusSet.setReportingEnabled(true); + setMode(MODE_ON); +} + +void RwDummy::doShutDown() { + statusSet.setReportingEnabled(false); + setMode(MODE_OFF); +} + +ReturnValue_t RwDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { return NOTHING_TO_SEND; } + +ReturnValue_t RwDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t RwDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t RwDummy::scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t RwDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void RwDummy::fillCommandAndReplyMap() {} + +uint32_t RwDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t RwDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(rws::RW_SPEED, &rwSpeed); + localDataPoolMap.emplace(rws::RAMP_TIME, &rampTime); + + localDataPoolMap.emplace(rws::TEMPERATURE_C, new PoolEntry({0})); + + localDataPoolMap.emplace(rws::CURR_SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(rws::REFERENCE_SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(rws::STATE, new PoolEntry({1}, true)); + localDataPoolMap.emplace(rws::CLC_MODE, new PoolEntry({0})); + + localDataPoolMap.emplace(rws::LAST_RESET_STATUS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::CURRRENT_RESET_STATUS, new PoolEntry({0})); + + localDataPoolMap.emplace(rws::TM_LAST_RESET_STATUS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_MCU_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::PRESSURE_SENSOR_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::PRESSURE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_RW_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_CLC_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_RW_CURR_SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_RW_REF_SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(rws::INVALID_CRC_PACKETS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::INVALID_LEN_PACKETS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::INVALID_CMD_PACKETS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::EXECUTED_REPLIES, new PoolEntry({0})); + localDataPoolMap.emplace(rws::COMMAND_REPLIES, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_BYTES_WRITTEN, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_BYTES_READ, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_PARITY_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_NOISE_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_FRAME_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_REG_OVERRUN_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_TOTAL_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::SPI_BYTES_WRITTEN, new PoolEntry({0})); + localDataPoolMap.emplace(rws::SPI_BYTES_READ, new PoolEntry({0})); + localDataPoolMap.emplace(rws::SPI_REG_OVERRUN_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::SPI_TOTAL_ERRORS, new PoolEntry({0})); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(statusSet.getSid(), false, 12.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(tmDataset.getSid(), false, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(lastResetStatusSet.getSid(), false, 30.0)); + return returnvalue::OK; +} + +LocalPoolDataSetBase *RwDummy::getDataSetHandle(sid_t sid) { + switch (sid.ownerSetId) { + case (rws::SetIds::STATUS_SET_ID): { + return &statusSet; + } + case (rws::SetIds::LAST_RESET_ID): { + return &lastResetStatusSet; + } + case (rws::SetIds::SPEED_CMD_SET): { + return &rwSpeedActuationSet; + } + case (rws::SetIds::TM_SET_ID): { + return &tmDataset; + } + } + return nullptr; +} diff --git a/dummies/RwDummy.h b/dummies/RwDummy.h new file mode 100644 index 0000000..512c425 --- /dev/null +++ b/dummies/RwDummy.h @@ -0,0 +1,43 @@ +#ifndef DUMMIES_RWDUMMY_H_ +#define DUMMIES_RWDUMMY_H_ + +#include +#include + +class RwDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + RwDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~RwDummy(); + + protected: + rws::StatusSet statusSet; + rws::LastResetSatus lastResetStatusSet; + rws::TmDataset tmDataset; + rws::RwSpeedActuationSet rwSpeedActuationSet; + + PoolEntry rwSpeed = PoolEntry({0}); + PoolEntry rampTime = PoolEntry({10}); + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; +}; + +#endif /* DUMMIES_RWDUMMY_H_ */ diff --git a/dummies/SaDeploymentDummy.cpp b/dummies/SaDeploymentDummy.cpp new file mode 100644 index 0000000..28588ea --- /dev/null +++ b/dummies/SaDeploymentDummy.cpp @@ -0,0 +1,7 @@ +#include "SaDeploymentDummy.h" + +SaDeplDummy::SaDeplDummy(object_id_t objectId) : SystemObject(objectId) {} + +SaDeplDummy::~SaDeplDummy() = default; + +ReturnValue_t SaDeplDummy::performOperation(uint8_t opCode) { return returnvalue::OK; } diff --git a/dummies/SaDeploymentDummy.h b/dummies/SaDeploymentDummy.h new file mode 100644 index 0000000..a9b7239 --- /dev/null +++ b/dummies/SaDeploymentDummy.h @@ -0,0 +1,19 @@ + +#ifndef DUMMIES_SADEPLOYMENT_H_ +#define DUMMIES_SADEPLOYMENT_H_ + +#include + +#include "SaDeploymentDummy.h" + +class SaDeplDummy : public SystemObject, public ExecutableObjectIF { + public: + SaDeplDummy(object_id_t objectId); + virtual ~SaDeplDummy(); + + ReturnValue_t performOperation(uint8_t opCode) override; + + protected: +}; + +#endif /* DUMMIES_SADEPLOYMENT_H_ */ diff --git a/dummies/ScexDummy.cpp b/dummies/ScexDummy.cpp new file mode 100644 index 0000000..1f47606 --- /dev/null +++ b/dummies/ScexDummy.cpp @@ -0,0 +1,40 @@ +#include "ScexDummy.h" + +ScexDummy::ScexDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie) {} + +ScexDummy::~ScexDummy() {} + +void ScexDummy::doStartUp() { setMode(MODE_ON); } + +void ScexDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t ScexDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { return NOTHING_TO_SEND; } + +ReturnValue_t ScexDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t ScexDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t ScexDummy::scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t ScexDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void ScexDummy::fillCommandAndReplyMap() {} + +uint32_t ScexDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t ScexDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + return returnvalue::OK; +} diff --git a/dummies/ScexDummy.h b/dummies/ScexDummy.h new file mode 100644 index 0000000..98762db --- /dev/null +++ b/dummies/ScexDummy.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +class ScexDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + ScexDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~ScexDummy(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; diff --git a/dummies/StarTrackerDummy.cpp b/dummies/StarTrackerDummy.cpp new file mode 100644 index 0000000..6e21b76 --- /dev/null +++ b/dummies/StarTrackerDummy.cpp @@ -0,0 +1,70 @@ +#include "StarTrackerDummy.h" + +#include + +StarTrackerDummy::StarTrackerDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie) {} + +StarTrackerDummy::~StarTrackerDummy() {} + +void StarTrackerDummy::doStartUp() { setMode(MODE_ON); } + +void StarTrackerDummy::doShutDown() { setMode(_MODE_POWER_DOWN); } + +ReturnValue_t StarTrackerDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t StarTrackerDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t StarTrackerDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t StarTrackerDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t StarTrackerDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void StarTrackerDummy::fillCommandAndReplyMap() {} + +uint32_t StarTrackerDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t StarTrackerDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(startracker::MCU_TEMPERATURE, new PoolEntry({10.0}, true)); + + localDataPoolMap.emplace(startracker::TICKS_SOLUTION_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TIME_SOLUTION_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CALI_QW, new PoolEntry({1.0}, true)); + localDataPoolMap.emplace(startracker::CALI_QX, new PoolEntry({0.0}, true)); + localDataPoolMap.emplace(startracker::CALI_QY, new PoolEntry({0.0}, true)); + localDataPoolMap.emplace(startracker::CALI_QZ, new PoolEntry({0.0}, true)); + localDataPoolMap.emplace(startracker::TRACK_CONFIDENCE, new PoolEntry({0.0})); + localDataPoolMap.emplace(startracker::TRACK_QW, new PoolEntry({1.0})); + localDataPoolMap.emplace(startracker::TRACK_QX, new PoolEntry({0.0})); + localDataPoolMap.emplace(startracker::TRACK_QY, new PoolEntry({0.0})); + localDataPoolMap.emplace(startracker::TRACK_QZ, new PoolEntry({0.0})); + localDataPoolMap.emplace(startracker::TRACK_REMOVED, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::STARS_CENTROIDED, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::STARS_MATCHED_DATABASE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_QW, new PoolEntry({1.0})); + localDataPoolMap.emplace(startracker::LISA_QX, new PoolEntry({0.0})); + localDataPoolMap.emplace(startracker::LISA_QY, new PoolEntry({0.0})); + localDataPoolMap.emplace(startracker::LISA_QZ, new PoolEntry({0.0})); + localDataPoolMap.emplace(startracker::LISA_PERC_CLOSE, new PoolEntry({0.0})); + localDataPoolMap.emplace(startracker::LISA_NR_CLOSE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRUST_WORTHY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::STABLE_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::STR_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SOLUTION_STRATEGY, new PoolEntry({0})); + return returnvalue::OK; +} diff --git a/dummies/StarTrackerDummy.h b/dummies/StarTrackerDummy.h new file mode 100644 index 0000000..bc5534c --- /dev/null +++ b/dummies/StarTrackerDummy.h @@ -0,0 +1,33 @@ +#ifndef DUMMIES_STARTRACKERDUMMY_H_ +#define DUMMIES_STARTRACKERDUMMY_H_ + +#include + +class StarTrackerDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + StarTrackerDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~StarTrackerDummy(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_STARTRACKERDUMMY_H_ */ diff --git a/dummies/SusDummy.cpp b/dummies/SusDummy.cpp new file mode 100644 index 0000000..3e03f19 --- /dev/null +++ b/dummies/SusDummy.cpp @@ -0,0 +1,44 @@ +#include "SusDummy.h" + +SusDummy::SusDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), susSet(this) {} + +SusDummy::~SusDummy() {} + +void SusDummy::doStartUp() { setMode(MODE_ON); } + +void SusDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t SusDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { return NOTHING_TO_SEND; } + +ReturnValue_t SusDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t SusDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t SusDummy::scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t SusDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void SusDummy::fillCommandAndReplyMap() {} + +uint32_t SusDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t SusDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(susMax1227::SusPoolIds::TEMPERATURE_C, + new PoolEntry({0}, 1, true)); + localDataPoolMap.emplace(susMax1227::SusPoolIds::CHANNEL_VEC, + new PoolEntry({2603, 781, 2760, 2048, 4056, 0}, true)); + + return returnvalue::OK; +} diff --git a/dummies/SusDummy.h b/dummies/SusDummy.h new file mode 100644 index 0000000..0df35f8 --- /dev/null +++ b/dummies/SusDummy.h @@ -0,0 +1,35 @@ +#ifndef DUMMIES_SUSDUMMY_H_ +#define DUMMIES_SUSDUMMY_H_ + +#include +#include + +class SusDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + SusDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + virtual ~SusDummy(); + + protected: + susMax1227::SusDataset susSet; + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_SUSDUMMY_H_ */ diff --git a/dummies/SyrlinksDummy.cpp b/dummies/SyrlinksDummy.cpp new file mode 100644 index 0000000..5deb1d9 --- /dev/null +++ b/dummies/SyrlinksDummy.cpp @@ -0,0 +1,47 @@ +#include "SyrlinksDummy.h" + +#include + +SyrlinksDummy::SyrlinksDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, + DeviceHandlerFailureIsolation *fdir) + : DeviceHandlerBase(objectId, comif, comCookie, fdir) {} + +SyrlinksDummy::~SyrlinksDummy() {} + +void SyrlinksDummy::doStartUp() { setMode(MODE_ON); } + +void SyrlinksDummy::doShutDown() { setMode(MODE_OFF); } + +ReturnValue_t SyrlinksDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t SyrlinksDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t SyrlinksDummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return returnvalue::OK; +} + +ReturnValue_t SyrlinksDummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return returnvalue::OK; +} + +ReturnValue_t SyrlinksDummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return returnvalue::OK; +} + +void SyrlinksDummy::fillCommandAndReplyMap() {} + +uint32_t SyrlinksDummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 1000; } + +ReturnValue_t SyrlinksDummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(syrlinks::TEMP_BASEBAND_BOARD, new PoolEntry({10}, true)); + localDataPoolMap.emplace(syrlinks::TEMP_POWER_AMPLIFIER, new PoolEntry({10}, true)); + return returnvalue::OK; +} diff --git a/dummies/SyrlinksDummy.h b/dummies/SyrlinksDummy.h new file mode 100644 index 0000000..216a702 --- /dev/null +++ b/dummies/SyrlinksDummy.h @@ -0,0 +1,34 @@ +#ifndef DUMMIES_SYRLINKSDUMMY_H_ +#define DUMMIES_SYRLINKSDUMMY_H_ + +#include + +class SyrlinksDummy : public DeviceHandlerBase { + public: + static const DeviceCommandId_t SIMPLE_COMMAND = 1; + static const DeviceCommandId_t PERIODIC_REPLY = 2; + + static const uint8_t SIMPLE_COMMAND_DATA = 1; + static const uint8_t PERIODIC_REPLY_DATA = 2; + + SyrlinksDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, + DeviceHandlerFailureIsolation *fdir); + virtual ~SyrlinksDummy(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; +}; + +#endif /* DUMMIES_SYRLINKSDUMMY_H_ */ diff --git a/dummies/TemperatureSensorInserter.cpp b/dummies/TemperatureSensorInserter.cpp new file mode 100644 index 0000000..942231f --- /dev/null +++ b/dummies/TemperatureSensorInserter.cpp @@ -0,0 +1,149 @@ +#include "TemperatureSensorInserter.h" + +#include +#include + +#include +#include +#include + +TemperatureSensorInserter::TemperatureSensorInserter( + object_id_t objectId, Max31865DummyMap tempSensorDummies_, + std::optional tempTmpSensorDummies_) + : SystemObject(objectId), + max31865DummyMap(std::move(tempSensorDummies_)), + tmp1075DummyMap(std::move(tempTmpSensorDummies_)) {} + +ReturnValue_t TemperatureSensorInserter::initialize() { + testCase = TestCase::NONE; + return returnvalue::OK; +} + +ReturnValue_t TemperatureSensorInserter::performOperation(uint8_t opCode) { + // TODO: deviceSensors + if (not tempsWereInitialized) { + for (auto& rtdDummy : max31865DummyMap) { + rtdDummy.second->setTemperature(10, true); + } + if (tmp1075DummyMap.has_value()) { + for (auto& tmpDummy : tmp1075DummyMap.value()) { + tmpDummy.second->setTemperature(10, true); + } + } + tempsWereInitialized = true; + } + + switch (testCase) { + case (TestCase::NONE): { + break; + } + case (TestCase::COLD_SYRLINKS): { + // TODO: How do I insert this? + // Does not work on EM, where a real syrlinks device is connected. + if (cycles == 15) { + lp_var_t tempSyrlinksBasebandBoard = + lp_var_t(objects::SYRLINKS_HANDLER, syrlinks::TEMP_BASEBAND_BOARD); + PoolReadGuard pg(&tempSyrlinksBasebandBoard); + tempSyrlinksBasebandBoard.value = -50; + } + if (cycles == 30) { + lp_var_t tempSyrlinksBasebandBoard = + lp_var_t(objects::SYRLINKS_HANDLER, syrlinks::TEMP_BASEBAND_BOARD); + PoolReadGuard pg(&tempSyrlinksBasebandBoard); + tempSyrlinksBasebandBoard.value = 0; + } + break; + } + case (TestCase::COLD_HPA): { + if (cycles == 15) { + sif::debug << "Setting cold HPA temperature" << std::endl; + max31865DummyMap[objects::RTD_9_IC12_HPA]->setTemperature(-60, true); + } + if (cycles == 30) { + sif::debug << "Setting HPA temperature back to normal" << std::endl; + max31865DummyMap[objects::RTD_9_IC12_HPA]->setTemperature(0, true); + } + break; + } + case (TestCase::COLD_MGT): { + if (cycles == 15) { + sif::debug << "Setting cold MGT temperature" << std::endl; + max31865DummyMap[objects::RTD_15_IC18_IMTQ]->setTemperature(-60, true); + } + if (cycles == 30) { + sif::debug << "Setting MGT temperature back to normal" << std::endl; + max31865DummyMap[objects::RTD_15_IC18_IMTQ]->setTemperature(0, true); + } + break; + } + case (TestCase::COLD_STR): + case (TestCase::COLD_STR_CONSECUTIVE): { + if (cycles == 15) { + sif::debug << "Setting cold STR temperature" << std::endl; + max31865DummyMap[objects::RTD_4_IC7_STARTRACKER]->setTemperature(-40, true); + } + if (cycles == 30) { + sif::debug << "Setting STR temperature back to normal" << std::endl; + max31865DummyMap[objects::RTD_4_IC7_STARTRACKER]->setTemperature(0, true); + } + if (testCase == TestCase::COLD_STR_CONSECUTIVE) { + if (cycles == 45) { + sif::debug << "Setting cold STR temperature again" << std::endl; + max31865DummyMap[objects::RTD_4_IC7_STARTRACKER]->setTemperature(-40, true); + } + if (cycles == 60) { + sif::debug << "Setting STR temperature back to normal again" << std::endl; + max31865DummyMap[objects::RTD_4_IC7_STARTRACKER]->setTemperature(0, true); + } + } + break; + } + case (TestCase::COLD_PLOC_CONSECUTIVE): { + if (cycles == 15) { + sif::debug << "Setting cold PLOC temperature" << std::endl; + max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(-15, true); + } + if (cycles == 30) { + sif::debug << "Setting warmer PLOC temperature" << std::endl; + max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(0, true); + } + if (cycles == 45) { + sif::debug << "Setting cold PLOC temperature again" << std::endl; + max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(-15, true); + } + if (cycles == 60) { + sif::debug << "Setting warmer PLOC temperature again" << std::endl; + max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(0, true); + } + break; + } + case (TestCase::COLD_CAMERA): { + if (cycles == 15) { + sif::debug << "Setting cold CAM temperature" << std::endl; + max31865DummyMap[objects::RTD_2_IC5_4K_CAMERA]->setTemperature(-40, true); + } + if (cycles == 30) { + sif::debug << "Setting CAM temperature back to normal" << std::endl; + max31865DummyMap[objects::RTD_2_IC5_4K_CAMERA]->setTemperature(0, true); + } + break; + } + case (TestCase::COLD_PLOC_STAYS_COLD): { + if (cycles == 15) { + sif::debug << "Setting cold PLOC temperature" << std::endl; + max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(-40, true); + } + break; + } + case (TestCase::COLD_CAMERA_STAYS_COLD): { + if (cycles == 15) { + sif::debug << "Setting cold PLOC temperature" << std::endl; + max31865DummyMap[objects::RTD_2_IC5_4K_CAMERA]->setTemperature(-40, true); + } + break; + } + } + cycles++; + return returnvalue::OK; +} +ReturnValue_t TemperatureSensorInserter::initializeAfterTaskCreation() { return returnvalue::OK; } diff --git a/dummies/TemperatureSensorInserter.h b/dummies/TemperatureSensorInserter.h new file mode 100644 index 0000000..9ca3c93 --- /dev/null +++ b/dummies/TemperatureSensorInserter.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include "Max31865Dummy.h" +#include "Tmp1075Dummy.h" + +class TemperatureSensorInserter : public ExecutableObjectIF, public SystemObject { + public: + using Max31865DummyMap = std::map; + using Tmp1075DummyMap = std::map; + explicit TemperatureSensorInserter(object_id_t objectId, Max31865DummyMap tempSensorDummies_, + std::optional tempTmpSensorDummies_); + + ReturnValue_t initialize() override; + ReturnValue_t initializeAfterTaskCreation() override; + + protected: + ReturnValue_t performOperation(uint8_t opCode) override; + + private: + Max31865DummyMap max31865DummyMap; + std::optional tmp1075DummyMap; + + enum TestCase { + NONE = 0, + COLD_SYRLINKS = 1, + COLD_HPA = 2, + COLD_MGT = 3, + COLD_STR = 4, + COLD_STR_CONSECUTIVE = 5, + COLD_CAMERA = 6, + COLD_PLOC_CONSECUTIVE = 7, + COLD_PLOC_STAYS_COLD = 8, + COLD_CAMERA_STAYS_COLD = 9 + }; + int iteration = 0; + uint32_t cycles = 0; + bool tempsWereInitialized = false; + TestCase testCase = TestCase::NONE; + + // void noise(); +}; diff --git a/dummies/Tmp1075Dummy.cpp b/dummies/Tmp1075Dummy.cpp new file mode 100644 index 0000000..7e61ace --- /dev/null +++ b/dummies/Tmp1075Dummy.cpp @@ -0,0 +1,64 @@ +#include "Tmp1075Dummy.h" + +#include +#include + +using namespace returnvalue; + +Tmp1075Dummy::Tmp1075Dummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comif, comCookie), set(this) {} + +void Tmp1075Dummy::doStartUp() { setMode(MODE_ON); } +void Tmp1075Dummy::doShutDown() { + PoolReadGuard pg(&set); + set.setValidity(false, true); + setMode(MODE_OFF); +} + +ReturnValue_t Tmp1075Dummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t Tmp1075Dummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t Tmp1075Dummy::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return NOTHING_TO_SEND; +} + +ReturnValue_t Tmp1075Dummy::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + return 0; +} + +ReturnValue_t Tmp1075Dummy::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + return 0; +} + +void Tmp1075Dummy::setTemperature(float temperature, bool valid) { + PoolReadGuard pg(&set); + set.temperatureCelcius.value = temperature; + set.setValidity(valid, true); +} + +void Tmp1075Dummy::fillCommandAndReplyMap() {} + +uint32_t Tmp1075Dummy::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 1000; } + +ReturnValue_t Tmp1075Dummy::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(TMP1075::TEMPERATURE_C_TMP1075, new PoolEntry({10.0}, true)); + return OK; +} + +ReturnValue_t Tmp1075Dummy::setHealth(HealthState health) { + if (health == FAULTY or health == PERMANENT_FAULTY) { + setMode(_MODE_SHUT_DOWN); + } + return DeviceHandlerBase::setHealth(health); +} + +LocalPoolDataSetBase *Tmp1075Dummy::getDataSetHandle(sid_t sid) { return &set; } diff --git a/dummies/Tmp1075Dummy.h b/dummies/Tmp1075Dummy.h new file mode 100644 index 0000000..feab4f9 --- /dev/null +++ b/dummies/Tmp1075Dummy.h @@ -0,0 +1,35 @@ +#ifndef EIVE_OBSW_TMP1075DUMMY_H +#define EIVE_OBSW_TMP1075DUMMY_H + +#include + +#include "fsfw/devicehandlers/DeviceHandlerBase.h" + +class Tmp1075Dummy : public DeviceHandlerBase { + public: + Tmp1075Dummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie); + void setTemperature(float temperature, bool setValid); + + private: + TMP1075::Tmp1075Dataset set; + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + ReturnValue_t setHealth(HealthState health) override; + + protected: + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; +}; + +#endif // EIVE_OBSW_TMP1075DUMMY_H diff --git a/dummies/helperFactory.cpp b/dummies/helperFactory.cpp new file mode 100644 index 0000000..92fce21 --- /dev/null +++ b/dummies/helperFactory.cpp @@ -0,0 +1,276 @@ +#include "helperFactory.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TemperatureSensorInserter.h" +#include "dummies/Max31865Dummy.h" +#include "dummies/SusDummy.h" +#include "dummies/Tmp1075Dummy.h" +#include "mission/genericFactory.h" +#include "mission/system/acs/acsModeTree.h" +#include "mission/system/com/comModeTree.h" +#include "mission/system/payload/payloadModeTree.h" +#include "mission/system/tcs/tcsModeTree.h" +#include "mission/tcs/defs.h" + +void dummy::createDummies(DummyCfg cfg, PowerSwitchIF& pwrSwitcher, GpioIF* gpioIF, + bool enableHkSets) { + new ComIFDummy(objects::DUMMY_COM_IF); + auto* comCookieDummy = new ComCookieDummy(); + if (cfg.addBpxBattDummy) { + new BatteryDummy(DhbConfig(objects::BPX_BATT_HANDLER)); + } + if (cfg.addCoreCtrlCfg) { + new CoreControllerDummy(objects::CORE_CONTROLLER); + } + if (cfg.addRtdComIFDummy) { + new ExecutableComIfDummy(objects::SPI_RTD_COM_IF); + } + std::array rwIds = {objects::RW1, objects::RW2, objects::RW3, objects::RW4}; + std::array rws; + rws[0] = new RwDummy(objects::RW1, objects::DUMMY_COM_IF, comCookieDummy); + rws[1] = new RwDummy(objects::RW2, objects::DUMMY_COM_IF, comCookieDummy); + rws[2] = new RwDummy(objects::RW3, objects::DUMMY_COM_IF, comCookieDummy); + rws[3] = new RwDummy(objects::RW4, objects::DUMMY_COM_IF, comCookieDummy); + ObjectFactory::createRwAssy(pwrSwitcher, power::Switches::PDU2_CH2_RW_5V, rws, rwIds); + new SaDeplDummy(objects::SOLAR_ARRAY_DEPL_HANDLER); + + if (cfg.addStrDummy) { + auto* strAssy = new StrAssembly(objects::STR_ASSY); + strAssy->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); + auto* strDummy = + new StarTrackerDummy(objects::STAR_TRACKER, objects::DUMMY_COM_IF, comCookieDummy); + strDummy->connectModeTreeParent(*strAssy); + } + if (cfg.addSyrlinksDummies) { + auto* syrlinksAssy = new SyrlinksAssembly(objects::SYRLINKS_ASSY); + syrlinksAssy->connectModeTreeParent(satsystem::com::SUBSYSTEM); + + auto syrlinksFdir = new SyrlinksFdir(objects::SYRLINKS_HANDLER); + auto* syrlinksDummy = new SyrlinksDummy(objects::SYRLINKS_HANDLER, objects::DUMMY_COM_IF, + comCookieDummy, syrlinksFdir); + syrlinksDummy->connectModeTreeParent(*syrlinksAssy); + } + auto* imtqAssy = new ImtqAssembly(objects::IMTQ_ASSY); + imtqAssy->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); + auto* imtqDummy = new ImtqDummy(objects::IMTQ_HANDLER, objects::DUMMY_COM_IF, comCookieDummy, + power::Switches::PDU1_CH3_MGT_5V, enableHkSets); + imtqDummy->enableThermalModule(ThermalStateCfg()); + imtqDummy->setPowerSwitcher(&pwrSwitcher); + imtqDummy->connectModeTreeParent(*imtqAssy); + if (cfg.addOnlyAcuDummy) { + new AcuDummy(objects::ACU_HANDLER, objects::DUMMY_COM_IF, comCookieDummy, enableHkSets); + } else if (cfg.addPowerDummies) { + new AcuDummy(objects::ACU_HANDLER, objects::DUMMY_COM_IF, comCookieDummy, enableHkSets); + new PduDummy(objects::PDU1_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + new PduDummy(objects::PDU2_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + new P60DockDummy(objects::P60DOCK_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + } + + if (cfg.addAcsBoardDummies) { + std::array assemblyDhbs{}; + assemblyDhbs[0] = + new MgmLIS3MDLDummy(objects::MGM_0_LIS3_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + assemblyDhbs[1] = + new MgmLIS3MDLDummy(objects::MGM_2_LIS3_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + assemblyDhbs[2] = + new MgmRm3100Dummy(objects::MGM_1_RM3100_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + assemblyDhbs[3] = + new MgmRm3100Dummy(objects::MGM_3_RM3100_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + assemblyDhbs[4] = + new GyroAdisDummy(objects::GYRO_0_ADIS_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + assemblyDhbs[5] = + new GyroL3GD20Dummy(objects::GYRO_1_L3G_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + assemblyDhbs[6] = + new GyroAdisDummy(objects::GYRO_2_ADIS_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + assemblyDhbs[7] = + new GyroL3GD20Dummy(objects::GYRO_3_L3G_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + auto* gpsCtrl = new GpsCtrlDummy(objects::GPS_CONTROLLER); + ObjectFactory::createAcsBoardAssy(pwrSwitcher, assemblyDhbs, gpsCtrl, gpioIF); + } + + if (cfg.addSusDummies) { + std::array suses{}; + suses[0] = + new SusDummy(objects::SUS_0_N_LOC_XFYFZM_PT_XF, objects::DUMMY_COM_IF, comCookieDummy); + suses[1] = + new SusDummy(objects::SUS_1_N_LOC_XBYFZM_PT_XB, objects::DUMMY_COM_IF, comCookieDummy); + suses[2] = + new SusDummy(objects::SUS_2_N_LOC_XFYBZB_PT_YB, objects::DUMMY_COM_IF, comCookieDummy); + suses[3] = + new SusDummy(objects::SUS_3_N_LOC_XFYBZF_PT_YF, objects::DUMMY_COM_IF, comCookieDummy); + suses[4] = + new SusDummy(objects::SUS_4_N_LOC_XMYFZF_PT_ZF, objects::DUMMY_COM_IF, comCookieDummy); + suses[5] = + new SusDummy(objects::SUS_5_N_LOC_XFYMZB_PT_ZB, objects::DUMMY_COM_IF, comCookieDummy); + suses[6] = + new SusDummy(objects::SUS_6_R_LOC_XFYBZM_PT_XF, objects::DUMMY_COM_IF, comCookieDummy); + suses[7] = + new SusDummy(objects::SUS_7_R_LOC_XBYBZM_PT_XB, objects::DUMMY_COM_IF, comCookieDummy); + suses[8] = + new SusDummy(objects::SUS_8_R_LOC_XBYBZB_PT_YB, objects::DUMMY_COM_IF, comCookieDummy); + suses[9] = + new SusDummy(objects::SUS_9_R_LOC_XBYBZB_PT_YF, objects::DUMMY_COM_IF, comCookieDummy); + suses[10] = + new SusDummy(objects::SUS_10_N_LOC_XMYBZF_PT_ZF, objects::DUMMY_COM_IF, comCookieDummy); + suses[11] = + new SusDummy(objects::SUS_11_R_LOC_XBYMZB_PT_ZB, objects::DUMMY_COM_IF, comCookieDummy); + ObjectFactory::createSusAssy(pwrSwitcher, suses); + } + + if (cfg.addTempSensorDummies) { + std::map rtdSensorDummies; + rtdSensorDummies.emplace(objects::RTD_0_IC3_PLOC_HEATSPREADER, + new Max31865Dummy(objects::RTD_0_IC3_PLOC_HEATSPREADER, + objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace(objects::RTD_1_IC4_PLOC_MISSIONBOARD, + new Max31865Dummy(objects::RTD_1_IC4_PLOC_MISSIONBOARD, + objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_2_IC5_4K_CAMERA, + new Max31865Dummy(objects::RTD_2_IC5_4K_CAMERA, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace(objects::RTD_3_IC6_DAC_HEATSPREADER, + new Max31865Dummy(objects::RTD_3_IC6_DAC_HEATSPREADER, + objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_4_IC7_STARTRACKER, + new Max31865Dummy(objects::RTD_4_IC7_STARTRACKER, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_5_IC8_RW1_MX_MY, + new Max31865Dummy(objects::RTD_5_IC8_RW1_MX_MY, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_6_IC9_DRO, + new Max31865Dummy(objects::RTD_6_IC9_DRO, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_7_IC10_SCEX, + new Max31865Dummy(objects::RTD_7_IC10_SCEX, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_8_IC11_X8, + new Max31865Dummy(objects::RTD_8_IC11_X8, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_9_IC12_HPA, + new Max31865Dummy(objects::RTD_9_IC12_HPA, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_10_IC13_PL_TX, + new Max31865Dummy(objects::RTD_10_IC13_PL_TX, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_11_IC14_MPA, + new Max31865Dummy(objects::RTD_11_IC14_MPA, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_12_IC15_ACU, + new Max31865Dummy(objects::RTD_12_IC15_ACU, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace(objects::RTD_13_IC16_PLPCDU_HEATSPREADER, + new Max31865Dummy(objects::RTD_13_IC16_PLPCDU_HEATSPREADER, + objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_14_IC17_TCS_BOARD, + new Max31865Dummy(objects::RTD_14_IC17_TCS_BOARD, objects::DUMMY_COM_IF, comCookieDummy)); + rtdSensorDummies.emplace( + objects::RTD_15_IC18_IMTQ, + new Max31865Dummy(objects::RTD_15_IC18_IMTQ, objects::DUMMY_COM_IF, comCookieDummy)); + + std::optional tmpSensorDummies; + if (cfg.addTmpDummies) { + TemperatureSensorInserter::Tmp1075DummyMap tmpDummyMap; + if (cfg.tmp1075Cfg.addTcsBrd0) { + tmpDummyMap.emplace(objects::TMP1075_HANDLER_TCS_0, + new Tmp1075Dummy(objects::TMP1075_HANDLER_TCS_0, objects::DUMMY_COM_IF, + comCookieDummy)); + } + if (cfg.tmp1075Cfg.addTcsBrd1) { + tmpDummyMap.emplace(objects::TMP1075_HANDLER_TCS_1, + new Tmp1075Dummy(objects::TMP1075_HANDLER_TCS_1, objects::DUMMY_COM_IF, + comCookieDummy)); + } + if (cfg.tmp1075Cfg.addPlPcdu0) { + tmpDummyMap.emplace(objects::TMP1075_HANDLER_PLPCDU_0, + new Tmp1075Dummy(objects::TMP1075_HANDLER_PLPCDU_0, + objects::DUMMY_COM_IF, comCookieDummy)); + } + if (cfg.tmp1075Cfg.addPlPcdu1) { + tmpDummyMap.emplace(objects::TMP1075_HANDLER_PLPCDU_1, + new Tmp1075Dummy(objects::TMP1075_HANDLER_PLPCDU_1, + objects::DUMMY_COM_IF, comCookieDummy)); + } + if (cfg.tmp1075Cfg.addIfBrd) { + tmpDummyMap.emplace(objects::TMP1075_HANDLER_IF_BOARD, + new Tmp1075Dummy(objects::TMP1075_HANDLER_IF_BOARD, + objects::DUMMY_COM_IF, comCookieDummy)); + } + tmpSensorDummies = std::move(tmpDummyMap); + } + new TemperatureSensorInserter(objects::THERMAL_TEMP_INSERTER, rtdSensorDummies, + tmpSensorDummies); + TcsBoardAssembly* tcsBoardAssy = + ObjectFactory::createTcsBoardAssy(pwrSwitcher, tcs::TCS_BOARD_SHORTLY_UNAVAILABLE); + for (auto& rtd : rtdSensorDummies) { + rtd.second->connectModeTreeParent(*tcsBoardAssy); + } + if (tmpSensorDummies.has_value()) { + for (auto& tmp : tmpSensorDummies.value()) { + tmp.second->connectModeTreeParent(satsystem::tcs::SUBSYSTEM); + } + } + } + if (cfg.addCamSwitcherDummy) { + auto* camSwitcher = new CamSwitcher(objects::CAM_SWITCHER, pwrSwitcher, + power::Switches::PDU2_CH8_PAYLOAD_CAMERA); + camSwitcher->connectModeTreeParent(satsystem::payload::SUBSYSTEM); + } + if (cfg.addScexDummy) { + auto* scexDummy = new ScexDummy(objects::SCEX, objects::DUMMY_COM_IF, comCookieDummy); + scexDummy->connectModeTreeParent(satsystem::payload::SUBSYSTEM); + } + if (cfg.addPlPcduDummy) { + auto* plPcduDummy = + new PlPcduDummy(objects::PLPCDU_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + plPcduDummy->connectModeTreeParent(satsystem::payload::SUBSYSTEM); + } + if (cfg.addPlocDummies) { + auto* plocMpsocDummy = + new PlocMpsocDummy(objects::PLOC_MPSOC_HANDLER, objects::DUMMY_COM_IF, comCookieDummy); + plocMpsocDummy->connectModeTreeParent(satsystem::payload::SUBSYSTEM); + auto* plocSupervisorDummy = new PlocSupervisorDummy( + objects::PLOC_SUPERVISOR_HANDLER, objects::DUMMY_COM_IF, comCookieDummy, pwrSwitcher); + plocSupervisorDummy->connectModeTreeParent(satsystem::payload::SUBSYSTEM); + } + if (cfg.addRadSensorDummy) { + auto* radSensorDummy = + new RadSensorDummy(objects::RAD_SENSOR, objects::DUMMY_COM_IF, comCookieDummy); + radSensorDummy->connectModeTreeParent(satsystem::payload::SUBSYSTEM); + } +} diff --git a/dummies/helperFactory.h b/dummies/helperFactory.h new file mode 100644 index 0000000..34d167f --- /dev/null +++ b/dummies/helperFactory.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +class GpioIF; + +namespace dummy { + +struct Tmp1075Cfg { + bool addTcsBrd0 = true; + bool addTcsBrd1 = true; + bool addPlPcdu0 = true; + bool addPlPcdu1 = true; + bool addIfBrd = true; +}; + +// Default values targeted towards EM. +struct DummyCfg { + bool addCoreCtrlCfg = true; + // Special variant because the ACU broke. Overrides addPowerDummies, only ACU dummy will be added. + bool addOnlyAcuDummy = false; + bool addPowerDummies = true; + bool addBpxBattDummy = true; + bool addSyrlinksDummies = true; + bool addAcsBoardDummies = true; + bool addSusDummies = true; + bool addTempSensorDummies = true; + bool addRtdComIFDummy = true; + bool addPlocDummies = true; + bool addStrDummy = true; + bool addTmpDummies = true; + bool addRadSensorDummy = true; + bool addPlPcduDummy = false; + Tmp1075Cfg tmp1075Cfg; + bool addCamSwitcherDummy = false; + bool addScexDummy = false; +}; + +void createDummies(DummyCfg cfg, PowerSwitchIF& pwrSwitch, GpioIF* gpioIF, bool enableHkSets); + +} // namespace dummy diff --git a/fsfw b/fsfw index 42bfedd..42867ad 160000 --- a/fsfw +++ b/fsfw @@ -1 +1 @@ -Subproject commit 42bfedd36c517d00d0be6321d1d828f429813ef1 +Subproject commit 42867ad0cba088ab1cb6cb672d001f991f7e4a60 diff --git a/generators/.gitignore b/generators/.gitignore new file mode 100644 index 0000000..c5e80e6 --- /dev/null +++ b/generators/.gitignore @@ -0,0 +1,4 @@ +.~lock* +/venv +/.idea/* +!/.idea/runConfigurations diff --git a/generators/.run/all.run.xml b/generators/.run/all.run.xml new file mode 100644 index 0000000..089f02d --- /dev/null +++ b/generators/.run/all.run.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/generators/.run/events.run.xml b/generators/.run/events.run.xml new file mode 100644 index 0000000..6bc73c9 --- /dev/null +++ b/generators/.run/events.run.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/generators/.run/objects.run.xml b/generators/.run/objects.run.xml new file mode 100644 index 0000000..df684cc --- /dev/null +++ b/generators/.run/objects.run.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/generators/.run/returnvalues.run.xml b/generators/.run/returnvalues.run.xml new file mode 100644 index 0000000..dcb00ef --- /dev/null +++ b/generators/.run/returnvalues.run.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/generators/bsp_hosted_events.csv b/generators/bsp_hosted_events.csv new file mode 100644 index 0000000..7b833ae --- /dev/null +++ b/generators/bsp_hosted_events.csv @@ -0,0 +1,326 @@ +Event ID (dec); Event ID (hex); Name; Severity; Description; File Path +2200;0x0898;STORE_SEND_WRITE_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2201;0x0899;STORE_WRITE_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2202;0x089a;STORE_SEND_READ_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2203;0x089b;STORE_READ_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2204;0x089c;UNEXPECTED_MSG;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2205;0x089d;STORING_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2206;0x089e;TM_DUMP_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2207;0x089f;STORE_INIT_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2208;0x08a0;STORE_INIT_EMPTY;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2209;0x08a1;STORE_CONTENT_CORRUPTED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2210;0x08a2;STORE_INITIALIZE;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2211;0x08a3;INIT_DONE;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2212;0x08a4;DUMP_FINISHED;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2213;0x08a5;DELETION_FINISHED;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2214;0x08a6;DELETION_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2215;0x08a7;AUTO_CATALOGS_SENDING_FAILED;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2600;0x0a28;GET_DATA_FAILED;LOW;No description;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +2601;0x0a29;STORE_DATA_FAILED;LOW;No description;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +2800;0x0af0;DEVICE_BUILDING_COMMAND_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2801;0x0af1;DEVICE_SENDING_COMMAND_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2802;0x0af2;DEVICE_REQUESTING_REPLY_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2803;0x0af3;DEVICE_READING_REPLY_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2804;0x0af4;DEVICE_INTERPRETING_REPLY_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2805;0x0af5;DEVICE_MISSED_REPLY;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2806;0x0af6;DEVICE_UNKNOWN_REPLY;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2807;0x0af7;DEVICE_UNREQUESTED_REPLY;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2808;0x0af8;INVALID_DEVICE_COMMAND;LOW;Indicates a SW bug in child class.;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2809;0x0af9;MONITORING_LIMIT_EXCEEDED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2810;0x0afa;MONITORING_AMBIGUOUS;HIGH;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2811;0x0afb;DEVICE_WANTS_HARD_REBOOT;HIGH;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +4300;0x10cc;SWITCH_WENT_OFF;LOW;No description;fsfw/src/fsfw/power/PowerSwitchIF.h +4301;0x10cd;FUSE_CURRENT_HIGH;LOW;No description;fsfw/src/fsfw/power/Fuse.h +4302;0x10ce;FUSE_WENT_OFF;LOW;No description;fsfw/src/fsfw/power/Fuse.h +4304;0x10d0;POWER_ABOVE_HIGH_LIMIT;LOW;No description;fsfw/src/fsfw/power/Fuse.h +4305;0x10d1;POWER_BELOW_LOW_LIMIT;LOW;No description;fsfw/src/fsfw/power/Fuse.h +5000;0x1388;HEATER_ON;INFO;No description;fsfw/src/fsfw/thermal/Heater.h +5001;0x1389;HEATER_OFF;INFO;No description;fsfw/src/fsfw/thermal/Heater.h +5002;0x138a;HEATER_TIMEOUT;LOW;No description;fsfw/src/fsfw/thermal/Heater.h +5003;0x138b;HEATER_STAYED_ON;LOW;No description;fsfw/src/fsfw/thermal/Heater.h +5004;0x138c;HEATER_STAYED_OFF;LOW;No description;fsfw/src/fsfw/thermal/Heater.h +5200;0x1450;TEMP_SENSOR_HIGH;LOW;No description;fsfw/src/fsfw/thermal/AbstractTemperatureSensor.h +5201;0x1451;TEMP_SENSOR_LOW;LOW;No description;fsfw/src/fsfw/thermal/AbstractTemperatureSensor.h +5202;0x1452;TEMP_SENSOR_GRADIENT;LOW;No description;fsfw/src/fsfw/thermal/AbstractTemperatureSensor.h +5901;0x170d;COMPONENT_TEMP_LOW;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +5902;0x170e;COMPONENT_TEMP_HIGH;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +5903;0x170f;COMPONENT_TEMP_OOL_LOW;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +5904;0x1710;COMPONENT_TEMP_OOL_HIGH;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +5905;0x1711;TEMP_NOT_IN_OP_RANGE;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +7101;0x1bbd;FDIR_CHANGED_STATE;INFO;No description;fsfw/src/fsfw/fdir/FailureIsolationBase.h +7102;0x1bbe;FDIR_STARTS_RECOVERY;MEDIUM;No description;fsfw/src/fsfw/fdir/FailureIsolationBase.h +7103;0x1bbf;FDIR_TURNS_OFF_DEVICE;MEDIUM;No description;fsfw/src/fsfw/fdir/FailureIsolationBase.h +7201;0x1c21;MONITOR_CHANGED_STATE;LOW;No description;fsfw/src/fsfw/monitoring/MonitoringIF.h +7202;0x1c22;VALUE_BELOW_LOW_LIMIT;LOW;No description;fsfw/src/fsfw/monitoring/MonitoringIF.h +7203;0x1c23;VALUE_ABOVE_HIGH_LIMIT;LOW;No description;fsfw/src/fsfw/monitoring/MonitoringIF.h +7204;0x1c24;VALUE_OUT_OF_RANGE;LOW;No description;fsfw/src/fsfw/monitoring/MonitoringIF.h +7400;0x1ce8;CHANGING_MODE;INFO;No description;fsfw/src/fsfw/modes/HasModesIF.h +7401;0x1ce9;MODE_INFO;INFO;No description;fsfw/src/fsfw/modes/HasModesIF.h +7402;0x1cea;FALLBACK_FAILED;HIGH;No description;fsfw/src/fsfw/modes/HasModesIF.h +7403;0x1ceb;MODE_TRANSITION_FAILED;LOW;No description;fsfw/src/fsfw/modes/HasModesIF.h +7404;0x1cec;CANT_KEEP_MODE;HIGH;No description;fsfw/src/fsfw/modes/HasModesIF.h +7405;0x1ced;OBJECT_IN_INVALID_MODE;LOW;No description;fsfw/src/fsfw/modes/HasModesIF.h +7406;0x1cee;FORCING_MODE;MEDIUM;No description;fsfw/src/fsfw/modes/HasModesIF.h +7407;0x1cef;MODE_CMD_REJECTED;LOW;No description;fsfw/src/fsfw/modes/HasModesIF.h +7506;0x1d52;HEALTH_INFO;INFO;No description;fsfw/src/fsfw/health/HasHealthIF.h +7507;0x1d53;CHILD_CHANGED_HEALTH;INFO;No description;fsfw/src/fsfw/health/HasHealthIF.h +7508;0x1d54;CHILD_PROBLEMS;LOW;No description;fsfw/src/fsfw/health/HasHealthIF.h +7509;0x1d55;OVERWRITING_HEALTH;LOW;No description;fsfw/src/fsfw/health/HasHealthIF.h +7510;0x1d56;TRYING_RECOVERY;MEDIUM;No description;fsfw/src/fsfw/health/HasHealthIF.h +7511;0x1d57;RECOVERY_STEP;MEDIUM;No description;fsfw/src/fsfw/health/HasHealthIF.h +7512;0x1d58;RECOVERY_DONE;MEDIUM;No description;fsfw/src/fsfw/health/HasHealthIF.h +7600;0x1db0;HANDLE_PACKET_FAILED;LOW;No description;fsfw/src/fsfw/tcdistribution/definitions.h +7900;0x1edc;RF_AVAILABLE;INFO;A RF available signal was detected. P1: raw RFA state, P2: 0;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +7901;0x1edd;RF_LOST;INFO;A previously found RF available signal was lost. P1: raw RFA state, P2: 0;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +7902;0x1ede;BIT_LOCK;INFO;A Bit Lock signal. Was detected. P1: raw BLO state, P2: 0;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +7903;0x1edf;BIT_LOCK_LOST;INFO;A previously found Bit Lock signal was lost. P1: raw BLO state, P2: 0;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +7905;0x1ee1;FRAME_PROCESSING_FAILED;LOW;The CCSDS Board could not interpret a TC;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +8900;0x22c4;CLOCK_SET;INFO;Clock has been set. P1: old timeval seconds. P2: new timeval seconds.;fsfw/src/fsfw/pus/Service9TimeManagement.h +8901;0x22c5;CLOCK_DUMP_LEGACY;INFO;Clock dump event. P1: timeval seconds P2: timeval milliseconds.;fsfw/src/fsfw/pus/Service9TimeManagement.h +8902;0x22c6;CLOCK_SET_FAILURE;LOW;Clock could not be set. P1: Returncode.;fsfw/src/fsfw/pus/Service9TimeManagement.h +8903;0x22c7;CLOCK_DUMP;INFO;Clock dump event. P1: timeval seconds P2: timeval microseconds.;fsfw/src/fsfw/pus/Service9TimeManagement.h +8904;0x22c8;CLOCK_DUMP_BEFORE_SETTING_TIME;INFO;No description;fsfw/src/fsfw/pus/Service9TimeManagement.h +8905;0x22c9;CLOCK_DUMP_AFTER_SETTING_TIME;INFO;No description;fsfw/src/fsfw/pus/Service9TimeManagement.h +9100;0x238c;TC_DELETION_FAILED;MEDIUM;Deletion of a TC from the map failed. P1: First 32 bit of request ID, P2. Last 32 bit of Request ID;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +9700;0x25e4;TEST;INFO;No description;fsfw/src/fsfw/pus/Service17Test.h +10600;0x2968;CHANGE_OF_SETUP_PARAMETER;LOW;No description;fsfw/src/fsfw_hal/devicehandlers/MgmLIS3MDLHandler.h +10800;0x2a30;STORE_ERROR;LOW;No description;fsfw/src/fsfw/cfdp/handler/defs.h +10801;0x2a31;MSG_QUEUE_ERROR;LOW;No description;fsfw/src/fsfw/cfdp/handler/defs.h +10802;0x2a32;SERIALIZATION_ERROR;LOW;No description;fsfw/src/fsfw/cfdp/handler/defs.h +10803;0x2a33;FILESTORE_ERROR;LOW;No description;fsfw/src/fsfw/cfdp/handler/defs.h +10804;0x2a34;FILENAME_TOO_LARGE_ERROR;LOW;P1: Transaction step ID, P2: 0 for source file name, 1 for dest file name;fsfw/src/fsfw/cfdp/handler/defs.h +10805;0x2a35;HANDLING_CFDP_REQUEST_FAILED;LOW;CFDP request handling failed. P2: Returncode.;fsfw/src/fsfw/cfdp/handler/defs.h +11200;0x2bc0;SAFE_RATE_VIOLATION;MEDIUM;The limits for the rotation in safe mode were violated.;mission/acs/defs.h +11201;0x2bc1;RATE_RECOVERY;MEDIUM;The system has recovered from a rate rotation violation.;mission/acs/defs.h +11202;0x2bc2;MULTIPLE_RW_INVALID;HIGH;Multiple RWs are invalid, uncommandable and therefore higher ACS modes cannot be maintained.;mission/acs/defs.h +11203;0x2bc3;MEKF_INVALID_INFO;INFO;MEKF was not able to compute a solution. P1: MEKF state on exit;mission/acs/defs.h +11204;0x2bc4;MEKF_RECOVERY;INFO;MEKF is able to compute a solution again.;mission/acs/defs.h +11205;0x2bc5;MEKF_AUTOMATIC_RESET;INFO;MEKF performed an automatic reset after detection of nonfinite values.;mission/acs/defs.h +11206;0x2bc6;PTG_CTRL_NO_ATTITUDE_INFORMATION;HIGH;For a prolonged time, no attitude information was available for the Pointing Controller. Falling back to Safe Mode.;mission/acs/defs.h +11207;0x2bc7;SAFE_MODE_CONTROLLER_FAILURE;HIGH;The ACS safe mode controller was not able to compute a solution and has failed. P1: Missing information about magnetic field, P2: Missing information about rotational rate;mission/acs/defs.h +11208;0x2bc8;TLE_TOO_OLD;INFO;The TLE for the SGP4 Propagator has become too old.;mission/acs/defs.h +11209;0x2bc9;TLE_FILE_READ_FAILED;LOW;The TLE could not be read from the filesystem.;mission/acs/defs.h +11210;0x2bca;PTG_RATE_VIOLATION;MEDIUM;The limits for the rotation in pointing mode were violated.;mission/acs/defs.h +11211;0x2bcb;DETUMBLE_TRANSITION_FAILED;HIGH;The detumble transition has failed. //! P1: Last detumble state before failure.;mission/acs/defs.h +11300;0x2c24;SWITCH_CMD_SENT;INFO;Indicates that a FSFW object requested setting a switch P1: 1 if on was requested, 0 for off | P2: Switch Index;mission/power/defs.h +11301;0x2c25;SWITCH_HAS_CHANGED;INFO;Indicated that a switch state has changed P1: New switch state, 1 for on, 0 for off | P2: Switch Index;mission/power/defs.h +11302;0x2c26;SWITCHING_Q7S_DENIED;MEDIUM;No description;mission/power/defs.h +11303;0x2c27;FDIR_REACTION_IGNORED;MEDIUM;No description;mission/power/defs.h +11304;0x2c28;DATASET_READ_FAILED;INFO;The dataset read for the inputs of the Power Controller has failed.;mission/power/defs.h +11305;0x2c29;VOLTAGE_OUT_OF_BOUNDS;HIGH;No description;mission/power/defs.h +11306;0x2c2a;TIMEDELTA_OUT_OF_BOUNDS;LOW;Time difference for Coulomb Counter was too large. P1: time in s * 10;mission/power/defs.h +11307;0x2c2b;POWER_LEVEL_LOW;HIGH;The State of Charge is below the limit for payload use. Setting Payload to faulty.;mission/power/defs.h +11308;0x2c2c;POWER_LEVEL_CRITICAL;HIGH;The State of Charge is below the limit for higher modes. Setting Reaction Wheels to faulty.;mission/power/defs.h +11400;0x2c88;GPIO_PULL_HIGH_FAILED;LOW;No description;mission/tcs/HeaterHandler.h +11401;0x2c89;GPIO_PULL_LOW_FAILED;LOW;No description;mission/tcs/HeaterHandler.h +11402;0x2c8a;HEATER_WENT_ON;INFO;No description;mission/tcs/HeaterHandler.h +11403;0x2c8b;HEATER_WENT_OFF;INFO;No description;mission/tcs/HeaterHandler.h +11404;0x2c8c;SWITCH_ALREADY_ON;INFO;No description;mission/tcs/HeaterHandler.h +11405;0x2c8d;SWITCH_ALREADY_OFF;INFO;No description;mission/tcs/HeaterHandler.h +11406;0x2c8e;MAIN_SWITCH_TIMEOUT;MEDIUM;No description;mission/tcs/HeaterHandler.h +11407;0x2c8f;FAULTY_HEATER_WAS_ON;LOW;No description;mission/tcs/HeaterHandler.h +11500;0x2cec;BURN_PHASE_START;INFO;P1: Burn duration in milliseconds, P2: Dry run flag;mission/SolarArrayDeploymentHandler.h +11501;0x2ced;BURN_PHASE_DONE;INFO;P1: Burn duration in milliseconds, P2: Dry run flag;mission/SolarArrayDeploymentHandler.h +11502;0x2cee;MAIN_SWITCH_ON_TIMEOUT;LOW;No description;mission/SolarArrayDeploymentHandler.h +11503;0x2cef;MAIN_SWITCH_OFF_TIMEOUT;LOW;No description;mission/SolarArrayDeploymentHandler.h +11504;0x2cf0;DEPL_SA1_GPIO_SWTICH_ON_FAILED;HIGH;No description;mission/SolarArrayDeploymentHandler.h +11505;0x2cf1;DEPL_SA2_GPIO_SWTICH_ON_FAILED;HIGH;No description;mission/SolarArrayDeploymentHandler.h +11506;0x2cf2;DEPL_SA1_GPIO_SWTICH_OFF_FAILED;HIGH;No description;mission/SolarArrayDeploymentHandler.h +11507;0x2cf3;DEPL_SA2_GPIO_SWTICH_OFF_FAILED;HIGH;No description;mission/SolarArrayDeploymentHandler.h +11508;0x2cf4;AUTONOMOUS_DEPLOYMENT_COMPLETED;INFO;No description;mission/SolarArrayDeploymentHandler.h +11601;0x2d51;MEMORY_READ_RPT_CRC_FAILURE;LOW;PLOC crc failure in telemetry packet;linux/payload/plocMpsocHelpers.h +11602;0x2d52;ACK_FAILURE;LOW;PLOC receive acknowledgment failure report P1: Command Id which leads the acknowledgment failure report P2: The status field inserted by the MPSoC into the data field;linux/payload/plocMpsocHelpers.h +11603;0x2d53;EXE_FAILURE;LOW;PLOC receive execution failure report P1: Command Id which leads the execution failure report P2: The status field inserted by the MPSoC into the data field;linux/payload/plocMpsocHelpers.h +11604;0x2d54;MPSOC_HANDLER_CRC_FAILURE;LOW;PLOC reply has invalid crc;linux/payload/plocMpsocHelpers.h +11605;0x2d55;MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH;LOW;Packet sequence count in received space packet does not match expected count P1: Expected sequence count P2: Received sequence count;linux/payload/plocMpsocHelpers.h +11606;0x2d56;MPSOC_SHUTDOWN_FAILED;HIGH;Supervisor fails to shutdown MPSoC. Requires to power off the PLOC and thus also to shutdown the supervisor.;linux/payload/plocMpsocHelpers.h +11607;0x2d57;SUPV_NOT_ON;LOW;SUPV not on for boot or shutdown process. P1: 0 for OFF transition, 1 for ON transition.;linux/payload/plocMpsocHelpers.h +11608;0x2d58;SUPV_REPLY_TIMEOUT;LOW;SUPV reply timeout.;linux/payload/plocMpsocHelpers.h +11609;0x2d59;CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE;LOW;Camera must be commanded on first.;linux/payload/plocMpsocHelpers.h +11701;0x2db5;SELF_TEST_I2C_FAILURE;LOW;Get self test result returns I2C failure P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11702;0x2db6;SELF_TEST_SPI_FAILURE;LOW;Get self test result returns SPI failure. This concerns the MTM connectivity. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11703;0x2db7;SELF_TEST_ADC_FAILURE;LOW;Get self test result returns failure in measurement of current and temperature. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11704;0x2db8;SELF_TEST_PWM_FAILURE;LOW;Get self test result returns PWM failure which concerns the coil actuation. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11705;0x2db9;SELF_TEST_TC_FAILURE;LOW;Get self test result returns TC failure (system failure) P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11706;0x2dba;SELF_TEST_MTM_RANGE_FAILURE;LOW;Get self test result returns failure that MTM values were outside of the expected range. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11707;0x2dbb;SELF_TEST_COIL_CURRENT_FAILURE;LOW;Get self test result returns failure indicating that the coil current was outside of the expected range P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11708;0x2dbc;INVALID_ERROR_BYTE;LOW;Received invalid error byte. This indicates an error of the communication link between IMTQ and OBC.;mission/acs/ImtqHandler.h +11801;0x2e19;ERROR_STATE;HIGH;Reaction wheel signals an error state;mission/acs/rwHelpers.h +11802;0x2e1a;RESET_OCCURED;LOW;No description;mission/acs/rwHelpers.h +11901;0x2e7d;BOOTING_FIRMWARE_FAILED_EVENT;LOW;Failed to boot firmware;mission/acs/str/StarTrackerHandler.h +11902;0x2e7e;BOOTING_BOOTLOADER_FAILED_EVENT;LOW;Failed to boot star tracker into bootloader mode;mission/acs/str/StarTrackerHandler.h +11903;0x2e7f;COM_ERROR_REPLY_RECEIVED;LOW;Received COM error. P1: Communication Error ID (datasheet p32);mission/acs/str/StarTrackerHandler.h +12001;0x2ee1;SUPV_MEMORY_READ_RPT_CRC_FAILURE;LOW;PLOC supervisor crc failure in telemetry packet;linux/payload/plocSupvDefs.h +12002;0x2ee2;SUPV_UNKNOWN_TM;LOW;Unhandled event. P1: APID, P2: Service ID;linux/payload/plocSupvDefs.h +12003;0x2ee3;SUPV_UNINIMPLEMENTED_TM;LOW;No description;linux/payload/plocSupvDefs.h +12004;0x2ee4;SUPV_ACK_FAILURE;LOW;PLOC supervisor received acknowledgment failure report;linux/payload/plocSupvDefs.h +12005;0x2ee5;SUPV_EXE_FAILURE;LOW;PLOC received execution failure report P1: ID of command for which the execution failed P2: Status code sent by the supervisor handler;linux/payload/plocSupvDefs.h +12006;0x2ee6;SUPV_CRC_FAILURE_EVENT;LOW;PLOC supervisor reply has invalid crc;linux/payload/plocSupvDefs.h +12007;0x2ee7;SUPV_HELPER_EXECUTING;LOW;Supervisor helper currently executing a command;linux/payload/plocSupvDefs.h +12008;0x2ee8;SUPV_MPSOC_SHUTDOWN_BUILD_FAILED;LOW;Failed to build the command to shutdown the MPSoC;linux/payload/plocSupvDefs.h +12009;0x2ee9;SUPV_ACK_UNKNOWN_COMMAND;LOW;Received ACK, but no related command is unknown or has not been sent by this software instance. P1: Module APID. P2: Service ID.;linux/payload/plocSupvDefs.h +12010;0x2eea;SUPV_EXE_ACK_UNKNOWN_COMMAND;LOW;Received ACK EXE, but no related command is unknown or has not been sent by this software instance. P1: Module APID. P2: Service ID.;linux/payload/plocSupvDefs.h +12100;0x2f44;SANITIZATION_FAILED;LOW;No description;bsp_q7s/fs/SdCardManager.h +12101;0x2f45;MOUNTED_SD_CARD;INFO;No description;bsp_q7s/fs/SdCardManager.h +12300;0x300c;SEND_MRAM_DUMP_FAILED;LOW;Failed to send mram dump command to supervisor handler P1: Return value of commandAction function P2: Start address of MRAM to dump with this command;linux/payload/PlocMemoryDumper.h +12301;0x300d;MRAM_DUMP_FAILED;LOW;Received completion failure report form PLOC supervisor handler P1: MRAM start address of failing dump command;linux/payload/PlocMemoryDumper.h +12302;0x300e;MRAM_DUMP_FINISHED;LOW;MRAM dump finished successfully;linux/payload/PlocMemoryDumper.h +12401;0x3071;INVALID_TC_FRAME;HIGH;No description;linux/ipcore/pdec.h +12402;0x3072;INVALID_FAR;HIGH;Read invalid FAR from PDEC after startup;linux/ipcore/pdec.h +12403;0x3073;CARRIER_LOCK;INFO;Carrier lock detected;linux/ipcore/pdec.h +12404;0x3074;BIT_LOCK_PDEC;INFO;Bit lock detected (data valid);linux/ipcore/pdec.h +12405;0x3075;LOST_CARRIER_LOCK_PDEC;INFO;Lost carrier lock;linux/ipcore/pdec.h +12406;0x3076;LOST_BIT_LOCK_PDEC;INFO;Lost bit lock;linux/ipcore/pdec.h +12407;0x3077;TOO_MANY_IRQS;MEDIUM;Too many IRQs over the time window of one second. P1: Allowed TCs;linux/ipcore/pdec.h +12408;0x3078;POLL_SYSCALL_ERROR_PDEC;MEDIUM;No description;linux/ipcore/pdec.h +12409;0x3079;WRITE_SYSCALL_ERROR_PDEC;HIGH;No description;linux/ipcore/pdec.h +12410;0x307a;PDEC_TRYING_RESET_WITH_INIT;LOW;Trying a PDEC reset with complete re-initialization;linux/ipcore/pdec.h +12411;0x307b;PDEC_TRYING_RESET_NO_INIT;LOW;Trying a PDEC reset without re-initialization.;linux/ipcore/pdec.h +12412;0x307c;PDEC_RESET_FAILED;HIGH;Failed to pull PDEC reset to low;linux/ipcore/pdec.h +12413;0x307d;OPEN_IRQ_FILE_FAILED;HIGH;Failed to open the IRQ uio file;linux/ipcore/pdec.h +12414;0x307e;PDEC_INIT_FAILED;HIGH;PDEC initialization failed. This might also be due to the persistent confiuration never becoming available, for example due to SD card issues.;linux/ipcore/pdec.h +12415;0x307f;PDEC_CONFIG_CORRUPTED;HIGH;The PDEC configuration area has been corrupted P1: The first configuration word P2: The second configuration word;linux/ipcore/pdec.h +12500;0x30d4;IMAGE_UPLOAD_FAILED;LOW;Image upload failed;linux/acs/StrComHandler.h +12501;0x30d5;IMAGE_DOWNLOAD_FAILED;LOW;Image download failed;linux/acs/StrComHandler.h +12502;0x30d6;IMAGE_UPLOAD_SUCCESSFUL;LOW;Uploading image to star tracker was successfulop;linux/acs/StrComHandler.h +12503;0x30d7;IMAGE_DOWNLOAD_SUCCESSFUL;LOW;Image download was successful;linux/acs/StrComHandler.h +12504;0x30d8;FLASH_WRITE_SUCCESSFUL;LOW;Finished flash write procedure successfully;linux/acs/StrComHandler.h +12505;0x30d9;FLASH_READ_SUCCESSFUL;LOW;Finished flash read procedure successfully;linux/acs/StrComHandler.h +12506;0x30da;FLASH_READ_FAILED;LOW;Flash read procedure failed;linux/acs/StrComHandler.h +12507;0x30db;FIRMWARE_UPDATE_SUCCESSFUL;LOW;Firmware update was successful;linux/acs/StrComHandler.h +12508;0x30dc;FIRMWARE_UPDATE_FAILED;LOW;Firmware update failed;linux/acs/StrComHandler.h +12509;0x30dd;STR_HELPER_READING_REPLY_FAILED;LOW;Failed to read communication interface reply data P1: Return code of failed communication interface read call P1: Upload/download position for which the read call failed;linux/acs/StrComHandler.h +12510;0x30de;STR_HELPER_COM_ERROR;LOW;Unexpected stop of decoding sequence P1: Return code of failed communication interface read call P1: Upload/download position for which the read call failed;linux/acs/StrComHandler.h +12511;0x30df;STR_COM_REPLY_TIMEOUT;LOW;Star tracker did not send a valid reply for a certain timeout. P1: Position of upload or download packet for which the packet wa sent. P2: Timeout;linux/acs/StrComHandler.h +12513;0x30e1;STR_HELPER_DEC_ERROR;LOW;Error during decoding of received reply occurred P1: Return value of decoding function P2: Position of upload/download packet, or address of flash write/read request;linux/acs/StrComHandler.h +12514;0x30e2;POSITION_MISMATCH;LOW;Position mismatch P1: The expected position and thus the position for which the image upload/download failed;linux/acs/StrComHandler.h +12515;0x30e3;STR_HELPER_FILE_NOT_EXISTS;LOW;Specified file does not exist P1: Internal state of str helper;linux/acs/StrComHandler.h +12516;0x30e4;STR_HELPER_SENDING_PACKET_FAILED;LOW;No description;linux/acs/StrComHandler.h +12517;0x30e5;STR_HELPER_REQUESTING_MSG_FAILED;LOW;No description;linux/acs/StrComHandler.h +12600;0x3138;MPSOC_FLASH_WRITE_FAILED;LOW;Flash write fails;linux/payload/PlocMpsocSpecialComHelper.h +12601;0x3139;MPSOC_FLASH_WRITE_SUCCESSFUL;INFO;Flash write successful;linux/payload/PlocMpsocSpecialComHelper.h +12602;0x313a;MPSOC_SENDING_COMMAND_FAILED;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12603;0x313b;MPSOC_HELPER_REQUESTING_REPLY_FAILED;LOW;Request receive message of communication interface failed P1: Return value returned by the communication interface requestReceiveMessage function P2: Internal state of MPSoC helper;linux/payload/PlocMpsocSpecialComHelper.h +12604;0x313c;MPSOC_HELPER_READING_REPLY_FAILED;LOW;Reading receive message of communication interface failed P1: Return value returned by the communication interface readingReceivedMessage function P2: Internal state of MPSoC helper;linux/payload/PlocMpsocSpecialComHelper.h +12605;0x313d;MPSOC_MISSING_ACK;LOW;Did not receive acknowledgment report P1: Number of bytes missing P2: Internal state of MPSoC helper;linux/payload/PlocMpsocSpecialComHelper.h +12606;0x313e;MPSOC_MISSING_EXE;LOW;Did not receive execution report P1: Number of bytes missing P2: Internal state of MPSoC helper;linux/payload/PlocMpsocSpecialComHelper.h +12607;0x313f;MPSOC_ACK_FAILURE_REPORT;LOW;Received acknowledgment failure report P1: Internal state of MPSoC;linux/payload/PlocMpsocSpecialComHelper.h +12608;0x3140;MPSOC_EXE_FAILURE_REPORT;LOW;Received execution failure report P1: Internal state of MPSoC;linux/payload/PlocMpsocSpecialComHelper.h +12609;0x3141;MPSOC_ACK_INVALID_APID;LOW;Expected acknowledgment report but received space packet with other apid P1: Apid of received space packet P2: Internal state of MPSoC;linux/payload/PlocMpsocSpecialComHelper.h +12610;0x3142;MPSOC_EXE_INVALID_APID;LOW;Expected execution report but received space packet with other apid P1: Apid of received space packet P2: Internal state of MPSoC;linux/payload/PlocMpsocSpecialComHelper.h +12611;0x3143;MPSOC_HELPER_SEQ_CNT_MISMATCH;LOW;Received sequence count does not match expected sequence count P1: Expected sequence count P2: Received sequence count;linux/payload/PlocMpsocSpecialComHelper.h +12612;0x3144;MPSOC_TM_SIZE_ERROR;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12613;0x3145;MPSOC_TM_CRC_MISSMATCH;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12614;0x3146;MPSOC_FLASH_READ_PACKET_ERROR;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12615;0x3147;MPSOC_FLASH_READ_FAILED;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12616;0x3148;MPSOC_FLASH_READ_SUCCESSFUL;INFO;No description;linux/payload/PlocMpsocSpecialComHelper.h +12617;0x3149;MPSOC_READ_TIMEOUT;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12700;0x319c;TRANSITION_BACK_TO_OFF;MEDIUM;Could not transition properly and went back to ALL OFF;mission/payload/PayloadPcduHandler.h +12701;0x319d;NEG_V_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12702;0x319e;U_DRO_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12703;0x319f;I_DRO_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12704;0x31a0;U_X8_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12705;0x31a1;I_X8_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12706;0x31a2;U_TX_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12707;0x31a3;I_TX_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12708;0x31a4;U_MPA_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12709;0x31a5;I_MPA_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12710;0x31a6;U_HPA_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12711;0x31a7;I_HPA_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12800;0x3200;TRANSITION_OTHER_SIDE_FAILED;HIGH;No description;mission/system/acs/AcsBoardAssembly.h +12801;0x3201;NOT_ENOUGH_DEVICES_DUAL_MODE;HIGH;No description;mission/system/acs/AcsBoardAssembly.h +12802;0x3202;POWER_STATE_MACHINE_TIMEOUT;MEDIUM;No description;mission/system/acs/AcsBoardAssembly.h +12803;0x3203;SIDE_SWITCH_TRANSITION_NOT_ALLOWED;LOW;Not implemented, would increase already high complexity. Operator should instead command the assembly off first and then command the assembly on into the desired mode/submode combination;mission/system/acs/AcsBoardAssembly.h +12804;0x3204;DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY;MEDIUM;This is triggered when the assembly would have normally switched the board side, but the GPS device of the other side was marked faulty. P1: Current submode.;mission/system/acs/AcsBoardAssembly.h +12900;0x3264;TRANSITION_OTHER_SIDE_FAILED;HIGH;No description;mission/system/acs/SusAssembly.h +12901;0x3265;NOT_ENOUGH_DEVICES_DUAL_MODE;HIGH;No description;mission/system/acs/SusAssembly.h +12902;0x3266;POWER_STATE_MACHINE_TIMEOUT;MEDIUM;No description;mission/system/acs/SusAssembly.h +12903;0x3267;SIDE_SWITCH_TRANSITION_NOT_ALLOWED;LOW;Not implemented, would increase already high complexity. Operator should instead command the assembly off first and then command the assembly on into the desired mode/submode combination;mission/system/acs/SusAssembly.h +13000;0x32c8;CHILDREN_LOST_MODE;MEDIUM;No description;mission/system/tcs/TcsBoardAssembly.h +13100;0x332c;GPS_FIX_CHANGE;INFO;Fix has changed. P1: New fix. P2: Missed fix changes 0: Not seen, 1: No Fix, 2: 2D-Fix, 3: 3D-Fix;linux/acs/GPSDefinitions.h +13101;0x332d;CANT_GET_FIX;MEDIUM;Could not get fix in maximum allowed time. Trying to reset both GNSS devices. P1: Maximum allowed time to get a fix after the GPS was switched on.;linux/acs/GPSDefinitions.h +13102;0x332e;RESET_FAIL;HIGH;Failed to reset an GNNS Device. P1: Board-Side.;linux/acs/GPSDefinitions.h +13200;0x3390;P60_BOOT_COUNT;INFO;P60 boot count is broadcasted once at SW startup. P1: Boot count;mission/power/P60DockHandler.h +13201;0x3391;BATT_MODE;INFO;Battery mode is broadcasted at startup. P1: Mode;mission/power/P60DockHandler.h +13202;0x3392;BATT_MODE_CHANGED;MEDIUM;Battery mode has changed. P1: Old mode. P2: New mode;mission/power/P60DockHandler.h +13600;0x3520;SUPV_UPDATE_FAILED;LOW;update failed;linux/payload/PlocSupvUartMan.h +13601;0x3521;SUPV_UPDATE_SUCCESSFUL;LOW;update successful;linux/payload/PlocSupvUartMan.h +13602;0x3522;SUPV_CONTINUE_UPDATE_FAILED;LOW;Continue update command failed;linux/payload/PlocSupvUartMan.h +13603;0x3523;SUPV_CONTINUE_UPDATE_SUCCESSFUL;LOW;Continue update command successful;linux/payload/PlocSupvUartMan.h +13604;0x3524;TERMINATED_UPDATE_PROCEDURE;LOW;Terminated update procedure by command;linux/payload/PlocSupvUartMan.h +13605;0x3525;SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL;LOW;Requesting event buffer was successful;linux/payload/PlocSupvUartMan.h +13606;0x3526;SUPV_EVENT_BUFFER_REQUEST_FAILED;LOW;Requesting event buffer failed;linux/payload/PlocSupvUartMan.h +13607;0x3527;SUPV_EVENT_BUFFER_REQUEST_TERMINATED;LOW;Terminated event buffer request by command P1: Number of packets read before process was terminated;linux/payload/PlocSupvUartMan.h +13608;0x3528;SUPV_MEM_CHECK_OK;INFO;No description;linux/payload/PlocSupvUartMan.h +13609;0x3529;SUPV_MEM_CHECK_FAIL;INFO;No description;linux/payload/PlocSupvUartMan.h +13616;0x3530;SUPV_SENDING_COMMAND_FAILED;LOW;No description;linux/payload/PlocSupvUartMan.h +13617;0x3531;SUPV_HELPER_REQUESTING_REPLY_FAILED;LOW;Request receive message of communication interface failed P1: Return value returned by the communication interface requestReceiveMessage function P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13618;0x3532;SUPV_HELPER_READING_REPLY_FAILED;LOW;Reading receive message of communication interface failed P1: Return value returned by the communication interface readingReceivedMessage function P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13619;0x3533;SUPV_MISSING_ACK;LOW;Did not receive acknowledgement report P1: Number of bytes missing P2: Internal state of MPSoC helper;linux/payload/PlocSupvUartMan.h +13620;0x3534;SUPV_MISSING_EXE;LOW;Supervisor did not receive execution report P1: Number of bytes missing P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13621;0x3535;SUPV_ACK_FAILURE_REPORT;LOW;Supervisor received acknowledgment failure report P1: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13622;0x3536;SUPV_EXE_FAILURE_REPORT;LOW;Execution report failure P1:;linux/payload/PlocSupvUartMan.h +13623;0x3537;SUPV_ACK_INVALID_APID;LOW;Supervisor expected acknowledgment report but received space packet with other apid P1: Apid of received space packet P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13624;0x3538;SUPV_EXE_INVALID_APID;LOW;Supervisor helper expected execution report but received space packet with other apid P1: Apid of received space packet P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13625;0x3539;ACK_RECEPTION_FAILURE;LOW;Failed to receive acknowledgment report P1: Return value P2: Apid of command for which the reception of the acknowledgment report failed;linux/payload/PlocSupvUartMan.h +13626;0x353a;EXE_RECEPTION_FAILURE;LOW;Failed to receive execution report P1: Return value P2: Apid of command for which the reception of the execution report failed;linux/payload/PlocSupvUartMan.h +13627;0x353b;WRITE_MEMORY_FAILED;LOW;Update procedure failed when sending packet. P1: First byte percent, third and fourth byte Sequence Count, P2: Bytes written;linux/payload/PlocSupvUartMan.h +13628;0x353c;SUPV_REPLY_SIZE_MISSMATCH;LOW;No description;linux/payload/PlocSupvUartMan.h +13629;0x353d;SUPV_REPLY_CRC_MISSMATCH;LOW;No description;linux/payload/PlocSupvUartMan.h +13630;0x353e;SUPV_UPDATE_PROGRESS;INFO;Will be triggered every 5 percent of the update progress. P1: First byte percent, third and fourth byte Sequence Count, P2: Bytes written;linux/payload/PlocSupvUartMan.h +13631;0x353f;HDLC_FRAME_REMOVAL_ERROR;INFO;No description;linux/payload/PlocSupvUartMan.h +13632;0x3540;HDLC_CRC_ERROR;INFO;No description;linux/payload/PlocSupvUartMan.h +13701;0x3585;TX_ON;INFO;Transmitter is on now. P1: Submode, P2: Current default datarate.;mission/com/syrlinksDefs.h +13702;0x3586;TX_OFF;INFO;Transmitter is off now.;mission/com/syrlinksDefs.h +13800;0x35e8;MISSING_PACKET;LOW;No description;mission/payload/scexHelpers.h +13801;0x35e9;EXPERIMENT_TIMEDOUT;LOW;No description;mission/payload/scexHelpers.h +13802;0x35ea;MULTI_PACKET_COMMAND_DONE;INFO;No description;mission/payload/scexHelpers.h +13803;0x35eb;FS_UNUSABLE;LOW;No description;mission/payload/scexHelpers.h +13901;0x364d;SET_CONFIGFILEVALUE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +13902;0x364e;GET_CONFIGFILEVALUE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +13903;0x364f;INSERT_CONFIGFILEVALUE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +13904;0x3650;WRITE_CONFIGFILE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +13905;0x3651;READ_CONFIGFILE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +14000;0x36b0;ALLOC_FAILURE;MEDIUM;No description;mission/sysDefs.h +14001;0x36b1;REBOOT_SW;LOW; Software reboot occurred. Can also be a systemd reboot. P1: Current Chip, P2: Current Copy;mission/sysDefs.h +14002;0x36b2;REBOOT_MECHANISM_TRIGGERED;MEDIUM;The reboot mechanism was triggered. P1: First 16 bits: Last Chip, Last 16 bits: Last Copy, P2: Each byte is the respective reboot count for the slots;mission/sysDefs.h +14003;0x36b3;REBOOT_HW;MEDIUM;No description;mission/sysDefs.h +14004;0x36b4;NO_SD_CARD_ACTIVE;HIGH;No SD card was active. Core controller will attempt to re-initialize a SD card.;mission/sysDefs.h +14005;0x36b5;VERSION_INFO;INFO;P1: Byte 0: Major, Byte 1: Minor, Byte 2: Patch, Byte 3: Has Git Hash P2: First four letters of Git SHA is the last byte of P1 is set.;mission/sysDefs.h +14006;0x36b6;CURRENT_IMAGE_INFO;INFO;P1: Current Chip, P2: Current Copy;mission/sysDefs.h +14007;0x36b7;REBOOT_COUNTER;INFO;Total reboot counter, which is the sum of the boot count of all individual images.;mission/sysDefs.h +14008;0x36b8;INDIVIDUAL_BOOT_COUNTS;INFO;Get the boot count of the individual images. P1: First 16 bits boot count of image 0 0, last 16 bits boot count of image 0 1. P2: First 16 bits boot count of image 1 0, last 16 bits boot count of image 1 1.;mission/sysDefs.h +14010;0x36ba;TRYING_I2C_RECOVERY;HIGH;I2C is unavailable. Trying recovery of I2C bus by power cycling all I2C devices.;mission/sysDefs.h +14011;0x36bb;I2C_REBOOT;HIGH;I2C is unavailable. Recovery did not work, performing full reboot.;mission/sysDefs.h +14012;0x36bc;PDEC_REBOOT;HIGH;PDEC recovery through reset was not possible, performing full reboot.;mission/sysDefs.h +14013;0x36bd;FIRMWARE_INFO;INFO;Version information of the firmware (not OBSW). P1: Byte 0: Major, Byte 1: Minor, Byte 2: Patch, Byte 3: Has Git Hash P2: First four letters of Git SHA is the last byte of P1 is set.;mission/sysDefs.h +14014;0x36be;ACTIVE_SD_INFO;INFO;Active SD card info. SD States: 0: OFF, 1: ON, 2: MOUNTED. P1: Active SD Card Index, 0 if none is active P2: First two bytes: SD state of SD card 0, last two bytes SD state of SD card 1;mission/sysDefs.h +14100;0x3714;NO_VALID_SENSOR_TEMPERATURE;MEDIUM;No description;mission/controller/tcsDefs.h +14101;0x3715;NO_HEALTHY_HEATER_AVAILABLE;MEDIUM;No description;mission/controller/tcsDefs.h +14102;0x3716;SYRLINKS_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14104;0x3718;OBC_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14105;0x3719;CAMERA_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14106;0x371a;PCDU_SYSTEM_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14107;0x371b;HEATER_NOT_OFF_FOR_OFF_MODE;MEDIUM;No description;mission/controller/tcsDefs.h +14108;0x371c;MGT_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14109;0x371d;TCS_SWITCHING_HEATER_ON;INFO;P1: Module index. P2: Heater index;mission/controller/tcsDefs.h +14110;0x371e;TCS_SWITCHING_HEATER_OFF;INFO;P1: Module index. P2: Heater index;mission/controller/tcsDefs.h +14111;0x371f;TCS_HEATER_MAX_BURN_TIME_REACHED;MEDIUM;P1: Heater index. P2: Maximum burn time for heater.;mission/controller/tcsDefs.h +14201;0x3779;TX_TIMER_EXPIRED;INFO;The transmit timer to protect the Syrlinks expired P1: The current timer value;mission/system/com/ComSubsystem.h +14202;0x377a;BIT_LOCK_TX_ON;INFO;Transmitter will be turned on due to detection of bitlock;mission/system/com/ComSubsystem.h +14300;0x37dc;POSSIBLE_FILE_CORRUPTION;LOW;P1: Result code of TM packet parser. P2: Timestamp of possibly corrupt file as a unix timestamp.;mission/persistentTmStoreDefs.h +14301;0x37dd;FILE_TOO_LARGE;LOW;File in store too large. P1: Detected file size P2: Allowed file size;mission/persistentTmStoreDefs.h +14302;0x37de;BUSY_DUMPING_EVENT;INFO;No description;mission/persistentTmStoreDefs.h +14305;0x37e1;DUMP_OK_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14306;0x37e2;DUMP_NOK_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14307;0x37e3;DUMP_MISC_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14308;0x37e4;DUMP_HK_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14309;0x37e5;DUMP_CFDP_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14310;0x37e6;DUMP_OK_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14311;0x37e7;DUMP_NOK_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14312;0x37e8;DUMP_MISC_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14313;0x37e9;DUMP_HK_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14314;0x37ea;DUMP_CFDP_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14500;0x38a4;TEMPERATURE_ALL_ONES_START;MEDIUM;Detected invalid values, starting invalid message counting;mission/acs/SusHandler.h +14501;0x38a5;TEMPERATURE_ALL_ONES_RECOVERY;INFO;Detected valid values for a prolonged time again, resetting all counters. P1: Number of periods with invalid messages. P2: Maximum invalid message counter.;mission/acs/SusHandler.h +14600;0x3908;FAULT_HANDLER_TRIGGERED;LOW;P1: CFDP fault handler code. P2: CFDP condition code.;mission/cfdp/defs.h diff --git a/generators/bsp_hosted_objects.csv b/generators/bsp_hosted_objects.csv new file mode 100644 index 0000000..227371d --- /dev/null +++ b/generators/bsp_hosted_objects.csv @@ -0,0 +1,176 @@ +0x42694269;TEST_TASK +0x43000002;ACS_CONTROLLER +0x43000003;CORE_CONTROLLER +0x43000004;POWER_CONTROLLER +0x43000006;GLOBAL_JSON_CFG +0x43000007;XIPHOS_WDT +0x43400001;THERMAL_CONTROLLER +0x44000001;DUMMY_HANDLER +0x44120006;MGM_0_LIS3_HANDLER +0x44120010;GYRO_0_ADIS_HANDLER +0x44120032;SUS_0_N_LOC_XFYFZM_PT_XF +0x44120033;SUS_1_N_LOC_XBYFZM_PT_XB +0x44120034;SUS_2_N_LOC_XFYBZB_PT_YB +0x44120035;SUS_3_N_LOC_XFYBZF_PT_YF +0x44120036;SUS_4_N_LOC_XMYFZF_PT_ZF +0x44120037;SUS_5_N_LOC_XFYMZB_PT_ZB +0x44120038;SUS_6_R_LOC_XFYBZM_PT_XF +0x44120039;SUS_7_R_LOC_XBYBZM_PT_XB +0x44120040;SUS_8_R_LOC_XBYBZB_PT_YB +0x44120041;SUS_9_R_LOC_XBYBZB_PT_YF +0x44120042;SUS_10_N_LOC_XMYBZF_PT_ZF +0x44120043;SUS_11_R_LOC_XBYMZB_PT_ZB +0x44120047;RW1 +0x44120107;MGM_1_RM3100_HANDLER +0x44120111;GYRO_1_L3G_HANDLER +0x44120148;RW2 +0x44120208;MGM_2_LIS3_HANDLER +0x44120212;GYRO_2_ADIS_HANDLER +0x44120249;RW3 +0x44120309;MGM_3_RM3100_HANDLER +0x44120313;GYRO_3_L3G_HANDLER +0x44120350;RW4 +0x44130001;STAR_TRACKER +0x44130045;GPS_CONTROLLER +0x44130046;GPS_0_HEALTH_DEV +0x44130047;GPS_1_HEALTH_DEV +0x44140013;IMTQ_POLLING +0x44140014;IMTQ_HANDLER +0x442000A1;PCDU_HANDLER +0x44250000;P60DOCK_HANDLER +0x44250001;PDU1_HANDLER +0x44250002;PDU2_HANDLER +0x44250003;ACU_HANDLER +0x44260000;BPX_BATT_HANDLER +0x44300000;PLPCDU_HANDLER +0x443200A5;RAD_SENSOR +0x44330000;PLOC_UPDATER +0x44330001;PLOC_MEMORY_DUMPER +0x44330002;STR_COM_IF +0x44330003;PLOC_MPSOC_HELPER +0x44330004;AXI_PTME_CONFIG +0x44330005;PTME_CONFIG +0x44330006;PTME_VC0_LIVE_TM +0x44330007;PTME_VC1_LOG_TM +0x44330008;PTME_VC2_HK_TM +0x44330009;PTME_VC3_CFDP_TM +0x44330015;PLOC_MPSOC_HANDLER +0x44330016;PLOC_SUPERVISOR_HANDLER +0x44330017;PLOC_SUPERVISOR_HELPER +0x44330018;PLOC_MPSOC_COMMUNICATION +0x44330032;SCEX +0x444100A2;SOLAR_ARRAY_DEPL_HANDLER +0x444100A4;HEATER_HANDLER +0x44420004;TMP1075_HANDLER_TCS_0 +0x44420005;TMP1075_HANDLER_TCS_1 +0x44420006;TMP1075_HANDLER_PLPCDU_0 +0x44420007;TMP1075_HANDLER_PLPCDU_1 +0x44420008;TMP1075_HANDLER_IF_BOARD +0x44420016;RTD_0_IC3_PLOC_HEATSPREADER +0x44420017;RTD_1_IC4_PLOC_MISSIONBOARD +0x44420018;RTD_2_IC5_4K_CAMERA +0x44420019;RTD_3_IC6_DAC_HEATSPREADER +0x44420020;RTD_4_IC7_STARTRACKER +0x44420021;RTD_5_IC8_RW1_MX_MY +0x44420022;RTD_6_IC9_DRO +0x44420023;RTD_7_IC10_SCEX +0x44420024;RTD_8_IC11_X8 +0x44420025;RTD_9_IC12_HPA +0x44420026;RTD_10_IC13_PL_TX +0x44420027;RTD_11_IC14_MPA +0x44420028;RTD_12_IC15_ACU +0x44420029;RTD_13_IC16_PLPCDU_HEATSPREADER +0x44420030;RTD_14_IC17_TCS_BOARD +0x44420031;RTD_15_IC18_IMTQ +0x445300A3;SYRLINKS_HANDLER +0x445300A4;SYRLINKS_COM_HANDLER +0x49000001;ARDUINO_COM_IF +0x49000002;DUMMY_COM_IF +0x49010006;SCEX_UART_READER +0x49030003;UART_COM_IF +0x49060004;ACS_BOARD_POLLING_TASK +0x49060005;RW_POLLING_TASK +0x49060006;SPI_RTD_COM_IF +0x49060007;SUS_POLLING_TASK +0x50000100;CCSDS_PACKET_DISTRIBUTOR +0x50000200;PUS_PACKET_DISTRIBUTOR +0x50000300;TCP_TMTC_SERVER +0x50000301;UDP_TMTC_SERVER +0x50000400;TCP_TMTC_POLLING_TASK +0x50000401;UDP_TMTC_POLLING_TASK +0x50000500;FILE_SYSTEM_HANDLER +0x50000550;SDC_MANAGER +0x50000600;PTME +0x50000700;PDEC_HANDLER +0x50000800;CCSDS_HANDLER +0x51000300;PUS_SERVICE_3 +0x51000400;PUS_SERVICE_5 +0x51000500;PUS_SERVICE_6 +0x51000800;PUS_SERVICE_8 +0x51002300;PUS_SERVICE_23 +0x51020100;PUS_SERVICE_201 +0x53000000;FSFW_OBJECTS_START +0x53000001;PUS_SERVICE_1_VERIFICATION +0x53000002;PUS_SERVICE_2_DEVICE_ACCESS +0x53000003;PUS_SERVICE_3_HOUSEKEEPING +0x53000005;PUS_SERVICE_5_EVENT_REPORTING +0x53000008;PUS_SERVICE_8_FUNCTION_MGMT +0x53000009;PUS_SERVICE_9_TIME_MGMT +0x53000011;PUS_SERVICE_11_TC_SCHEDULER +0x53000015;PUS_SERVICE_15_TM_STORAGE +0x53000017;PUS_SERVICE_17_TEST +0x53000020;PUS_SERVICE_20_PARAMETERS +0x53000200;PUS_SERVICE_200_MODE_MGMT +0x53000201;PUS_SERVICE_201_HEALTH +0x53001000;CFDP_PACKET_DISTRIBUTOR +0x53010000;HEALTH_TABLE +0x53010100;MODE_STORE +0x53030000;EVENT_MANAGER +0x53040000;INTERNAL_ERROR_REPORTER +0x534f0100;TC_STORE +0x534f0200;TM_STORE +0x534f0300;IPC_STORE +0x53500010;TIME_STAMPER +0x53500020;VERIFICATION_REPORTER +0x53ffffff;FSFW_OBJECTS_END +0x60000000;HEATER_0_PLOC_PROC_BRD +0x60000001;HEATER_1_PCDU_BRD +0x60000002;HEATER_2_ACS_BRD +0x60000003;HEATER_3_OBC_BRD +0x60000004;HEATER_4_CAMERA +0x60000005;HEATER_5_STR +0x60000006;HEATER_6_DRO +0x60000007;HEATER_7_SYRLINKS +0x73000001;ACS_BOARD_ASS +0x73000002;SUS_BOARD_ASS +0x73000003;TCS_BOARD_ASS +0x73000004;RW_ASSY +0x73000006;CAM_SWITCHER +0x73000007;SYRLINKS_ASSY +0x73000008;IMTQ_ASSY +0x73000009;STR_ASSY +0x73000100;TM_FUNNEL +0x73000101;PUS_TM_FUNNEL +0x73000102;CFDP_TM_FUNNEL +0x73000205;CFDP_HANDLER +0x73000206;CFDP_DISTRIBUTOR +0x73000207;CFDP_FAULT_HANDLER +0x73010000;EIVE_SYSTEM +0x73010001;ACS_SUBSYSTEM +0x73010002;PL_SUBSYSTEM +0x73010003;TCS_SUBSYSTEM +0x73010004;COM_SUBSYSTEM +0x73010005;EPS_SUBSYSTEM +0x73020001;MISC_TM_STORE +0x73020002;OK_TM_STORE +0x73020003;NOT_OK_TM_STORE +0x73020004;HK_TM_STORE +0x73030000;CFDP_TM_STORE +0x73040000;LIVE_TM_TASK +0x73040001;LOG_STORE_AND_TM_TASK +0x73040002;HK_STORE_AND_TM_TASK +0x73040003;CFDP_STORE_AND_TM_TASK +0x73040004;DOWNLINK_RAM_STORE +0x90000003;THERMAL_TEMP_INSERTER +0xCAFECAFE;DUMMY_INTERFACE +0xFFFFFFFF;NO_OBJECT diff --git a/generators/bsp_hosted_returnvalues.csv b/generators/bsp_hosted_returnvalues.csv new file mode 100644 index 0000000..52acf3d --- /dev/null +++ b/generators/bsp_hosted_returnvalues.csv @@ -0,0 +1,533 @@ +Full ID (hex); Name; Description; Unique ID; Subsytem Name; File Path +0x0000;OK;System-wide code for ok.;0;HasReturnvaluesIF;fsfw/returnvalues/returnvalue.h +0x0001;Failed;Unspecified system-wide code for failed.;1;HasReturnvaluesIF;fsfw/returnvalues/returnvalue.h +0x0201;OM_InsertionFailed;No description;1;OBJECT_MANAGER_IF;fsfw/src/fsfw/objectmanager/ObjectManagerIF.h +0x0202;OM_NotFound;No description;2;OBJECT_MANAGER_IF;fsfw/src/fsfw/objectmanager/ObjectManagerIF.h +0x0203;OM_ChildInitFailed;No description;3;OBJECT_MANAGER_IF;fsfw/src/fsfw/objectmanager/ObjectManagerIF.h +0x0204;OM_InternalErrReporterUninit;No description;4;OBJECT_MANAGER_IF;fsfw/src/fsfw/objectmanager/ObjectManagerIF.h +0x03a0;DHB_InvalidChannel;No description;160;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03b0;DHB_AperiodicReply;No description;176;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03b1;DHB_IgnoreReplyData;No description;177;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03b2;DHB_IgnoreFullPacket;No description;178;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03c0;DHB_NothingToSend;No description;192;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03c2;DHB_CommandMapError;No description;194;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03d0;DHB_NoSwitch;No description;208;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03e0;DHB_ChildTimeout;No description;224;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03e1;DHB_SwitchFailed;No description;225;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x0400;RMP_ReplyOk;No description;0;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0401;RMP_ReplyGeneralErrorCode;No description;1;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0402;RMP_ReplyUnusedPacketTypeOrCommandCode;No description;2;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0403;RMP_ReplyInvalidKey;No description;3;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0404;RMP_ReplyInvalidDataCrc;No description;4;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0405;RMP_ReplyEarlyEop;No description;5;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0406;RMP_ReplyTooMuchData;No description;6;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0407;RMP_ReplyEep;No description;7;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0408;RMP_ReplyReserved;No description;8;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0409;RMP_ReplyVerifyBufferOverrun;No description;9;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x040a;RMP_ReplyCommandNotImplementedOrNotAuthorised;No description;10;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x040b;RMP_ReplyRmwDataLengthError;No description;11;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x040c;RMP_ReplyInvalidTargetLogicalAddress;No description;12;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04c0;RMP_ReplyInterfaceBusy;No description;192;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04c1;RMP_ReplyTransmissionError;No description;193;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04c2;RMP_ReplyInvalidData;No description;194;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04c3;RMP_ReplyNotSupported;No description;195;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d0;RMP_ReplyNoReply;No description;208;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d1;RMP_ReplyNotSent;No description;209;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d2;RMP_ReplyNotYetSent;No description;210;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d3;RMP_ReplyMissmatch;No description;211;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d4;RMP_ReplyTimeout;No description;212;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e1;RMP_CommandNoDescriptorsAvailable;No description;225;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e2;RMP_CommandBufferFull;No description;226;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e3;RMP_CommandChannelOutOfRange;No description;227;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e6;RMP_CommandChannelDeactivated;No description;230;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e7;RMP_CommandPortOutOfRange;No description;231;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e8;RMP_CommandPortInUse;No description;232;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e9;RMP_CommandNoChannel;No description;233;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04ea;RMP_NoHwCrc;No description;234;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f0;RMP_LinkDown;No description;240;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f1;RMP_SpwCredit;No description;241;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f2;RMP_SpwEscape;No description;242;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f3;RMP_SpwDisconnect;No description;243;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f4;RMP_SpwParity;No description;244;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f5;RMP_SpwWriteSync;No description;245;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f6;RMP_SpwInvalidAddress;No description;246;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f7;RMP_SpwEarlyEop;No description;247;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f8;RMP_SpwDma;No description;248;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f9;RMP_SpwLinkError;No description;249;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0500;PS_SwitchOff;No description;0;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0501;PS_SwitchOn;No description;1;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0502;PS_SwitchTimeout;No description;2;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0503;PS_FuseOn;No description;3;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0504;PS_FuseOff;No description;4;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0505;PS_SwitchUnknown;No description;5;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0601;PP_DoItMyself;No description;1;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0602;PP_PointsToVariable;No description;2;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0603;PP_PointsToMemory;No description;3;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0604;PP_ActivityCompleted;No description;4;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0605;PP_PointsToVectorUint8;No description;5;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0606;PP_PointsToVectorUint16;No description;6;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0607;PP_PointsToVectorUint32;No description;7;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0608;PP_PointsToVectorFloat;No description;8;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06a0;PP_DumpNotSupported;No description;160;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e0;PP_InvalidSize;No description;224;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e1;PP_InvalidAddress;No description;225;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e2;PP_InvalidContent;No description;226;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e3;PP_UnalignedAccess;No description;227;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e4;PP_WriteProtected;No description;228;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0801;DPS_InvalidParameterDefinition;No description;1;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0802;DPS_SetWasAlreadyRead;No description;2;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0803;DPS_CommitingWithoutReading;No description;3;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0804;DPS_DataSetUninitialised;No description;4;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0805;DPS_DataSetFull;No description;5;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0806;DPS_PoolVarNull;No description;6;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0b01;SB_ChildNotFound;No description;1;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0b02;SB_ChildInfoUpdated;No description;2;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0b03;SB_ChildDoesntHaveModes;No description;3;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0b04;SB_CouldNotInsertChild;No description;4;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0b05;SB_TableContainsInvalidObjectId;No description;5;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0c02;MS_InvalidEntry;No description;2;MODE_STORE_IF;fsfw/src/fsfw/subsystem/modes/ModeStoreIF.h +0x0c03;MS_TooManyElements;No description;3;MODE_STORE_IF;fsfw/src/fsfw/subsystem/modes/ModeStoreIF.h +0x0c04;MS_CantStoreEmpty;No description;4;MODE_STORE_IF;fsfw/src/fsfw/subsystem/modes/ModeStoreIF.h +0x0d01;SS_SequenceAlreadyExists;No description;1;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d02;SS_TableAlreadyExists;No description;2;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d03;SS_TableDoesNotExist;No description;3;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d04;SS_TableOrSequenceLengthInvalid;No description;4;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d05;SS_SequenceDoesNotExist;No description;5;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d06;SS_TableContainsInvalidObjectId;No description;6;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d07;SS_FallbackSequenceDoesNotExist;No description;7;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d08;SS_NoTargetTable;No description;8;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d09;SS_SequenceOrTableTooLong;No description;9;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d0b;SS_IsFallbackSequence;No description;11;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d0c;SS_AccessDenied;No description;12;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d0e;SS_TableInUse;No description;14;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0da1;SS_TargetTableNotReached;No description;161;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0da2;SS_TableCheckFailed;No description;162;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0e01;HM_InvalidMode;No description;1;HAS_MODES_IF;fsfw/src/fsfw/modes/HasModesIF.h +0x0e02;HM_TransNotAllowed;No description;2;HAS_MODES_IF;fsfw/src/fsfw/modes/HasModesIF.h +0x0e03;HM_InTransition;No description;3;HAS_MODES_IF;fsfw/src/fsfw/modes/HasModesIF.h +0x0e04;HM_InvalidSubmode;No description;4;HAS_MODES_IF;fsfw/src/fsfw/modes/HasModesIF.h +0x0f01;CM_UnknownCommand;No description;1;COMMAND_MESSAGE;fsfw/src/fsfw/ipc/CommandMessageIF.h +0x1000;TIM_UnsupportedTimeFormat;No description;0;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1001;TIM_NotEnoughInformationForTargetFormat;No description;1;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1002;TIM_LengthMismatch;No description;2;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1003;TIM_InvalidTimeFormat;No description;3;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1004;TIM_InvalidDayOfYear;No description;4;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1005;TIM_TimeDoesNotFitFormat;No description;5;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1201;AB_NeedSecondStep;No description;1;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x1202;AB_NeedToReconfigure;No description;2;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x1203;AB_ModeFallback;No description;3;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x1204;AB_ChildNotCommandable;No description;4;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x1205;AB_NeedToChangeHealth;No description;5;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x12a1;AB_NotEnoughChildrenInCorrectState;No description;161;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x13e0;MH_UnknownCmd;No description;224;MEMORY_HELPER;fsfw/src/fsfw/memory/MemoryHelper.h +0x13e1;MH_InvalidAddress;No description;225;MEMORY_HELPER;fsfw/src/fsfw/memory/MemoryHelper.h +0x13e2;MH_InvalidSize;No description;226;MEMORY_HELPER;fsfw/src/fsfw/memory/MemoryHelper.h +0x13e3;MH_StateMismatch;No description;227;MEMORY_HELPER;fsfw/src/fsfw/memory/MemoryHelper.h +0x1401;SE_BufferTooShort;No description;1;SERIALIZE_IF;fsfw/src/fsfw/serialize/SerializeIF.h +0x1402;SE_StreamTooShort;No description;2;SERIALIZE_IF;fsfw/src/fsfw/serialize/SerializeIF.h +0x1403;SE_TooManyElements;No description;3;SERIALIZE_IF;fsfw/src/fsfw/serialize/SerializeIF.h +0x1601;FMM_MapFull;No description;1;FIXED_MULTIMAP;fsfw/src/fsfw/container/FixedOrderedMultimap.h +0x1602;FMM_KeyDoesNotExist;No description;2;FIXED_MULTIMAP;fsfw/src/fsfw/container/FixedOrderedMultimap.h +0x1701;HHI_ObjectNotHealthy;No description;1;HAS_HEALTH_IF;fsfw/src/fsfw/health/HasHealthIF.h +0x1702;HHI_InvalidHealthState;No description;2;HAS_HEALTH_IF;fsfw/src/fsfw/health/HasHealthIF.h +0x1703;HHI_IsExternallyControlled;No description;3;HAS_HEALTH_IF;fsfw/src/fsfw/health/HasHealthIF.h +0x1801;FF_Full;No description;1;FIFO_CLASS;fsfw/src/fsfw/container/FIFOBase.h +0x1802;FF_Empty;No description;2;FIFO_CLASS;fsfw/src/fsfw/container/FIFOBase.h +0x1a01;TRC_NotEnoughSensors;No description;1;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1a02;TRC_LowestValueOol;No description;2;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1a03;TRC_HighestValueOol;No description;3;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1a04;TRC_BothValuesOol;No description;4;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1a05;TRC_DuplexOol;No description;5;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1b00;TCC_NoDestinationFound;No description;0;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b01;TCC_InvalidCcsdsVersion;No description;1;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b02;TCC_InvalidApid;No description;2;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b03;TCC_InvalidPacketType;No description;3;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b04;TCC_InvalidSecHeaderField;No description;4;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b05;TCC_IncorrectPrimaryHeader;No description;5;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b07;TCC_IncompletePacket;No description;7;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b08;TCC_InvalidPusVersion;No description;8;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b09;TCC_IncorrectChecksum;No description;9;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b0a;TCC_IllegalPacketSubtype;No description;10;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b0b;TCC_IncorrectSecondaryHeader;No description;11;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1c01;TCD_PacketLost;No description;1;PACKET_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/TcDistributorBase.h +0x1c02;TCD_DestinationNotFound;No description;2;PACKET_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/TcDistributorBase.h +0x1c03;TCD_ServiceIdAlreadyExists;No description;3;PACKET_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/TcDistributorBase.h +0x1d01;ATC_ActivityStarted;No description;1;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1d02;ATC_InvalidSubservice;No description;2;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1d03;ATC_IllegalApplicationData;No description;3;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1d04;ATC_SendTmFailed;No description;4;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1d05;ATC_Timeout;No description;5;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1e00;PUS_InvalidPusVersion;No description;0;PUS_IF;fsfw/src/fsfw/tmtcpacket/pus/PusIF.h +0x1e01;PUS_InvalidCrc16;No description;1;PUS_IF;fsfw/src/fsfw/tmtcpacket/pus/PusIF.h +0x2001;CSB_ExecutionComplete;No description;1;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2002;CSB_NoStepMessage;No description;2;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2003;CSB_ObjectBusy;No description;3;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2004;CSB_Busy;No description;4;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2005;CSB_InvalidTc;No description;5;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2006;CSB_InvalidObject;No description;6;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2007;CSB_InvalidReply;No description;7;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2101;TMB_Busy;No description;1;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2102;TMB_Full;No description;2;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2103;TMB_Empty;No description;3;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2104;TMB_NullRequested;No description;4;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2105;TMB_TooLarge;No description;5;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2106;TMB_NotReady;No description;6;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2107;TMB_DumpError;No description;7;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2108;TMB_CrcError;No description;8;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2109;TMB_Timeout;No description;9;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210a;TMB_IdlePacketFound;No description;10;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210b;TMB_TelecommandFound;No description;11;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210c;TMB_NoPusATm;No description;12;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210d;TMB_TooSmall;No description;13;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210e;TMB_BlockNotFound;No description;14;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210f;TMB_InvalidRequest;No description;15;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2201;TMF_Busy;No description;1;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2202;TMF_LastPacketFound;No description;2;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2203;TMF_StopFetch;No description;3;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2204;TMF_Timeout;No description;4;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2205;TMF_TmChannelFull;No description;5;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2206;TMF_NotStored;No description;6;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2207;TMF_AllDeleted;No description;7;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2208;TMF_InvalidData;No description;8;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2209;TMF_NotReady;No description;9;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2401;MT_TooDetailedRequest;No description;1;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2402;MT_TooGeneralRequest;No description;2;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2403;MT_NoMatch;No description;3;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2404;MT_Full;No description;4;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2405;MT_NewNodeCreated;No description;5;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2501;EV_ListenerNotFound;No description;1;EVENT_MANAGER_IF;fsfw/src/fsfw/events/EventManagerIF.h +0x2600;FDI_YourFault;No description;0;HANDLES_FAILURES_IF;fsfw/src/fsfw/fdir/ConfirmsFailuresIF.h +0x2601;FDI_MyFault;No description;1;HANDLES_FAILURES_IF;fsfw/src/fsfw/fdir/ConfirmsFailuresIF.h +0x2602;FDI_ConfirmLater;No description;2;HANDLES_FAILURES_IF;fsfw/src/fsfw/fdir/ConfirmsFailuresIF.h +0x27a0;DHI_NoCommandData;No description;160;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a1;DHI_CommandNotSupported;No description;161;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a2;DHI_CommandAlreadySent;No description;162;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a3;DHI_CommandWasNotSent;No description;163;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a4;DHI_CantSwitchAddress;No description;164;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a5;DHI_WrongModeForCommand;No description;165;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a6;DHI_Timeout;No description;166;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a7;DHI_Busy;No description;167;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a8;DHI_NoReplyExpected;No description;168;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a9;DHI_NonOpTemperature;No description;169;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27aa;DHI_CommandNotImplemented;No description;170;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27ab;DHI_NonOpStateOfCharge;No description;171;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27b0;DHI_ChecksumError;No description;176;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27b1;DHI_LengthMissmatch;No description;177;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27b2;DHI_InvalidData;No description;178;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27b3;DHI_ProtocolError;No description;179;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27c0;DHI_DeviceDidNotExecute;No description;192;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27c1;DHI_DeviceReportedError;No description;193;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27c2;DHI_UnknownDeviceReply;No description;194;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27c3;DHI_DeviceReplyInvalid;No description;195;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27d0;DHI_InvalidCommandParameter;No description;208;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27d1;DHI_InvalidNumberOrLengthOfParameters;No description;209;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x2801;SM_DataTooLarge;No description;1;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2802;SM_DataStorageFull;No description;2;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2803;SM_IllegalStorageId;No description;3;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2804;SM_DataDoesNotExist;No description;4;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2805;SM_IllegalAddress;No description;5;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2806;SM_PoolTooLarge;No description;6;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2901;TC_InvalidTargetState;No description;1;THERMAL_COMPONENT_IF;fsfw/src/fsfw/thermal/ThermalComponentIF.h +0x29f1;TC_AboveOperationalLimit;No description;241;THERMAL_COMPONENT_IF;fsfw/src/fsfw/thermal/ThermalComponentIF.h +0x29f2;TC_BelowOperationalLimit;No description;242;THERMAL_COMPONENT_IF;fsfw/src/fsfw/thermal/ThermalComponentIF.h +0x2a01;IEC_NoConfigurationTable;No description;1;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a02;IEC_NoCpuTable;No description;2;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a03;IEC_InvalidWorkspaceAddress;No description;3;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a04;IEC_TooLittleWorkspace;No description;4;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a05;IEC_WorkspaceAllocation;No description;5;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a06;IEC_InterruptStackTooSmall;No description;6;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a07;IEC_ThreadExitted;No description;7;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a08;IEC_InconsistentMpInformation;No description;8;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a09;IEC_InvalidNode;No description;9;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0a;IEC_NoMpci;No description;10;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0b;IEC_BadPacket;No description;11;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0c;IEC_OutOfPackets;No description;12;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0d;IEC_OutOfGlobalObjects;No description;13;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0e;IEC_OutOfProxies;No description;14;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0f;IEC_InvalidGlobalId;No description;15;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a10;IEC_BadStackHook;No description;16;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a11;IEC_BadAttributes;No description;17;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a12;IEC_ImplementationKeyCreateInconsistency;No description;18;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a13;IEC_ImplementationBlockingOperationCancel;No description;19;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a14;IEC_MutexObtainFromBadState;No description;20;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a15;IEC_UnlimitedAndMaximumIs0;No description;21;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2c01;CCS_BcIsSetVrCommand;No description;1;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2c02;CCS_BcIsUnlockCommand;No description;2;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cb0;CCS_BcIllegalCommand;No description;176;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cb1;CCS_BoardReadingNotFinished;No description;177;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cd0;CCS_IllegalFlagCombination;No description;208;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cd1;CCS_ShorterThanHeader;No description;209;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cd2;CCS_TooShortBlockedPacket;No description;210;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cd3;CCS_TooShortMapExtraction;No description;211;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce0;CCS_WrongSymbol;No description;224;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce1;CCS_DoubleStart;No description;225;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce2;CCS_StartSymbolMissed;No description;226;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce3;CCS_EndWithoutStart;No description;227;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce4;CCS_TooLarge;No description;228;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce5;CCS_TooShort;No description;229;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce6;CCS_WrongTfVersion;No description;230;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce7;CCS_WrongSpacecraftId;No description;231;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce8;CCS_NoValidFrameType;No description;232;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce9;CCS_CrcFailed;No description;233;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cea;CCS_VcNotFound;No description;234;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ceb;CCS_ForwardingFailed;No description;235;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cec;CCS_ContentTooLarge;No description;236;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ced;CCS_ResidualData;No description;237;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cee;CCS_DataCorrupted;No description;238;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cef;CCS_IllegalSegmentationFlag;No description;239;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf0;CCS_NsPositiveW;No description;240;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf1;CCS_NsNegativeW;No description;241;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf2;CCS_NsLockout;No description;242;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf3;CCS_FarmInLockout;No description;243;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf4;CCS_FarmInWait;No description;244;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2d01;PAW_UnknownDatatype;No description;1;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d02;PAW_DatatypeMissmatch;No description;2;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d03;PAW_Readonly;No description;3;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d04;PAW_TooBig;No description;4;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d05;PAW_SourceNotSet;No description;5;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d06;PAW_OutOfBounds;No description;6;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d07;PAW_NotSet;No description;7;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d08;PAW_ColumnOrRowsZero;No description;8;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2e01;HPA_InvalidIdentifierId;No description;1;HAS_PARAMETERS_IF;fsfw/src/fsfw/parameters/HasParametersIF.h +0x2e02;HPA_InvalidDomainId;No description;2;HAS_PARAMETERS_IF;fsfw/src/fsfw/parameters/HasParametersIF.h +0x2e03;HPA_InvalidValue;No description;3;HAS_PARAMETERS_IF;fsfw/src/fsfw/parameters/HasParametersIF.h +0x2e05;HPA_ReadOnly;No description;5;HAS_PARAMETERS_IF;fsfw/src/fsfw/parameters/HasParametersIF.h +0x2f01;ASC_TooLongForTargetType;No description;1;ASCII_CONVERTER;fsfw/src/fsfw/globalfunctions/AsciiConverter.h +0x2f02;ASC_InvalidCharacters;No description;2;ASCII_CONVERTER;fsfw/src/fsfw/globalfunctions/AsciiConverter.h +0x2f03;ASC_BufferTooSmall;No description;3;ASCII_CONVERTER;fsfw/src/fsfw/globalfunctions/AsciiConverter.h +0x3001;POS_InPowerTransition;No description;1;POWER_SWITCHER;fsfw/src/fsfw/power/PowerSwitcher.h +0x3002;POS_SwitchStateMismatch;No description;2;POWER_SWITCHER;fsfw/src/fsfw/power/PowerSwitcher.h +0x3101;LIM_Unchecked;No description;1;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3102;LIM_Invalid;No description;2;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3103;LIM_Unselected;No description;3;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3104;LIM_BelowLowLimit;No description;4;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3105;LIM_AboveHighLimit;No description;5;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3106;LIM_UnexpectedValue;No description;6;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3107;LIM_OutOfRange;No description;7;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31a0;LIM_FirstSample;No description;160;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31e0;LIM_InvalidSize;No description;224;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31e1;LIM_WrongType;No description;225;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31e2;LIM_WrongPid;No description;226;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31e3;LIM_WrongLimitId;No description;227;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31ee;LIM_MonitorNotFound;No description;238;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3201;CF_ObjectHasNoFunctions;No description;1;COMMANDS_ACTIONS_IF;fsfw/src/fsfw/action/CommandsActionsIF.h +0x3202;CF_AlreadyCommanding;No description;2;COMMANDS_ACTIONS_IF;fsfw/src/fsfw/action/CommandsActionsIF.h +0x3301;HF_IsBusy;No description;1;HAS_ACTIONS_IF;fsfw/src/fsfw/action/HasActionsIF.h +0x3302;HF_InvalidParameters;No description;2;HAS_ACTIONS_IF;fsfw/src/fsfw/action/HasActionsIF.h +0x3303;HF_ExecutionFinished;No description;3;HAS_ACTIONS_IF;fsfw/src/fsfw/action/HasActionsIF.h +0x3304;HF_InvalidActionId;No description;4;HAS_ACTIONS_IF;fsfw/src/fsfw/action/HasActionsIF.h +0x3401;DC_NoReplyReceived;No description;1;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3402;DC_ProtocolError;No description;2;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3403;DC_Nullpointer;No description;3;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3404;DC_InvalidCookieType;No description;4;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3405;DC_NotActive;No description;5;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3406;DC_TooMuchData;No description;6;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3407;DC_Busy;No description;7;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3601;CFDP_InvalidTlvType;No description;1;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3602;CFDP_InvalidDirectiveField;No description;2;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3603;CFDP_InvalidPduDatafieldLen;No description;3;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3604;CFDP_InvalidAckDirectiveFields;No description;4;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3605;CFDP_MetadataCantParseOptions;No description;5;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3606;CFDP_NakCantParseOptions;No description;6;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3607;CFDP_FinishedCantParseFsResponses;No description;7;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3608;CFDP_FilestoreRequiresSecondFile;No description;8;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3609;CFDP_FilestoreResponseCantParseFsMessage;No description;9;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x360a;CFDP_InvalidPduFormat;No description;10;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3700;CFDP_SourceTransactionPending;No description;0;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3701;CFDP_FileDoesNotExist;No description;1;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3702;CFDP_FileSegmentLenInvalid;No description;2;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3703;CFDP_SourceNameEmpty;No description;3;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3704;CFDP_DestNameEmpty;No description;4;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3705;CFDP_WrongRemoteCfgEntityId;No description;5;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3706;CFDP_TargetMsgQueueFull;No description;6;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3707;CFDP_TmStoreFull;No description;7;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3801;TSI_BadTimestamp;No description;1;TIME_STAMPER_IF;fsfw/src/fsfw/timemanager/TimeStampIF.h +0x39a1;SGP4_InvalidEccentricity;No description;161;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a2;SGP4_InvalidMeanMotion;No description;162;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a3;SGP4_InvalidPerturbationElements;No description;163;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a4;SGP4_InvalidSemiLatusRectum;No description;164;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a5;SGP4_InvalidEpochElements;No description;165;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a6;SGP4_SatelliteHasDecayed;No description;166;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39b1;SGP4_TleTooOld;No description;177;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39b2;SGP4_TleNotInitialized;No description;178;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x3a01;MUX_NotEnoughResources;No description;1;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a02;MUX_InsufficientMemory;No description;2;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a03;MUX_NoPrivilege;No description;3;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a04;MUX_WrongAttributeSetting;No description;4;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a05;MUX_MutexAlreadyLocked;No description;5;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a06;MUX_MutexNotFound;No description;6;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a07;MUX_MutexMaxLocks;No description;7;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a08;MUX_CurrThreadAlreadyOwnsMutex;No description;8;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a09;MUX_CurrThreadDoesNotOwnMutex;No description;9;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a0a;MUX_MutexTimeout;No description;10;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a0b;MUX_MutexInvalidId;No description;11;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a0c;MUX_MutexDestroyedWhileWaiting;No description;12;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3b01;MQI_Empty;No description;1;MESSAGE_QUEUE_IF;fsfw/src/fsfw/ipc/MessageQueueIF.h +0x3b02;MQI_Full;No space left for more messages;2;MESSAGE_QUEUE_IF;fsfw/src/fsfw/ipc/MessageQueueIF.h +0x3b03;MQI_NoReplyPartner;Returned if a reply method was called without partner;3;MESSAGE_QUEUE_IF;fsfw/src/fsfw/ipc/MessageQueueIF.h +0x3b04;MQI_DestinationInvalid;Returned if the target destination is invalid.;4;MESSAGE_QUEUE_IF;fsfw/src/fsfw/ipc/MessageQueueIF.h +0x3c01;SPH_SemaphoreTimeout;No description;1;SEMAPHORE_IF;fsfw/src/fsfw/tasks/SemaphoreIF.h +0x3c02;SPH_SemaphoreNotOwned;No description;2;SEMAPHORE_IF;fsfw/src/fsfw/tasks/SemaphoreIF.h +0x3c03;SPH_SemaphoreInvalid;No description;3;SEMAPHORE_IF;fsfw/src/fsfw/tasks/SemaphoreIF.h +0x3d00;LPIF_PoolEntryNotFound;No description;0;LOCAL_POOL_OWNER_IF;fsfw/src/fsfw/datapoollocal/localPoolDefinitions.h +0x3d01;LPIF_PoolEntryTypeConflict;No description;1;LOCAL_POOL_OWNER_IF;fsfw/src/fsfw/datapoollocal/localPoolDefinitions.h +0x3ea0;PVA_InvalidReadWriteMode;No description;160;POOL_VARIABLE_IF;fsfw/src/fsfw/datapool/PoolVariableIF.h +0x3ea1;PVA_InvalidPoolEntry;No description;161;POOL_VARIABLE_IF;fsfw/src/fsfw/datapool/PoolVariableIF.h +0x3f00;HKM_QueueOrDestinationInvalid;No description;0;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f01;HKM_WrongHkPacketType;No description;1;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f02;HKM_ReportingStatusUnchanged;No description;2;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f03;HKM_PeriodicHelperInvalid;No description;3;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f04;HKM_PoolobjectNotFound;No description;4;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f05;HKM_DatasetNotFound;No description;5;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x4001;DLEE_StreamTooShort;No description;1;DLE_ENCODER;fsfw/src/fsfw/globalfunctions/DleEncoder.h +0x4002;DLEE_DecodingError;No description;2;DLE_ENCODER;fsfw/src/fsfw/globalfunctions/DleEncoder.h +0x4301;PUS11_InvalidTypeTimeWindow;No description;1;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4302;PUS11_InvalidTimeWindow;No description;2;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4303;PUS11_TimeshiftingNotPossible;No description;3;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4304;PUS11_InvalidRelativeTime;No description;4;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4305;PUS11_ContainedTcTooSmall;No description;5;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4306;PUS11_ContainedTcCrcMissmatch;No description;6;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4307;PUS11_MapIsFull;No description;7;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4400;FILS_GenericFileError;No description;0;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4401;FILS_GenericDirError;No description;1;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4402;FILS_FilesystemInactive;No description;2;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4403;FILS_GenericRenameError;No description;3;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4404;FILS_IsBusy;No description;4;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4405;FILS_InvalidParameters;No description;5;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440a;FILS_FileDoesNotExist;No description;10;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440b;FILS_FileAlreadyExists;No description;11;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440c;FILS_NotAFile;No description;12;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440d;FILS_FileLocked;No description;13;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440e;FILS_PermissionDenied;No description;14;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4415;FILS_DirectoryDoesNotExist;No description;21;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4416;FILS_DirectoryAlreadyExists;No description;22;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4417;FILS_NotADirectory;No description;23;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4418;FILS_DirectoryNotEmpty;No description;24;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x441e;FILS_SequencePacketMissingWrite;No description;30;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x441f;FILS_SequencePacketMissingRead;No description;31;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4500;UXOS_ExecutionFinished;Execution of the current command has finished;0;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4501;UXOS_CommandPending;Command is pending. This will also be returned if the user tries to load another command but a command is still pending;1;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4502;UXOS_BytesRead;Some bytes have been read from the executing process;2;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4503;UXOS_CommandError;Command execution failed;3;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4504;UXOS_NoCommandLoadedOrPending;;4;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4506;UXOS_PcloseCallError;No description;6;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4600;HSPI_OpeningFileFailed;No description;0;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4601;HSPI_FullDuplexTransferFailed;No description;1;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4602;HSPI_HalfDuplexTransferFailed;No description;2;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4603;HSPI_Timeout;No description;3;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4604;HSPI_Busy;No description;4;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4605;HSPI_GenericError;No description;5;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4701;HURT_UartReadFailure;No description;1;HAL_UART;fsfw/src/fsfw_hal/linux/serial/SerialComIF.h +0x4702;HURT_UartReadSizeMissmatch;No description;2;HAL_UART;fsfw/src/fsfw_hal/linux/serial/SerialComIF.h +0x4703;HURT_UartRxBufferTooSmall;No description;3;HAL_UART;fsfw/src/fsfw_hal/linux/serial/SerialComIF.h +0x4901;HGIO_UnknownGpioId;No description;1;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4902;HGIO_DriveGpioFailure;No description;2;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4903;HGIO_GpioTypeFailure;No description;3;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4904;HGIO_GpioInvalidInstance;No description;4;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4905;HGIO_GpioDuplicateDetected;No description;5;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4906;HGIO_GpioInitFailed;No description;6;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4907;HGIO_GpioGetValueFailed;No description;7;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4d00;SPPA_NoPacketFound;No description;0;SPACE_PACKET_PARSER;fsfw/src/fsfw/tmtcservices/SpacePacketParser.h +0x4d01;SPPA_SplitPacket;No description;1;SPACE_PACKET_PARSER;fsfw/src/fsfw/tmtcservices/SpacePacketParser.h +0x50a1;HEATER_CommandNotSupported;No description;161;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x50a2;HEATER_InitFailed;No description;162;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x50a3;HEATER_InvalidSwitchNr;No description;163;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x50a4;HEATER_MainSwitchSetTimeout;No description;164;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x50a5;HEATER_CommandAlreadyWaiting;No description;165;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x51a0;SYRLINKS_CrcFailure;No description;160;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a1;SYRLINKS_UartFraminOrParityErrorAck;No description;161;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a2;SYRLINKS_BadCharacterAck;No description;162;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a3;SYRLINKS_BadParameterValueAck;No description;163;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a4;SYRLINKS_BadEndOfFrameAck;No description;164;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a5;SYRLINKS_UnknownCommandIdAck;No description;165;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a6;SYRLINKS_BadCrcAck;No description;166;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a7;SYRLINKS_ReplyWrongSize;No description;167;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a8;SYRLINKS_MissingStartFrameCharacter;No description;168;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x5200;IMTQ_InvalidCommandCode;No description;0;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5201;IMTQ_MgmMeasurementLowLevelError;No description;1;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5202;IMTQ_ActuateCmdLowLevelError;No description;2;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5203;IMTQ_ParameterMissing;No description;3;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5204;IMTQ_ParameterInvalid;No description;4;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5205;IMTQ_CcUnavailable;No description;5;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5206;IMTQ_InternalProcessingError;No description;6;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5207;IMTQ_RejectedWithoutReason;No description;7;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5208;IMTQ_CmdErrUnknown;No description;8;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5209;IMTQ_StartupCfgError;No description;9;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x520a;IMTQ_UnexpectedSelfTestReply;The status reply to a self test command was received but no self test command has been sent. This should normally never happen.;10;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x53a0;RWHA_InvalidSpeed;Action Message with invalid speed was received. Valid speeds must be in the range of [-65000, 1000] or [1000, 65000];160;RW_HANDLER;mission/acs/RwHandler.h +0x53a1;RWHA_InvalidRampTime;Action Message with invalid ramp time was received.;161;RW_HANDLER;mission/acs/RwHandler.h +0x53a2;RWHA_SetSpeedCommandInvalidLength;Received set speed command has invalid length. Should be 6.;162;RW_HANDLER;mission/acs/RwHandler.h +0x53a3;RWHA_ExecutionFailed;Command execution failed;163;RW_HANDLER;mission/acs/RwHandler.h +0x53a4;RWHA_CrcError;Reaction wheel reply has invalid crc;164;RW_HANDLER;mission/acs/RwHandler.h +0x53a5;RWHA_ValueNotRead;No description;165;RW_HANDLER;mission/acs/RwHandler.h +0x53b0;RWHA_SpiWriteFailure;No description;176;RW_HANDLER;mission/acs/rwHelpers.h +0x53b1;RWHA_SpiReadFailure;Used by the spi send function to tell a failing read call;177;RW_HANDLER;mission/acs/rwHelpers.h +0x53b2;RWHA_MissingStartSign;Can be used by the HDLC decoding mechanism to inform about a missing start sign 0x7E;178;RW_HANDLER;mission/acs/rwHelpers.h +0x53b3;RWHA_InvalidSubstitute;Can be used by the HDLC decoding mechanism to inform about an invalid substitution combination;179;RW_HANDLER;mission/acs/rwHelpers.h +0x53b4;RWHA_MissingEndSign;HDLC decoding mechanism never receives the end sign 0x7E;180;RW_HANDLER;mission/acs/rwHelpers.h +0x53b5;RWHA_NoReply;Reaction wheel only responds with empty frames.;181;RW_HANDLER;mission/acs/rwHelpers.h +0x53b6;RWHA_NoStartMarker;Expected a start marker as first byte;182;RW_HANDLER;mission/acs/rwHelpers.h +0x53b7;RWHA_SpiReadTimeout;Timeout when reading reply;183;RW_HANDLER;mission/acs/rwHelpers.h +0x54a0;STRH_TemperatureReqFailed;Status in temperature reply signals error;160;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a1;STRH_PingFailed;Ping command failed;161;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a2;STRH_VersionReqFailed;Status in version reply signals error;162;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a3;STRH_InterfaceReqFailed;Status in interface reply signals error;163;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a4;STRH_PowerReqFailed;Status in power reply signals error;164;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a5;STRH_SetParamFailed;Status of reply to parameter set command signals error;165;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a6;STRH_ActionFailed;Status of reply to action command signals error;166;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a7;STRH_FilePathTooLong;Received invalid path string. Exceeds allowed length;167;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a8;STRH_FilenameTooLong;Name of file received with command is too long;168;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a9;STRH_InvalidProgram;Received version reply with invalid program ID;169;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54aa;STRH_ReplyError;Status field reply signals error;170;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54ab;STRH_CommandTooShort;Received command which is too short (some data is missing for proper execution);171;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54ac;STRH_InvalidLength;Received command with invalid length (too few or too many parameters);172;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54ad;STRH_RegionMismatch;Region mismatch between send and received data;173;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54ae;STRH_AddressMismatch;Address mismatch between send and received data;174;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54af;STRH_LengthMismatch;Length field mismatch between send and received data;175;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b0;STRH_FileNotExists;Specified file does not exist;176;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b1;STRH_InvalidType;Download blob pixel command has invalid type field;177;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b2;STRH_InvalidId;Received FPGA action command with invalid ID;178;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b3;STRH_ReplyTooShort;Received reply is too short;179;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b4;STRH_CrcFailure;Received reply with invalid CRC;180;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b5;STRH_StrHelperExecuting;Star tracker handler currently executing a command and using the communication interface;181;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b6;STRH_StartrackerAlreadyBooted;Star tracker is already in firmware mode;182;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b7;STRH_StartrackerNotRunningFirmware;Star tracker must be in firmware mode to run this command;183;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b8;STRH_StartrackerNotRunningBootloader;Star tracker must be in bootloader mode to run this command;184;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x59a0;SUSS_ErrorUnlockMutex;No description;160;SUS_HANDLER;mission/acs/archive/LegacySusHandler.h +0x59a1;SUSS_ErrorLockMutex;No description;161;SUS_HANDLER;mission/acs/archive/LegacySusHandler.h +0x5e00;GOMS_PacketTooLong;No description;0;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e01;GOMS_InvalidTableId;No description;1;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e02;GOMS_InvalidAddress;No description;2;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e03;GOMS_InvalidParamSize;No description;3;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e04;GOMS_InvalidPayloadSize;No description;4;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e05;GOMS_UnknownReplyId;No description;5;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x61a0;CCSDS_CommandNotImplemented;Received action message with unknown action id;160;CCSDS_HANDLER;mission/com/CcsdsIpCoreHandler.h +0x6301;JSONBASE_JsonFileNotExists;Specified json file does not exist;1;ARCSEC_JSON_BASE;mission/acs/str/ArcsecJsonParamBase.h +0x6302;JSONBASE_SetNotExists;Requested set does not exist in json file;2;ARCSEC_JSON_BASE;mission/acs/str/ArcsecJsonParamBase.h +0x6303;JSONBASE_ParamNotExists;Requested parameter does not exist in json file;3;ARCSEC_JSON_BASE;mission/acs/str/ArcsecJsonParamBase.h +0x64a0;NVMB_KeyNotExists;Specified key does not exist in json file;160;NVM_PARAM_BASE;mission/memory/NvmParameterBase.h +0x67a0;SADPL_CommandNotSupported;No description;160;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x67a1;SADPL_DeploymentAlreadyExecuting;No description;161;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x67a2;SADPL_MainSwitchTimeoutFailure;No description;162;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x67a3;SADPL_SwitchingDeplSa1Failed;No description;163;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x67a4;SADPL_SwitchingDeplSa2Failed;No description;164;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x6aa0;ACSCTRL_FileDeletionFailed;File deletion failed and at least one file is still existent.;160;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6aa1;ACSCTRL_WriteFileFailed;Writing the TLE to the file has failed.;161;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6aa2;ACSCTRL_ReadFileFailed;Reading the TLE to the file has failed.;162;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6aa3;ACSCTRL_SingleRwUnavailable;A single RW has failed.;163;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6aa4;ACSCTRL_MultipleRwUnavailable;Multiple RWs have failed.;164;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6b02;ACSMEKF_MekfUninitialized;No description;2;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b03;ACSMEKF_MekfNoGyrData;No description;3;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b04;ACSMEKF_MekfNoModelVectors;No description;4;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b05;ACSMEKF_MekfNoSusMgmStrData;No description;5;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b06;ACSMEKF_MekfCovarianceInversionFailed;No description;6;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b07;ACSMEKF_MekfNotFinite;No description;7;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b08;ACSMEKF_MekfInitialized;No description;8;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b09;ACSMEKF_MekfRunning;No description;9;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6e00;PTM_DumpDone;No description;0;PERSISTENT_TM_STORE;mission/tmtc/PersistentTmStore.h +0x6e01;PTM_BusyDumping;No description;1;PERSISTENT_TM_STORE;mission/tmtc/PersistentTmStore.h +0x6f00;TMS_IsBusy;No description;0;TM_SINK;mission/tmtc/DirectTmSinkIF.h +0x6f01;TMS_PartiallyWritten;No description;1;TM_SINK;mission/tmtc/DirectTmSinkIF.h +0x6f02;TMS_NoWriteActive;No description;2;TM_SINK;mission/tmtc/DirectTmSinkIF.h +0x6f03;TMS_Timeout;No description;3;TM_SINK;mission/tmtc/DirectTmSinkIF.h +0x7000;VCS_ChannelDoesNotExist;No description;0;VIRTUAL_CHANNEL;mission/com/VirtualChannel.h diff --git a/generators/bsp_hosted_subsystems.csv b/generators/bsp_hosted_subsystems.csv new file mode 100644 index 0000000..36828fd --- /dev/null +++ b/generators/bsp_hosted_subsystems.csv @@ -0,0 +1,64 @@ +22;MEMORY +26;OBSW +28;CDH +59;TCS_1 +42;PCDU_1 +43;POWER_SWITCH_IF +50;HEATER +52;T_SENSORS +70;FDIR +71;FDIR_1 +72;FDIR_2 +73;HK +74;SYSTEM_MANAGER +75;SYSTEM_MANAGER_1 +76;TMTC_DISTRIBUTION +79;SYSTEM_1 +80;PUS_SERVICE_1 +82;PUS_SERVICE_2 +83;PUS_SERVICE_3 +85;PUS_SERVICE_5 +86;PUS_SERVICE_6 +88;PUS_SERVICE_8 +89;PUS_SERVICE_9 +91;PUS_SERVICE_11 +97;PUS_SERVICE_17 +103;PUS_SERVICE_23 +106;MGM_LIS3MDL +107;MGM_RM3100 +108;CFDP +112;ACS_SUBSYSTEM +113;PCDU_HANDLER +114;HEATER_HANDLER +115;SA_DEPL_HANDLER +116;PLOC_MPSOC_HANDLER +117;IMTQ_HANDLER +118;RW_HANDLER +119;STR_HANDLER +120;PLOC_SUPERVISOR_HANDLER +121;FILE_SYSTEM +122;PLOC_UPDATER +123;PLOC_MEMORY_DUMPER +124;PDEC_HANDLER +125;STR_HELPER +126;PLOC_MPSOC_HELPER +127;PL_PCDU_HANDLER +128;ACS_BOARD_ASS +129;SUS_BOARD_ASS +130;TCS_BOARD_ASS +131;GPS_HANDLER +132;P60_DOCK_HANDLER +133;PDU1_HANDLER +134;PDU2_HANDLER +135;ACU_HANDLER +136;PLOC_SUPV_HELPER +137;SYRLINKS +138;SCEX_HANDLER +139;CONFIGHANDLER +140;CORE +141;TCS_CONTROLLER +142;COM_SUBSYSTEM +143;PERSISTENT_TM_STORE +144;SYRLINKS_COM +145;SUS_HANDLER +146;CFDP_APP diff --git a/generators/bsp_q7s_events.csv b/generators/bsp_q7s_events.csv new file mode 100644 index 0000000..7b833ae --- /dev/null +++ b/generators/bsp_q7s_events.csv @@ -0,0 +1,326 @@ +Event ID (dec); Event ID (hex); Name; Severity; Description; File Path +2200;0x0898;STORE_SEND_WRITE_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2201;0x0899;STORE_WRITE_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2202;0x089a;STORE_SEND_READ_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2203;0x089b;STORE_READ_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2204;0x089c;UNEXPECTED_MSG;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2205;0x089d;STORING_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2206;0x089e;TM_DUMP_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2207;0x089f;STORE_INIT_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2208;0x08a0;STORE_INIT_EMPTY;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2209;0x08a1;STORE_CONTENT_CORRUPTED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2210;0x08a2;STORE_INITIALIZE;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2211;0x08a3;INIT_DONE;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2212;0x08a4;DUMP_FINISHED;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2213;0x08a5;DELETION_FINISHED;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2214;0x08a6;DELETION_FAILED;LOW;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2215;0x08a7;AUTO_CATALOGS_SENDING_FAILED;INFO;No description;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +2600;0x0a28;GET_DATA_FAILED;LOW;No description;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +2601;0x0a29;STORE_DATA_FAILED;LOW;No description;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +2800;0x0af0;DEVICE_BUILDING_COMMAND_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2801;0x0af1;DEVICE_SENDING_COMMAND_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2802;0x0af2;DEVICE_REQUESTING_REPLY_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2803;0x0af3;DEVICE_READING_REPLY_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2804;0x0af4;DEVICE_INTERPRETING_REPLY_FAILED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2805;0x0af5;DEVICE_MISSED_REPLY;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2806;0x0af6;DEVICE_UNKNOWN_REPLY;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2807;0x0af7;DEVICE_UNREQUESTED_REPLY;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2808;0x0af8;INVALID_DEVICE_COMMAND;LOW;Indicates a SW bug in child class.;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2809;0x0af9;MONITORING_LIMIT_EXCEEDED;LOW;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2810;0x0afa;MONITORING_AMBIGUOUS;HIGH;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +2811;0x0afb;DEVICE_WANTS_HARD_REBOOT;HIGH;No description;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +4300;0x10cc;SWITCH_WENT_OFF;LOW;No description;fsfw/src/fsfw/power/PowerSwitchIF.h +4301;0x10cd;FUSE_CURRENT_HIGH;LOW;No description;fsfw/src/fsfw/power/Fuse.h +4302;0x10ce;FUSE_WENT_OFF;LOW;No description;fsfw/src/fsfw/power/Fuse.h +4304;0x10d0;POWER_ABOVE_HIGH_LIMIT;LOW;No description;fsfw/src/fsfw/power/Fuse.h +4305;0x10d1;POWER_BELOW_LOW_LIMIT;LOW;No description;fsfw/src/fsfw/power/Fuse.h +5000;0x1388;HEATER_ON;INFO;No description;fsfw/src/fsfw/thermal/Heater.h +5001;0x1389;HEATER_OFF;INFO;No description;fsfw/src/fsfw/thermal/Heater.h +5002;0x138a;HEATER_TIMEOUT;LOW;No description;fsfw/src/fsfw/thermal/Heater.h +5003;0x138b;HEATER_STAYED_ON;LOW;No description;fsfw/src/fsfw/thermal/Heater.h +5004;0x138c;HEATER_STAYED_OFF;LOW;No description;fsfw/src/fsfw/thermal/Heater.h +5200;0x1450;TEMP_SENSOR_HIGH;LOW;No description;fsfw/src/fsfw/thermal/AbstractTemperatureSensor.h +5201;0x1451;TEMP_SENSOR_LOW;LOW;No description;fsfw/src/fsfw/thermal/AbstractTemperatureSensor.h +5202;0x1452;TEMP_SENSOR_GRADIENT;LOW;No description;fsfw/src/fsfw/thermal/AbstractTemperatureSensor.h +5901;0x170d;COMPONENT_TEMP_LOW;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +5902;0x170e;COMPONENT_TEMP_HIGH;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +5903;0x170f;COMPONENT_TEMP_OOL_LOW;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +5904;0x1710;COMPONENT_TEMP_OOL_HIGH;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +5905;0x1711;TEMP_NOT_IN_OP_RANGE;LOW;No description;fsfw/src/fsfw/thermal/ThermalComponentIF.h +7101;0x1bbd;FDIR_CHANGED_STATE;INFO;No description;fsfw/src/fsfw/fdir/FailureIsolationBase.h +7102;0x1bbe;FDIR_STARTS_RECOVERY;MEDIUM;No description;fsfw/src/fsfw/fdir/FailureIsolationBase.h +7103;0x1bbf;FDIR_TURNS_OFF_DEVICE;MEDIUM;No description;fsfw/src/fsfw/fdir/FailureIsolationBase.h +7201;0x1c21;MONITOR_CHANGED_STATE;LOW;No description;fsfw/src/fsfw/monitoring/MonitoringIF.h +7202;0x1c22;VALUE_BELOW_LOW_LIMIT;LOW;No description;fsfw/src/fsfw/monitoring/MonitoringIF.h +7203;0x1c23;VALUE_ABOVE_HIGH_LIMIT;LOW;No description;fsfw/src/fsfw/monitoring/MonitoringIF.h +7204;0x1c24;VALUE_OUT_OF_RANGE;LOW;No description;fsfw/src/fsfw/monitoring/MonitoringIF.h +7400;0x1ce8;CHANGING_MODE;INFO;No description;fsfw/src/fsfw/modes/HasModesIF.h +7401;0x1ce9;MODE_INFO;INFO;No description;fsfw/src/fsfw/modes/HasModesIF.h +7402;0x1cea;FALLBACK_FAILED;HIGH;No description;fsfw/src/fsfw/modes/HasModesIF.h +7403;0x1ceb;MODE_TRANSITION_FAILED;LOW;No description;fsfw/src/fsfw/modes/HasModesIF.h +7404;0x1cec;CANT_KEEP_MODE;HIGH;No description;fsfw/src/fsfw/modes/HasModesIF.h +7405;0x1ced;OBJECT_IN_INVALID_MODE;LOW;No description;fsfw/src/fsfw/modes/HasModesIF.h +7406;0x1cee;FORCING_MODE;MEDIUM;No description;fsfw/src/fsfw/modes/HasModesIF.h +7407;0x1cef;MODE_CMD_REJECTED;LOW;No description;fsfw/src/fsfw/modes/HasModesIF.h +7506;0x1d52;HEALTH_INFO;INFO;No description;fsfw/src/fsfw/health/HasHealthIF.h +7507;0x1d53;CHILD_CHANGED_HEALTH;INFO;No description;fsfw/src/fsfw/health/HasHealthIF.h +7508;0x1d54;CHILD_PROBLEMS;LOW;No description;fsfw/src/fsfw/health/HasHealthIF.h +7509;0x1d55;OVERWRITING_HEALTH;LOW;No description;fsfw/src/fsfw/health/HasHealthIF.h +7510;0x1d56;TRYING_RECOVERY;MEDIUM;No description;fsfw/src/fsfw/health/HasHealthIF.h +7511;0x1d57;RECOVERY_STEP;MEDIUM;No description;fsfw/src/fsfw/health/HasHealthIF.h +7512;0x1d58;RECOVERY_DONE;MEDIUM;No description;fsfw/src/fsfw/health/HasHealthIF.h +7600;0x1db0;HANDLE_PACKET_FAILED;LOW;No description;fsfw/src/fsfw/tcdistribution/definitions.h +7900;0x1edc;RF_AVAILABLE;INFO;A RF available signal was detected. P1: raw RFA state, P2: 0;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +7901;0x1edd;RF_LOST;INFO;A previously found RF available signal was lost. P1: raw RFA state, P2: 0;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +7902;0x1ede;BIT_LOCK;INFO;A Bit Lock signal. Was detected. P1: raw BLO state, P2: 0;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +7903;0x1edf;BIT_LOCK_LOST;INFO;A previously found Bit Lock signal was lost. P1: raw BLO state, P2: 0;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +7905;0x1ee1;FRAME_PROCESSING_FAILED;LOW;The CCSDS Board could not interpret a TC;fsfw/src/fsfw/datalinklayer/DataLinkLayer.h +8900;0x22c4;CLOCK_SET;INFO;Clock has been set. P1: old timeval seconds. P2: new timeval seconds.;fsfw/src/fsfw/pus/Service9TimeManagement.h +8901;0x22c5;CLOCK_DUMP_LEGACY;INFO;Clock dump event. P1: timeval seconds P2: timeval milliseconds.;fsfw/src/fsfw/pus/Service9TimeManagement.h +8902;0x22c6;CLOCK_SET_FAILURE;LOW;Clock could not be set. P1: Returncode.;fsfw/src/fsfw/pus/Service9TimeManagement.h +8903;0x22c7;CLOCK_DUMP;INFO;Clock dump event. P1: timeval seconds P2: timeval microseconds.;fsfw/src/fsfw/pus/Service9TimeManagement.h +8904;0x22c8;CLOCK_DUMP_BEFORE_SETTING_TIME;INFO;No description;fsfw/src/fsfw/pus/Service9TimeManagement.h +8905;0x22c9;CLOCK_DUMP_AFTER_SETTING_TIME;INFO;No description;fsfw/src/fsfw/pus/Service9TimeManagement.h +9100;0x238c;TC_DELETION_FAILED;MEDIUM;Deletion of a TC from the map failed. P1: First 32 bit of request ID, P2. Last 32 bit of Request ID;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +9700;0x25e4;TEST;INFO;No description;fsfw/src/fsfw/pus/Service17Test.h +10600;0x2968;CHANGE_OF_SETUP_PARAMETER;LOW;No description;fsfw/src/fsfw_hal/devicehandlers/MgmLIS3MDLHandler.h +10800;0x2a30;STORE_ERROR;LOW;No description;fsfw/src/fsfw/cfdp/handler/defs.h +10801;0x2a31;MSG_QUEUE_ERROR;LOW;No description;fsfw/src/fsfw/cfdp/handler/defs.h +10802;0x2a32;SERIALIZATION_ERROR;LOW;No description;fsfw/src/fsfw/cfdp/handler/defs.h +10803;0x2a33;FILESTORE_ERROR;LOW;No description;fsfw/src/fsfw/cfdp/handler/defs.h +10804;0x2a34;FILENAME_TOO_LARGE_ERROR;LOW;P1: Transaction step ID, P2: 0 for source file name, 1 for dest file name;fsfw/src/fsfw/cfdp/handler/defs.h +10805;0x2a35;HANDLING_CFDP_REQUEST_FAILED;LOW;CFDP request handling failed. P2: Returncode.;fsfw/src/fsfw/cfdp/handler/defs.h +11200;0x2bc0;SAFE_RATE_VIOLATION;MEDIUM;The limits for the rotation in safe mode were violated.;mission/acs/defs.h +11201;0x2bc1;RATE_RECOVERY;MEDIUM;The system has recovered from a rate rotation violation.;mission/acs/defs.h +11202;0x2bc2;MULTIPLE_RW_INVALID;HIGH;Multiple RWs are invalid, uncommandable and therefore higher ACS modes cannot be maintained.;mission/acs/defs.h +11203;0x2bc3;MEKF_INVALID_INFO;INFO;MEKF was not able to compute a solution. P1: MEKF state on exit;mission/acs/defs.h +11204;0x2bc4;MEKF_RECOVERY;INFO;MEKF is able to compute a solution again.;mission/acs/defs.h +11205;0x2bc5;MEKF_AUTOMATIC_RESET;INFO;MEKF performed an automatic reset after detection of nonfinite values.;mission/acs/defs.h +11206;0x2bc6;PTG_CTRL_NO_ATTITUDE_INFORMATION;HIGH;For a prolonged time, no attitude information was available for the Pointing Controller. Falling back to Safe Mode.;mission/acs/defs.h +11207;0x2bc7;SAFE_MODE_CONTROLLER_FAILURE;HIGH;The ACS safe mode controller was not able to compute a solution and has failed. P1: Missing information about magnetic field, P2: Missing information about rotational rate;mission/acs/defs.h +11208;0x2bc8;TLE_TOO_OLD;INFO;The TLE for the SGP4 Propagator has become too old.;mission/acs/defs.h +11209;0x2bc9;TLE_FILE_READ_FAILED;LOW;The TLE could not be read from the filesystem.;mission/acs/defs.h +11210;0x2bca;PTG_RATE_VIOLATION;MEDIUM;The limits for the rotation in pointing mode were violated.;mission/acs/defs.h +11211;0x2bcb;DETUMBLE_TRANSITION_FAILED;HIGH;The detumble transition has failed. //! P1: Last detumble state before failure.;mission/acs/defs.h +11300;0x2c24;SWITCH_CMD_SENT;INFO;Indicates that a FSFW object requested setting a switch P1: 1 if on was requested, 0 for off | P2: Switch Index;mission/power/defs.h +11301;0x2c25;SWITCH_HAS_CHANGED;INFO;Indicated that a switch state has changed P1: New switch state, 1 for on, 0 for off | P2: Switch Index;mission/power/defs.h +11302;0x2c26;SWITCHING_Q7S_DENIED;MEDIUM;No description;mission/power/defs.h +11303;0x2c27;FDIR_REACTION_IGNORED;MEDIUM;No description;mission/power/defs.h +11304;0x2c28;DATASET_READ_FAILED;INFO;The dataset read for the inputs of the Power Controller has failed.;mission/power/defs.h +11305;0x2c29;VOLTAGE_OUT_OF_BOUNDS;HIGH;No description;mission/power/defs.h +11306;0x2c2a;TIMEDELTA_OUT_OF_BOUNDS;LOW;Time difference for Coulomb Counter was too large. P1: time in s * 10;mission/power/defs.h +11307;0x2c2b;POWER_LEVEL_LOW;HIGH;The State of Charge is below the limit for payload use. Setting Payload to faulty.;mission/power/defs.h +11308;0x2c2c;POWER_LEVEL_CRITICAL;HIGH;The State of Charge is below the limit for higher modes. Setting Reaction Wheels to faulty.;mission/power/defs.h +11400;0x2c88;GPIO_PULL_HIGH_FAILED;LOW;No description;mission/tcs/HeaterHandler.h +11401;0x2c89;GPIO_PULL_LOW_FAILED;LOW;No description;mission/tcs/HeaterHandler.h +11402;0x2c8a;HEATER_WENT_ON;INFO;No description;mission/tcs/HeaterHandler.h +11403;0x2c8b;HEATER_WENT_OFF;INFO;No description;mission/tcs/HeaterHandler.h +11404;0x2c8c;SWITCH_ALREADY_ON;INFO;No description;mission/tcs/HeaterHandler.h +11405;0x2c8d;SWITCH_ALREADY_OFF;INFO;No description;mission/tcs/HeaterHandler.h +11406;0x2c8e;MAIN_SWITCH_TIMEOUT;MEDIUM;No description;mission/tcs/HeaterHandler.h +11407;0x2c8f;FAULTY_HEATER_WAS_ON;LOW;No description;mission/tcs/HeaterHandler.h +11500;0x2cec;BURN_PHASE_START;INFO;P1: Burn duration in milliseconds, P2: Dry run flag;mission/SolarArrayDeploymentHandler.h +11501;0x2ced;BURN_PHASE_DONE;INFO;P1: Burn duration in milliseconds, P2: Dry run flag;mission/SolarArrayDeploymentHandler.h +11502;0x2cee;MAIN_SWITCH_ON_TIMEOUT;LOW;No description;mission/SolarArrayDeploymentHandler.h +11503;0x2cef;MAIN_SWITCH_OFF_TIMEOUT;LOW;No description;mission/SolarArrayDeploymentHandler.h +11504;0x2cf0;DEPL_SA1_GPIO_SWTICH_ON_FAILED;HIGH;No description;mission/SolarArrayDeploymentHandler.h +11505;0x2cf1;DEPL_SA2_GPIO_SWTICH_ON_FAILED;HIGH;No description;mission/SolarArrayDeploymentHandler.h +11506;0x2cf2;DEPL_SA1_GPIO_SWTICH_OFF_FAILED;HIGH;No description;mission/SolarArrayDeploymentHandler.h +11507;0x2cf3;DEPL_SA2_GPIO_SWTICH_OFF_FAILED;HIGH;No description;mission/SolarArrayDeploymentHandler.h +11508;0x2cf4;AUTONOMOUS_DEPLOYMENT_COMPLETED;INFO;No description;mission/SolarArrayDeploymentHandler.h +11601;0x2d51;MEMORY_READ_RPT_CRC_FAILURE;LOW;PLOC crc failure in telemetry packet;linux/payload/plocMpsocHelpers.h +11602;0x2d52;ACK_FAILURE;LOW;PLOC receive acknowledgment failure report P1: Command Id which leads the acknowledgment failure report P2: The status field inserted by the MPSoC into the data field;linux/payload/plocMpsocHelpers.h +11603;0x2d53;EXE_FAILURE;LOW;PLOC receive execution failure report P1: Command Id which leads the execution failure report P2: The status field inserted by the MPSoC into the data field;linux/payload/plocMpsocHelpers.h +11604;0x2d54;MPSOC_HANDLER_CRC_FAILURE;LOW;PLOC reply has invalid crc;linux/payload/plocMpsocHelpers.h +11605;0x2d55;MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH;LOW;Packet sequence count in received space packet does not match expected count P1: Expected sequence count P2: Received sequence count;linux/payload/plocMpsocHelpers.h +11606;0x2d56;MPSOC_SHUTDOWN_FAILED;HIGH;Supervisor fails to shutdown MPSoC. Requires to power off the PLOC and thus also to shutdown the supervisor.;linux/payload/plocMpsocHelpers.h +11607;0x2d57;SUPV_NOT_ON;LOW;SUPV not on for boot or shutdown process. P1: 0 for OFF transition, 1 for ON transition.;linux/payload/plocMpsocHelpers.h +11608;0x2d58;SUPV_REPLY_TIMEOUT;LOW;SUPV reply timeout.;linux/payload/plocMpsocHelpers.h +11609;0x2d59;CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE;LOW;Camera must be commanded on first.;linux/payload/plocMpsocHelpers.h +11701;0x2db5;SELF_TEST_I2C_FAILURE;LOW;Get self test result returns I2C failure P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11702;0x2db6;SELF_TEST_SPI_FAILURE;LOW;Get self test result returns SPI failure. This concerns the MTM connectivity. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11703;0x2db7;SELF_TEST_ADC_FAILURE;LOW;Get self test result returns failure in measurement of current and temperature. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11704;0x2db8;SELF_TEST_PWM_FAILURE;LOW;Get self test result returns PWM failure which concerns the coil actuation. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11705;0x2db9;SELF_TEST_TC_FAILURE;LOW;Get self test result returns TC failure (system failure) P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11706;0x2dba;SELF_TEST_MTM_RANGE_FAILURE;LOW;Get self test result returns failure that MTM values were outside of the expected range. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11707;0x2dbb;SELF_TEST_COIL_CURRENT_FAILURE;LOW;Get self test result returns failure indicating that the coil current was outside of the expected range P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA;mission/acs/ImtqHandler.h +11708;0x2dbc;INVALID_ERROR_BYTE;LOW;Received invalid error byte. This indicates an error of the communication link between IMTQ and OBC.;mission/acs/ImtqHandler.h +11801;0x2e19;ERROR_STATE;HIGH;Reaction wheel signals an error state;mission/acs/rwHelpers.h +11802;0x2e1a;RESET_OCCURED;LOW;No description;mission/acs/rwHelpers.h +11901;0x2e7d;BOOTING_FIRMWARE_FAILED_EVENT;LOW;Failed to boot firmware;mission/acs/str/StarTrackerHandler.h +11902;0x2e7e;BOOTING_BOOTLOADER_FAILED_EVENT;LOW;Failed to boot star tracker into bootloader mode;mission/acs/str/StarTrackerHandler.h +11903;0x2e7f;COM_ERROR_REPLY_RECEIVED;LOW;Received COM error. P1: Communication Error ID (datasheet p32);mission/acs/str/StarTrackerHandler.h +12001;0x2ee1;SUPV_MEMORY_READ_RPT_CRC_FAILURE;LOW;PLOC supervisor crc failure in telemetry packet;linux/payload/plocSupvDefs.h +12002;0x2ee2;SUPV_UNKNOWN_TM;LOW;Unhandled event. P1: APID, P2: Service ID;linux/payload/plocSupvDefs.h +12003;0x2ee3;SUPV_UNINIMPLEMENTED_TM;LOW;No description;linux/payload/plocSupvDefs.h +12004;0x2ee4;SUPV_ACK_FAILURE;LOW;PLOC supervisor received acknowledgment failure report;linux/payload/plocSupvDefs.h +12005;0x2ee5;SUPV_EXE_FAILURE;LOW;PLOC received execution failure report P1: ID of command for which the execution failed P2: Status code sent by the supervisor handler;linux/payload/plocSupvDefs.h +12006;0x2ee6;SUPV_CRC_FAILURE_EVENT;LOW;PLOC supervisor reply has invalid crc;linux/payload/plocSupvDefs.h +12007;0x2ee7;SUPV_HELPER_EXECUTING;LOW;Supervisor helper currently executing a command;linux/payload/plocSupvDefs.h +12008;0x2ee8;SUPV_MPSOC_SHUTDOWN_BUILD_FAILED;LOW;Failed to build the command to shutdown the MPSoC;linux/payload/plocSupvDefs.h +12009;0x2ee9;SUPV_ACK_UNKNOWN_COMMAND;LOW;Received ACK, but no related command is unknown or has not been sent by this software instance. P1: Module APID. P2: Service ID.;linux/payload/plocSupvDefs.h +12010;0x2eea;SUPV_EXE_ACK_UNKNOWN_COMMAND;LOW;Received ACK EXE, but no related command is unknown or has not been sent by this software instance. P1: Module APID. P2: Service ID.;linux/payload/plocSupvDefs.h +12100;0x2f44;SANITIZATION_FAILED;LOW;No description;bsp_q7s/fs/SdCardManager.h +12101;0x2f45;MOUNTED_SD_CARD;INFO;No description;bsp_q7s/fs/SdCardManager.h +12300;0x300c;SEND_MRAM_DUMP_FAILED;LOW;Failed to send mram dump command to supervisor handler P1: Return value of commandAction function P2: Start address of MRAM to dump with this command;linux/payload/PlocMemoryDumper.h +12301;0x300d;MRAM_DUMP_FAILED;LOW;Received completion failure report form PLOC supervisor handler P1: MRAM start address of failing dump command;linux/payload/PlocMemoryDumper.h +12302;0x300e;MRAM_DUMP_FINISHED;LOW;MRAM dump finished successfully;linux/payload/PlocMemoryDumper.h +12401;0x3071;INVALID_TC_FRAME;HIGH;No description;linux/ipcore/pdec.h +12402;0x3072;INVALID_FAR;HIGH;Read invalid FAR from PDEC after startup;linux/ipcore/pdec.h +12403;0x3073;CARRIER_LOCK;INFO;Carrier lock detected;linux/ipcore/pdec.h +12404;0x3074;BIT_LOCK_PDEC;INFO;Bit lock detected (data valid);linux/ipcore/pdec.h +12405;0x3075;LOST_CARRIER_LOCK_PDEC;INFO;Lost carrier lock;linux/ipcore/pdec.h +12406;0x3076;LOST_BIT_LOCK_PDEC;INFO;Lost bit lock;linux/ipcore/pdec.h +12407;0x3077;TOO_MANY_IRQS;MEDIUM;Too many IRQs over the time window of one second. P1: Allowed TCs;linux/ipcore/pdec.h +12408;0x3078;POLL_SYSCALL_ERROR_PDEC;MEDIUM;No description;linux/ipcore/pdec.h +12409;0x3079;WRITE_SYSCALL_ERROR_PDEC;HIGH;No description;linux/ipcore/pdec.h +12410;0x307a;PDEC_TRYING_RESET_WITH_INIT;LOW;Trying a PDEC reset with complete re-initialization;linux/ipcore/pdec.h +12411;0x307b;PDEC_TRYING_RESET_NO_INIT;LOW;Trying a PDEC reset without re-initialization.;linux/ipcore/pdec.h +12412;0x307c;PDEC_RESET_FAILED;HIGH;Failed to pull PDEC reset to low;linux/ipcore/pdec.h +12413;0x307d;OPEN_IRQ_FILE_FAILED;HIGH;Failed to open the IRQ uio file;linux/ipcore/pdec.h +12414;0x307e;PDEC_INIT_FAILED;HIGH;PDEC initialization failed. This might also be due to the persistent confiuration never becoming available, for example due to SD card issues.;linux/ipcore/pdec.h +12415;0x307f;PDEC_CONFIG_CORRUPTED;HIGH;The PDEC configuration area has been corrupted P1: The first configuration word P2: The second configuration word;linux/ipcore/pdec.h +12500;0x30d4;IMAGE_UPLOAD_FAILED;LOW;Image upload failed;linux/acs/StrComHandler.h +12501;0x30d5;IMAGE_DOWNLOAD_FAILED;LOW;Image download failed;linux/acs/StrComHandler.h +12502;0x30d6;IMAGE_UPLOAD_SUCCESSFUL;LOW;Uploading image to star tracker was successfulop;linux/acs/StrComHandler.h +12503;0x30d7;IMAGE_DOWNLOAD_SUCCESSFUL;LOW;Image download was successful;linux/acs/StrComHandler.h +12504;0x30d8;FLASH_WRITE_SUCCESSFUL;LOW;Finished flash write procedure successfully;linux/acs/StrComHandler.h +12505;0x30d9;FLASH_READ_SUCCESSFUL;LOW;Finished flash read procedure successfully;linux/acs/StrComHandler.h +12506;0x30da;FLASH_READ_FAILED;LOW;Flash read procedure failed;linux/acs/StrComHandler.h +12507;0x30db;FIRMWARE_UPDATE_SUCCESSFUL;LOW;Firmware update was successful;linux/acs/StrComHandler.h +12508;0x30dc;FIRMWARE_UPDATE_FAILED;LOW;Firmware update failed;linux/acs/StrComHandler.h +12509;0x30dd;STR_HELPER_READING_REPLY_FAILED;LOW;Failed to read communication interface reply data P1: Return code of failed communication interface read call P1: Upload/download position for which the read call failed;linux/acs/StrComHandler.h +12510;0x30de;STR_HELPER_COM_ERROR;LOW;Unexpected stop of decoding sequence P1: Return code of failed communication interface read call P1: Upload/download position for which the read call failed;linux/acs/StrComHandler.h +12511;0x30df;STR_COM_REPLY_TIMEOUT;LOW;Star tracker did not send a valid reply for a certain timeout. P1: Position of upload or download packet for which the packet wa sent. P2: Timeout;linux/acs/StrComHandler.h +12513;0x30e1;STR_HELPER_DEC_ERROR;LOW;Error during decoding of received reply occurred P1: Return value of decoding function P2: Position of upload/download packet, or address of flash write/read request;linux/acs/StrComHandler.h +12514;0x30e2;POSITION_MISMATCH;LOW;Position mismatch P1: The expected position and thus the position for which the image upload/download failed;linux/acs/StrComHandler.h +12515;0x30e3;STR_HELPER_FILE_NOT_EXISTS;LOW;Specified file does not exist P1: Internal state of str helper;linux/acs/StrComHandler.h +12516;0x30e4;STR_HELPER_SENDING_PACKET_FAILED;LOW;No description;linux/acs/StrComHandler.h +12517;0x30e5;STR_HELPER_REQUESTING_MSG_FAILED;LOW;No description;linux/acs/StrComHandler.h +12600;0x3138;MPSOC_FLASH_WRITE_FAILED;LOW;Flash write fails;linux/payload/PlocMpsocSpecialComHelper.h +12601;0x3139;MPSOC_FLASH_WRITE_SUCCESSFUL;INFO;Flash write successful;linux/payload/PlocMpsocSpecialComHelper.h +12602;0x313a;MPSOC_SENDING_COMMAND_FAILED;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12603;0x313b;MPSOC_HELPER_REQUESTING_REPLY_FAILED;LOW;Request receive message of communication interface failed P1: Return value returned by the communication interface requestReceiveMessage function P2: Internal state of MPSoC helper;linux/payload/PlocMpsocSpecialComHelper.h +12604;0x313c;MPSOC_HELPER_READING_REPLY_FAILED;LOW;Reading receive message of communication interface failed P1: Return value returned by the communication interface readingReceivedMessage function P2: Internal state of MPSoC helper;linux/payload/PlocMpsocSpecialComHelper.h +12605;0x313d;MPSOC_MISSING_ACK;LOW;Did not receive acknowledgment report P1: Number of bytes missing P2: Internal state of MPSoC helper;linux/payload/PlocMpsocSpecialComHelper.h +12606;0x313e;MPSOC_MISSING_EXE;LOW;Did not receive execution report P1: Number of bytes missing P2: Internal state of MPSoC helper;linux/payload/PlocMpsocSpecialComHelper.h +12607;0x313f;MPSOC_ACK_FAILURE_REPORT;LOW;Received acknowledgment failure report P1: Internal state of MPSoC;linux/payload/PlocMpsocSpecialComHelper.h +12608;0x3140;MPSOC_EXE_FAILURE_REPORT;LOW;Received execution failure report P1: Internal state of MPSoC;linux/payload/PlocMpsocSpecialComHelper.h +12609;0x3141;MPSOC_ACK_INVALID_APID;LOW;Expected acknowledgment report but received space packet with other apid P1: Apid of received space packet P2: Internal state of MPSoC;linux/payload/PlocMpsocSpecialComHelper.h +12610;0x3142;MPSOC_EXE_INVALID_APID;LOW;Expected execution report but received space packet with other apid P1: Apid of received space packet P2: Internal state of MPSoC;linux/payload/PlocMpsocSpecialComHelper.h +12611;0x3143;MPSOC_HELPER_SEQ_CNT_MISMATCH;LOW;Received sequence count does not match expected sequence count P1: Expected sequence count P2: Received sequence count;linux/payload/PlocMpsocSpecialComHelper.h +12612;0x3144;MPSOC_TM_SIZE_ERROR;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12613;0x3145;MPSOC_TM_CRC_MISSMATCH;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12614;0x3146;MPSOC_FLASH_READ_PACKET_ERROR;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12615;0x3147;MPSOC_FLASH_READ_FAILED;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12616;0x3148;MPSOC_FLASH_READ_SUCCESSFUL;INFO;No description;linux/payload/PlocMpsocSpecialComHelper.h +12617;0x3149;MPSOC_READ_TIMEOUT;LOW;No description;linux/payload/PlocMpsocSpecialComHelper.h +12700;0x319c;TRANSITION_BACK_TO_OFF;MEDIUM;Could not transition properly and went back to ALL OFF;mission/payload/PayloadPcduHandler.h +12701;0x319d;NEG_V_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12702;0x319e;U_DRO_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12703;0x319f;I_DRO_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12704;0x31a0;U_X8_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12705;0x31a1;I_X8_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12706;0x31a2;U_TX_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12707;0x31a3;I_TX_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12708;0x31a4;U_MPA_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12709;0x31a5;I_MPA_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12710;0x31a6;U_HPA_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12711;0x31a7;I_HPA_OUT_OF_BOUNDS;MEDIUM;P1: 0 -> too low, 1 -> too high P2: Float value;mission/payload/PayloadPcduHandler.h +12800;0x3200;TRANSITION_OTHER_SIDE_FAILED;HIGH;No description;mission/system/acs/AcsBoardAssembly.h +12801;0x3201;NOT_ENOUGH_DEVICES_DUAL_MODE;HIGH;No description;mission/system/acs/AcsBoardAssembly.h +12802;0x3202;POWER_STATE_MACHINE_TIMEOUT;MEDIUM;No description;mission/system/acs/AcsBoardAssembly.h +12803;0x3203;SIDE_SWITCH_TRANSITION_NOT_ALLOWED;LOW;Not implemented, would increase already high complexity. Operator should instead command the assembly off first and then command the assembly on into the desired mode/submode combination;mission/system/acs/AcsBoardAssembly.h +12804;0x3204;DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY;MEDIUM;This is triggered when the assembly would have normally switched the board side, but the GPS device of the other side was marked faulty. P1: Current submode.;mission/system/acs/AcsBoardAssembly.h +12900;0x3264;TRANSITION_OTHER_SIDE_FAILED;HIGH;No description;mission/system/acs/SusAssembly.h +12901;0x3265;NOT_ENOUGH_DEVICES_DUAL_MODE;HIGH;No description;mission/system/acs/SusAssembly.h +12902;0x3266;POWER_STATE_MACHINE_TIMEOUT;MEDIUM;No description;mission/system/acs/SusAssembly.h +12903;0x3267;SIDE_SWITCH_TRANSITION_NOT_ALLOWED;LOW;Not implemented, would increase already high complexity. Operator should instead command the assembly off first and then command the assembly on into the desired mode/submode combination;mission/system/acs/SusAssembly.h +13000;0x32c8;CHILDREN_LOST_MODE;MEDIUM;No description;mission/system/tcs/TcsBoardAssembly.h +13100;0x332c;GPS_FIX_CHANGE;INFO;Fix has changed. P1: New fix. P2: Missed fix changes 0: Not seen, 1: No Fix, 2: 2D-Fix, 3: 3D-Fix;linux/acs/GPSDefinitions.h +13101;0x332d;CANT_GET_FIX;MEDIUM;Could not get fix in maximum allowed time. Trying to reset both GNSS devices. P1: Maximum allowed time to get a fix after the GPS was switched on.;linux/acs/GPSDefinitions.h +13102;0x332e;RESET_FAIL;HIGH;Failed to reset an GNNS Device. P1: Board-Side.;linux/acs/GPSDefinitions.h +13200;0x3390;P60_BOOT_COUNT;INFO;P60 boot count is broadcasted once at SW startup. P1: Boot count;mission/power/P60DockHandler.h +13201;0x3391;BATT_MODE;INFO;Battery mode is broadcasted at startup. P1: Mode;mission/power/P60DockHandler.h +13202;0x3392;BATT_MODE_CHANGED;MEDIUM;Battery mode has changed. P1: Old mode. P2: New mode;mission/power/P60DockHandler.h +13600;0x3520;SUPV_UPDATE_FAILED;LOW;update failed;linux/payload/PlocSupvUartMan.h +13601;0x3521;SUPV_UPDATE_SUCCESSFUL;LOW;update successful;linux/payload/PlocSupvUartMan.h +13602;0x3522;SUPV_CONTINUE_UPDATE_FAILED;LOW;Continue update command failed;linux/payload/PlocSupvUartMan.h +13603;0x3523;SUPV_CONTINUE_UPDATE_SUCCESSFUL;LOW;Continue update command successful;linux/payload/PlocSupvUartMan.h +13604;0x3524;TERMINATED_UPDATE_PROCEDURE;LOW;Terminated update procedure by command;linux/payload/PlocSupvUartMan.h +13605;0x3525;SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL;LOW;Requesting event buffer was successful;linux/payload/PlocSupvUartMan.h +13606;0x3526;SUPV_EVENT_BUFFER_REQUEST_FAILED;LOW;Requesting event buffer failed;linux/payload/PlocSupvUartMan.h +13607;0x3527;SUPV_EVENT_BUFFER_REQUEST_TERMINATED;LOW;Terminated event buffer request by command P1: Number of packets read before process was terminated;linux/payload/PlocSupvUartMan.h +13608;0x3528;SUPV_MEM_CHECK_OK;INFO;No description;linux/payload/PlocSupvUartMan.h +13609;0x3529;SUPV_MEM_CHECK_FAIL;INFO;No description;linux/payload/PlocSupvUartMan.h +13616;0x3530;SUPV_SENDING_COMMAND_FAILED;LOW;No description;linux/payload/PlocSupvUartMan.h +13617;0x3531;SUPV_HELPER_REQUESTING_REPLY_FAILED;LOW;Request receive message of communication interface failed P1: Return value returned by the communication interface requestReceiveMessage function P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13618;0x3532;SUPV_HELPER_READING_REPLY_FAILED;LOW;Reading receive message of communication interface failed P1: Return value returned by the communication interface readingReceivedMessage function P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13619;0x3533;SUPV_MISSING_ACK;LOW;Did not receive acknowledgement report P1: Number of bytes missing P2: Internal state of MPSoC helper;linux/payload/PlocSupvUartMan.h +13620;0x3534;SUPV_MISSING_EXE;LOW;Supervisor did not receive execution report P1: Number of bytes missing P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13621;0x3535;SUPV_ACK_FAILURE_REPORT;LOW;Supervisor received acknowledgment failure report P1: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13622;0x3536;SUPV_EXE_FAILURE_REPORT;LOW;Execution report failure P1:;linux/payload/PlocSupvUartMan.h +13623;0x3537;SUPV_ACK_INVALID_APID;LOW;Supervisor expected acknowledgment report but received space packet with other apid P1: Apid of received space packet P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13624;0x3538;SUPV_EXE_INVALID_APID;LOW;Supervisor helper expected execution report but received space packet with other apid P1: Apid of received space packet P2: Internal state of supervisor helper;linux/payload/PlocSupvUartMan.h +13625;0x3539;ACK_RECEPTION_FAILURE;LOW;Failed to receive acknowledgment report P1: Return value P2: Apid of command for which the reception of the acknowledgment report failed;linux/payload/PlocSupvUartMan.h +13626;0x353a;EXE_RECEPTION_FAILURE;LOW;Failed to receive execution report P1: Return value P2: Apid of command for which the reception of the execution report failed;linux/payload/PlocSupvUartMan.h +13627;0x353b;WRITE_MEMORY_FAILED;LOW;Update procedure failed when sending packet. P1: First byte percent, third and fourth byte Sequence Count, P2: Bytes written;linux/payload/PlocSupvUartMan.h +13628;0x353c;SUPV_REPLY_SIZE_MISSMATCH;LOW;No description;linux/payload/PlocSupvUartMan.h +13629;0x353d;SUPV_REPLY_CRC_MISSMATCH;LOW;No description;linux/payload/PlocSupvUartMan.h +13630;0x353e;SUPV_UPDATE_PROGRESS;INFO;Will be triggered every 5 percent of the update progress. P1: First byte percent, third and fourth byte Sequence Count, P2: Bytes written;linux/payload/PlocSupvUartMan.h +13631;0x353f;HDLC_FRAME_REMOVAL_ERROR;INFO;No description;linux/payload/PlocSupvUartMan.h +13632;0x3540;HDLC_CRC_ERROR;INFO;No description;linux/payload/PlocSupvUartMan.h +13701;0x3585;TX_ON;INFO;Transmitter is on now. P1: Submode, P2: Current default datarate.;mission/com/syrlinksDefs.h +13702;0x3586;TX_OFF;INFO;Transmitter is off now.;mission/com/syrlinksDefs.h +13800;0x35e8;MISSING_PACKET;LOW;No description;mission/payload/scexHelpers.h +13801;0x35e9;EXPERIMENT_TIMEDOUT;LOW;No description;mission/payload/scexHelpers.h +13802;0x35ea;MULTI_PACKET_COMMAND_DONE;INFO;No description;mission/payload/scexHelpers.h +13803;0x35eb;FS_UNUSABLE;LOW;No description;mission/payload/scexHelpers.h +13901;0x364d;SET_CONFIGFILEVALUE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +13902;0x364e;GET_CONFIGFILEVALUE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +13903;0x364f;INSERT_CONFIGFILEVALUE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +13904;0x3650;WRITE_CONFIGFILE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +13905;0x3651;READ_CONFIGFILE_FAILED;MEDIUM;No description;mission/utility/GlobalConfigHandler.h +14000;0x36b0;ALLOC_FAILURE;MEDIUM;No description;mission/sysDefs.h +14001;0x36b1;REBOOT_SW;LOW; Software reboot occurred. Can also be a systemd reboot. P1: Current Chip, P2: Current Copy;mission/sysDefs.h +14002;0x36b2;REBOOT_MECHANISM_TRIGGERED;MEDIUM;The reboot mechanism was triggered. P1: First 16 bits: Last Chip, Last 16 bits: Last Copy, P2: Each byte is the respective reboot count for the slots;mission/sysDefs.h +14003;0x36b3;REBOOT_HW;MEDIUM;No description;mission/sysDefs.h +14004;0x36b4;NO_SD_CARD_ACTIVE;HIGH;No SD card was active. Core controller will attempt to re-initialize a SD card.;mission/sysDefs.h +14005;0x36b5;VERSION_INFO;INFO;P1: Byte 0: Major, Byte 1: Minor, Byte 2: Patch, Byte 3: Has Git Hash P2: First four letters of Git SHA is the last byte of P1 is set.;mission/sysDefs.h +14006;0x36b6;CURRENT_IMAGE_INFO;INFO;P1: Current Chip, P2: Current Copy;mission/sysDefs.h +14007;0x36b7;REBOOT_COUNTER;INFO;Total reboot counter, which is the sum of the boot count of all individual images.;mission/sysDefs.h +14008;0x36b8;INDIVIDUAL_BOOT_COUNTS;INFO;Get the boot count of the individual images. P1: First 16 bits boot count of image 0 0, last 16 bits boot count of image 0 1. P2: First 16 bits boot count of image 1 0, last 16 bits boot count of image 1 1.;mission/sysDefs.h +14010;0x36ba;TRYING_I2C_RECOVERY;HIGH;I2C is unavailable. Trying recovery of I2C bus by power cycling all I2C devices.;mission/sysDefs.h +14011;0x36bb;I2C_REBOOT;HIGH;I2C is unavailable. Recovery did not work, performing full reboot.;mission/sysDefs.h +14012;0x36bc;PDEC_REBOOT;HIGH;PDEC recovery through reset was not possible, performing full reboot.;mission/sysDefs.h +14013;0x36bd;FIRMWARE_INFO;INFO;Version information of the firmware (not OBSW). P1: Byte 0: Major, Byte 1: Minor, Byte 2: Patch, Byte 3: Has Git Hash P2: First four letters of Git SHA is the last byte of P1 is set.;mission/sysDefs.h +14014;0x36be;ACTIVE_SD_INFO;INFO;Active SD card info. SD States: 0: OFF, 1: ON, 2: MOUNTED. P1: Active SD Card Index, 0 if none is active P2: First two bytes: SD state of SD card 0, last two bytes SD state of SD card 1;mission/sysDefs.h +14100;0x3714;NO_VALID_SENSOR_TEMPERATURE;MEDIUM;No description;mission/controller/tcsDefs.h +14101;0x3715;NO_HEALTHY_HEATER_AVAILABLE;MEDIUM;No description;mission/controller/tcsDefs.h +14102;0x3716;SYRLINKS_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14104;0x3718;OBC_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14105;0x3719;CAMERA_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14106;0x371a;PCDU_SYSTEM_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14107;0x371b;HEATER_NOT_OFF_FOR_OFF_MODE;MEDIUM;No description;mission/controller/tcsDefs.h +14108;0x371c;MGT_OVERHEATING;HIGH;No description;mission/controller/tcsDefs.h +14109;0x371d;TCS_SWITCHING_HEATER_ON;INFO;P1: Module index. P2: Heater index;mission/controller/tcsDefs.h +14110;0x371e;TCS_SWITCHING_HEATER_OFF;INFO;P1: Module index. P2: Heater index;mission/controller/tcsDefs.h +14111;0x371f;TCS_HEATER_MAX_BURN_TIME_REACHED;MEDIUM;P1: Heater index. P2: Maximum burn time for heater.;mission/controller/tcsDefs.h +14201;0x3779;TX_TIMER_EXPIRED;INFO;The transmit timer to protect the Syrlinks expired P1: The current timer value;mission/system/com/ComSubsystem.h +14202;0x377a;BIT_LOCK_TX_ON;INFO;Transmitter will be turned on due to detection of bitlock;mission/system/com/ComSubsystem.h +14300;0x37dc;POSSIBLE_FILE_CORRUPTION;LOW;P1: Result code of TM packet parser. P2: Timestamp of possibly corrupt file as a unix timestamp.;mission/persistentTmStoreDefs.h +14301;0x37dd;FILE_TOO_LARGE;LOW;File in store too large. P1: Detected file size P2: Allowed file size;mission/persistentTmStoreDefs.h +14302;0x37de;BUSY_DUMPING_EVENT;INFO;No description;mission/persistentTmStoreDefs.h +14305;0x37e1;DUMP_OK_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14306;0x37e2;DUMP_NOK_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14307;0x37e3;DUMP_MISC_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14308;0x37e4;DUMP_HK_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14309;0x37e5;DUMP_CFDP_STORE_DONE;INFO;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14310;0x37e6;DUMP_OK_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14311;0x37e7;DUMP_NOK_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14312;0x37e8;DUMP_MISC_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14313;0x37e9;DUMP_HK_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14314;0x37ea;DUMP_CFDP_CANCELLED;LOW;P1: Number of dumped packets. P2: Total dumped bytes.;mission/persistentTmStoreDefs.h +14500;0x38a4;TEMPERATURE_ALL_ONES_START;MEDIUM;Detected invalid values, starting invalid message counting;mission/acs/SusHandler.h +14501;0x38a5;TEMPERATURE_ALL_ONES_RECOVERY;INFO;Detected valid values for a prolonged time again, resetting all counters. P1: Number of periods with invalid messages. P2: Maximum invalid message counter.;mission/acs/SusHandler.h +14600;0x3908;FAULT_HANDLER_TRIGGERED;LOW;P1: CFDP fault handler code. P2: CFDP condition code.;mission/cfdp/defs.h diff --git a/generators/bsp_q7s_objects.csv b/generators/bsp_q7s_objects.csv new file mode 100644 index 0000000..8fc1bd2 --- /dev/null +++ b/generators/bsp_q7s_objects.csv @@ -0,0 +1,180 @@ +0x00005060;P60DOCK_TEST_TASK +0x43000002;ACS_CONTROLLER +0x43000003;CORE_CONTROLLER +0x43000004;POWER_CONTROLLER +0x43000006;GLOBAL_JSON_CFG +0x43000007;XIPHOS_WDT +0x43400001;THERMAL_CONTROLLER +0x44120006;MGM_0_LIS3_HANDLER +0x44120010;GYRO_0_ADIS_HANDLER +0x44120032;SUS_0_N_LOC_XFYFZM_PT_XF +0x44120033;SUS_1_N_LOC_XBYFZM_PT_XB +0x44120034;SUS_2_N_LOC_XFYBZB_PT_YB +0x44120035;SUS_3_N_LOC_XFYBZF_PT_YF +0x44120036;SUS_4_N_LOC_XMYFZF_PT_ZF +0x44120037;SUS_5_N_LOC_XFYMZB_PT_ZB +0x44120038;SUS_6_R_LOC_XFYBZM_PT_XF +0x44120039;SUS_7_R_LOC_XBYBZM_PT_XB +0x44120040;SUS_8_R_LOC_XBYBZB_PT_YB +0x44120041;SUS_9_R_LOC_XBYBZB_PT_YF +0x44120042;SUS_10_N_LOC_XMYBZF_PT_ZF +0x44120043;SUS_11_R_LOC_XBYMZB_PT_ZB +0x44120047;RW1 +0x44120107;MGM_1_RM3100_HANDLER +0x44120111;GYRO_1_L3G_HANDLER +0x44120148;RW2 +0x44120208;MGM_2_LIS3_HANDLER +0x44120212;GYRO_2_ADIS_HANDLER +0x44120249;RW3 +0x44120309;MGM_3_RM3100_HANDLER +0x44120313;GYRO_3_L3G_HANDLER +0x44120350;RW4 +0x44130001;STAR_TRACKER +0x44130045;GPS_CONTROLLER +0x44130046;GPS_0_HEALTH_DEV +0x44130047;GPS_1_HEALTH_DEV +0x44140013;IMTQ_POLLING +0x44140014;IMTQ_HANDLER +0x442000A1;PCDU_HANDLER +0x44250000;P60DOCK_HANDLER +0x44250001;PDU1_HANDLER +0x44250002;PDU2_HANDLER +0x44250003;ACU_HANDLER +0x44260000;BPX_BATT_HANDLER +0x44300000;PLPCDU_HANDLER +0x443200A5;RAD_SENSOR +0x44330000;PLOC_UPDATER +0x44330001;PLOC_MEMORY_DUMPER +0x44330002;STR_COM_IF +0x44330003;PLOC_MPSOC_HELPER +0x44330004;AXI_PTME_CONFIG +0x44330005;PTME_CONFIG +0x44330006;PTME_VC0_LIVE_TM +0x44330007;PTME_VC1_LOG_TM +0x44330008;PTME_VC2_HK_TM +0x44330009;PTME_VC3_CFDP_TM +0x44330015;PLOC_MPSOC_HANDLER +0x44330016;PLOC_SUPERVISOR_HANDLER +0x44330017;PLOC_SUPERVISOR_HELPER +0x44330018;PLOC_MPSOC_COMMUNICATION +0x44330032;SCEX +0x444100A2;SOLAR_ARRAY_DEPL_HANDLER +0x444100A4;HEATER_HANDLER +0x44420004;TMP1075_HANDLER_TCS_0 +0x44420005;TMP1075_HANDLER_TCS_1 +0x44420006;TMP1075_HANDLER_PLPCDU_0 +0x44420007;TMP1075_HANDLER_PLPCDU_1 +0x44420008;TMP1075_HANDLER_IF_BOARD +0x44420016;RTD_0_IC3_PLOC_HEATSPREADER +0x44420017;RTD_1_IC4_PLOC_MISSIONBOARD +0x44420018;RTD_2_IC5_4K_CAMERA +0x44420019;RTD_3_IC6_DAC_HEATSPREADER +0x44420020;RTD_4_IC7_STARTRACKER +0x44420021;RTD_5_IC8_RW1_MX_MY +0x44420022;RTD_6_IC9_DRO +0x44420023;RTD_7_IC10_SCEX +0x44420024;RTD_8_IC11_X8 +0x44420025;RTD_9_IC12_HPA +0x44420026;RTD_10_IC13_PL_TX +0x44420027;RTD_11_IC14_MPA +0x44420028;RTD_12_IC15_ACU +0x44420029;RTD_13_IC16_PLPCDU_HEATSPREADER +0x44420030;RTD_14_IC17_TCS_BOARD +0x44420031;RTD_15_IC18_IMTQ +0x445300A3;SYRLINKS_HANDLER +0x445300A4;SYRLINKS_COM_HANDLER +0x49000000;ARDUINO_COM_IF +0x49010005;GPIO_IF +0x49010006;SCEX_UART_READER +0x49020004;SPI_MAIN_COM_IF +0x49030003;UART_COM_IF +0x49040002;I2C_COM_IF +0x49050001;CSP_COM_IF +0x49060004;ACS_BOARD_POLLING_TASK +0x49060005;RW_POLLING_TASK +0x49060006;SPI_RTD_COM_IF +0x49060007;SUS_POLLING_TASK +0x50000100;CCSDS_PACKET_DISTRIBUTOR +0x50000200;PUS_PACKET_DISTRIBUTOR +0x50000300;TCP_TMTC_SERVER +0x50000301;UDP_TMTC_SERVER +0x50000400;TCP_TMTC_POLLING_TASK +0x50000401;UDP_TMTC_POLLING_TASK +0x50000500;FILE_SYSTEM_HANDLER +0x50000550;SDC_MANAGER +0x50000600;PTME +0x50000700;PDEC_HANDLER +0x50000800;CCSDS_HANDLER +0x51000500;PUS_SERVICE_6 +0x53000000;FSFW_OBJECTS_START +0x53000001;PUS_SERVICE_1_VERIFICATION +0x53000002;PUS_SERVICE_2_DEVICE_ACCESS +0x53000003;PUS_SERVICE_3_HOUSEKEEPING +0x53000005;PUS_SERVICE_5_EVENT_REPORTING +0x53000008;PUS_SERVICE_8_FUNCTION_MGMT +0x53000009;PUS_SERVICE_9_TIME_MGMT +0x53000011;PUS_SERVICE_11_TC_SCHEDULER +0x53000015;PUS_SERVICE_15_TM_STORAGE +0x53000017;PUS_SERVICE_17_TEST +0x53000020;PUS_SERVICE_20_PARAMETERS +0x53000200;PUS_SERVICE_200_MODE_MGMT +0x53000201;PUS_SERVICE_201_HEALTH +0x53001000;CFDP_PACKET_DISTRIBUTOR +0x53010000;HEALTH_TABLE +0x53010100;MODE_STORE +0x53030000;EVENT_MANAGER +0x53040000;INTERNAL_ERROR_REPORTER +0x534f0100;TC_STORE +0x534f0200;TM_STORE +0x534f0300;IPC_STORE +0x53500010;TIME_STAMPER +0x53500020;VERIFICATION_REPORTER +0x53ffffff;FSFW_OBJECTS_END +0x54000010;SPI_TEST +0x54000020;UART_TEST +0x54000030;I2C_TEST +0x54000040;DUMMY_COM_IF +0x5400AFFE;DUMMY_HANDLER +0x5400CAFE;DUMMY_INTERFACE +0x54123456;LIBGPIOD_TEST +0x54694269;TEST_TASK +0x60000000;HEATER_0_PLOC_PROC_BRD +0x60000001;HEATER_1_PCDU_BRD +0x60000002;HEATER_2_ACS_BRD +0x60000003;HEATER_3_OBC_BRD +0x60000004;HEATER_4_CAMERA +0x60000005;HEATER_5_STR +0x60000006;HEATER_6_DRO +0x60000007;HEATER_7_SYRLINKS +0x73000001;ACS_BOARD_ASS +0x73000002;SUS_BOARD_ASS +0x73000003;TCS_BOARD_ASS +0x73000004;RW_ASSY +0x73000006;CAM_SWITCHER +0x73000007;SYRLINKS_ASSY +0x73000008;IMTQ_ASSY +0x73000009;STR_ASSY +0x73000100;TM_FUNNEL +0x73000101;PUS_TM_FUNNEL +0x73000102;CFDP_TM_FUNNEL +0x73000205;CFDP_HANDLER +0x73000206;CFDP_DISTRIBUTOR +0x73000207;CFDP_FAULT_HANDLER +0x73010000;EIVE_SYSTEM +0x73010001;ACS_SUBSYSTEM +0x73010002;PL_SUBSYSTEM +0x73010003;TCS_SUBSYSTEM +0x73010004;COM_SUBSYSTEM +0x73010005;EPS_SUBSYSTEM +0x73020001;MISC_TM_STORE +0x73020002;OK_TM_STORE +0x73020003;NOT_OK_TM_STORE +0x73020004;HK_TM_STORE +0x73030000;CFDP_TM_STORE +0x73040000;LIVE_TM_TASK +0x73040001;LOG_STORE_AND_TM_TASK +0x73040002;HK_STORE_AND_TM_TASK +0x73040003;CFDP_STORE_AND_TM_TASK +0x73040004;DOWNLINK_RAM_STORE +0x90000003;THERMAL_TEMP_INSERTER +0xFFFFFFFF;NO_OBJECT diff --git a/generators/bsp_q7s_returnvalues.csv b/generators/bsp_q7s_returnvalues.csv new file mode 100644 index 0000000..ec9a7fe --- /dev/null +++ b/generators/bsp_q7s_returnvalues.csv @@ -0,0 +1,633 @@ +Full ID (hex); Name; Description; Unique ID; Subsytem Name; File Path +0x0000;OK;System-wide code for ok.;0;HasReturnvaluesIF;fsfw/returnvalues/returnvalue.h +0x0001;Failed;Unspecified system-wide code for failed.;1;HasReturnvaluesIF;fsfw/returnvalues/returnvalue.h +0x0201;OM_InsertionFailed;No description;1;OBJECT_MANAGER_IF;fsfw/src/fsfw/objectmanager/ObjectManagerIF.h +0x0202;OM_NotFound;No description;2;OBJECT_MANAGER_IF;fsfw/src/fsfw/objectmanager/ObjectManagerIF.h +0x0203;OM_ChildInitFailed;No description;3;OBJECT_MANAGER_IF;fsfw/src/fsfw/objectmanager/ObjectManagerIF.h +0x0204;OM_InternalErrReporterUninit;No description;4;OBJECT_MANAGER_IF;fsfw/src/fsfw/objectmanager/ObjectManagerIF.h +0x03a0;DHB_InvalidChannel;No description;160;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03b0;DHB_AperiodicReply;No description;176;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03b1;DHB_IgnoreReplyData;No description;177;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03b2;DHB_IgnoreFullPacket;No description;178;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03c0;DHB_NothingToSend;No description;192;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03c2;DHB_CommandMapError;No description;194;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03d0;DHB_NoSwitch;No description;208;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03e0;DHB_ChildTimeout;No description;224;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x03e1;DHB_SwitchFailed;No description;225;DEVICE_HANDLER_BASE;fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h +0x0400;RMP_ReplyOk;No description;0;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0401;RMP_ReplyGeneralErrorCode;No description;1;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0402;RMP_ReplyUnusedPacketTypeOrCommandCode;No description;2;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0403;RMP_ReplyInvalidKey;No description;3;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0404;RMP_ReplyInvalidDataCrc;No description;4;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0405;RMP_ReplyEarlyEop;No description;5;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0406;RMP_ReplyTooMuchData;No description;6;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0407;RMP_ReplyEep;No description;7;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0408;RMP_ReplyReserved;No description;8;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0409;RMP_ReplyVerifyBufferOverrun;No description;9;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x040a;RMP_ReplyCommandNotImplementedOrNotAuthorised;No description;10;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x040b;RMP_ReplyRmwDataLengthError;No description;11;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x040c;RMP_ReplyInvalidTargetLogicalAddress;No description;12;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04c0;RMP_ReplyInterfaceBusy;No description;192;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04c1;RMP_ReplyTransmissionError;No description;193;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04c2;RMP_ReplyInvalidData;No description;194;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04c3;RMP_ReplyNotSupported;No description;195;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d0;RMP_ReplyNoReply;No description;208;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d1;RMP_ReplyNotSent;No description;209;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d2;RMP_ReplyNotYetSent;No description;210;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d3;RMP_ReplyMissmatch;No description;211;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04d4;RMP_ReplyTimeout;No description;212;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e1;RMP_CommandNoDescriptorsAvailable;No description;225;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e2;RMP_CommandBufferFull;No description;226;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e3;RMP_CommandChannelOutOfRange;No description;227;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e6;RMP_CommandChannelDeactivated;No description;230;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e7;RMP_CommandPortOutOfRange;No description;231;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e8;RMP_CommandPortInUse;No description;232;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04e9;RMP_CommandNoChannel;No description;233;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04ea;RMP_NoHwCrc;No description;234;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f0;RMP_LinkDown;No description;240;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f1;RMP_SpwCredit;No description;241;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f2;RMP_SpwEscape;No description;242;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f3;RMP_SpwDisconnect;No description;243;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f4;RMP_SpwParity;No description;244;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f5;RMP_SpwWriteSync;No description;245;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f6;RMP_SpwInvalidAddress;No description;246;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f7;RMP_SpwEarlyEop;No description;247;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f8;RMP_SpwDma;No description;248;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x04f9;RMP_SpwLinkError;No description;249;RMAP_CHANNEL;fsfw/src/fsfw/rmap/RMAP.h +0x0500;PS_SwitchOff;No description;0;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0501;PS_SwitchOn;No description;1;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0502;PS_SwitchTimeout;No description;2;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0503;PS_FuseOn;No description;3;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0504;PS_FuseOff;No description;4;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0505;PS_SwitchUnknown;No description;5;POWER_SWITCH_IF;fsfw/src/fsfw/power/PowerSwitchIF.h +0x0601;PP_DoItMyself;No description;1;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0602;PP_PointsToVariable;No description;2;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0603;PP_PointsToMemory;No description;3;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0604;PP_ActivityCompleted;No description;4;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0605;PP_PointsToVectorUint8;No description;5;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0606;PP_PointsToVectorUint16;No description;6;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0607;PP_PointsToVectorUint32;No description;7;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0608;PP_PointsToVectorFloat;No description;8;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06a0;PP_DumpNotSupported;No description;160;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e0;PP_InvalidSize;No description;224;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e1;PP_InvalidAddress;No description;225;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e2;PP_InvalidContent;No description;226;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e3;PP_UnalignedAccess;No description;227;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x06e4;PP_WriteProtected;No description;228;HAS_MEMORY_IF;fsfw/src/fsfw/memory/HasMemoryIF.h +0x0801;DPS_InvalidParameterDefinition;No description;1;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0802;DPS_SetWasAlreadyRead;No description;2;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0803;DPS_CommitingWithoutReading;No description;3;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0804;DPS_DataSetUninitialised;No description;4;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0805;DPS_DataSetFull;No description;5;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0806;DPS_PoolVarNull;No description;6;DATA_SET_CLASS;fsfw/src/fsfw/datapool/DataSetIF.h +0x0b01;SB_ChildNotFound;No description;1;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0b02;SB_ChildInfoUpdated;No description;2;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0b03;SB_ChildDoesntHaveModes;No description;3;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0b04;SB_CouldNotInsertChild;No description;4;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0b05;SB_TableContainsInvalidObjectId;No description;5;SUBSYSTEM_BASE;fsfw/src/fsfw/subsystem/SubsystemBase.h +0x0c02;MS_InvalidEntry;No description;2;MODE_STORE_IF;fsfw/src/fsfw/subsystem/modes/ModeStoreIF.h +0x0c03;MS_TooManyElements;No description;3;MODE_STORE_IF;fsfw/src/fsfw/subsystem/modes/ModeStoreIF.h +0x0c04;MS_CantStoreEmpty;No description;4;MODE_STORE_IF;fsfw/src/fsfw/subsystem/modes/ModeStoreIF.h +0x0d01;SS_SequenceAlreadyExists;No description;1;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d02;SS_TableAlreadyExists;No description;2;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d03;SS_TableDoesNotExist;No description;3;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d04;SS_TableOrSequenceLengthInvalid;No description;4;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d05;SS_SequenceDoesNotExist;No description;5;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d06;SS_TableContainsInvalidObjectId;No description;6;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d07;SS_FallbackSequenceDoesNotExist;No description;7;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d08;SS_NoTargetTable;No description;8;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d09;SS_SequenceOrTableTooLong;No description;9;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d0b;SS_IsFallbackSequence;No description;11;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d0c;SS_AccessDenied;No description;12;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0d0e;SS_TableInUse;No description;14;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0da1;SS_TargetTableNotReached;No description;161;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0da2;SS_TableCheckFailed;No description;162;SUBSYSTEM;fsfw/src/fsfw/subsystem/Subsystem.h +0x0e01;HM_InvalidMode;No description;1;HAS_MODES_IF;fsfw/src/fsfw/modes/HasModesIF.h +0x0e02;HM_TransNotAllowed;No description;2;HAS_MODES_IF;fsfw/src/fsfw/modes/HasModesIF.h +0x0e03;HM_InTransition;No description;3;HAS_MODES_IF;fsfw/src/fsfw/modes/HasModesIF.h +0x0e04;HM_InvalidSubmode;No description;4;HAS_MODES_IF;fsfw/src/fsfw/modes/HasModesIF.h +0x0f01;CM_UnknownCommand;No description;1;COMMAND_MESSAGE;fsfw/src/fsfw/ipc/CommandMessageIF.h +0x1000;TIM_UnsupportedTimeFormat;No description;0;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1001;TIM_NotEnoughInformationForTargetFormat;No description;1;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1002;TIM_LengthMismatch;No description;2;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1003;TIM_InvalidTimeFormat;No description;3;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1004;TIM_InvalidDayOfYear;No description;4;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1005;TIM_TimeDoesNotFitFormat;No description;5;CCSDS_TIME_HELPER_CLASS;fsfw/src/fsfw/timemanager/CCSDSTime.h +0x1201;AB_NeedSecondStep;No description;1;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x1202;AB_NeedToReconfigure;No description;2;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x1203;AB_ModeFallback;No description;3;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x1204;AB_ChildNotCommandable;No description;4;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x1205;AB_NeedToChangeHealth;No description;5;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x12a1;AB_NotEnoughChildrenInCorrectState;No description;161;ASSEMBLY_BASE;fsfw/src/fsfw/devicehandlers/AssemblyBase.h +0x13e0;MH_UnknownCmd;No description;224;MEMORY_HELPER;fsfw/src/fsfw/memory/MemoryHelper.h +0x13e1;MH_InvalidAddress;No description;225;MEMORY_HELPER;fsfw/src/fsfw/memory/MemoryHelper.h +0x13e2;MH_InvalidSize;No description;226;MEMORY_HELPER;fsfw/src/fsfw/memory/MemoryHelper.h +0x13e3;MH_StateMismatch;No description;227;MEMORY_HELPER;fsfw/src/fsfw/memory/MemoryHelper.h +0x1401;SE_BufferTooShort;No description;1;SERIALIZE_IF;fsfw/src/fsfw/serialize/SerializeIF.h +0x1402;SE_StreamTooShort;No description;2;SERIALIZE_IF;fsfw/src/fsfw/serialize/SerializeIF.h +0x1403;SE_TooManyElements;No description;3;SERIALIZE_IF;fsfw/src/fsfw/serialize/SerializeIF.h +0x1601;FMM_MapFull;No description;1;FIXED_MULTIMAP;fsfw/src/fsfw/container/FixedOrderedMultimap.h +0x1602;FMM_KeyDoesNotExist;No description;2;FIXED_MULTIMAP;fsfw/src/fsfw/container/FixedOrderedMultimap.h +0x1701;HHI_ObjectNotHealthy;No description;1;HAS_HEALTH_IF;fsfw/src/fsfw/health/HasHealthIF.h +0x1702;HHI_InvalidHealthState;No description;2;HAS_HEALTH_IF;fsfw/src/fsfw/health/HasHealthIF.h +0x1703;HHI_IsExternallyControlled;No description;3;HAS_HEALTH_IF;fsfw/src/fsfw/health/HasHealthIF.h +0x1801;FF_Full;No description;1;FIFO_CLASS;fsfw/src/fsfw/container/FIFOBase.h +0x1802;FF_Empty;No description;2;FIFO_CLASS;fsfw/src/fsfw/container/FIFOBase.h +0x1a01;TRC_NotEnoughSensors;No description;1;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1a02;TRC_LowestValueOol;No description;2;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1a03;TRC_HighestValueOol;No description;3;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1a04;TRC_BothValuesOol;No description;4;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1a05;TRC_DuplexOol;No description;5;TRIPLE_REDUNDACY_CHECK;fsfw/src/fsfw/monitoring/TriplexMonitor.h +0x1b00;TCC_NoDestinationFound;No description;0;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b01;TCC_InvalidCcsdsVersion;No description;1;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b02;TCC_InvalidApid;No description;2;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b03;TCC_InvalidPacketType;No description;3;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b04;TCC_InvalidSecHeaderField;No description;4;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b05;TCC_IncorrectPrimaryHeader;No description;5;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b07;TCC_IncompletePacket;No description;7;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b08;TCC_InvalidPusVersion;No description;8;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b09;TCC_IncorrectChecksum;No description;9;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b0a;TCC_IllegalPacketSubtype;No description;10;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1b0b;TCC_IncorrectSecondaryHeader;No description;11;TMTC_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/definitions.h +0x1c01;TCD_PacketLost;No description;1;PACKET_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/TcDistributorBase.h +0x1c02;TCD_DestinationNotFound;No description;2;PACKET_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/TcDistributorBase.h +0x1c03;TCD_ServiceIdAlreadyExists;No description;3;PACKET_DISTRIBUTION;fsfw/src/fsfw/tcdistribution/TcDistributorBase.h +0x1d01;ATC_ActivityStarted;No description;1;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1d02;ATC_InvalidSubservice;No description;2;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1d03;ATC_IllegalApplicationData;No description;3;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1d04;ATC_SendTmFailed;No description;4;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1d05;ATC_Timeout;No description;5;ACCEPTS_TELECOMMANDS_IF;fsfw/src/fsfw/tmtcservices/AcceptsTelecommandsIF.h +0x1e00;PUS_InvalidPusVersion;No description;0;PUS_IF;fsfw/src/fsfw/tmtcpacket/pus/PusIF.h +0x1e01;PUS_InvalidCrc16;No description;1;PUS_IF;fsfw/src/fsfw/tmtcpacket/pus/PusIF.h +0x2001;CSB_ExecutionComplete;No description;1;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2002;CSB_NoStepMessage;No description;2;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2003;CSB_ObjectBusy;No description;3;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2004;CSB_Busy;No description;4;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2005;CSB_InvalidTc;No description;5;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2006;CSB_InvalidObject;No description;6;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2007;CSB_InvalidReply;No description;7;COMMAND_SERVICE_BASE;fsfw/src/fsfw/tmtcservices/CommandingServiceBase.h +0x2101;TMB_Busy;No description;1;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2102;TMB_Full;No description;2;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2103;TMB_Empty;No description;3;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2104;TMB_NullRequested;No description;4;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2105;TMB_TooLarge;No description;5;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2106;TMB_NotReady;No description;6;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2107;TMB_DumpError;No description;7;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2108;TMB_CrcError;No description;8;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2109;TMB_Timeout;No description;9;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210a;TMB_IdlePacketFound;No description;10;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210b;TMB_TelecommandFound;No description;11;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210c;TMB_NoPusATm;No description;12;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210d;TMB_TooSmall;No description;13;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210e;TMB_BlockNotFound;No description;14;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x210f;TMB_InvalidRequest;No description;15;TM_STORE_BACKEND_IF;fsfw/src/fsfw/tmstorage/TmStoreBackendIF.h +0x2201;TMF_Busy;No description;1;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2202;TMF_LastPacketFound;No description;2;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2203;TMF_StopFetch;No description;3;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2204;TMF_Timeout;No description;4;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2205;TMF_TmChannelFull;No description;5;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2206;TMF_NotStored;No description;6;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2207;TMF_AllDeleted;No description;7;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2208;TMF_InvalidData;No description;8;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2209;TMF_NotReady;No description;9;TM_STORE_FRONTEND_IF;fsfw/src/fsfw/tmstorage/TmStoreFrontendIF.h +0x2401;MT_TooDetailedRequest;No description;1;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2402;MT_TooGeneralRequest;No description;2;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2403;MT_NoMatch;No description;3;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2404;MT_Full;No description;4;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2405;MT_NewNodeCreated;No description;5;MATCH_TREE_CLASS;fsfw/src/fsfw/globalfunctions/matching/MatchTree.h +0x2501;EV_ListenerNotFound;No description;1;EVENT_MANAGER_IF;fsfw/src/fsfw/events/EventManagerIF.h +0x2600;FDI_YourFault;No description;0;HANDLES_FAILURES_IF;fsfw/src/fsfw/fdir/ConfirmsFailuresIF.h +0x2601;FDI_MyFault;No description;1;HANDLES_FAILURES_IF;fsfw/src/fsfw/fdir/ConfirmsFailuresIF.h +0x2602;FDI_ConfirmLater;No description;2;HANDLES_FAILURES_IF;fsfw/src/fsfw/fdir/ConfirmsFailuresIF.h +0x27a0;DHI_NoCommandData;No description;160;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a1;DHI_CommandNotSupported;No description;161;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a2;DHI_CommandAlreadySent;No description;162;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a3;DHI_CommandWasNotSent;No description;163;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a4;DHI_CantSwitchAddress;No description;164;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a5;DHI_WrongModeForCommand;No description;165;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a6;DHI_Timeout;No description;166;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a7;DHI_Busy;No description;167;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a8;DHI_NoReplyExpected;No description;168;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27a9;DHI_NonOpTemperature;No description;169;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27aa;DHI_CommandNotImplemented;No description;170;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27ab;DHI_NonOpStateOfCharge;No description;171;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27b0;DHI_ChecksumError;No description;176;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27b1;DHI_LengthMissmatch;No description;177;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27b2;DHI_InvalidData;No description;178;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27b3;DHI_ProtocolError;No description;179;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27c0;DHI_DeviceDidNotExecute;No description;192;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27c1;DHI_DeviceReportedError;No description;193;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27c2;DHI_UnknownDeviceReply;No description;194;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27c3;DHI_DeviceReplyInvalid;No description;195;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27d0;DHI_InvalidCommandParameter;No description;208;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x27d1;DHI_InvalidNumberOrLengthOfParameters;No description;209;DEVICE_HANDLER_IF;fsfw/src/fsfw/devicehandlers/DeviceHandlerIF.h +0x2801;SM_DataTooLarge;No description;1;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2802;SM_DataStorageFull;No description;2;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2803;SM_IllegalStorageId;No description;3;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2804;SM_DataDoesNotExist;No description;4;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2805;SM_IllegalAddress;No description;5;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2806;SM_PoolTooLarge;No description;6;STORAGE_MANAGER_IF;fsfw/src/fsfw/storagemanager/StorageManagerIF.h +0x2901;TC_InvalidTargetState;No description;1;THERMAL_COMPONENT_IF;fsfw/src/fsfw/thermal/ThermalComponentIF.h +0x29f1;TC_AboveOperationalLimit;No description;241;THERMAL_COMPONENT_IF;fsfw/src/fsfw/thermal/ThermalComponentIF.h +0x29f2;TC_BelowOperationalLimit;No description;242;THERMAL_COMPONENT_IF;fsfw/src/fsfw/thermal/ThermalComponentIF.h +0x2a01;IEC_NoConfigurationTable;No description;1;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a02;IEC_NoCpuTable;No description;2;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a03;IEC_InvalidWorkspaceAddress;No description;3;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a04;IEC_TooLittleWorkspace;No description;4;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a05;IEC_WorkspaceAllocation;No description;5;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a06;IEC_InterruptStackTooSmall;No description;6;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a07;IEC_ThreadExitted;No description;7;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a08;IEC_InconsistentMpInformation;No description;8;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a09;IEC_InvalidNode;No description;9;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0a;IEC_NoMpci;No description;10;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0b;IEC_BadPacket;No description;11;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0c;IEC_OutOfPackets;No description;12;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0d;IEC_OutOfGlobalObjects;No description;13;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0e;IEC_OutOfProxies;No description;14;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a0f;IEC_InvalidGlobalId;No description;15;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a10;IEC_BadStackHook;No description;16;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a11;IEC_BadAttributes;No description;17;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a12;IEC_ImplementationKeyCreateInconsistency;No description;18;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a13;IEC_ImplementationBlockingOperationCancel;No description;19;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a14;IEC_MutexObtainFromBadState;No description;20;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2a15;IEC_UnlimitedAndMaximumIs0;No description;21;INTERNAL_ERROR_CODES;fsfw/src/fsfw/osal/InternalErrorCodes.h +0x2c01;CCS_BcIsSetVrCommand;No description;1;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2c02;CCS_BcIsUnlockCommand;No description;2;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cb0;CCS_BcIllegalCommand;No description;176;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cb1;CCS_BoardReadingNotFinished;No description;177;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cd0;CCS_IllegalFlagCombination;No description;208;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cd1;CCS_ShorterThanHeader;No description;209;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cd2;CCS_TooShortBlockedPacket;No description;210;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cd3;CCS_TooShortMapExtraction;No description;211;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce0;CCS_WrongSymbol;No description;224;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce1;CCS_DoubleStart;No description;225;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce2;CCS_StartSymbolMissed;No description;226;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce3;CCS_EndWithoutStart;No description;227;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce4;CCS_TooLarge;No description;228;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce5;CCS_TooShort;No description;229;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce6;CCS_WrongTfVersion;No description;230;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce7;CCS_WrongSpacecraftId;No description;231;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce8;CCS_NoValidFrameType;No description;232;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ce9;CCS_CrcFailed;No description;233;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cea;CCS_VcNotFound;No description;234;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ceb;CCS_ForwardingFailed;No description;235;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cec;CCS_ContentTooLarge;No description;236;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2ced;CCS_ResidualData;No description;237;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cee;CCS_DataCorrupted;No description;238;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cef;CCS_IllegalSegmentationFlag;No description;239;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf0;CCS_NsPositiveW;No description;240;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf1;CCS_NsNegativeW;No description;241;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf2;CCS_NsLockout;No description;242;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf3;CCS_FarmInLockout;No description;243;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2cf4;CCS_FarmInWait;No description;244;CCSDS_HANDLER_IF;fsfw/src/fsfw/datalinklayer/CCSDSReturnValuesIF.h +0x2d01;PAW_UnknownDatatype;No description;1;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d02;PAW_DatatypeMissmatch;No description;2;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d03;PAW_Readonly;No description;3;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d04;PAW_TooBig;No description;4;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d05;PAW_SourceNotSet;No description;5;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d06;PAW_OutOfBounds;No description;6;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d07;PAW_NotSet;No description;7;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2d08;PAW_ColumnOrRowsZero;No description;8;PARAMETER_WRAPPER;fsfw/src/fsfw/parameters/ParameterWrapper.h +0x2e01;HPA_InvalidIdentifierId;No description;1;HAS_PARAMETERS_IF;fsfw/src/fsfw/parameters/HasParametersIF.h +0x2e02;HPA_InvalidDomainId;No description;2;HAS_PARAMETERS_IF;fsfw/src/fsfw/parameters/HasParametersIF.h +0x2e03;HPA_InvalidValue;No description;3;HAS_PARAMETERS_IF;fsfw/src/fsfw/parameters/HasParametersIF.h +0x2e05;HPA_ReadOnly;No description;5;HAS_PARAMETERS_IF;fsfw/src/fsfw/parameters/HasParametersIF.h +0x2f01;ASC_TooLongForTargetType;No description;1;ASCII_CONVERTER;fsfw/src/fsfw/globalfunctions/AsciiConverter.h +0x2f02;ASC_InvalidCharacters;No description;2;ASCII_CONVERTER;fsfw/src/fsfw/globalfunctions/AsciiConverter.h +0x2f03;ASC_BufferTooSmall;No description;3;ASCII_CONVERTER;fsfw/src/fsfw/globalfunctions/AsciiConverter.h +0x3001;POS_InPowerTransition;No description;1;POWER_SWITCHER;fsfw/src/fsfw/power/PowerSwitcher.h +0x3002;POS_SwitchStateMismatch;No description;2;POWER_SWITCHER;fsfw/src/fsfw/power/PowerSwitcher.h +0x3101;LIM_Unchecked;No description;1;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3102;LIM_Invalid;No description;2;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3103;LIM_Unselected;No description;3;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3104;LIM_BelowLowLimit;No description;4;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3105;LIM_AboveHighLimit;No description;5;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3106;LIM_UnexpectedValue;No description;6;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3107;LIM_OutOfRange;No description;7;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31a0;LIM_FirstSample;No description;160;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31e0;LIM_InvalidSize;No description;224;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31e1;LIM_WrongType;No description;225;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31e2;LIM_WrongPid;No description;226;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31e3;LIM_WrongLimitId;No description;227;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x31ee;LIM_MonitorNotFound;No description;238;LIMITS_IF;fsfw/src/fsfw/monitoring/MonitoringIF.h +0x3201;CF_ObjectHasNoFunctions;No description;1;COMMANDS_ACTIONS_IF;fsfw/src/fsfw/action/CommandsActionsIF.h +0x3202;CF_AlreadyCommanding;No description;2;COMMANDS_ACTIONS_IF;fsfw/src/fsfw/action/CommandsActionsIF.h +0x3301;HF_IsBusy;No description;1;HAS_ACTIONS_IF;fsfw/src/fsfw/action/HasActionsIF.h +0x3302;HF_InvalidParameters;No description;2;HAS_ACTIONS_IF;fsfw/src/fsfw/action/HasActionsIF.h +0x3303;HF_ExecutionFinished;No description;3;HAS_ACTIONS_IF;fsfw/src/fsfw/action/HasActionsIF.h +0x3304;HF_InvalidActionId;No description;4;HAS_ACTIONS_IF;fsfw/src/fsfw/action/HasActionsIF.h +0x3401;DC_NoReplyReceived;No description;1;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3402;DC_ProtocolError;No description;2;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3403;DC_Nullpointer;No description;3;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3404;DC_InvalidCookieType;No description;4;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3405;DC_NotActive;No description;5;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3406;DC_TooMuchData;No description;6;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3407;DC_Busy;No description;7;DEVICE_COMMUNICATION_IF;fsfw/src/fsfw/devicehandlers/DeviceCommunicationIF.h +0x3601;CFDP_InvalidTlvType;No description;1;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3602;CFDP_InvalidDirectiveField;No description;2;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3603;CFDP_InvalidPduDatafieldLen;No description;3;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3604;CFDP_InvalidAckDirectiveFields;No description;4;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3605;CFDP_MetadataCantParseOptions;No description;5;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3606;CFDP_NakCantParseOptions;No description;6;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3607;CFDP_FinishedCantParseFsResponses;No description;7;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3608;CFDP_FilestoreRequiresSecondFile;No description;8;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3609;CFDP_FilestoreResponseCantParseFsMessage;No description;9;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x360a;CFDP_InvalidPduFormat;No description;10;CFDP_BASE;fsfw/src/fsfw/cfdp/definitions.h +0x3700;CFDP_SourceTransactionPending;No description;0;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3701;CFDP_FileDoesNotExist;No description;1;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3702;CFDP_FileSegmentLenInvalid;No description;2;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3703;CFDP_SourceNameEmpty;No description;3;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3704;CFDP_DestNameEmpty;No description;4;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3705;CFDP_WrongRemoteCfgEntityId;No description;5;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3706;CFDP_TargetMsgQueueFull;No description;6;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3707;CFDP_TmStoreFull;No description;7;CFDP_HANDLER;fsfw/src/fsfw/cfdp/handler/defs.h +0x3801;TSI_BadTimestamp;No description;1;TIME_STAMPER_IF;fsfw/src/fsfw/timemanager/TimeStampIF.h +0x39a1;SGP4_InvalidEccentricity;No description;161;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a2;SGP4_InvalidMeanMotion;No description;162;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a3;SGP4_InvalidPerturbationElements;No description;163;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a4;SGP4_InvalidSemiLatusRectum;No description;164;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a5;SGP4_InvalidEpochElements;No description;165;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39a6;SGP4_SatelliteHasDecayed;No description;166;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39b1;SGP4_TleTooOld;No description;177;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x39b2;SGP4_TleNotInitialized;No description;178;SGP4PROPAGATOR_CLASS;fsfw/src/fsfw/coordinates/Sgp4Propagator.h +0x3a01;MUX_NotEnoughResources;No description;1;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a02;MUX_InsufficientMemory;No description;2;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a03;MUX_NoPrivilege;No description;3;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a04;MUX_WrongAttributeSetting;No description;4;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a05;MUX_MutexAlreadyLocked;No description;5;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a06;MUX_MutexNotFound;No description;6;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a07;MUX_MutexMaxLocks;No description;7;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a08;MUX_CurrThreadAlreadyOwnsMutex;No description;8;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a09;MUX_CurrThreadDoesNotOwnMutex;No description;9;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a0a;MUX_MutexTimeout;No description;10;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a0b;MUX_MutexInvalidId;No description;11;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3a0c;MUX_MutexDestroyedWhileWaiting;No description;12;MUTEX_IF;fsfw/src/fsfw/ipc/MutexIF.h +0x3b01;MQI_Empty;No description;1;MESSAGE_QUEUE_IF;fsfw/src/fsfw/ipc/MessageQueueIF.h +0x3b02;MQI_Full;No space left for more messages;2;MESSAGE_QUEUE_IF;fsfw/src/fsfw/ipc/MessageQueueIF.h +0x3b03;MQI_NoReplyPartner;Returned if a reply method was called without partner;3;MESSAGE_QUEUE_IF;fsfw/src/fsfw/ipc/MessageQueueIF.h +0x3b04;MQI_DestinationInvalid;Returned if the target destination is invalid.;4;MESSAGE_QUEUE_IF;fsfw/src/fsfw/ipc/MessageQueueIF.h +0x3c01;SPH_SemaphoreTimeout;No description;1;SEMAPHORE_IF;fsfw/src/fsfw/tasks/SemaphoreIF.h +0x3c02;SPH_SemaphoreNotOwned;No description;2;SEMAPHORE_IF;fsfw/src/fsfw/tasks/SemaphoreIF.h +0x3c03;SPH_SemaphoreInvalid;No description;3;SEMAPHORE_IF;fsfw/src/fsfw/tasks/SemaphoreIF.h +0x3d00;LPIF_PoolEntryNotFound;No description;0;LOCAL_POOL_OWNER_IF;fsfw/src/fsfw/datapoollocal/localPoolDefinitions.h +0x3d01;LPIF_PoolEntryTypeConflict;No description;1;LOCAL_POOL_OWNER_IF;fsfw/src/fsfw/datapoollocal/localPoolDefinitions.h +0x3ea0;PVA_InvalidReadWriteMode;No description;160;POOL_VARIABLE_IF;fsfw/src/fsfw/datapool/PoolVariableIF.h +0x3ea1;PVA_InvalidPoolEntry;No description;161;POOL_VARIABLE_IF;fsfw/src/fsfw/datapool/PoolVariableIF.h +0x3f00;HKM_QueueOrDestinationInvalid;No description;0;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f01;HKM_WrongHkPacketType;No description;1;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f02;HKM_ReportingStatusUnchanged;No description;2;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f03;HKM_PeriodicHelperInvalid;No description;3;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f04;HKM_PoolobjectNotFound;No description;4;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x3f05;HKM_DatasetNotFound;No description;5;HOUSEKEEPING_MANAGER;fsfw/src/fsfw/datapoollocal/LocalDataPoolManager.h +0x4001;DLEE_StreamTooShort;No description;1;DLE_ENCODER;fsfw/src/fsfw/globalfunctions/DleEncoder.h +0x4002;DLEE_DecodingError;No description;2;DLE_ENCODER;fsfw/src/fsfw/globalfunctions/DleEncoder.h +0x4301;PUS11_InvalidTypeTimeWindow;No description;1;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4302;PUS11_InvalidTimeWindow;No description;2;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4303;PUS11_TimeshiftingNotPossible;No description;3;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4304;PUS11_InvalidRelativeTime;No description;4;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4305;PUS11_ContainedTcTooSmall;No description;5;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4306;PUS11_ContainedTcCrcMissmatch;No description;6;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4307;PUS11_MapIsFull;No description;7;PUS_SERVICE_11;fsfw/src/fsfw/pus/Service11TelecommandScheduling.h +0x4400;FILS_GenericFileError;No description;0;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4401;FILS_GenericDirError;No description;1;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4402;FILS_FilesystemInactive;No description;2;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4403;FILS_GenericRenameError;No description;3;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4404;FILS_IsBusy;No description;4;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4405;FILS_InvalidParameters;No description;5;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440a;FILS_FileDoesNotExist;No description;10;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440b;FILS_FileAlreadyExists;No description;11;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440c;FILS_NotAFile;No description;12;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440d;FILS_FileLocked;No description;13;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x440e;FILS_PermissionDenied;No description;14;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4415;FILS_DirectoryDoesNotExist;No description;21;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4416;FILS_DirectoryAlreadyExists;No description;22;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4417;FILS_NotADirectory;No description;23;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4418;FILS_DirectoryNotEmpty;No description;24;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x441e;FILS_SequencePacketMissingWrite;No description;30;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x441f;FILS_SequencePacketMissingRead;No description;31;FILE_SYSTEM;fsfw/src/fsfw/filesystem/HasFileSystemIF.h +0x4500;UXOS_ExecutionFinished;Execution of the current command has finished;0;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4501;UXOS_CommandPending;Command is pending. This will also be returned if the user tries to load another command but a command is still pending;1;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4502;UXOS_BytesRead;Some bytes have been read from the executing process;2;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4503;UXOS_CommandError;Command execution failed;3;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4504;UXOS_NoCommandLoadedOrPending;;4;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4506;UXOS_PcloseCallError;No description;6;LINUX_OSAL;fsfw/src/fsfw_hal/linux/CommandExecutor.h +0x4600;HSPI_OpeningFileFailed;No description;0;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4601;HSPI_FullDuplexTransferFailed;No description;1;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4602;HSPI_HalfDuplexTransferFailed;No description;2;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4603;HSPI_Timeout;No description;3;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4604;HSPI_Busy;No description;4;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4605;HSPI_GenericError;No description;5;HAL_SPI;fsfw/src/fsfw_hal/common/spi/spiCommon.h +0x4701;HURT_UartReadFailure;No description;1;HAL_UART;fsfw/src/fsfw_hal/linux/serial/SerialComIF.h +0x4702;HURT_UartReadSizeMissmatch;No description;2;HAL_UART;fsfw/src/fsfw_hal/linux/serial/SerialComIF.h +0x4703;HURT_UartRxBufferTooSmall;No description;3;HAL_UART;fsfw/src/fsfw_hal/linux/serial/SerialComIF.h +0x4901;HGIO_UnknownGpioId;No description;1;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4902;HGIO_DriveGpioFailure;No description;2;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4903;HGIO_GpioTypeFailure;No description;3;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4904;HGIO_GpioInvalidInstance;No description;4;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4905;HGIO_GpioDuplicateDetected;No description;5;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4906;HGIO_GpioInitFailed;No description;6;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4907;HGIO_GpioGetValueFailed;No description;7;HAL_GPIO;fsfw/src/fsfw_hal/linux/gpio/LinuxLibgpioIF.h +0x4d00;SPPA_NoPacketFound;No description;0;SPACE_PACKET_PARSER;fsfw/src/fsfw/tmtcservices/SpacePacketParser.h +0x4d01;SPPA_SplitPacket;No description;1;SPACE_PACKET_PARSER;fsfw/src/fsfw/tmtcservices/SpacePacketParser.h +0x50a1;HEATER_CommandNotSupported;No description;161;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x50a2;HEATER_InitFailed;No description;162;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x50a3;HEATER_InvalidSwitchNr;No description;163;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x50a4;HEATER_MainSwitchSetTimeout;No description;164;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x50a5;HEATER_CommandAlreadyWaiting;No description;165;HEATER_HANDLER;mission/tcs/HeaterHandler.h +0x51a0;SYRLINKS_CrcFailure;No description;160;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a1;SYRLINKS_UartFraminOrParityErrorAck;No description;161;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a2;SYRLINKS_BadCharacterAck;No description;162;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a3;SYRLINKS_BadParameterValueAck;No description;163;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a4;SYRLINKS_BadEndOfFrameAck;No description;164;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a5;SYRLINKS_UnknownCommandIdAck;No description;165;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a6;SYRLINKS_BadCrcAck;No description;166;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a7;SYRLINKS_ReplyWrongSize;No description;167;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x51a8;SYRLINKS_MissingStartFrameCharacter;No description;168;SYRLINKS_HANDLER;mission/com/SyrlinksHandler.h +0x5200;IMTQ_InvalidCommandCode;No description;0;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5201;IMTQ_MgmMeasurementLowLevelError;No description;1;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5202;IMTQ_ActuateCmdLowLevelError;No description;2;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5203;IMTQ_ParameterMissing;No description;3;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5204;IMTQ_ParameterInvalid;No description;4;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5205;IMTQ_CcUnavailable;No description;5;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5206;IMTQ_InternalProcessingError;No description;6;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5207;IMTQ_RejectedWithoutReason;No description;7;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5208;IMTQ_CmdErrUnknown;No description;8;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x5209;IMTQ_StartupCfgError;No description;9;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x520a;IMTQ_UnexpectedSelfTestReply;The status reply to a self test command was received but no self test command has been sent. This should normally never happen.;10;IMTQ_HANDLER;mission/acs/imtqHelpers.h +0x53a0;RWHA_InvalidSpeed;Action Message with invalid speed was received. Valid speeds must be in the range of [-65000, 1000] or [1000, 65000];160;RW_HANDLER;mission/acs/RwHandler.h +0x53a1;RWHA_InvalidRampTime;Action Message with invalid ramp time was received.;161;RW_HANDLER;mission/acs/RwHandler.h +0x53a2;RWHA_SetSpeedCommandInvalidLength;Received set speed command has invalid length. Should be 6.;162;RW_HANDLER;mission/acs/RwHandler.h +0x53a3;RWHA_ExecutionFailed;Command execution failed;163;RW_HANDLER;mission/acs/RwHandler.h +0x53a4;RWHA_CrcError;Reaction wheel reply has invalid crc;164;RW_HANDLER;mission/acs/RwHandler.h +0x53a5;RWHA_ValueNotRead;No description;165;RW_HANDLER;mission/acs/RwHandler.h +0x53b0;RWHA_SpiWriteFailure;No description;176;RW_HANDLER;mission/acs/rwHelpers.h +0x53b1;RWHA_SpiReadFailure;Used by the spi send function to tell a failing read call;177;RW_HANDLER;mission/acs/rwHelpers.h +0x53b2;RWHA_MissingStartSign;Can be used by the HDLC decoding mechanism to inform about a missing start sign 0x7E;178;RW_HANDLER;mission/acs/rwHelpers.h +0x53b3;RWHA_InvalidSubstitute;Can be used by the HDLC decoding mechanism to inform about an invalid substitution combination;179;RW_HANDLER;mission/acs/rwHelpers.h +0x53b4;RWHA_MissingEndSign;HDLC decoding mechanism never receives the end sign 0x7E;180;RW_HANDLER;mission/acs/rwHelpers.h +0x53b5;RWHA_NoReply;Reaction wheel only responds with empty frames.;181;RW_HANDLER;mission/acs/rwHelpers.h +0x53b6;RWHA_NoStartMarker;Expected a start marker as first byte;182;RW_HANDLER;mission/acs/rwHelpers.h +0x53b7;RWHA_SpiReadTimeout;Timeout when reading reply;183;RW_HANDLER;mission/acs/rwHelpers.h +0x54a0;STRH_TemperatureReqFailed;Status in temperature reply signals error;160;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a1;STRH_PingFailed;Ping command failed;161;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a2;STRH_VersionReqFailed;Status in version reply signals error;162;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a3;STRH_InterfaceReqFailed;Status in interface reply signals error;163;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a4;STRH_PowerReqFailed;Status in power reply signals error;164;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a5;STRH_SetParamFailed;Status of reply to parameter set command signals error;165;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a6;STRH_ActionFailed;Status of reply to action command signals error;166;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a7;STRH_FilePathTooLong;Received invalid path string. Exceeds allowed length;167;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a8;STRH_FilenameTooLong;Name of file received with command is too long;168;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54a9;STRH_InvalidProgram;Received version reply with invalid program ID;169;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54aa;STRH_ReplyError;Status field reply signals error;170;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54ab;STRH_CommandTooShort;Received command which is too short (some data is missing for proper execution);171;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54ac;STRH_InvalidLength;Received command with invalid length (too few or too many parameters);172;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54ad;STRH_RegionMismatch;Region mismatch between send and received data;173;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54ae;STRH_AddressMismatch;Address mismatch between send and received data;174;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54af;STRH_LengthMismatch;Length field mismatch between send and received data;175;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b0;STRH_FileNotExists;Specified file does not exist;176;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b1;STRH_InvalidType;Download blob pixel command has invalid type field;177;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b2;STRH_InvalidId;Received FPGA action command with invalid ID;178;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b3;STRH_ReplyTooShort;Received reply is too short;179;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b4;STRH_CrcFailure;Received reply with invalid CRC;180;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b5;STRH_StrHelperExecuting;Star tracker handler currently executing a command and using the communication interface;181;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b6;STRH_StartrackerAlreadyBooted;Star tracker is already in firmware mode;182;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b7;STRH_StartrackerNotRunningFirmware;Star tracker must be in firmware mode to run this command;183;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x54b8;STRH_StartrackerNotRunningBootloader;Star tracker must be in bootloader mode to run this command;184;STR_HANDLER;mission/acs/str/StarTrackerHandler.h +0x55e0;DWLPWRON_InvalidMode;Received command has invalid JESD mode (valid modes are 0 - 5);224;DWLPWRON_CMD;linux/payload/plocMpsocHelpers.h +0x55e1;DWLPWRON_InvalidLaneRate;Received command has invalid lane rate (valid lane rate are 0 - 9);225;DWLPWRON_CMD;linux/payload/plocMpsocHelpers.h +0x5800;PLSPVhLP_RequestDone;No description;0;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x5801;PLSPVhLP_NoPacketFound;No description;1;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x5802;PLSPVhLP_DecodeBufTooSmall;No description;2;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x5803;PLSPVhLP_PossiblePacketLossConsecutiveStart;No description;3;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x5804;PLSPVhLP_PossiblePacketLossConsecutiveEnd;No description;4;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x5805;PLSPVhLP_HdlcError;No description;5;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x58a0;PLSPVhLP_FileClosedAccidentally;File accidentally close;160;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x58a1;PLSPVhLP_ProcessTerminated;Process has been terminated by command;161;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x58a2;PLSPVhLP_PathNotExists;Received command with invalid pathname;162;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x58a3;PLSPVhLP_EventBufferReplyInvalidApid;Expected event buffer TM but received space packet with other APID;163;PLOC_SUPV_HELPER;linux/payload/PlocSupvUartMan.h +0x59a0;SUSS_ErrorUnlockMutex;No description;160;SUS_HANDLER;mission/acs/archive/LegacySusHandler.h +0x59a1;SUSS_ErrorLockMutex;No description;161;SUS_HANDLER;mission/acs/archive/LegacySusHandler.h +0x5aa0;IPCI_PapbBusy;No description;160;CCSDS_IP_CORE_BRIDGE;linux/ipcore/PapbVcInterface.h +0x5ba0;PTME_UnknownVcId;No description;160;PTME;linux/ipcore/Ptme.h +0x5d01;STRHLP_SdNotMounted;SD card specified in path string not mounted;1;STR_HELPER;linux/acs/StrComHandler.h +0x5d02;STRHLP_FileNotExists;Specified file does not exist on filesystem;2;STR_HELPER;linux/acs/StrComHandler.h +0x5d03;STRHLP_PathNotExists;Specified path does not exist;3;STR_HELPER;linux/acs/StrComHandler.h +0x5d04;STRHLP_FileCreationFailed;Failed to create download image or read flash file;4;STR_HELPER;linux/acs/StrComHandler.h +0x5d05;STRHLP_RegionMismatch;Region in flash write/read reply does not match expected region;5;STR_HELPER;linux/acs/StrComHandler.h +0x5d06;STRHLP_AddressMismatch;Address in flash write/read reply does not match expected address;6;STR_HELPER;linux/acs/StrComHandler.h +0x5d07;STRHLP_LengthMismatch;Length in flash write/read reply does not match expected length;7;STR_HELPER;linux/acs/StrComHandler.h +0x5d08;STRHLP_StatusError;Status field in reply signals error;8;STR_HELPER;linux/acs/StrComHandler.h +0x5d09;STRHLP_InvalidTypeId;Reply has invalid type ID (should be of action reply type);9;STR_HELPER;linux/acs/StrComHandler.h +0x5d0a;STRHLP_ReceptionTimeout;No description;10;STR_HELPER;linux/acs/StrComHandler.h +0x5d0b;STRHLP_DecodingError;No description;11;STR_HELPER;linux/acs/StrComHandler.h +0x5e00;GOMS_PacketTooLong;No description;0;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e01;GOMS_InvalidTableId;No description;1;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e02;GOMS_InvalidAddress;No description;2;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e03;GOMS_InvalidParamSize;No description;3;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e04;GOMS_InvalidPayloadSize;No description;4;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5e05;GOMS_UnknownReplyId;No description;5;GOM_SPACE_HANDLER;mission/power/GomspaceDeviceHandler.h +0x5fa0;PLMEMDUMP_MramAddressTooHigh;The capacity of the MRAM amounts to 512 kB. Thus the maximum address must not be higher than 0x7d000.;160;PLOC_MEMORY_DUMPER;linux/payload/PlocMemoryDumper.h +0x5fa1;PLMEMDUMP_MramInvalidAddressCombination;The specified end address is lower than the start address;161;PLOC_MEMORY_DUMPER;linux/payload/PlocMemoryDumper.h +0x60a0;PDEC_AbandonedCltuRetval;No description;160;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a1;PDEC_FrameDirtyRetval;No description;161;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a2;PDEC_FrameIllegalMultipleReasons;No description;162;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a3;PDEC_AdDiscardedLockoutRetval;No description;163;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a4;PDEC_AdDiscardedWaitRetval;No description;164;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a5;PDEC_AdDiscardedNsVs;No description;165;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a6;PDEC_NoReportRetval;No description;166;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a7;PDEC_ErrorVersionNumberRetval;No description;167;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a8;PDEC_IllegalCombinationRetval;No description;168;PDEC_HANDLER;linux/ipcore/pdec.h +0x60a9;PDEC_InvalidScIdRetval;No description;169;PDEC_HANDLER;linux/ipcore/pdec.h +0x60aa;PDEC_InvalidVcIdMsbRetval;No description;170;PDEC_HANDLER;linux/ipcore/pdec.h +0x60ab;PDEC_InvalidVcIdLsbRetval;No description;171;PDEC_HANDLER;linux/ipcore/pdec.h +0x60ac;PDEC_NsNotZeroRetval;No description;172;PDEC_HANDLER;linux/ipcore/pdec.h +0x60ae;PDEC_InvalidBcCc;No description;174;PDEC_HANDLER;linux/ipcore/pdec.h +0x60b0;PDEC_CommandNotImplemented;Received action message with unknown action id;176;PDEC_HANDLER;linux/ipcore/pdec.h +0x61a0;CCSDS_CommandNotImplemented;Received action message with unknown action id;160;CCSDS_HANDLER;mission/com/CcsdsIpCoreHandler.h +0x62a0;RS_RateNotSupported;The commanded rate is not supported by the current FPGA design;160;RATE_SETTER;linux/ipcore/PtmeConfig.h +0x62a1;RS_BadBitRate;Bad bitrate has been commanded (e.g. 0);161;RATE_SETTER;linux/ipcore/PtmeConfig.h +0x62a2;RS_ClkInversionFailed;Failed to invert clock and thus change the time the data is updated with respect to the tx clock;162;RATE_SETTER;linux/ipcore/PtmeConfig.h +0x62a3;RS_TxManipulatorConfigFailed;Failed to change configuration bit of tx clock manipulator;163;RATE_SETTER;linux/ipcore/PtmeConfig.h +0x6301;JSONBASE_JsonFileNotExists;Specified json file does not exist;1;ARCSEC_JSON_BASE;mission/acs/str/ArcsecJsonParamBase.h +0x6302;JSONBASE_SetNotExists;Requested set does not exist in json file;2;ARCSEC_JSON_BASE;mission/acs/str/ArcsecJsonParamBase.h +0x6303;JSONBASE_ParamNotExists;Requested parameter does not exist in json file;3;ARCSEC_JSON_BASE;mission/acs/str/ArcsecJsonParamBase.h +0x64a0;NVMB_KeyNotExists;Specified key does not exist in json file;160;NVM_PARAM_BASE;mission/memory/NvmParameterBase.h +0x65a0;FSHLP_SdNotMounted;SD card specified with path string not mounted;160;FILE_SYSTEM_HELPER;bsp_q7s/fs/FilesystemHelper.h +0x65a1;FSHLP_FileNotExists;Specified file does not exist on filesystem;161;FILE_SYSTEM_HELPER;bsp_q7s/fs/FilesystemHelper.h +0x66a0;PLMPHLP_FileWriteError;File error occured for file transfers from OBC to the MPSoC.;160;PLOC_MPSOC_HELPER;linux/payload/PlocMpsocSpecialComHelper.h +0x66a1;PLMPHLP_FileReadError;File error occured for file transfers from MPSoC to OBC.;161;PLOC_MPSOC_HELPER;linux/payload/PlocMpsocSpecialComHelper.h +0x67a0;SADPL_CommandNotSupported;No description;160;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x67a1;SADPL_DeploymentAlreadyExecuting;No description;161;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x67a2;SADPL_MainSwitchTimeoutFailure;No description;162;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x67a3;SADPL_SwitchingDeplSa1Failed;No description;163;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x67a4;SADPL_SwitchingDeplSa2Failed;No description;164;SA_DEPL_HANDLER;mission/SolarArrayDeploymentHandler.h +0x6810;MPSOCRTVIF_CommandTimeout;Command has timed out.;16;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a0;MPSOCRTVIF_CrcFailure;Space Packet received from PLOC has invalid CRC;160;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a1;MPSOCRTVIF_ReceivedAckFailure;Received ACK failure reply from PLOC;161;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a2;MPSOCRTVIF_ReceivedExeFailure;Received execution failure reply from PLOC;162;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a3;MPSOCRTVIF_InvalidApid;Received space packet with invalid APID from PLOC;163;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a4;MPSOCRTVIF_InvalidLength;Received command with invalid length;164;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a5;MPSOCRTVIF_FilenameTooLong;Filename of file in OBC filesystem is too long;165;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a6;MPSOCRTVIF_MpsocHelperExecuting;MPSoC helper is currently executing a command;166;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a7;MPSOCRTVIF_MpsocFilenameTooLong;Filename of MPSoC file is to long (max. 256 bytes);167;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a8;MPSOCRTVIF_InvalidParameter;Command has invalid parameter;168;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x68a9;MPSOCRTVIF_NameTooLong;Received command has file string with invalid length;169;MPSOC_RETURN_VALUES_IF;linux/payload/plocMpsocHelpers.h +0x69a0;SPVRTVIF_CrcFailure;Space Packet received from PLOC supervisor has invalid CRC;160;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a1;SPVRTVIF_InvalidServiceId;No description;161;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a2;SPVRTVIF_ReceivedAckFailure;Received ACK failure reply from PLOC supervisor;162;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a3;SPVRTVIF_ReceivedExeFailure;Received execution failure reply from PLOC supervisor;163;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a4;SPVRTVIF_InvalidApid;Received space packet with invalid APID from PLOC supervisor;164;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a5;SPVRTVIF_GetTimeFailure;Failed to read current system time;165;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a6;SPVRTVIF_InvalidWatchdog;Received command with invalid watchdog parameter. Valid watchdogs are 0 for PS, 1 for PL and 2 for INT;166;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a7;SPVRTVIF_InvalidWatchdogTimeout;Received watchdog timeout config command with invalid timeout. Valid timeouts must be in the range between 1000 and 360000 ms.;167;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a8;SPVRTVIF_InvalidLatchupId;Received latchup config command with invalid latchup ID;168;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69a9;SPVRTVIF_SweepPeriodTooSmall;Received set adc sweep period command with invalid sweep period. Must be larger than 21.;169;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69aa;SPVRTVIF_InvalidTestParam;Receive auto EM test command with invalid test param. Valid params are 1 and 2.;170;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69ab;SPVRTVIF_MramPacketParsingFailure;Returned when scanning for MRAM dump packets failed.;171;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69ac;SPVRTVIF_InvalidMramAddresses;Returned when the start and stop addresses of the MRAM dump or MRAM wipe commands are invalid (e.g. start address bigger than stop address);172;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69ad;SPVRTVIF_NoMramPacket;Expect reception of an MRAM dump packet but received space packet with other apid.;173;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69ae;SPVRTVIF_PathDoesNotExist;Path to PLOC directory on SD card does not exist;174;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69af;SPVRTVIF_MramFileNotExists;MRAM dump file does not exists. The file should actually already have been created with the reception of the first dump packet.;175;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69b0;SPVRTVIF_InvalidReplyLength;No description;176;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69b1;SPVRTVIF_InvalidLength;Received action command has invalid length;177;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69b2;SPVRTVIF_FilenameTooLong;Filename too long;178;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69b3;SPVRTVIF_UpdateStatusReportInvalidLength;Received update status report with invalid packet length field;179;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69b4;SPVRTVIF_UpdateCrcFailure;Update status report does not contain expected CRC. There might be a bit flip in the update memory region.;180;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69b5;SPVRTVIF_SupvHelperExecuting;Supervisor helper task ist currently executing a command (wait until helper tas has finished or interrupt by sending the terminate command);181;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69c0;SPVRTVIF_BufTooSmall;No description;192;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x69c1;SPVRTVIF_NoReplyTimeout;No description;193;SUPV_RETURN_VALUES_IF;linux/payload/plocSupvDefs.h +0x6aa0;ACSCTRL_FileDeletionFailed;File deletion failed and at least one file is still existent.;160;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6aa1;ACSCTRL_WriteFileFailed;Writing the TLE to the file has failed.;161;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6aa2;ACSCTRL_ReadFileFailed;Reading the TLE to the file has failed.;162;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6aa3;ACSCTRL_SingleRwUnavailable;A single RW has failed.;163;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6aa4;ACSCTRL_MultipleRwUnavailable;Multiple RWs have failed.;164;ACS_CTRL;mission/controller/controllerdefinitions/AcsCtrlDefinitions.h +0x6b02;ACSMEKF_MekfUninitialized;No description;2;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b03;ACSMEKF_MekfNoGyrData;No description;3;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b04;ACSMEKF_MekfNoModelVectors;No description;4;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b05;ACSMEKF_MekfNoSusMgmStrData;No description;5;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b06;ACSMEKF_MekfCovarianceInversionFailed;No description;6;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b07;ACSMEKF_MekfNotFinite;No description;7;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b08;ACSMEKF_MekfInitialized;No description;8;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6b09;ACSMEKF_MekfRunning;No description;9;ACS_MEKF;mission/controller/acs/MultiplicativeKalmanFilter.h +0x6c00;SDMA_OpOngoing;No description;0;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c01;SDMA_AlreadyOn;No description;1;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c02;SDMA_AlreadyMounted;No description;2;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c03;SDMA_AlreadyOff;No description;3;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c0a;SDMA_StatusFileNexists;No description;10;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c0b;SDMA_StatusFileFormatInvalid;No description;11;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c0c;SDMA_MountError;No description;12;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c0d;SDMA_UnmountError;No description;13;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c0e;SDMA_SystemCallError;No description;14;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6c0f;SDMA_PopenCallError;No description;15;SD_CARD_MANAGER;bsp_q7s/fs/SdCardManager.h +0x6d00;LPH_SdNotReady;No description;0;LOCAL_PARAM_HANDLER;bsp_q7s/memory/LocalParameterHandler.h +0x6e00;PTM_DumpDone;No description;0;PERSISTENT_TM_STORE;mission/tmtc/PersistentTmStore.h +0x6e01;PTM_BusyDumping;No description;1;PERSISTENT_TM_STORE;mission/tmtc/PersistentTmStore.h +0x6f00;TMS_IsBusy;No description;0;TM_SINK;mission/tmtc/DirectTmSinkIF.h +0x6f01;TMS_PartiallyWritten;No description;1;TM_SINK;mission/tmtc/DirectTmSinkIF.h +0x6f02;TMS_NoWriteActive;No description;2;TM_SINK;mission/tmtc/DirectTmSinkIF.h +0x6f03;TMS_Timeout;No description;3;TM_SINK;mission/tmtc/DirectTmSinkIF.h +0x7000;VCS_ChannelDoesNotExist;No description;0;VIRTUAL_CHANNEL;mission/com/VirtualChannel.h +0x7100;PLMPCOM_PacketReceived;No description;0;PLOC_MPSOC_COM;linux/payload/MpsocCommunication.h +0x7101;PLMPCOM_FaultyPacketSize;No description;1;PLOC_MPSOC_COM;linux/payload/MpsocCommunication.h +0x7102;PLMPCOM_CrcCheckFailed;No description;2;PLOC_MPSOC_COM;linux/payload/MpsocCommunication.h +0x7300;SCBU_KeyNotFound;No description;0;SCRATCH_BUFFER;bsp_q7s/memory/scratchApi.h diff --git a/generators/bsp_q7s_subsystems.csv b/generators/bsp_q7s_subsystems.csv new file mode 100644 index 0000000..36828fd --- /dev/null +++ b/generators/bsp_q7s_subsystems.csv @@ -0,0 +1,64 @@ +22;MEMORY +26;OBSW +28;CDH +59;TCS_1 +42;PCDU_1 +43;POWER_SWITCH_IF +50;HEATER +52;T_SENSORS +70;FDIR +71;FDIR_1 +72;FDIR_2 +73;HK +74;SYSTEM_MANAGER +75;SYSTEM_MANAGER_1 +76;TMTC_DISTRIBUTION +79;SYSTEM_1 +80;PUS_SERVICE_1 +82;PUS_SERVICE_2 +83;PUS_SERVICE_3 +85;PUS_SERVICE_5 +86;PUS_SERVICE_6 +88;PUS_SERVICE_8 +89;PUS_SERVICE_9 +91;PUS_SERVICE_11 +97;PUS_SERVICE_17 +103;PUS_SERVICE_23 +106;MGM_LIS3MDL +107;MGM_RM3100 +108;CFDP +112;ACS_SUBSYSTEM +113;PCDU_HANDLER +114;HEATER_HANDLER +115;SA_DEPL_HANDLER +116;PLOC_MPSOC_HANDLER +117;IMTQ_HANDLER +118;RW_HANDLER +119;STR_HANDLER +120;PLOC_SUPERVISOR_HANDLER +121;FILE_SYSTEM +122;PLOC_UPDATER +123;PLOC_MEMORY_DUMPER +124;PDEC_HANDLER +125;STR_HELPER +126;PLOC_MPSOC_HELPER +127;PL_PCDU_HANDLER +128;ACS_BOARD_ASS +129;SUS_BOARD_ASS +130;TCS_BOARD_ASS +131;GPS_HANDLER +132;P60_DOCK_HANDLER +133;PDU1_HANDLER +134;PDU2_HANDLER +135;ACU_HANDLER +136;PLOC_SUPV_HELPER +137;SYRLINKS +138;SCEX_HANDLER +139;CONFIGHANDLER +140;CORE +141;TCS_CONTROLLER +142;COM_SUBSYSTEM +143;PERSISTENT_TM_STORE +144;SYRLINKS_COM +145;SUS_HANDLER +146;CFDP_APP diff --git a/generators/definitions.py b/generators/definitions.py new file mode 100644 index 0000000..321d2f5 --- /dev/null +++ b/generators/definitions.py @@ -0,0 +1,23 @@ +import os +import enum +from pathlib import Path + + +def determine_obsw_root_path() -> str: + for _ in range(5): + if os.path.exists("CMakeLists.txt"): + return os.path.abspath(os.curdir) + else: + os.chdir("..") + + +PATH_VAR_ROOT = os.path.dirname(os.path.realpath(__file__)) +ROOT_DIR = PATH_VAR_ROOT +OBSW_ROOT_DIR = Path(determine_obsw_root_path()) +DATABASE_NAME = "eive_mod.db" + + +class BspType(enum.Enum): + BSP_Q7S = "bsp_q7s" + BSP_HOSTED = "bsp_hosted" + BSP_LINUX_BOARD = "bsp_linux_board" diff --git a/generators/deps/.gitignore b/generators/deps/.gitignore new file mode 100644 index 0000000..a031d04 --- /dev/null +++ b/generators/deps/.gitignore @@ -0,0 +1,3 @@ +/* +!/*.sh +!/.gitignore diff --git a/generators/deps/install_fsfwgen.sh b/generators/deps/install_fsfwgen.sh new file mode 100755 index 0000000..599c069 --- /dev/null +++ b/generators/deps/install_fsfwgen.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +git clone https://egit.irs.uni-stuttgart.de/fsfw/fsfwgen.git diff --git a/generators/events/__init__.py b/generators/events/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/generators/events/event_parser.py b/generators/events/event_parser.py new file mode 100644 index 0000000..f7463cf --- /dev/null +++ b/generators/events/event_parser.py @@ -0,0 +1,178 @@ +"""Part of the Mission Operation Database Exporter for the EVIE project. +Event exporter. +""" +import datetime +import logging +import time +import os +from pathlib import Path + +from fsfwgen.events.event_parser import ( + handle_csv_export, + handle_cpp_export, + SubsystemDefinitionParser, + EventParser, + EventDictT, +) +from fsfwgen.parserbase.file_list_parser import FileListParser +from fsfwgen.utility.printer import PrettyPrinter +from fsfwgen.utility.file_management import copy_file +from definitions import BspType, ROOT_DIR, OBSW_ROOT_DIR + +_LOGGER = logging.getLogger(__name__) +DATE_TODAY = datetime.datetime.now() +DATE_STRING_FULL = DATE_TODAY.strftime("%Y-%m-%d %H:%M:%S") + +PRINT_EVENTS = False +PRINT_SUBSYSTEM_TABLE = False +EXPORT_SUBSYSTEM_TABLE = True + +GENERATE_CPP = True +GENERATE_CPP_H = True +GENERATE_CSV = True +COPY_CPP_FILE = True +COPY_CPP_H_FILE = True +MOVE_CSV_FILE = True + +PARSE_HOST_BSP = True + +# Store these files relative to the events folder +CPP_FILENAME = Path( + f"{os.path.dirname(os.path.realpath(__file__))}/translateEvents.cpp" +) +CPP_H_FILENAME = Path( + f"{os.path.dirname(os.path.realpath(__file__))}/translateEvents.h" +) + +FILE_SEPARATOR = ";" + + +class BspConfig: + def __init__(self, bsp_select: BspType): + self.bsp_select = bsp_select + self.bsp_dir_name = self.bsp_select.value + + # Store this file in the root of the generators folder + self.csv_filename = Path(f"{ROOT_DIR}/{self.bsp_dir_name}_events.csv") + self.subsystems_csv_filename = Path( + f"{ROOT_DIR}/{self.bsp_dir_name}_subsystems.csv" + ) + self.csv_copy_dest = Path(f"{OBSW_ROOT_DIR}/tmtc/eive_tmtc/config/events.csv") + self.subsystem_csv_copy_dest = Path( + f"{OBSW_ROOT_DIR}/tmtc/eive_tmtc/config/subsystems.csv" + ) + + if ( + self.bsp_select == BspType.BSP_Q7S + or self.bsp_select == BspType.BSP_LINUX_BOARD + ): + self.fsfw_config_root = Path(f"{OBSW_ROOT_DIR}/linux/fsfwconfig") + + else: + self.fsfw_config_root = Path( + f"{OBSW_ROOT_DIR}/{self.bsp_dir_name}/fsfwconfig" + ) + + self.cpp_copy_dest = Path(f"{self.fsfw_config_root}/events/") + + self.subystem_defs_destinations = [ + f"{self.fsfw_config_root}/events/subsystemIdRanges.h", + f"{OBSW_ROOT_DIR}/fsfw/src/fsfw/events/fwSubsystemIdRanges.h", + f"{OBSW_ROOT_DIR}/common/config/eive/eventSubsystemIds.h", + ] + + self.header_defs_destinations = [ + f"{OBSW_ROOT_DIR}/mission/", + f"{OBSW_ROOT_DIR}/fsfw/", + f"{self.fsfw_config_root}", + f"{OBSW_ROOT_DIR}/test/", + f"{OBSW_ROOT_DIR}/bsp_q7s/", + f"{OBSW_ROOT_DIR}/linux/", + ] + + def subsystem_defs_as_paths(self): + return [Path(x) for x in self.subystem_defs_destinations] + + def header_defs_as_paths(self): + return [Path(x) for x in self.header_defs_destinations] + + +def parse_events( + bsp_type: BspType, + generate_csv: bool, + generate_cpp: bool, + copy_csv_to_eive_tmtc: bool, +): + bsp_cfg = BspConfig(bsp_type) + _LOGGER.info(f"EventParser: Parsing events for {bsp_type.name}") + # Small delay for clean printout + time.sleep(0.01) + event_list = generate_event_list(bsp_cfg, copy_csv_to_eive_tmtc) + if PRINT_EVENTS: + PrettyPrinter.pprint(event_list) + # Delay for clean printout + time.sleep(0.1) + if generate_csv: + handle_csv_export( + file_name=bsp_cfg.csv_filename, + event_list=event_list, + file_separator=FILE_SEPARATOR, + ) + if copy_csv_to_eive_tmtc: + _LOGGER.info(f"Copying CSV file to {bsp_cfg.cpp_copy_dest}") + copy_file( + filename=bsp_cfg.csv_filename, + destination=bsp_cfg.csv_copy_dest, + delete_existing_file=True, + ) + + if generate_cpp: + handle_cpp_export( + event_list=event_list, + date_string=DATE_STRING_FULL, + file_name=CPP_FILENAME, + generate_header=GENERATE_CPP_H, + header_file_name=CPP_H_FILENAME, + ) + if COPY_CPP_FILE: + _LOGGER.info( + f"EventParser: Copying CPP translation file to {bsp_cfg.cpp_copy_dest}" + ) + copy_file(CPP_FILENAME, bsp_cfg.cpp_copy_dest) + copy_file(CPP_H_FILENAME, bsp_cfg.cpp_copy_dest) + _LOGGER.info(f"Parsing done for {bsp_type.name}") + + +def generate_event_list(cfg: BspConfig, copy_csv_to_eive_tmtc: bool) -> EventDictT: + subsystem_parser = SubsystemDefinitionParser(cfg.subsystem_defs_as_paths()) + subsystem_table = subsystem_parser.parse_files() + _LOGGER.info(f"Found {len(subsystem_table)} subsystem definitions.") + if PRINT_SUBSYSTEM_TABLE: + PrettyPrinter.pprint(subsystem_table) + if EXPORT_SUBSYSTEM_TABLE: + PrettyPrinter.pprint(subsystem_table) + with open(cfg.subsystems_csv_filename, "w") as file: + for name_str, table_entry in subsystem_table.items(): + subsystem_id = int(table_entry[0]) + file.write(f"{subsystem_id}{FILE_SEPARATOR}{name_str}\n") + if copy_csv_to_eive_tmtc: + _LOGGER.info(f"Copying CSV file to {cfg.cpp_copy_dest}") + copy_file( + filename=cfg.subsystems_csv_filename, + destination=cfg.subsystem_csv_copy_dest, + delete_existing_file=True, + ) + + event_header_parser = FileListParser(cfg.header_defs_as_paths()) + event_headers = event_header_parser.parse_header_files( + True, "Parsing event header file list:\n", True + ) + # PrettyPrinter.pprint(event_headers) + # myEventList = parseHeaderFiles(subsystem_table, event_headers) + event_parser = EventParser(event_headers, subsystem_table) + event_parser.obsw_root_path = OBSW_ROOT_DIR + event_parser.set_moving_window_mode(moving_window_size=7) + event_table = event_parser.parse_files() + events_sorted = dict(sorted(event_table.items())) + _LOGGER.info(f"Found {len(events_sorted)} entries") + return events_sorted diff --git a/generators/events/translateEvents.cpp b/generators/events/translateEvents.cpp new file mode 100644 index 0000000..7a8da30 --- /dev/null +++ b/generators/events/translateEvents.cpp @@ -0,0 +1,990 @@ +/** + * @brief Auto-generated event translation file. Contains 325 translations. + * @details + * Generated on: 2024-05-06 13:47:38 + */ +#include "translateEvents.h" + +const char *STORE_SEND_WRITE_FAILED_STRING = "STORE_SEND_WRITE_FAILED"; +const char *STORE_WRITE_FAILED_STRING = "STORE_WRITE_FAILED"; +const char *STORE_SEND_READ_FAILED_STRING = "STORE_SEND_READ_FAILED"; +const char *STORE_READ_FAILED_STRING = "STORE_READ_FAILED"; +const char *UNEXPECTED_MSG_STRING = "UNEXPECTED_MSG"; +const char *STORING_FAILED_STRING = "STORING_FAILED"; +const char *TM_DUMP_FAILED_STRING = "TM_DUMP_FAILED"; +const char *STORE_INIT_FAILED_STRING = "STORE_INIT_FAILED"; +const char *STORE_INIT_EMPTY_STRING = "STORE_INIT_EMPTY"; +const char *STORE_CONTENT_CORRUPTED_STRING = "STORE_CONTENT_CORRUPTED"; +const char *STORE_INITIALIZE_STRING = "STORE_INITIALIZE"; +const char *INIT_DONE_STRING = "INIT_DONE"; +const char *DUMP_FINISHED_STRING = "DUMP_FINISHED"; +const char *DELETION_FINISHED_STRING = "DELETION_FINISHED"; +const char *DELETION_FAILED_STRING = "DELETION_FAILED"; +const char *AUTO_CATALOGS_SENDING_FAILED_STRING = "AUTO_CATALOGS_SENDING_FAILED"; +const char *GET_DATA_FAILED_STRING = "GET_DATA_FAILED"; +const char *STORE_DATA_FAILED_STRING = "STORE_DATA_FAILED"; +const char *DEVICE_BUILDING_COMMAND_FAILED_STRING = "DEVICE_BUILDING_COMMAND_FAILED"; +const char *DEVICE_SENDING_COMMAND_FAILED_STRING = "DEVICE_SENDING_COMMAND_FAILED"; +const char *DEVICE_REQUESTING_REPLY_FAILED_STRING = "DEVICE_REQUESTING_REPLY_FAILED"; +const char *DEVICE_READING_REPLY_FAILED_STRING = "DEVICE_READING_REPLY_FAILED"; +const char *DEVICE_INTERPRETING_REPLY_FAILED_STRING = "DEVICE_INTERPRETING_REPLY_FAILED"; +const char *DEVICE_MISSED_REPLY_STRING = "DEVICE_MISSED_REPLY"; +const char *DEVICE_UNKNOWN_REPLY_STRING = "DEVICE_UNKNOWN_REPLY"; +const char *DEVICE_UNREQUESTED_REPLY_STRING = "DEVICE_UNREQUESTED_REPLY"; +const char *INVALID_DEVICE_COMMAND_STRING = "INVALID_DEVICE_COMMAND"; +const char *MONITORING_LIMIT_EXCEEDED_STRING = "MONITORING_LIMIT_EXCEEDED"; +const char *MONITORING_AMBIGUOUS_STRING = "MONITORING_AMBIGUOUS"; +const char *DEVICE_WANTS_HARD_REBOOT_STRING = "DEVICE_WANTS_HARD_REBOOT"; +const char *SWITCH_WENT_OFF_STRING = "SWITCH_WENT_OFF"; +const char *FUSE_CURRENT_HIGH_STRING = "FUSE_CURRENT_HIGH"; +const char *FUSE_WENT_OFF_STRING = "FUSE_WENT_OFF"; +const char *POWER_ABOVE_HIGH_LIMIT_STRING = "POWER_ABOVE_HIGH_LIMIT"; +const char *POWER_BELOW_LOW_LIMIT_STRING = "POWER_BELOW_LOW_LIMIT"; +const char *HEATER_ON_STRING = "HEATER_ON"; +const char *HEATER_OFF_STRING = "HEATER_OFF"; +const char *HEATER_TIMEOUT_STRING = "HEATER_TIMEOUT"; +const char *HEATER_STAYED_ON_STRING = "HEATER_STAYED_ON"; +const char *HEATER_STAYED_OFF_STRING = "HEATER_STAYED_OFF"; +const char *TEMP_SENSOR_HIGH_STRING = "TEMP_SENSOR_HIGH"; +const char *TEMP_SENSOR_LOW_STRING = "TEMP_SENSOR_LOW"; +const char *TEMP_SENSOR_GRADIENT_STRING = "TEMP_SENSOR_GRADIENT"; +const char *COMPONENT_TEMP_LOW_STRING = "COMPONENT_TEMP_LOW"; +const char *COMPONENT_TEMP_HIGH_STRING = "COMPONENT_TEMP_HIGH"; +const char *COMPONENT_TEMP_OOL_LOW_STRING = "COMPONENT_TEMP_OOL_LOW"; +const char *COMPONENT_TEMP_OOL_HIGH_STRING = "COMPONENT_TEMP_OOL_HIGH"; +const char *TEMP_NOT_IN_OP_RANGE_STRING = "TEMP_NOT_IN_OP_RANGE"; +const char *FDIR_CHANGED_STATE_STRING = "FDIR_CHANGED_STATE"; +const char *FDIR_STARTS_RECOVERY_STRING = "FDIR_STARTS_RECOVERY"; +const char *FDIR_TURNS_OFF_DEVICE_STRING = "FDIR_TURNS_OFF_DEVICE"; +const char *MONITOR_CHANGED_STATE_STRING = "MONITOR_CHANGED_STATE"; +const char *VALUE_BELOW_LOW_LIMIT_STRING = "VALUE_BELOW_LOW_LIMIT"; +const char *VALUE_ABOVE_HIGH_LIMIT_STRING = "VALUE_ABOVE_HIGH_LIMIT"; +const char *VALUE_OUT_OF_RANGE_STRING = "VALUE_OUT_OF_RANGE"; +const char *CHANGING_MODE_STRING = "CHANGING_MODE"; +const char *MODE_INFO_STRING = "MODE_INFO"; +const char *FALLBACK_FAILED_STRING = "FALLBACK_FAILED"; +const char *MODE_TRANSITION_FAILED_STRING = "MODE_TRANSITION_FAILED"; +const char *CANT_KEEP_MODE_STRING = "CANT_KEEP_MODE"; +const char *OBJECT_IN_INVALID_MODE_STRING = "OBJECT_IN_INVALID_MODE"; +const char *FORCING_MODE_STRING = "FORCING_MODE"; +const char *MODE_CMD_REJECTED_STRING = "MODE_CMD_REJECTED"; +const char *HEALTH_INFO_STRING = "HEALTH_INFO"; +const char *CHILD_CHANGED_HEALTH_STRING = "CHILD_CHANGED_HEALTH"; +const char *CHILD_PROBLEMS_STRING = "CHILD_PROBLEMS"; +const char *OVERWRITING_HEALTH_STRING = "OVERWRITING_HEALTH"; +const char *TRYING_RECOVERY_STRING = "TRYING_RECOVERY"; +const char *RECOVERY_STEP_STRING = "RECOVERY_STEP"; +const char *RECOVERY_DONE_STRING = "RECOVERY_DONE"; +const char *HANDLE_PACKET_FAILED_STRING = "HANDLE_PACKET_FAILED"; +const char *RF_AVAILABLE_STRING = "RF_AVAILABLE"; +const char *RF_LOST_STRING = "RF_LOST"; +const char *BIT_LOCK_STRING = "BIT_LOCK"; +const char *BIT_LOCK_LOST_STRING = "BIT_LOCK_LOST"; +const char *FRAME_PROCESSING_FAILED_STRING = "FRAME_PROCESSING_FAILED"; +const char *CLOCK_SET_STRING = "CLOCK_SET"; +const char *CLOCK_DUMP_LEGACY_STRING = "CLOCK_DUMP_LEGACY"; +const char *CLOCK_SET_FAILURE_STRING = "CLOCK_SET_FAILURE"; +const char *CLOCK_DUMP_STRING = "CLOCK_DUMP"; +const char *CLOCK_DUMP_BEFORE_SETTING_TIME_STRING = "CLOCK_DUMP_BEFORE_SETTING_TIME"; +const char *CLOCK_DUMP_AFTER_SETTING_TIME_STRING = "CLOCK_DUMP_AFTER_SETTING_TIME"; +const char *TC_DELETION_FAILED_STRING = "TC_DELETION_FAILED"; +const char *TEST_STRING = "TEST"; +const char *CHANGE_OF_SETUP_PARAMETER_STRING = "CHANGE_OF_SETUP_PARAMETER"; +const char *STORE_ERROR_STRING = "STORE_ERROR"; +const char *MSG_QUEUE_ERROR_STRING = "MSG_QUEUE_ERROR"; +const char *SERIALIZATION_ERROR_STRING = "SERIALIZATION_ERROR"; +const char *FILESTORE_ERROR_STRING = "FILESTORE_ERROR"; +const char *FILENAME_TOO_LARGE_ERROR_STRING = "FILENAME_TOO_LARGE_ERROR"; +const char *HANDLING_CFDP_REQUEST_FAILED_STRING = "HANDLING_CFDP_REQUEST_FAILED"; +const char *SAFE_RATE_VIOLATION_STRING = "SAFE_RATE_VIOLATION"; +const char *RATE_RECOVERY_STRING = "RATE_RECOVERY"; +const char *MULTIPLE_RW_INVALID_STRING = "MULTIPLE_RW_INVALID"; +const char *MEKF_INVALID_INFO_STRING = "MEKF_INVALID_INFO"; +const char *MEKF_RECOVERY_STRING = "MEKF_RECOVERY"; +const char *MEKF_AUTOMATIC_RESET_STRING = "MEKF_AUTOMATIC_RESET"; +const char *PTG_CTRL_NO_ATTITUDE_INFORMATION_STRING = "PTG_CTRL_NO_ATTITUDE_INFORMATION"; +const char *SAFE_MODE_CONTROLLER_FAILURE_STRING = "SAFE_MODE_CONTROLLER_FAILURE"; +const char *TLE_TOO_OLD_STRING = "TLE_TOO_OLD"; +const char *TLE_FILE_READ_FAILED_STRING = "TLE_FILE_READ_FAILED"; +const char *PTG_RATE_VIOLATION_STRING = "PTG_RATE_VIOLATION"; +const char *DETUMBLE_TRANSITION_FAILED_STRING = "DETUMBLE_TRANSITION_FAILED"; +const char *SWITCH_CMD_SENT_STRING = "SWITCH_CMD_SENT"; +const char *SWITCH_HAS_CHANGED_STRING = "SWITCH_HAS_CHANGED"; +const char *SWITCHING_Q7S_DENIED_STRING = "SWITCHING_Q7S_DENIED"; +const char *FDIR_REACTION_IGNORED_STRING = "FDIR_REACTION_IGNORED"; +const char *DATASET_READ_FAILED_STRING = "DATASET_READ_FAILED"; +const char *VOLTAGE_OUT_OF_BOUNDS_STRING = "VOLTAGE_OUT_OF_BOUNDS"; +const char *TIMEDELTA_OUT_OF_BOUNDS_STRING = "TIMEDELTA_OUT_OF_BOUNDS"; +const char *POWER_LEVEL_LOW_STRING = "POWER_LEVEL_LOW"; +const char *POWER_LEVEL_CRITICAL_STRING = "POWER_LEVEL_CRITICAL"; +const char *GPIO_PULL_HIGH_FAILED_STRING = "GPIO_PULL_HIGH_FAILED"; +const char *GPIO_PULL_LOW_FAILED_STRING = "GPIO_PULL_LOW_FAILED"; +const char *HEATER_WENT_ON_STRING = "HEATER_WENT_ON"; +const char *HEATER_WENT_OFF_STRING = "HEATER_WENT_OFF"; +const char *SWITCH_ALREADY_ON_STRING = "SWITCH_ALREADY_ON"; +const char *SWITCH_ALREADY_OFF_STRING = "SWITCH_ALREADY_OFF"; +const char *MAIN_SWITCH_TIMEOUT_STRING = "MAIN_SWITCH_TIMEOUT"; +const char *FAULTY_HEATER_WAS_ON_STRING = "FAULTY_HEATER_WAS_ON"; +const char *BURN_PHASE_START_STRING = "BURN_PHASE_START"; +const char *BURN_PHASE_DONE_STRING = "BURN_PHASE_DONE"; +const char *MAIN_SWITCH_ON_TIMEOUT_STRING = "MAIN_SWITCH_ON_TIMEOUT"; +const char *MAIN_SWITCH_OFF_TIMEOUT_STRING = "MAIN_SWITCH_OFF_TIMEOUT"; +const char *DEPL_SA1_GPIO_SWTICH_ON_FAILED_STRING = "DEPL_SA1_GPIO_SWTICH_ON_FAILED"; +const char *DEPL_SA2_GPIO_SWTICH_ON_FAILED_STRING = "DEPL_SA2_GPIO_SWTICH_ON_FAILED"; +const char *DEPL_SA1_GPIO_SWTICH_OFF_FAILED_STRING = "DEPL_SA1_GPIO_SWTICH_OFF_FAILED"; +const char *DEPL_SA2_GPIO_SWTICH_OFF_FAILED_STRING = "DEPL_SA2_GPIO_SWTICH_OFF_FAILED"; +const char *AUTONOMOUS_DEPLOYMENT_COMPLETED_STRING = "AUTONOMOUS_DEPLOYMENT_COMPLETED"; +const char *MEMORY_READ_RPT_CRC_FAILURE_STRING = "MEMORY_READ_RPT_CRC_FAILURE"; +const char *ACK_FAILURE_STRING = "ACK_FAILURE"; +const char *EXE_FAILURE_STRING = "EXE_FAILURE"; +const char *MPSOC_HANDLER_CRC_FAILURE_STRING = "MPSOC_HANDLER_CRC_FAILURE"; +const char *MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH_STRING = "MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH"; +const char *MPSOC_SHUTDOWN_FAILED_STRING = "MPSOC_SHUTDOWN_FAILED"; +const char *SUPV_NOT_ON_STRING = "SUPV_NOT_ON"; +const char *SUPV_REPLY_TIMEOUT_STRING = "SUPV_REPLY_TIMEOUT"; +const char *CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE_STRING = "CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE"; +const char *SELF_TEST_I2C_FAILURE_STRING = "SELF_TEST_I2C_FAILURE"; +const char *SELF_TEST_SPI_FAILURE_STRING = "SELF_TEST_SPI_FAILURE"; +const char *SELF_TEST_ADC_FAILURE_STRING = "SELF_TEST_ADC_FAILURE"; +const char *SELF_TEST_PWM_FAILURE_STRING = "SELF_TEST_PWM_FAILURE"; +const char *SELF_TEST_TC_FAILURE_STRING = "SELF_TEST_TC_FAILURE"; +const char *SELF_TEST_MTM_RANGE_FAILURE_STRING = "SELF_TEST_MTM_RANGE_FAILURE"; +const char *SELF_TEST_COIL_CURRENT_FAILURE_STRING = "SELF_TEST_COIL_CURRENT_FAILURE"; +const char *INVALID_ERROR_BYTE_STRING = "INVALID_ERROR_BYTE"; +const char *ERROR_STATE_STRING = "ERROR_STATE"; +const char *RESET_OCCURED_STRING = "RESET_OCCURED"; +const char *BOOTING_FIRMWARE_FAILED_EVENT_STRING = "BOOTING_FIRMWARE_FAILED_EVENT"; +const char *BOOTING_BOOTLOADER_FAILED_EVENT_STRING = "BOOTING_BOOTLOADER_FAILED_EVENT"; +const char *COM_ERROR_REPLY_RECEIVED_STRING = "COM_ERROR_REPLY_RECEIVED"; +const char *SUPV_MEMORY_READ_RPT_CRC_FAILURE_STRING = "SUPV_MEMORY_READ_RPT_CRC_FAILURE"; +const char *SUPV_UNKNOWN_TM_STRING = "SUPV_UNKNOWN_TM"; +const char *SUPV_UNINIMPLEMENTED_TM_STRING = "SUPV_UNINIMPLEMENTED_TM"; +const char *SUPV_ACK_FAILURE_STRING = "SUPV_ACK_FAILURE"; +const char *SUPV_EXE_FAILURE_STRING = "SUPV_EXE_FAILURE"; +const char *SUPV_CRC_FAILURE_EVENT_STRING = "SUPV_CRC_FAILURE_EVENT"; +const char *SUPV_HELPER_EXECUTING_STRING = "SUPV_HELPER_EXECUTING"; +const char *SUPV_MPSOC_SHUTDOWN_BUILD_FAILED_STRING = "SUPV_MPSOC_SHUTDOWN_BUILD_FAILED"; +const char *SUPV_ACK_UNKNOWN_COMMAND_STRING = "SUPV_ACK_UNKNOWN_COMMAND"; +const char *SUPV_EXE_ACK_UNKNOWN_COMMAND_STRING = "SUPV_EXE_ACK_UNKNOWN_COMMAND"; +const char *SANITIZATION_FAILED_STRING = "SANITIZATION_FAILED"; +const char *MOUNTED_SD_CARD_STRING = "MOUNTED_SD_CARD"; +const char *SEND_MRAM_DUMP_FAILED_STRING = "SEND_MRAM_DUMP_FAILED"; +const char *MRAM_DUMP_FAILED_STRING = "MRAM_DUMP_FAILED"; +const char *MRAM_DUMP_FINISHED_STRING = "MRAM_DUMP_FINISHED"; +const char *INVALID_TC_FRAME_STRING = "INVALID_TC_FRAME"; +const char *INVALID_FAR_STRING = "INVALID_FAR"; +const char *CARRIER_LOCK_STRING = "CARRIER_LOCK"; +const char *BIT_LOCK_PDEC_STRING = "BIT_LOCK_PDEC"; +const char *LOST_CARRIER_LOCK_PDEC_STRING = "LOST_CARRIER_LOCK_PDEC"; +const char *LOST_BIT_LOCK_PDEC_STRING = "LOST_BIT_LOCK_PDEC"; +const char *TOO_MANY_IRQS_STRING = "TOO_MANY_IRQS"; +const char *POLL_SYSCALL_ERROR_PDEC_STRING = "POLL_SYSCALL_ERROR_PDEC"; +const char *WRITE_SYSCALL_ERROR_PDEC_STRING = "WRITE_SYSCALL_ERROR_PDEC"; +const char *PDEC_TRYING_RESET_WITH_INIT_STRING = "PDEC_TRYING_RESET_WITH_INIT"; +const char *PDEC_TRYING_RESET_NO_INIT_STRING = "PDEC_TRYING_RESET_NO_INIT"; +const char *PDEC_RESET_FAILED_STRING = "PDEC_RESET_FAILED"; +const char *OPEN_IRQ_FILE_FAILED_STRING = "OPEN_IRQ_FILE_FAILED"; +const char *PDEC_INIT_FAILED_STRING = "PDEC_INIT_FAILED"; +const char *PDEC_CONFIG_CORRUPTED_STRING = "PDEC_CONFIG_CORRUPTED"; +const char *IMAGE_UPLOAD_FAILED_STRING = "IMAGE_UPLOAD_FAILED"; +const char *IMAGE_DOWNLOAD_FAILED_STRING = "IMAGE_DOWNLOAD_FAILED"; +const char *IMAGE_UPLOAD_SUCCESSFUL_STRING = "IMAGE_UPLOAD_SUCCESSFUL"; +const char *IMAGE_DOWNLOAD_SUCCESSFUL_STRING = "IMAGE_DOWNLOAD_SUCCESSFUL"; +const char *FLASH_WRITE_SUCCESSFUL_STRING = "FLASH_WRITE_SUCCESSFUL"; +const char *FLASH_READ_SUCCESSFUL_STRING = "FLASH_READ_SUCCESSFUL"; +const char *FLASH_READ_FAILED_STRING = "FLASH_READ_FAILED"; +const char *FIRMWARE_UPDATE_SUCCESSFUL_STRING = "FIRMWARE_UPDATE_SUCCESSFUL"; +const char *FIRMWARE_UPDATE_FAILED_STRING = "FIRMWARE_UPDATE_FAILED"; +const char *STR_HELPER_READING_REPLY_FAILED_STRING = "STR_HELPER_READING_REPLY_FAILED"; +const char *STR_HELPER_COM_ERROR_STRING = "STR_HELPER_COM_ERROR"; +const char *STR_COM_REPLY_TIMEOUT_STRING = "STR_COM_REPLY_TIMEOUT"; +const char *STR_HELPER_DEC_ERROR_STRING = "STR_HELPER_DEC_ERROR"; +const char *POSITION_MISMATCH_STRING = "POSITION_MISMATCH"; +const char *STR_HELPER_FILE_NOT_EXISTS_STRING = "STR_HELPER_FILE_NOT_EXISTS"; +const char *STR_HELPER_SENDING_PACKET_FAILED_STRING = "STR_HELPER_SENDING_PACKET_FAILED"; +const char *STR_HELPER_REQUESTING_MSG_FAILED_STRING = "STR_HELPER_REQUESTING_MSG_FAILED"; +const char *MPSOC_FLASH_WRITE_FAILED_STRING = "MPSOC_FLASH_WRITE_FAILED"; +const char *MPSOC_FLASH_WRITE_SUCCESSFUL_STRING = "MPSOC_FLASH_WRITE_SUCCESSFUL"; +const char *MPSOC_SENDING_COMMAND_FAILED_STRING = "MPSOC_SENDING_COMMAND_FAILED"; +const char *MPSOC_HELPER_REQUESTING_REPLY_FAILED_STRING = "MPSOC_HELPER_REQUESTING_REPLY_FAILED"; +const char *MPSOC_HELPER_READING_REPLY_FAILED_STRING = "MPSOC_HELPER_READING_REPLY_FAILED"; +const char *MPSOC_MISSING_ACK_STRING = "MPSOC_MISSING_ACK"; +const char *MPSOC_MISSING_EXE_STRING = "MPSOC_MISSING_EXE"; +const char *MPSOC_ACK_FAILURE_REPORT_STRING = "MPSOC_ACK_FAILURE_REPORT"; +const char *MPSOC_EXE_FAILURE_REPORT_STRING = "MPSOC_EXE_FAILURE_REPORT"; +const char *MPSOC_ACK_INVALID_APID_STRING = "MPSOC_ACK_INVALID_APID"; +const char *MPSOC_EXE_INVALID_APID_STRING = "MPSOC_EXE_INVALID_APID"; +const char *MPSOC_HELPER_SEQ_CNT_MISMATCH_STRING = "MPSOC_HELPER_SEQ_CNT_MISMATCH"; +const char *MPSOC_TM_SIZE_ERROR_STRING = "MPSOC_TM_SIZE_ERROR"; +const char *MPSOC_TM_CRC_MISSMATCH_STRING = "MPSOC_TM_CRC_MISSMATCH"; +const char *MPSOC_FLASH_READ_PACKET_ERROR_STRING = "MPSOC_FLASH_READ_PACKET_ERROR"; +const char *MPSOC_FLASH_READ_FAILED_STRING = "MPSOC_FLASH_READ_FAILED"; +const char *MPSOC_FLASH_READ_SUCCESSFUL_STRING = "MPSOC_FLASH_READ_SUCCESSFUL"; +const char *MPSOC_READ_TIMEOUT_STRING = "MPSOC_READ_TIMEOUT"; +const char *TRANSITION_BACK_TO_OFF_STRING = "TRANSITION_BACK_TO_OFF"; +const char *NEG_V_OUT_OF_BOUNDS_STRING = "NEG_V_OUT_OF_BOUNDS"; +const char *U_DRO_OUT_OF_BOUNDS_STRING = "U_DRO_OUT_OF_BOUNDS"; +const char *I_DRO_OUT_OF_BOUNDS_STRING = "I_DRO_OUT_OF_BOUNDS"; +const char *U_X8_OUT_OF_BOUNDS_STRING = "U_X8_OUT_OF_BOUNDS"; +const char *I_X8_OUT_OF_BOUNDS_STRING = "I_X8_OUT_OF_BOUNDS"; +const char *U_TX_OUT_OF_BOUNDS_STRING = "U_TX_OUT_OF_BOUNDS"; +const char *I_TX_OUT_OF_BOUNDS_STRING = "I_TX_OUT_OF_BOUNDS"; +const char *U_MPA_OUT_OF_BOUNDS_STRING = "U_MPA_OUT_OF_BOUNDS"; +const char *I_MPA_OUT_OF_BOUNDS_STRING = "I_MPA_OUT_OF_BOUNDS"; +const char *U_HPA_OUT_OF_BOUNDS_STRING = "U_HPA_OUT_OF_BOUNDS"; +const char *I_HPA_OUT_OF_BOUNDS_STRING = "I_HPA_OUT_OF_BOUNDS"; +const char *TRANSITION_OTHER_SIDE_FAILED_STRING = "TRANSITION_OTHER_SIDE_FAILED"; +const char *NOT_ENOUGH_DEVICES_DUAL_MODE_STRING = "NOT_ENOUGH_DEVICES_DUAL_MODE"; +const char *POWER_STATE_MACHINE_TIMEOUT_STRING = "POWER_STATE_MACHINE_TIMEOUT"; +const char *SIDE_SWITCH_TRANSITION_NOT_ALLOWED_STRING = "SIDE_SWITCH_TRANSITION_NOT_ALLOWED"; +const char *DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY_STRING = "DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY"; +const char *TRANSITION_OTHER_SIDE_FAILED_12900_STRING = "TRANSITION_OTHER_SIDE_FAILED_12900"; +const char *NOT_ENOUGH_DEVICES_DUAL_MODE_12901_STRING = "NOT_ENOUGH_DEVICES_DUAL_MODE_12901"; +const char *POWER_STATE_MACHINE_TIMEOUT_12902_STRING = "POWER_STATE_MACHINE_TIMEOUT_12902"; +const char *SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903_STRING = "SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903"; +const char *CHILDREN_LOST_MODE_STRING = "CHILDREN_LOST_MODE"; +const char *GPS_FIX_CHANGE_STRING = "GPS_FIX_CHANGE"; +const char *CANT_GET_FIX_STRING = "CANT_GET_FIX"; +const char *RESET_FAIL_STRING = "RESET_FAIL"; +const char *P60_BOOT_COUNT_STRING = "P60_BOOT_COUNT"; +const char *BATT_MODE_STRING = "BATT_MODE"; +const char *BATT_MODE_CHANGED_STRING = "BATT_MODE_CHANGED"; +const char *SUPV_UPDATE_FAILED_STRING = "SUPV_UPDATE_FAILED"; +const char *SUPV_UPDATE_SUCCESSFUL_STRING = "SUPV_UPDATE_SUCCESSFUL"; +const char *SUPV_CONTINUE_UPDATE_FAILED_STRING = "SUPV_CONTINUE_UPDATE_FAILED"; +const char *SUPV_CONTINUE_UPDATE_SUCCESSFUL_STRING = "SUPV_CONTINUE_UPDATE_SUCCESSFUL"; +const char *TERMINATED_UPDATE_PROCEDURE_STRING = "TERMINATED_UPDATE_PROCEDURE"; +const char *SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL_STRING = "SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL"; +const char *SUPV_EVENT_BUFFER_REQUEST_FAILED_STRING = "SUPV_EVENT_BUFFER_REQUEST_FAILED"; +const char *SUPV_EVENT_BUFFER_REQUEST_TERMINATED_STRING = "SUPV_EVENT_BUFFER_REQUEST_TERMINATED"; +const char *SUPV_MEM_CHECK_OK_STRING = "SUPV_MEM_CHECK_OK"; +const char *SUPV_MEM_CHECK_FAIL_STRING = "SUPV_MEM_CHECK_FAIL"; +const char *SUPV_SENDING_COMMAND_FAILED_STRING = "SUPV_SENDING_COMMAND_FAILED"; +const char *SUPV_HELPER_REQUESTING_REPLY_FAILED_STRING = "SUPV_HELPER_REQUESTING_REPLY_FAILED"; +const char *SUPV_HELPER_READING_REPLY_FAILED_STRING = "SUPV_HELPER_READING_REPLY_FAILED"; +const char *SUPV_MISSING_ACK_STRING = "SUPV_MISSING_ACK"; +const char *SUPV_MISSING_EXE_STRING = "SUPV_MISSING_EXE"; +const char *SUPV_ACK_FAILURE_REPORT_STRING = "SUPV_ACK_FAILURE_REPORT"; +const char *SUPV_EXE_FAILURE_REPORT_STRING = "SUPV_EXE_FAILURE_REPORT"; +const char *SUPV_ACK_INVALID_APID_STRING = "SUPV_ACK_INVALID_APID"; +const char *SUPV_EXE_INVALID_APID_STRING = "SUPV_EXE_INVALID_APID"; +const char *ACK_RECEPTION_FAILURE_STRING = "ACK_RECEPTION_FAILURE"; +const char *EXE_RECEPTION_FAILURE_STRING = "EXE_RECEPTION_FAILURE"; +const char *WRITE_MEMORY_FAILED_STRING = "WRITE_MEMORY_FAILED"; +const char *SUPV_REPLY_SIZE_MISSMATCH_STRING = "SUPV_REPLY_SIZE_MISSMATCH"; +const char *SUPV_REPLY_CRC_MISSMATCH_STRING = "SUPV_REPLY_CRC_MISSMATCH"; +const char *SUPV_UPDATE_PROGRESS_STRING = "SUPV_UPDATE_PROGRESS"; +const char *HDLC_FRAME_REMOVAL_ERROR_STRING = "HDLC_FRAME_REMOVAL_ERROR"; +const char *HDLC_CRC_ERROR_STRING = "HDLC_CRC_ERROR"; +const char *TX_ON_STRING = "TX_ON"; +const char *TX_OFF_STRING = "TX_OFF"; +const char *MISSING_PACKET_STRING = "MISSING_PACKET"; +const char *EXPERIMENT_TIMEDOUT_STRING = "EXPERIMENT_TIMEDOUT"; +const char *MULTI_PACKET_COMMAND_DONE_STRING = "MULTI_PACKET_COMMAND_DONE"; +const char *FS_UNUSABLE_STRING = "FS_UNUSABLE"; +const char *SET_CONFIGFILEVALUE_FAILED_STRING = "SET_CONFIGFILEVALUE_FAILED"; +const char *GET_CONFIGFILEVALUE_FAILED_STRING = "GET_CONFIGFILEVALUE_FAILED"; +const char *INSERT_CONFIGFILEVALUE_FAILED_STRING = "INSERT_CONFIGFILEVALUE_FAILED"; +const char *WRITE_CONFIGFILE_FAILED_STRING = "WRITE_CONFIGFILE_FAILED"; +const char *READ_CONFIGFILE_FAILED_STRING = "READ_CONFIGFILE_FAILED"; +const char *ALLOC_FAILURE_STRING = "ALLOC_FAILURE"; +const char *REBOOT_SW_STRING = "REBOOT_SW"; +const char *REBOOT_MECHANISM_TRIGGERED_STRING = "REBOOT_MECHANISM_TRIGGERED"; +const char *REBOOT_HW_STRING = "REBOOT_HW"; +const char *NO_SD_CARD_ACTIVE_STRING = "NO_SD_CARD_ACTIVE"; +const char *VERSION_INFO_STRING = "VERSION_INFO"; +const char *CURRENT_IMAGE_INFO_STRING = "CURRENT_IMAGE_INFO"; +const char *REBOOT_COUNTER_STRING = "REBOOT_COUNTER"; +const char *INDIVIDUAL_BOOT_COUNTS_STRING = "INDIVIDUAL_BOOT_COUNTS"; +const char *TRYING_I2C_RECOVERY_STRING = "TRYING_I2C_RECOVERY"; +const char *I2C_REBOOT_STRING = "I2C_REBOOT"; +const char *PDEC_REBOOT_STRING = "PDEC_REBOOT"; +const char *FIRMWARE_INFO_STRING = "FIRMWARE_INFO"; +const char *ACTIVE_SD_INFO_STRING = "ACTIVE_SD_INFO"; +const char *NO_VALID_SENSOR_TEMPERATURE_STRING = "NO_VALID_SENSOR_TEMPERATURE"; +const char *NO_HEALTHY_HEATER_AVAILABLE_STRING = "NO_HEALTHY_HEATER_AVAILABLE"; +const char *SYRLINKS_OVERHEATING_STRING = "SYRLINKS_OVERHEATING"; +const char *OBC_OVERHEATING_STRING = "OBC_OVERHEATING"; +const char *CAMERA_OVERHEATING_STRING = "CAMERA_OVERHEATING"; +const char *PCDU_SYSTEM_OVERHEATING_STRING = "PCDU_SYSTEM_OVERHEATING"; +const char *HEATER_NOT_OFF_FOR_OFF_MODE_STRING = "HEATER_NOT_OFF_FOR_OFF_MODE"; +const char *MGT_OVERHEATING_STRING = "MGT_OVERHEATING"; +const char *TCS_SWITCHING_HEATER_ON_STRING = "TCS_SWITCHING_HEATER_ON"; +const char *TCS_SWITCHING_HEATER_OFF_STRING = "TCS_SWITCHING_HEATER_OFF"; +const char *TCS_HEATER_MAX_BURN_TIME_REACHED_STRING = "TCS_HEATER_MAX_BURN_TIME_REACHED"; +const char *TX_TIMER_EXPIRED_STRING = "TX_TIMER_EXPIRED"; +const char *BIT_LOCK_TX_ON_STRING = "BIT_LOCK_TX_ON"; +const char *POSSIBLE_FILE_CORRUPTION_STRING = "POSSIBLE_FILE_CORRUPTION"; +const char *FILE_TOO_LARGE_STRING = "FILE_TOO_LARGE"; +const char *BUSY_DUMPING_EVENT_STRING = "BUSY_DUMPING_EVENT"; +const char *DUMP_OK_STORE_DONE_STRING = "DUMP_OK_STORE_DONE"; +const char *DUMP_NOK_STORE_DONE_STRING = "DUMP_NOK_STORE_DONE"; +const char *DUMP_MISC_STORE_DONE_STRING = "DUMP_MISC_STORE_DONE"; +const char *DUMP_HK_STORE_DONE_STRING = "DUMP_HK_STORE_DONE"; +const char *DUMP_CFDP_STORE_DONE_STRING = "DUMP_CFDP_STORE_DONE"; +const char *DUMP_OK_CANCELLED_STRING = "DUMP_OK_CANCELLED"; +const char *DUMP_NOK_CANCELLED_STRING = "DUMP_NOK_CANCELLED"; +const char *DUMP_MISC_CANCELLED_STRING = "DUMP_MISC_CANCELLED"; +const char *DUMP_HK_CANCELLED_STRING = "DUMP_HK_CANCELLED"; +const char *DUMP_CFDP_CANCELLED_STRING = "DUMP_CFDP_CANCELLED"; +const char *TEMPERATURE_ALL_ONES_START_STRING = "TEMPERATURE_ALL_ONES_START"; +const char *TEMPERATURE_ALL_ONES_RECOVERY_STRING = "TEMPERATURE_ALL_ONES_RECOVERY"; +const char *FAULT_HANDLER_TRIGGERED_STRING = "FAULT_HANDLER_TRIGGERED"; + +const char *translateEvents(Event event) { + switch ((event & 0xFFFF)) { + case (2200): + return STORE_SEND_WRITE_FAILED_STRING; + case (2201): + return STORE_WRITE_FAILED_STRING; + case (2202): + return STORE_SEND_READ_FAILED_STRING; + case (2203): + return STORE_READ_FAILED_STRING; + case (2204): + return UNEXPECTED_MSG_STRING; + case (2205): + return STORING_FAILED_STRING; + case (2206): + return TM_DUMP_FAILED_STRING; + case (2207): + return STORE_INIT_FAILED_STRING; + case (2208): + return STORE_INIT_EMPTY_STRING; + case (2209): + return STORE_CONTENT_CORRUPTED_STRING; + case (2210): + return STORE_INITIALIZE_STRING; + case (2211): + return INIT_DONE_STRING; + case (2212): + return DUMP_FINISHED_STRING; + case (2213): + return DELETION_FINISHED_STRING; + case (2214): + return DELETION_FAILED_STRING; + case (2215): + return AUTO_CATALOGS_SENDING_FAILED_STRING; + case (2600): + return GET_DATA_FAILED_STRING; + case (2601): + return STORE_DATA_FAILED_STRING; + case (2800): + return DEVICE_BUILDING_COMMAND_FAILED_STRING; + case (2801): + return DEVICE_SENDING_COMMAND_FAILED_STRING; + case (2802): + return DEVICE_REQUESTING_REPLY_FAILED_STRING; + case (2803): + return DEVICE_READING_REPLY_FAILED_STRING; + case (2804): + return DEVICE_INTERPRETING_REPLY_FAILED_STRING; + case (2805): + return DEVICE_MISSED_REPLY_STRING; + case (2806): + return DEVICE_UNKNOWN_REPLY_STRING; + case (2807): + return DEVICE_UNREQUESTED_REPLY_STRING; + case (2808): + return INVALID_DEVICE_COMMAND_STRING; + case (2809): + return MONITORING_LIMIT_EXCEEDED_STRING; + case (2810): + return MONITORING_AMBIGUOUS_STRING; + case (2811): + return DEVICE_WANTS_HARD_REBOOT_STRING; + case (4300): + return SWITCH_WENT_OFF_STRING; + case (4301): + return FUSE_CURRENT_HIGH_STRING; + case (4302): + return FUSE_WENT_OFF_STRING; + case (4304): + return POWER_ABOVE_HIGH_LIMIT_STRING; + case (4305): + return POWER_BELOW_LOW_LIMIT_STRING; + case (5000): + return HEATER_ON_STRING; + case (5001): + return HEATER_OFF_STRING; + case (5002): + return HEATER_TIMEOUT_STRING; + case (5003): + return HEATER_STAYED_ON_STRING; + case (5004): + return HEATER_STAYED_OFF_STRING; + case (5200): + return TEMP_SENSOR_HIGH_STRING; + case (5201): + return TEMP_SENSOR_LOW_STRING; + case (5202): + return TEMP_SENSOR_GRADIENT_STRING; + case (5901): + return COMPONENT_TEMP_LOW_STRING; + case (5902): + return COMPONENT_TEMP_HIGH_STRING; + case (5903): + return COMPONENT_TEMP_OOL_LOW_STRING; + case (5904): + return COMPONENT_TEMP_OOL_HIGH_STRING; + case (5905): + return TEMP_NOT_IN_OP_RANGE_STRING; + case (7101): + return FDIR_CHANGED_STATE_STRING; + case (7102): + return FDIR_STARTS_RECOVERY_STRING; + case (7103): + return FDIR_TURNS_OFF_DEVICE_STRING; + case (7201): + return MONITOR_CHANGED_STATE_STRING; + case (7202): + return VALUE_BELOW_LOW_LIMIT_STRING; + case (7203): + return VALUE_ABOVE_HIGH_LIMIT_STRING; + case (7204): + return VALUE_OUT_OF_RANGE_STRING; + case (7400): + return CHANGING_MODE_STRING; + case (7401): + return MODE_INFO_STRING; + case (7402): + return FALLBACK_FAILED_STRING; + case (7403): + return MODE_TRANSITION_FAILED_STRING; + case (7404): + return CANT_KEEP_MODE_STRING; + case (7405): + return OBJECT_IN_INVALID_MODE_STRING; + case (7406): + return FORCING_MODE_STRING; + case (7407): + return MODE_CMD_REJECTED_STRING; + case (7506): + return HEALTH_INFO_STRING; + case (7507): + return CHILD_CHANGED_HEALTH_STRING; + case (7508): + return CHILD_PROBLEMS_STRING; + case (7509): + return OVERWRITING_HEALTH_STRING; + case (7510): + return TRYING_RECOVERY_STRING; + case (7511): + return RECOVERY_STEP_STRING; + case (7512): + return RECOVERY_DONE_STRING; + case (7600): + return HANDLE_PACKET_FAILED_STRING; + case (7900): + return RF_AVAILABLE_STRING; + case (7901): + return RF_LOST_STRING; + case (7902): + return BIT_LOCK_STRING; + case (7903): + return BIT_LOCK_LOST_STRING; + case (7905): + return FRAME_PROCESSING_FAILED_STRING; + case (8900): + return CLOCK_SET_STRING; + case (8901): + return CLOCK_DUMP_LEGACY_STRING; + case (8902): + return CLOCK_SET_FAILURE_STRING; + case (8903): + return CLOCK_DUMP_STRING; + case (8904): + return CLOCK_DUMP_BEFORE_SETTING_TIME_STRING; + case (8905): + return CLOCK_DUMP_AFTER_SETTING_TIME_STRING; + case (9100): + return TC_DELETION_FAILED_STRING; + case (9700): + return TEST_STRING; + case (10600): + return CHANGE_OF_SETUP_PARAMETER_STRING; + case (10800): + return STORE_ERROR_STRING; + case (10801): + return MSG_QUEUE_ERROR_STRING; + case (10802): + return SERIALIZATION_ERROR_STRING; + case (10803): + return FILESTORE_ERROR_STRING; + case (10804): + return FILENAME_TOO_LARGE_ERROR_STRING; + case (10805): + return HANDLING_CFDP_REQUEST_FAILED_STRING; + case (11200): + return SAFE_RATE_VIOLATION_STRING; + case (11201): + return RATE_RECOVERY_STRING; + case (11202): + return MULTIPLE_RW_INVALID_STRING; + case (11203): + return MEKF_INVALID_INFO_STRING; + case (11204): + return MEKF_RECOVERY_STRING; + case (11205): + return MEKF_AUTOMATIC_RESET_STRING; + case (11206): + return PTG_CTRL_NO_ATTITUDE_INFORMATION_STRING; + case (11207): + return SAFE_MODE_CONTROLLER_FAILURE_STRING; + case (11208): + return TLE_TOO_OLD_STRING; + case (11209): + return TLE_FILE_READ_FAILED_STRING; + case (11210): + return PTG_RATE_VIOLATION_STRING; + case (11211): + return DETUMBLE_TRANSITION_FAILED_STRING; + case (11300): + return SWITCH_CMD_SENT_STRING; + case (11301): + return SWITCH_HAS_CHANGED_STRING; + case (11302): + return SWITCHING_Q7S_DENIED_STRING; + case (11303): + return FDIR_REACTION_IGNORED_STRING; + case (11304): + return DATASET_READ_FAILED_STRING; + case (11305): + return VOLTAGE_OUT_OF_BOUNDS_STRING; + case (11306): + return TIMEDELTA_OUT_OF_BOUNDS_STRING; + case (11307): + return POWER_LEVEL_LOW_STRING; + case (11308): + return POWER_LEVEL_CRITICAL_STRING; + case (11400): + return GPIO_PULL_HIGH_FAILED_STRING; + case (11401): + return GPIO_PULL_LOW_FAILED_STRING; + case (11402): + return HEATER_WENT_ON_STRING; + case (11403): + return HEATER_WENT_OFF_STRING; + case (11404): + return SWITCH_ALREADY_ON_STRING; + case (11405): + return SWITCH_ALREADY_OFF_STRING; + case (11406): + return MAIN_SWITCH_TIMEOUT_STRING; + case (11407): + return FAULTY_HEATER_WAS_ON_STRING; + case (11500): + return BURN_PHASE_START_STRING; + case (11501): + return BURN_PHASE_DONE_STRING; + case (11502): + return MAIN_SWITCH_ON_TIMEOUT_STRING; + case (11503): + return MAIN_SWITCH_OFF_TIMEOUT_STRING; + case (11504): + return DEPL_SA1_GPIO_SWTICH_ON_FAILED_STRING; + case (11505): + return DEPL_SA2_GPIO_SWTICH_ON_FAILED_STRING; + case (11506): + return DEPL_SA1_GPIO_SWTICH_OFF_FAILED_STRING; + case (11507): + return DEPL_SA2_GPIO_SWTICH_OFF_FAILED_STRING; + case (11508): + return AUTONOMOUS_DEPLOYMENT_COMPLETED_STRING; + case (11601): + return MEMORY_READ_RPT_CRC_FAILURE_STRING; + case (11602): + return ACK_FAILURE_STRING; + case (11603): + return EXE_FAILURE_STRING; + case (11604): + return MPSOC_HANDLER_CRC_FAILURE_STRING; + case (11605): + return MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH_STRING; + case (11606): + return MPSOC_SHUTDOWN_FAILED_STRING; + case (11607): + return SUPV_NOT_ON_STRING; + case (11608): + return SUPV_REPLY_TIMEOUT_STRING; + case (11609): + return CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE_STRING; + case (11701): + return SELF_TEST_I2C_FAILURE_STRING; + case (11702): + return SELF_TEST_SPI_FAILURE_STRING; + case (11703): + return SELF_TEST_ADC_FAILURE_STRING; + case (11704): + return SELF_TEST_PWM_FAILURE_STRING; + case (11705): + return SELF_TEST_TC_FAILURE_STRING; + case (11706): + return SELF_TEST_MTM_RANGE_FAILURE_STRING; + case (11707): + return SELF_TEST_COIL_CURRENT_FAILURE_STRING; + case (11708): + return INVALID_ERROR_BYTE_STRING; + case (11801): + return ERROR_STATE_STRING; + case (11802): + return RESET_OCCURED_STRING; + case (11901): + return BOOTING_FIRMWARE_FAILED_EVENT_STRING; + case (11902): + return BOOTING_BOOTLOADER_FAILED_EVENT_STRING; + case (11903): + return COM_ERROR_REPLY_RECEIVED_STRING; + case (12001): + return SUPV_MEMORY_READ_RPT_CRC_FAILURE_STRING; + case (12002): + return SUPV_UNKNOWN_TM_STRING; + case (12003): + return SUPV_UNINIMPLEMENTED_TM_STRING; + case (12004): + return SUPV_ACK_FAILURE_STRING; + case (12005): + return SUPV_EXE_FAILURE_STRING; + case (12006): + return SUPV_CRC_FAILURE_EVENT_STRING; + case (12007): + return SUPV_HELPER_EXECUTING_STRING; + case (12008): + return SUPV_MPSOC_SHUTDOWN_BUILD_FAILED_STRING; + case (12009): + return SUPV_ACK_UNKNOWN_COMMAND_STRING; + case (12010): + return SUPV_EXE_ACK_UNKNOWN_COMMAND_STRING; + case (12100): + return SANITIZATION_FAILED_STRING; + case (12101): + return MOUNTED_SD_CARD_STRING; + case (12300): + return SEND_MRAM_DUMP_FAILED_STRING; + case (12301): + return MRAM_DUMP_FAILED_STRING; + case (12302): + return MRAM_DUMP_FINISHED_STRING; + case (12401): + return INVALID_TC_FRAME_STRING; + case (12402): + return INVALID_FAR_STRING; + case (12403): + return CARRIER_LOCK_STRING; + case (12404): + return BIT_LOCK_PDEC_STRING; + case (12405): + return LOST_CARRIER_LOCK_PDEC_STRING; + case (12406): + return LOST_BIT_LOCK_PDEC_STRING; + case (12407): + return TOO_MANY_IRQS_STRING; + case (12408): + return POLL_SYSCALL_ERROR_PDEC_STRING; + case (12409): + return WRITE_SYSCALL_ERROR_PDEC_STRING; + case (12410): + return PDEC_TRYING_RESET_WITH_INIT_STRING; + case (12411): + return PDEC_TRYING_RESET_NO_INIT_STRING; + case (12412): + return PDEC_RESET_FAILED_STRING; + case (12413): + return OPEN_IRQ_FILE_FAILED_STRING; + case (12414): + return PDEC_INIT_FAILED_STRING; + case (12415): + return PDEC_CONFIG_CORRUPTED_STRING; + case (12500): + return IMAGE_UPLOAD_FAILED_STRING; + case (12501): + return IMAGE_DOWNLOAD_FAILED_STRING; + case (12502): + return IMAGE_UPLOAD_SUCCESSFUL_STRING; + case (12503): + return IMAGE_DOWNLOAD_SUCCESSFUL_STRING; + case (12504): + return FLASH_WRITE_SUCCESSFUL_STRING; + case (12505): + return FLASH_READ_SUCCESSFUL_STRING; + case (12506): + return FLASH_READ_FAILED_STRING; + case (12507): + return FIRMWARE_UPDATE_SUCCESSFUL_STRING; + case (12508): + return FIRMWARE_UPDATE_FAILED_STRING; + case (12509): + return STR_HELPER_READING_REPLY_FAILED_STRING; + case (12510): + return STR_HELPER_COM_ERROR_STRING; + case (12511): + return STR_COM_REPLY_TIMEOUT_STRING; + case (12513): + return STR_HELPER_DEC_ERROR_STRING; + case (12514): + return POSITION_MISMATCH_STRING; + case (12515): + return STR_HELPER_FILE_NOT_EXISTS_STRING; + case (12516): + return STR_HELPER_SENDING_PACKET_FAILED_STRING; + case (12517): + return STR_HELPER_REQUESTING_MSG_FAILED_STRING; + case (12600): + return MPSOC_FLASH_WRITE_FAILED_STRING; + case (12601): + return MPSOC_FLASH_WRITE_SUCCESSFUL_STRING; + case (12602): + return MPSOC_SENDING_COMMAND_FAILED_STRING; + case (12603): + return MPSOC_HELPER_REQUESTING_REPLY_FAILED_STRING; + case (12604): + return MPSOC_HELPER_READING_REPLY_FAILED_STRING; + case (12605): + return MPSOC_MISSING_ACK_STRING; + case (12606): + return MPSOC_MISSING_EXE_STRING; + case (12607): + return MPSOC_ACK_FAILURE_REPORT_STRING; + case (12608): + return MPSOC_EXE_FAILURE_REPORT_STRING; + case (12609): + return MPSOC_ACK_INVALID_APID_STRING; + case (12610): + return MPSOC_EXE_INVALID_APID_STRING; + case (12611): + return MPSOC_HELPER_SEQ_CNT_MISMATCH_STRING; + case (12612): + return MPSOC_TM_SIZE_ERROR_STRING; + case (12613): + return MPSOC_TM_CRC_MISSMATCH_STRING; + case (12614): + return MPSOC_FLASH_READ_PACKET_ERROR_STRING; + case (12615): + return MPSOC_FLASH_READ_FAILED_STRING; + case (12616): + return MPSOC_FLASH_READ_SUCCESSFUL_STRING; + case (12617): + return MPSOC_READ_TIMEOUT_STRING; + case (12700): + return TRANSITION_BACK_TO_OFF_STRING; + case (12701): + return NEG_V_OUT_OF_BOUNDS_STRING; + case (12702): + return U_DRO_OUT_OF_BOUNDS_STRING; + case (12703): + return I_DRO_OUT_OF_BOUNDS_STRING; + case (12704): + return U_X8_OUT_OF_BOUNDS_STRING; + case (12705): + return I_X8_OUT_OF_BOUNDS_STRING; + case (12706): + return U_TX_OUT_OF_BOUNDS_STRING; + case (12707): + return I_TX_OUT_OF_BOUNDS_STRING; + case (12708): + return U_MPA_OUT_OF_BOUNDS_STRING; + case (12709): + return I_MPA_OUT_OF_BOUNDS_STRING; + case (12710): + return U_HPA_OUT_OF_BOUNDS_STRING; + case (12711): + return I_HPA_OUT_OF_BOUNDS_STRING; + case (12800): + return TRANSITION_OTHER_SIDE_FAILED_STRING; + case (12801): + return NOT_ENOUGH_DEVICES_DUAL_MODE_STRING; + case (12802): + return POWER_STATE_MACHINE_TIMEOUT_STRING; + case (12803): + return SIDE_SWITCH_TRANSITION_NOT_ALLOWED_STRING; + case (12804): + return DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY_STRING; + case (12900): + return TRANSITION_OTHER_SIDE_FAILED_12900_STRING; + case (12901): + return NOT_ENOUGH_DEVICES_DUAL_MODE_12901_STRING; + case (12902): + return POWER_STATE_MACHINE_TIMEOUT_12902_STRING; + case (12903): + return SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903_STRING; + case (13000): + return CHILDREN_LOST_MODE_STRING; + case (13100): + return GPS_FIX_CHANGE_STRING; + case (13101): + return CANT_GET_FIX_STRING; + case (13102): + return RESET_FAIL_STRING; + case (13200): + return P60_BOOT_COUNT_STRING; + case (13201): + return BATT_MODE_STRING; + case (13202): + return BATT_MODE_CHANGED_STRING; + case (13600): + return SUPV_UPDATE_FAILED_STRING; + case (13601): + return SUPV_UPDATE_SUCCESSFUL_STRING; + case (13602): + return SUPV_CONTINUE_UPDATE_FAILED_STRING; + case (13603): + return SUPV_CONTINUE_UPDATE_SUCCESSFUL_STRING; + case (13604): + return TERMINATED_UPDATE_PROCEDURE_STRING; + case (13605): + return SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL_STRING; + case (13606): + return SUPV_EVENT_BUFFER_REQUEST_FAILED_STRING; + case (13607): + return SUPV_EVENT_BUFFER_REQUEST_TERMINATED_STRING; + case (13608): + return SUPV_MEM_CHECK_OK_STRING; + case (13609): + return SUPV_MEM_CHECK_FAIL_STRING; + case (13616): + return SUPV_SENDING_COMMAND_FAILED_STRING; + case (13617): + return SUPV_HELPER_REQUESTING_REPLY_FAILED_STRING; + case (13618): + return SUPV_HELPER_READING_REPLY_FAILED_STRING; + case (13619): + return SUPV_MISSING_ACK_STRING; + case (13620): + return SUPV_MISSING_EXE_STRING; + case (13621): + return SUPV_ACK_FAILURE_REPORT_STRING; + case (13622): + return SUPV_EXE_FAILURE_REPORT_STRING; + case (13623): + return SUPV_ACK_INVALID_APID_STRING; + case (13624): + return SUPV_EXE_INVALID_APID_STRING; + case (13625): + return ACK_RECEPTION_FAILURE_STRING; + case (13626): + return EXE_RECEPTION_FAILURE_STRING; + case (13627): + return WRITE_MEMORY_FAILED_STRING; + case (13628): + return SUPV_REPLY_SIZE_MISSMATCH_STRING; + case (13629): + return SUPV_REPLY_CRC_MISSMATCH_STRING; + case (13630): + return SUPV_UPDATE_PROGRESS_STRING; + case (13631): + return HDLC_FRAME_REMOVAL_ERROR_STRING; + case (13632): + return HDLC_CRC_ERROR_STRING; + case (13701): + return TX_ON_STRING; + case (13702): + return TX_OFF_STRING; + case (13800): + return MISSING_PACKET_STRING; + case (13801): + return EXPERIMENT_TIMEDOUT_STRING; + case (13802): + return MULTI_PACKET_COMMAND_DONE_STRING; + case (13803): + return FS_UNUSABLE_STRING; + case (13901): + return SET_CONFIGFILEVALUE_FAILED_STRING; + case (13902): + return GET_CONFIGFILEVALUE_FAILED_STRING; + case (13903): + return INSERT_CONFIGFILEVALUE_FAILED_STRING; + case (13904): + return WRITE_CONFIGFILE_FAILED_STRING; + case (13905): + return READ_CONFIGFILE_FAILED_STRING; + case (14000): + return ALLOC_FAILURE_STRING; + case (14001): + return REBOOT_SW_STRING; + case (14002): + return REBOOT_MECHANISM_TRIGGERED_STRING; + case (14003): + return REBOOT_HW_STRING; + case (14004): + return NO_SD_CARD_ACTIVE_STRING; + case (14005): + return VERSION_INFO_STRING; + case (14006): + return CURRENT_IMAGE_INFO_STRING; + case (14007): + return REBOOT_COUNTER_STRING; + case (14008): + return INDIVIDUAL_BOOT_COUNTS_STRING; + case (14010): + return TRYING_I2C_RECOVERY_STRING; + case (14011): + return I2C_REBOOT_STRING; + case (14012): + return PDEC_REBOOT_STRING; + case (14013): + return FIRMWARE_INFO_STRING; + case (14014): + return ACTIVE_SD_INFO_STRING; + case (14100): + return NO_VALID_SENSOR_TEMPERATURE_STRING; + case (14101): + return NO_HEALTHY_HEATER_AVAILABLE_STRING; + case (14102): + return SYRLINKS_OVERHEATING_STRING; + case (14104): + return OBC_OVERHEATING_STRING; + case (14105): + return CAMERA_OVERHEATING_STRING; + case (14106): + return PCDU_SYSTEM_OVERHEATING_STRING; + case (14107): + return HEATER_NOT_OFF_FOR_OFF_MODE_STRING; + case (14108): + return MGT_OVERHEATING_STRING; + case (14109): + return TCS_SWITCHING_HEATER_ON_STRING; + case (14110): + return TCS_SWITCHING_HEATER_OFF_STRING; + case (14111): + return TCS_HEATER_MAX_BURN_TIME_REACHED_STRING; + case (14201): + return TX_TIMER_EXPIRED_STRING; + case (14202): + return BIT_LOCK_TX_ON_STRING; + case (14300): + return POSSIBLE_FILE_CORRUPTION_STRING; + case (14301): + return FILE_TOO_LARGE_STRING; + case (14302): + return BUSY_DUMPING_EVENT_STRING; + case (14305): + return DUMP_OK_STORE_DONE_STRING; + case (14306): + return DUMP_NOK_STORE_DONE_STRING; + case (14307): + return DUMP_MISC_STORE_DONE_STRING; + case (14308): + return DUMP_HK_STORE_DONE_STRING; + case (14309): + return DUMP_CFDP_STORE_DONE_STRING; + case (14310): + return DUMP_OK_CANCELLED_STRING; + case (14311): + return DUMP_NOK_CANCELLED_STRING; + case (14312): + return DUMP_MISC_CANCELLED_STRING; + case (14313): + return DUMP_HK_CANCELLED_STRING; + case (14314): + return DUMP_CFDP_CANCELLED_STRING; + case (14500): + return TEMPERATURE_ALL_ONES_START_STRING; + case (14501): + return TEMPERATURE_ALL_ONES_RECOVERY_STRING; + case (14600): + return FAULT_HANDLER_TRIGGERED_STRING; + default: + return "UNKNOWN_EVENT"; + } + return 0; +} diff --git a/generators/events/translateEvents.h b/generators/events/translateEvents.h new file mode 100644 index 0000000..9955431 --- /dev/null +++ b/generators/events/translateEvents.h @@ -0,0 +1,8 @@ +#ifndef FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ +#define FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ + +#include "fsfw/events/Event.h" + +const char *translateEvents(Event event); + +#endif /* FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ */ diff --git a/generators/gen.py b/generators/gen.py new file mode 100755 index 0000000..0557450 --- /dev/null +++ b/generators/gen.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import logging +import sys + +import colorlog +from colorlog import ColoredFormatter + +from definitions import BspType +from objects.objects import parse_objects +from events.event_parser import parse_events +from returnvalues.returnvalues_parser import parse_returnvalues +from fsfwgen.core import ( + return_generic_args_parser, + init_printout, +) + +_LOGGER = logging.getLogger() + + +def main(): + set_up_logger() + init_printout(project_string="EIVE") + parser = return_generic_args_parser() + args = parser.parse_args() + parse_handling_for_bsp(args.type, BspType.BSP_HOSTED) + parse_handling_for_bsp(args.type, BspType.BSP_Q7S) + + +def set_up_logger(): + handler = colorlog.StreamHandler(stream=sys.stdout) + formatter = ColoredFormatter( + "%(log_color)s%(levelname)-8s%(reset)s %(blue)s%(message)s", + datefmt=None, + reset=True, + log_colors={ + "DEBUG": "cyan", + "INFO": "green", + "WARNING": "yellow", + "ERROR": "red", + "CRITICAL": "red,bg_white", + }, + secondary_log_colors={}, + style="%", + ) + handler.setFormatter(formatter) + _LOGGER.addHandler(handler) + _LOGGER.setLevel(logging.INFO) + + +def parse_handling_for_bsp(type_arg: str, bsp_select: BspType): + if bsp_select == BspType.BSP_Q7S: + copy_to_eive_tmtc = True + else: + copy_to_eive_tmtc = False + if type_arg == "objects": + _LOGGER.info(f"Generating objects data") + parse_objects(bsp_select, copy_to_eive_tmtc) + elif type_arg == "events": + _LOGGER.info(f"Generating event data") + parse_events(bsp_select, True, True, copy_to_eive_tmtc) + elif type_arg == "returnvalues": + _LOGGER.info("Generating returnvalue data") + parse_returnvalues(bsp_select, copy_to_eive_tmtc) + elif type_arg == "all": + _LOGGER.info("Generating all data") + parse_objects(bsp_select, copy_to_eive_tmtc) + parse_events(bsp_select, True, True, copy_to_eive_tmtc) + parse_returnvalues(bsp_select, copy_to_eive_tmtc) + + +if __name__ == "__main__": + main() diff --git a/generators/objects/__init__.py b/generators/objects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/generators/objects/objects.py b/generators/objects/objects.py new file mode 100644 index 0000000..644aec7 --- /dev/null +++ b/generators/objects/objects.py @@ -0,0 +1,140 @@ +"""Part of the Mission Information Base Exporter for the SOURCE project by KSat. +Object exporter. +""" +import datetime +import logging +import os +from pathlib import Path + +from fsfwgen.objects.objects import ( + sql_object_exporter, + ObjectDefinitionParser, + write_translation_file, + export_object_file, + write_translation_header_file, +) +from fsfwgen.utility.printer import PrettyPrinter +from fsfwgen.utility.file_management import copy_file + +from definitions import BspType, DATABASE_NAME, OBSW_ROOT_DIR, ROOT_DIR + +_LOGGER = logging.getLogger(__name__) +DATE_TODAY = datetime.datetime.now() +DATE_STRING_FULL = DATE_TODAY.strftime("%Y-%m-%d %H:%M:%S") + +GENERATE_CSV = True +MOVE_CSV = True + +GENERATE_CPP = True +COPY_CPP = True + +GENERATE_HEADER = True +PRINT_OBJECTS = False + + +class BspConfig: + def __init__(self, bsp_select: BspType): + self.bsp_select = bsp_select + self.bsp_dir_name = bsp_select.value + if ( + self.bsp_select == BspType.BSP_Q7S + or self.bsp_select == BspType.BSP_LINUX_BOARD + ): + self.fsfw_config_root = f"{OBSW_ROOT_DIR}/linux/fsfwconfig" + else: + self.fsfw_config_root = f"{OBSW_ROOT_DIR}/{self.bsp_dir_name}/fsfwconfig" + self.cpp_copy_dest = Path(f"{self.fsfw_config_root}/objects/") + self.csv_obj_filename = f"{ROOT_DIR}/{self.bsp_dir_name}_objects.csv" + self.objects_path = Path(f"{self.fsfw_config_root}/objects/systemObjectList.h") + self.objects_defs = [ + self.objects_path, + FRAMEWORK_OBJECT_PATH, + COMMON_OBJECTS_PATH, + ] + + +EXPORT_TO_SQL = True + +CPP_FILENAME = f"{os.path.dirname(os.path.realpath(__file__))}//translateObjects.cpp" +CPP_H_FILENAME = f"{os.path.dirname(os.path.realpath(__file__))}//translateObjects.h" +CSV_COPY_DEST = f"{OBSW_ROOT_DIR}/tmtc/eive_tmtc/config/objects.csv" +FILE_SEPARATOR = ";" + + +FRAMEWORK_OBJECT_PATH = Path( + f"{OBSW_ROOT_DIR}/fsfw/src/fsfw/objectmanager/frameworkObjects.h" +) +COMMON_OBJECTS_PATH = Path(f"{OBSW_ROOT_DIR}/common/config/eive/objects.h") + +SQL_DELETE_OBJECTS_CMD = """ + DROP TABLE IF EXISTS Objects + """ + +SQL_CREATE_OBJECTS_CMD = """ + CREATE TABLE IF NOT EXISTS Objects( + id INTEGER PRIMARY KEY, + objectid TEXT, + name TEXT + ) + """ + +SQL_INSERT_INTO_OBJECTS_CMD = """ +INSERT INTO Objects(objectid, name) +VALUES(?,?) +""" + + +def parse_objects(bsp_select: BspType, copy_to_eive_tmtc: bool): + cfg = BspConfig(bsp_select) + # fetch objects + object_parser = ObjectDefinitionParser(cfg.objects_defs) + subsystem_definitions = object_parser.parse_files() + # id_subsystem_definitions.update(framework_subsystem_definitions) + list_items = sorted(subsystem_definitions.items()) + _LOGGER.info(f"ObjectParser: Number of objects: {len(list_items)}") + + if PRINT_OBJECTS: + PrettyPrinter.pprint(list_items) + + handle_file_export(cfg, list_items, copy_to_eive_tmtc) + if EXPORT_TO_SQL: + _LOGGER.info("ObjectParser: Exporting to SQL") + sql_object_exporter( + object_table=list_items, + delete_cmd=SQL_DELETE_OBJECTS_CMD, + insert_cmd=SQL_INSERT_INTO_OBJECTS_CMD, + create_cmd=SQL_CREATE_OBJECTS_CMD, + db_filename=f"{ROOT_DIR}/{DATABASE_NAME}", + ) + + +def handle_file_export(cfg: BspConfig, list_items, copy_to_eive_tmtc: bool): + if GENERATE_CPP: + _LOGGER.info("ObjectParser: Generating C++ translation file") + write_translation_file( + filename=CPP_FILENAME, + list_of_entries=list_items, + date_string_full=DATE_STRING_FULL, + ) + if COPY_CPP: + _LOGGER.info( + "ObjectParser: Copying object file to " + str(cfg.cpp_copy_dest) + ) + copy_file(Path(CPP_FILENAME), cfg.cpp_copy_dest) + if GENERATE_HEADER: + write_translation_header_file(filename=CPP_H_FILENAME) + copy_file(filename=Path(CPP_H_FILENAME), destination=cfg.cpp_copy_dest) + if GENERATE_CSV: + _LOGGER.info("ObjectParser: Generating text export") + export_object_file( + filename=cfg.csv_obj_filename, + object_list=list_items, + file_separator=FILE_SEPARATOR, + ) + if copy_to_eive_tmtc: + _LOGGER.info(f"ObjectParser: Copying CSV file to {CSV_COPY_DEST}") + copy_file( + filename=Path(cfg.csv_obj_filename), + destination=Path(CSV_COPY_DEST), + delete_existing_file=True, + ) diff --git a/generators/objects/translateObjects.cpp b/generators/objects/translateObjects.cpp new file mode 100644 index 0000000..048a77d --- /dev/null +++ b/generators/objects/translateObjects.cpp @@ -0,0 +1,556 @@ +/** + * @brief Auto-generated object translation file. + * @details + * Contains 180 translations. + * Generated on: 2024-05-06 13:47:38 + */ +#include "translateObjects.h" + +const char *P60DOCK_TEST_TASK_STRING = "P60DOCK_TEST_TASK"; +const char *ACS_CONTROLLER_STRING = "ACS_CONTROLLER"; +const char *CORE_CONTROLLER_STRING = "CORE_CONTROLLER"; +const char *POWER_CONTROLLER_STRING = "POWER_CONTROLLER"; +const char *GLOBAL_JSON_CFG_STRING = "GLOBAL_JSON_CFG"; +const char *XIPHOS_WDT_STRING = "XIPHOS_WDT"; +const char *THERMAL_CONTROLLER_STRING = "THERMAL_CONTROLLER"; +const char *MGM_0_LIS3_HANDLER_STRING = "MGM_0_LIS3_HANDLER"; +const char *GYRO_0_ADIS_HANDLER_STRING = "GYRO_0_ADIS_HANDLER"; +const char *SUS_0_N_LOC_XFYFZM_PT_XF_STRING = "SUS_0_N_LOC_XFYFZM_PT_XF"; +const char *SUS_1_N_LOC_XBYFZM_PT_XB_STRING = "SUS_1_N_LOC_XBYFZM_PT_XB"; +const char *SUS_2_N_LOC_XFYBZB_PT_YB_STRING = "SUS_2_N_LOC_XFYBZB_PT_YB"; +const char *SUS_3_N_LOC_XFYBZF_PT_YF_STRING = "SUS_3_N_LOC_XFYBZF_PT_YF"; +const char *SUS_4_N_LOC_XMYFZF_PT_ZF_STRING = "SUS_4_N_LOC_XMYFZF_PT_ZF"; +const char *SUS_5_N_LOC_XFYMZB_PT_ZB_STRING = "SUS_5_N_LOC_XFYMZB_PT_ZB"; +const char *SUS_6_R_LOC_XFYBZM_PT_XF_STRING = "SUS_6_R_LOC_XFYBZM_PT_XF"; +const char *SUS_7_R_LOC_XBYBZM_PT_XB_STRING = "SUS_7_R_LOC_XBYBZM_PT_XB"; +const char *SUS_8_R_LOC_XBYBZB_PT_YB_STRING = "SUS_8_R_LOC_XBYBZB_PT_YB"; +const char *SUS_9_R_LOC_XBYBZB_PT_YF_STRING = "SUS_9_R_LOC_XBYBZB_PT_YF"; +const char *SUS_10_N_LOC_XMYBZF_PT_ZF_STRING = "SUS_10_N_LOC_XMYBZF_PT_ZF"; +const char *SUS_11_R_LOC_XBYMZB_PT_ZB_STRING = "SUS_11_R_LOC_XBYMZB_PT_ZB"; +const char *RW1_STRING = "RW1"; +const char *MGM_1_RM3100_HANDLER_STRING = "MGM_1_RM3100_HANDLER"; +const char *GYRO_1_L3G_HANDLER_STRING = "GYRO_1_L3G_HANDLER"; +const char *RW2_STRING = "RW2"; +const char *MGM_2_LIS3_HANDLER_STRING = "MGM_2_LIS3_HANDLER"; +const char *GYRO_2_ADIS_HANDLER_STRING = "GYRO_2_ADIS_HANDLER"; +const char *RW3_STRING = "RW3"; +const char *MGM_3_RM3100_HANDLER_STRING = "MGM_3_RM3100_HANDLER"; +const char *GYRO_3_L3G_HANDLER_STRING = "GYRO_3_L3G_HANDLER"; +const char *RW4_STRING = "RW4"; +const char *STAR_TRACKER_STRING = "STAR_TRACKER"; +const char *GPS_CONTROLLER_STRING = "GPS_CONTROLLER"; +const char *GPS_0_HEALTH_DEV_STRING = "GPS_0_HEALTH_DEV"; +const char *GPS_1_HEALTH_DEV_STRING = "GPS_1_HEALTH_DEV"; +const char *IMTQ_POLLING_STRING = "IMTQ_POLLING"; +const char *IMTQ_HANDLER_STRING = "IMTQ_HANDLER"; +const char *PCDU_HANDLER_STRING = "PCDU_HANDLER"; +const char *P60DOCK_HANDLER_STRING = "P60DOCK_HANDLER"; +const char *PDU1_HANDLER_STRING = "PDU1_HANDLER"; +const char *PDU2_HANDLER_STRING = "PDU2_HANDLER"; +const char *ACU_HANDLER_STRING = "ACU_HANDLER"; +const char *BPX_BATT_HANDLER_STRING = "BPX_BATT_HANDLER"; +const char *PLPCDU_HANDLER_STRING = "PLPCDU_HANDLER"; +const char *RAD_SENSOR_STRING = "RAD_SENSOR"; +const char *PLOC_UPDATER_STRING = "PLOC_UPDATER"; +const char *PLOC_MEMORY_DUMPER_STRING = "PLOC_MEMORY_DUMPER"; +const char *STR_COM_IF_STRING = "STR_COM_IF"; +const char *PLOC_MPSOC_HELPER_STRING = "PLOC_MPSOC_HELPER"; +const char *AXI_PTME_CONFIG_STRING = "AXI_PTME_CONFIG"; +const char *PTME_CONFIG_STRING = "PTME_CONFIG"; +const char *PTME_VC0_LIVE_TM_STRING = "PTME_VC0_LIVE_TM"; +const char *PTME_VC1_LOG_TM_STRING = "PTME_VC1_LOG_TM"; +const char *PTME_VC2_HK_TM_STRING = "PTME_VC2_HK_TM"; +const char *PTME_VC3_CFDP_TM_STRING = "PTME_VC3_CFDP_TM"; +const char *PLOC_MPSOC_HANDLER_STRING = "PLOC_MPSOC_HANDLER"; +const char *PLOC_SUPERVISOR_HANDLER_STRING = "PLOC_SUPERVISOR_HANDLER"; +const char *PLOC_SUPERVISOR_HELPER_STRING = "PLOC_SUPERVISOR_HELPER"; +const char *PLOC_MPSOC_COMMUNICATION_STRING = "PLOC_MPSOC_COMMUNICATION"; +const char *SCEX_STRING = "SCEX"; +const char *SOLAR_ARRAY_DEPL_HANDLER_STRING = "SOLAR_ARRAY_DEPL_HANDLER"; +const char *HEATER_HANDLER_STRING = "HEATER_HANDLER"; +const char *TMP1075_HANDLER_TCS_0_STRING = "TMP1075_HANDLER_TCS_0"; +const char *TMP1075_HANDLER_TCS_1_STRING = "TMP1075_HANDLER_TCS_1"; +const char *TMP1075_HANDLER_PLPCDU_0_STRING = "TMP1075_HANDLER_PLPCDU_0"; +const char *TMP1075_HANDLER_PLPCDU_1_STRING = "TMP1075_HANDLER_PLPCDU_1"; +const char *TMP1075_HANDLER_IF_BOARD_STRING = "TMP1075_HANDLER_IF_BOARD"; +const char *RTD_0_IC3_PLOC_HEATSPREADER_STRING = "RTD_0_IC3_PLOC_HEATSPREADER"; +const char *RTD_1_IC4_PLOC_MISSIONBOARD_STRING = "RTD_1_IC4_PLOC_MISSIONBOARD"; +const char *RTD_2_IC5_4K_CAMERA_STRING = "RTD_2_IC5_4K_CAMERA"; +const char *RTD_3_IC6_DAC_HEATSPREADER_STRING = "RTD_3_IC6_DAC_HEATSPREADER"; +const char *RTD_4_IC7_STARTRACKER_STRING = "RTD_4_IC7_STARTRACKER"; +const char *RTD_5_IC8_RW1_MX_MY_STRING = "RTD_5_IC8_RW1_MX_MY"; +const char *RTD_6_IC9_DRO_STRING = "RTD_6_IC9_DRO"; +const char *RTD_7_IC10_SCEX_STRING = "RTD_7_IC10_SCEX"; +const char *RTD_8_IC11_X8_STRING = "RTD_8_IC11_X8"; +const char *RTD_9_IC12_HPA_STRING = "RTD_9_IC12_HPA"; +const char *RTD_10_IC13_PL_TX_STRING = "RTD_10_IC13_PL_TX"; +const char *RTD_11_IC14_MPA_STRING = "RTD_11_IC14_MPA"; +const char *RTD_12_IC15_ACU_STRING = "RTD_12_IC15_ACU"; +const char *RTD_13_IC16_PLPCDU_HEATSPREADER_STRING = "RTD_13_IC16_PLPCDU_HEATSPREADER"; +const char *RTD_14_IC17_TCS_BOARD_STRING = "RTD_14_IC17_TCS_BOARD"; +const char *RTD_15_IC18_IMTQ_STRING = "RTD_15_IC18_IMTQ"; +const char *SYRLINKS_HANDLER_STRING = "SYRLINKS_HANDLER"; +const char *SYRLINKS_COM_HANDLER_STRING = "SYRLINKS_COM_HANDLER"; +const char *ARDUINO_COM_IF_STRING = "ARDUINO_COM_IF"; +const char *GPIO_IF_STRING = "GPIO_IF"; +const char *SCEX_UART_READER_STRING = "SCEX_UART_READER"; +const char *SPI_MAIN_COM_IF_STRING = "SPI_MAIN_COM_IF"; +const char *UART_COM_IF_STRING = "UART_COM_IF"; +const char *I2C_COM_IF_STRING = "I2C_COM_IF"; +const char *CSP_COM_IF_STRING = "CSP_COM_IF"; +const char *ACS_BOARD_POLLING_TASK_STRING = "ACS_BOARD_POLLING_TASK"; +const char *RW_POLLING_TASK_STRING = "RW_POLLING_TASK"; +const char *SPI_RTD_COM_IF_STRING = "SPI_RTD_COM_IF"; +const char *SUS_POLLING_TASK_STRING = "SUS_POLLING_TASK"; +const char *CCSDS_PACKET_DISTRIBUTOR_STRING = "CCSDS_PACKET_DISTRIBUTOR"; +const char *PUS_PACKET_DISTRIBUTOR_STRING = "PUS_PACKET_DISTRIBUTOR"; +const char *TCP_TMTC_SERVER_STRING = "TCP_TMTC_SERVER"; +const char *UDP_TMTC_SERVER_STRING = "UDP_TMTC_SERVER"; +const char *TCP_TMTC_POLLING_TASK_STRING = "TCP_TMTC_POLLING_TASK"; +const char *UDP_TMTC_POLLING_TASK_STRING = "UDP_TMTC_POLLING_TASK"; +const char *FILE_SYSTEM_HANDLER_STRING = "FILE_SYSTEM_HANDLER"; +const char *SDC_MANAGER_STRING = "SDC_MANAGER"; +const char *PTME_STRING = "PTME"; +const char *PDEC_HANDLER_STRING = "PDEC_HANDLER"; +const char *CCSDS_HANDLER_STRING = "CCSDS_HANDLER"; +const char *PUS_SERVICE_6_STRING = "PUS_SERVICE_6"; +const char *FSFW_OBJECTS_START_STRING = "FSFW_OBJECTS_START"; +const char *PUS_SERVICE_1_VERIFICATION_STRING = "PUS_SERVICE_1_VERIFICATION"; +const char *PUS_SERVICE_2_DEVICE_ACCESS_STRING = "PUS_SERVICE_2_DEVICE_ACCESS"; +const char *PUS_SERVICE_3_HOUSEKEEPING_STRING = "PUS_SERVICE_3_HOUSEKEEPING"; +const char *PUS_SERVICE_5_EVENT_REPORTING_STRING = "PUS_SERVICE_5_EVENT_REPORTING"; +const char *PUS_SERVICE_8_FUNCTION_MGMT_STRING = "PUS_SERVICE_8_FUNCTION_MGMT"; +const char *PUS_SERVICE_9_TIME_MGMT_STRING = "PUS_SERVICE_9_TIME_MGMT"; +const char *PUS_SERVICE_11_TC_SCHEDULER_STRING = "PUS_SERVICE_11_TC_SCHEDULER"; +const char *PUS_SERVICE_15_TM_STORAGE_STRING = "PUS_SERVICE_15_TM_STORAGE"; +const char *PUS_SERVICE_17_TEST_STRING = "PUS_SERVICE_17_TEST"; +const char *PUS_SERVICE_20_PARAMETERS_STRING = "PUS_SERVICE_20_PARAMETERS"; +const char *PUS_SERVICE_200_MODE_MGMT_STRING = "PUS_SERVICE_200_MODE_MGMT"; +const char *PUS_SERVICE_201_HEALTH_STRING = "PUS_SERVICE_201_HEALTH"; +const char *CFDP_PACKET_DISTRIBUTOR_STRING = "CFDP_PACKET_DISTRIBUTOR"; +const char *HEALTH_TABLE_STRING = "HEALTH_TABLE"; +const char *MODE_STORE_STRING = "MODE_STORE"; +const char *EVENT_MANAGER_STRING = "EVENT_MANAGER"; +const char *INTERNAL_ERROR_REPORTER_STRING = "INTERNAL_ERROR_REPORTER"; +const char *TC_STORE_STRING = "TC_STORE"; +const char *TM_STORE_STRING = "TM_STORE"; +const char *IPC_STORE_STRING = "IPC_STORE"; +const char *TIME_STAMPER_STRING = "TIME_STAMPER"; +const char *VERIFICATION_REPORTER_STRING = "VERIFICATION_REPORTER"; +const char *FSFW_OBJECTS_END_STRING = "FSFW_OBJECTS_END"; +const char *SPI_TEST_STRING = "SPI_TEST"; +const char *UART_TEST_STRING = "UART_TEST"; +const char *I2C_TEST_STRING = "I2C_TEST"; +const char *DUMMY_COM_IF_STRING = "DUMMY_COM_IF"; +const char *DUMMY_HANDLER_STRING = "DUMMY_HANDLER"; +const char *DUMMY_INTERFACE_STRING = "DUMMY_INTERFACE"; +const char *LIBGPIOD_TEST_STRING = "LIBGPIOD_TEST"; +const char *TEST_TASK_STRING = "TEST_TASK"; +const char *HEATER_0_PLOC_PROC_BRD_STRING = "HEATER_0_PLOC_PROC_BRD"; +const char *HEATER_1_PCDU_BRD_STRING = "HEATER_1_PCDU_BRD"; +const char *HEATER_2_ACS_BRD_STRING = "HEATER_2_ACS_BRD"; +const char *HEATER_3_OBC_BRD_STRING = "HEATER_3_OBC_BRD"; +const char *HEATER_4_CAMERA_STRING = "HEATER_4_CAMERA"; +const char *HEATER_5_STR_STRING = "HEATER_5_STR"; +const char *HEATER_6_DRO_STRING = "HEATER_6_DRO"; +const char *HEATER_7_SYRLINKS_STRING = "HEATER_7_SYRLINKS"; +const char *ACS_BOARD_ASS_STRING = "ACS_BOARD_ASS"; +const char *SUS_BOARD_ASS_STRING = "SUS_BOARD_ASS"; +const char *TCS_BOARD_ASS_STRING = "TCS_BOARD_ASS"; +const char *RW_ASSY_STRING = "RW_ASSY"; +const char *CAM_SWITCHER_STRING = "CAM_SWITCHER"; +const char *SYRLINKS_ASSY_STRING = "SYRLINKS_ASSY"; +const char *IMTQ_ASSY_STRING = "IMTQ_ASSY"; +const char *STR_ASSY_STRING = "STR_ASSY"; +const char *TM_FUNNEL_STRING = "TM_FUNNEL"; +const char *PUS_TM_FUNNEL_STRING = "PUS_TM_FUNNEL"; +const char *CFDP_TM_FUNNEL_STRING = "CFDP_TM_FUNNEL"; +const char *CFDP_HANDLER_STRING = "CFDP_HANDLER"; +const char *CFDP_DISTRIBUTOR_STRING = "CFDP_DISTRIBUTOR"; +const char *CFDP_FAULT_HANDLER_STRING = "CFDP_FAULT_HANDLER"; +const char *EIVE_SYSTEM_STRING = "EIVE_SYSTEM"; +const char *ACS_SUBSYSTEM_STRING = "ACS_SUBSYSTEM"; +const char *PL_SUBSYSTEM_STRING = "PL_SUBSYSTEM"; +const char *TCS_SUBSYSTEM_STRING = "TCS_SUBSYSTEM"; +const char *COM_SUBSYSTEM_STRING = "COM_SUBSYSTEM"; +const char *EPS_SUBSYSTEM_STRING = "EPS_SUBSYSTEM"; +const char *MISC_TM_STORE_STRING = "MISC_TM_STORE"; +const char *OK_TM_STORE_STRING = "OK_TM_STORE"; +const char *NOT_OK_TM_STORE_STRING = "NOT_OK_TM_STORE"; +const char *HK_TM_STORE_STRING = "HK_TM_STORE"; +const char *CFDP_TM_STORE_STRING = "CFDP_TM_STORE"; +const char *LIVE_TM_TASK_STRING = "LIVE_TM_TASK"; +const char *LOG_STORE_AND_TM_TASK_STRING = "LOG_STORE_AND_TM_TASK"; +const char *HK_STORE_AND_TM_TASK_STRING = "HK_STORE_AND_TM_TASK"; +const char *CFDP_STORE_AND_TM_TASK_STRING = "CFDP_STORE_AND_TM_TASK"; +const char *DOWNLINK_RAM_STORE_STRING = "DOWNLINK_RAM_STORE"; +const char *THERMAL_TEMP_INSERTER_STRING = "THERMAL_TEMP_INSERTER"; +const char *NO_OBJECT_STRING = "NO_OBJECT"; + +const char *translateObject(object_id_t object) { + switch ((object & 0xFFFFFFFF)) { + case 0x00005060: + return P60DOCK_TEST_TASK_STRING; + case 0x43000002: + return ACS_CONTROLLER_STRING; + case 0x43000003: + return CORE_CONTROLLER_STRING; + case 0x43000004: + return POWER_CONTROLLER_STRING; + case 0x43000006: + return GLOBAL_JSON_CFG_STRING; + case 0x43000007: + return XIPHOS_WDT_STRING; + case 0x43400001: + return THERMAL_CONTROLLER_STRING; + case 0x44120006: + return MGM_0_LIS3_HANDLER_STRING; + case 0x44120010: + return GYRO_0_ADIS_HANDLER_STRING; + case 0x44120032: + return SUS_0_N_LOC_XFYFZM_PT_XF_STRING; + case 0x44120033: + return SUS_1_N_LOC_XBYFZM_PT_XB_STRING; + case 0x44120034: + return SUS_2_N_LOC_XFYBZB_PT_YB_STRING; + case 0x44120035: + return SUS_3_N_LOC_XFYBZF_PT_YF_STRING; + case 0x44120036: + return SUS_4_N_LOC_XMYFZF_PT_ZF_STRING; + case 0x44120037: + return SUS_5_N_LOC_XFYMZB_PT_ZB_STRING; + case 0x44120038: + return SUS_6_R_LOC_XFYBZM_PT_XF_STRING; + case 0x44120039: + return SUS_7_R_LOC_XBYBZM_PT_XB_STRING; + case 0x44120040: + return SUS_8_R_LOC_XBYBZB_PT_YB_STRING; + case 0x44120041: + return SUS_9_R_LOC_XBYBZB_PT_YF_STRING; + case 0x44120042: + return SUS_10_N_LOC_XMYBZF_PT_ZF_STRING; + case 0x44120043: + return SUS_11_R_LOC_XBYMZB_PT_ZB_STRING; + case 0x44120047: + return RW1_STRING; + case 0x44120107: + return MGM_1_RM3100_HANDLER_STRING; + case 0x44120111: + return GYRO_1_L3G_HANDLER_STRING; + case 0x44120148: + return RW2_STRING; + case 0x44120208: + return MGM_2_LIS3_HANDLER_STRING; + case 0x44120212: + return GYRO_2_ADIS_HANDLER_STRING; + case 0x44120249: + return RW3_STRING; + case 0x44120309: + return MGM_3_RM3100_HANDLER_STRING; + case 0x44120313: + return GYRO_3_L3G_HANDLER_STRING; + case 0x44120350: + return RW4_STRING; + case 0x44130001: + return STAR_TRACKER_STRING; + case 0x44130045: + return GPS_CONTROLLER_STRING; + case 0x44130046: + return GPS_0_HEALTH_DEV_STRING; + case 0x44130047: + return GPS_1_HEALTH_DEV_STRING; + case 0x44140013: + return IMTQ_POLLING_STRING; + case 0x44140014: + return IMTQ_HANDLER_STRING; + case 0x442000A1: + return PCDU_HANDLER_STRING; + case 0x44250000: + return P60DOCK_HANDLER_STRING; + case 0x44250001: + return PDU1_HANDLER_STRING; + case 0x44250002: + return PDU2_HANDLER_STRING; + case 0x44250003: + return ACU_HANDLER_STRING; + case 0x44260000: + return BPX_BATT_HANDLER_STRING; + case 0x44300000: + return PLPCDU_HANDLER_STRING; + case 0x443200A5: + return RAD_SENSOR_STRING; + case 0x44330000: + return PLOC_UPDATER_STRING; + case 0x44330001: + return PLOC_MEMORY_DUMPER_STRING; + case 0x44330002: + return STR_COM_IF_STRING; + case 0x44330003: + return PLOC_MPSOC_HELPER_STRING; + case 0x44330004: + return AXI_PTME_CONFIG_STRING; + case 0x44330005: + return PTME_CONFIG_STRING; + case 0x44330006: + return PTME_VC0_LIVE_TM_STRING; + case 0x44330007: + return PTME_VC1_LOG_TM_STRING; + case 0x44330008: + return PTME_VC2_HK_TM_STRING; + case 0x44330009: + return PTME_VC3_CFDP_TM_STRING; + case 0x44330015: + return PLOC_MPSOC_HANDLER_STRING; + case 0x44330016: + return PLOC_SUPERVISOR_HANDLER_STRING; + case 0x44330017: + return PLOC_SUPERVISOR_HELPER_STRING; + case 0x44330018: + return PLOC_MPSOC_COMMUNICATION_STRING; + case 0x44330032: + return SCEX_STRING; + case 0x444100A2: + return SOLAR_ARRAY_DEPL_HANDLER_STRING; + case 0x444100A4: + return HEATER_HANDLER_STRING; + case 0x44420004: + return TMP1075_HANDLER_TCS_0_STRING; + case 0x44420005: + return TMP1075_HANDLER_TCS_1_STRING; + case 0x44420006: + return TMP1075_HANDLER_PLPCDU_0_STRING; + case 0x44420007: + return TMP1075_HANDLER_PLPCDU_1_STRING; + case 0x44420008: + return TMP1075_HANDLER_IF_BOARD_STRING; + case 0x44420016: + return RTD_0_IC3_PLOC_HEATSPREADER_STRING; + case 0x44420017: + return RTD_1_IC4_PLOC_MISSIONBOARD_STRING; + case 0x44420018: + return RTD_2_IC5_4K_CAMERA_STRING; + case 0x44420019: + return RTD_3_IC6_DAC_HEATSPREADER_STRING; + case 0x44420020: + return RTD_4_IC7_STARTRACKER_STRING; + case 0x44420021: + return RTD_5_IC8_RW1_MX_MY_STRING; + case 0x44420022: + return RTD_6_IC9_DRO_STRING; + case 0x44420023: + return RTD_7_IC10_SCEX_STRING; + case 0x44420024: + return RTD_8_IC11_X8_STRING; + case 0x44420025: + return RTD_9_IC12_HPA_STRING; + case 0x44420026: + return RTD_10_IC13_PL_TX_STRING; + case 0x44420027: + return RTD_11_IC14_MPA_STRING; + case 0x44420028: + return RTD_12_IC15_ACU_STRING; + case 0x44420029: + return RTD_13_IC16_PLPCDU_HEATSPREADER_STRING; + case 0x44420030: + return RTD_14_IC17_TCS_BOARD_STRING; + case 0x44420031: + return RTD_15_IC18_IMTQ_STRING; + case 0x445300A3: + return SYRLINKS_HANDLER_STRING; + case 0x445300A4: + return SYRLINKS_COM_HANDLER_STRING; + case 0x49000000: + return ARDUINO_COM_IF_STRING; + case 0x49010005: + return GPIO_IF_STRING; + case 0x49010006: + return SCEX_UART_READER_STRING; + case 0x49020004: + return SPI_MAIN_COM_IF_STRING; + case 0x49030003: + return UART_COM_IF_STRING; + case 0x49040002: + return I2C_COM_IF_STRING; + case 0x49050001: + return CSP_COM_IF_STRING; + case 0x49060004: + return ACS_BOARD_POLLING_TASK_STRING; + case 0x49060005: + return RW_POLLING_TASK_STRING; + case 0x49060006: + return SPI_RTD_COM_IF_STRING; + case 0x49060007: + return SUS_POLLING_TASK_STRING; + case 0x50000100: + return CCSDS_PACKET_DISTRIBUTOR_STRING; + case 0x50000200: + return PUS_PACKET_DISTRIBUTOR_STRING; + case 0x50000300: + return TCP_TMTC_SERVER_STRING; + case 0x50000301: + return UDP_TMTC_SERVER_STRING; + case 0x50000400: + return TCP_TMTC_POLLING_TASK_STRING; + case 0x50000401: + return UDP_TMTC_POLLING_TASK_STRING; + case 0x50000500: + return FILE_SYSTEM_HANDLER_STRING; + case 0x50000550: + return SDC_MANAGER_STRING; + case 0x50000600: + return PTME_STRING; + case 0x50000700: + return PDEC_HANDLER_STRING; + case 0x50000800: + return CCSDS_HANDLER_STRING; + case 0x51000500: + return PUS_SERVICE_6_STRING; + case 0x53000000: + return FSFW_OBJECTS_START_STRING; + case 0x53000001: + return PUS_SERVICE_1_VERIFICATION_STRING; + case 0x53000002: + return PUS_SERVICE_2_DEVICE_ACCESS_STRING; + case 0x53000003: + return PUS_SERVICE_3_HOUSEKEEPING_STRING; + case 0x53000005: + return PUS_SERVICE_5_EVENT_REPORTING_STRING; + case 0x53000008: + return PUS_SERVICE_8_FUNCTION_MGMT_STRING; + case 0x53000009: + return PUS_SERVICE_9_TIME_MGMT_STRING; + case 0x53000011: + return PUS_SERVICE_11_TC_SCHEDULER_STRING; + case 0x53000015: + return PUS_SERVICE_15_TM_STORAGE_STRING; + case 0x53000017: + return PUS_SERVICE_17_TEST_STRING; + case 0x53000020: + return PUS_SERVICE_20_PARAMETERS_STRING; + case 0x53000200: + return PUS_SERVICE_200_MODE_MGMT_STRING; + case 0x53000201: + return PUS_SERVICE_201_HEALTH_STRING; + case 0x53001000: + return CFDP_PACKET_DISTRIBUTOR_STRING; + case 0x53010000: + return HEALTH_TABLE_STRING; + case 0x53010100: + return MODE_STORE_STRING; + case 0x53030000: + return EVENT_MANAGER_STRING; + case 0x53040000: + return INTERNAL_ERROR_REPORTER_STRING; + case 0x534f0100: + return TC_STORE_STRING; + case 0x534f0200: + return TM_STORE_STRING; + case 0x534f0300: + return IPC_STORE_STRING; + case 0x53500010: + return TIME_STAMPER_STRING; + case 0x53500020: + return VERIFICATION_REPORTER_STRING; + case 0x53ffffff: + return FSFW_OBJECTS_END_STRING; + case 0x54000010: + return SPI_TEST_STRING; + case 0x54000020: + return UART_TEST_STRING; + case 0x54000030: + return I2C_TEST_STRING; + case 0x54000040: + return DUMMY_COM_IF_STRING; + case 0x5400AFFE: + return DUMMY_HANDLER_STRING; + case 0x5400CAFE: + return DUMMY_INTERFACE_STRING; + case 0x54123456: + return LIBGPIOD_TEST_STRING; + case 0x54694269: + return TEST_TASK_STRING; + case 0x60000000: + return HEATER_0_PLOC_PROC_BRD_STRING; + case 0x60000001: + return HEATER_1_PCDU_BRD_STRING; + case 0x60000002: + return HEATER_2_ACS_BRD_STRING; + case 0x60000003: + return HEATER_3_OBC_BRD_STRING; + case 0x60000004: + return HEATER_4_CAMERA_STRING; + case 0x60000005: + return HEATER_5_STR_STRING; + case 0x60000006: + return HEATER_6_DRO_STRING; + case 0x60000007: + return HEATER_7_SYRLINKS_STRING; + case 0x73000001: + return ACS_BOARD_ASS_STRING; + case 0x73000002: + return SUS_BOARD_ASS_STRING; + case 0x73000003: + return TCS_BOARD_ASS_STRING; + case 0x73000004: + return RW_ASSY_STRING; + case 0x73000006: + return CAM_SWITCHER_STRING; + case 0x73000007: + return SYRLINKS_ASSY_STRING; + case 0x73000008: + return IMTQ_ASSY_STRING; + case 0x73000009: + return STR_ASSY_STRING; + case 0x73000100: + return TM_FUNNEL_STRING; + case 0x73000101: + return PUS_TM_FUNNEL_STRING; + case 0x73000102: + return CFDP_TM_FUNNEL_STRING; + case 0x73000205: + return CFDP_HANDLER_STRING; + case 0x73000206: + return CFDP_DISTRIBUTOR_STRING; + case 0x73000207: + return CFDP_FAULT_HANDLER_STRING; + case 0x73010000: + return EIVE_SYSTEM_STRING; + case 0x73010001: + return ACS_SUBSYSTEM_STRING; + case 0x73010002: + return PL_SUBSYSTEM_STRING; + case 0x73010003: + return TCS_SUBSYSTEM_STRING; + case 0x73010004: + return COM_SUBSYSTEM_STRING; + case 0x73010005: + return EPS_SUBSYSTEM_STRING; + case 0x73020001: + return MISC_TM_STORE_STRING; + case 0x73020002: + return OK_TM_STORE_STRING; + case 0x73020003: + return NOT_OK_TM_STORE_STRING; + case 0x73020004: + return HK_TM_STORE_STRING; + case 0x73030000: + return CFDP_TM_STORE_STRING; + case 0x73040000: + return LIVE_TM_TASK_STRING; + case 0x73040001: + return LOG_STORE_AND_TM_TASK_STRING; + case 0x73040002: + return HK_STORE_AND_TM_TASK_STRING; + case 0x73040003: + return CFDP_STORE_AND_TM_TASK_STRING; + case 0x73040004: + return DOWNLINK_RAM_STORE_STRING; + case 0x90000003: + return THERMAL_TEMP_INSERTER_STRING; + case 0xFFFFFFFF: + return NO_OBJECT_STRING; + default: + return "UNKNOWN_OBJECT"; + } + return 0; +} diff --git a/generators/objects/translateObjects.h b/generators/objects/translateObjects.h new file mode 100644 index 0000000..257912f --- /dev/null +++ b/generators/objects/translateObjects.h @@ -0,0 +1,8 @@ +#ifndef FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ +#define FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ + +#include + +const char *translateObject(object_id_t object); + +#endif /* FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ */ diff --git a/generators/requirements.txt b/generators/requirements.txt new file mode 100644 index 0000000..c4afd24 --- /dev/null +++ b/generators/requirements.txt @@ -0,0 +1,2 @@ +colorlog==6.7.0 +git+https://egit.irs.uni-stuttgart.de/fsfw/fsfwgen@v0.3.2#egg=fsfwgen diff --git a/generators/returnvalues/__init__.py b/generators/returnvalues/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/generators/returnvalues/returnvalues_parser.py b/generators/returnvalues/returnvalues_parser.py new file mode 100644 index 0000000..544cd64 --- /dev/null +++ b/generators/returnvalues/returnvalues_parser.py @@ -0,0 +1,140 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Part of the MIB export tools for the EIVE project by. +Returnvalue exporter. +""" +import logging +from pathlib import Path + +from fsfwgen.utility.file_management import copy_file +from fsfwgen.parserbase.file_list_parser import FileListParser +from fsfwgen.returnvalues.returnvalues_parser import ( + InterfaceParser, + ReturnValueParser, + RetvalDictT, +) +from fsfwgen.utility.sql_writer import SqlWriter + +from definitions import BspType, DATABASE_NAME, ROOT_DIR, OBSW_ROOT_DIR + +_LOGGER = logging.getLogger(__name__) +EXPORT_TO_FILE = True +COPY_CSV_FILE = True +EXPORT_TO_SQL = True +PRINT_TABLES = False + + +FILE_SEPARATOR = ";" +MAX_STRING_LENGTH = 32 + + +CSV_COPY_DEST = Path(f"{OBSW_ROOT_DIR}/tmtc/eive_tmtc/config/returnvalues.csv") + + +SQL_DELETE_RETURNVALUES_CMD = """ + DROP TABLE IF EXISTS Returnvalues +""" + +SQL_CREATE_RETURNVALUES_CMD = """ + CREATE TABLE IF NOT EXISTS Returnvalues ( + id INTEGER PRIMARY KEY, + code TEXT, + name TEXT, + interface TEXT, + file TEXT, + description TEXT + ) +""" + +SQL_INSERT_RETURNVALUES_CMD = """ +INSERT INTO Returnvalues(code,name,interface,file,description) +VALUES(?,?,?,?,?) +""" + + +class BspConfig: + def __init__(self, bps_select: BspType): + self.bsp_dir_name = bps_select.value + self.csv_retval_filename = Path( + f"{ROOT_DIR}/{self.bsp_dir_name}_returnvalues.csv" + ) + self.add_linux_folder = False + if bps_select == BspType.BSP_Q7S or bps_select == BspType.BSP_LINUX_BOARD: + self.fsfw_config_root = f"{OBSW_ROOT_DIR}/linux/fsfwconfig" + self.add_linux_folder = True + else: + self.fsfw_config_root = f"{OBSW_ROOT_DIR}/{self.bsp_dir_name}/fsfwconfig" + self.bsp_path = f"{OBSW_ROOT_DIR}/{self.bsp_dir_name}" + self.retval_sources = [ + f"{OBSW_ROOT_DIR}/mission/", + f"{OBSW_ROOT_DIR}/fsfw/", + f"{self.bsp_path}", + ] + if self.add_linux_folder: + self.retval_sources.append(f"{OBSW_ROOT_DIR}/linux") + + def if_definition_files(self): + return [ + f"{OBSW_ROOT_DIR}/fsfw/src/fsfw/returnvalues/FwClassIds.h", + f"{OBSW_ROOT_DIR}/common/config/eive/resultClassIds.h", + f"{self.fsfw_config_root}/returnvalues/classIds.h", + ] + + def retval_sources_as_path(self): + return [Path(x) for x in self.retval_sources] + + +def parse_returnvalues(bsp_select: BspType, copy_to_eive_tmtc: bool): + cfg = BspConfig(bsp_select) + returnvalue_table = generate_returnvalue_table(cfg) + if EXPORT_TO_FILE: + ReturnValueParser.export_to_csv( + cfg.csv_retval_filename, returnvalue_table, FILE_SEPARATOR + ) + if COPY_CSV_FILE: + if copy_to_eive_tmtc: + _LOGGER.info(f"Copying CSV to {CSV_COPY_DEST}") + copy_file( + filename=cfg.csv_retval_filename, + destination=CSV_COPY_DEST, + delete_existing_file=True, + ) + if EXPORT_TO_SQL: + _LOGGER.info("ReturnvalueParser: Exporting to SQL") + sql_retval_exporter( + returnvalue_table, db_filename=f"{ROOT_DIR}/{DATABASE_NAME}" + ) + + +def generate_returnvalue_table(cfg: BspConfig): + """Core function to parse for the return values""" + interface_parser = InterfaceParser( + file_list=cfg.if_definition_files(), print_table=PRINT_TABLES + ) + interfaces = interface_parser.parse_files() + header_parser = FileListParser(cfg.retval_sources_as_path()) + header_list = header_parser.parse_header_files(True, "Parsing header file list: ") + returnvalue_parser = ReturnValueParser(interfaces, header_list, PRINT_TABLES) + returnvalue_parser.obsw_root_path = OBSW_ROOT_DIR + returnvalue_parser.set_moving_window_mode(moving_window_size=7) + returnvalue_table = returnvalue_parser.parse_files(True) + _LOGGER.info(f"ReturnvalueParser: Found {len(returnvalue_table)} returnvalues") + return returnvalue_table + + +def sql_retval_exporter(returnvalue_table: RetvalDictT, db_filename: str): + sql_writer = SqlWriter(db_filename=db_filename) + sql_writer.open(SQL_CREATE_RETURNVALUES_CMD) + for entry in returnvalue_table.items(): + sql_writer.write_entries( + SQL_INSERT_RETURNVALUES_CMD, + ( + entry[0], + entry[1].name, + entry[1].description, + entry[1].unique_id, + entry[1].subsystem_name, + ), + ) + sql_writer.commit() + sql_writer.close() diff --git a/generators/system/__init__.py b/generators/system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/generators/system/eive-system.yml b/generators/system/eive-system.yml new file mode 100644 index 0000000..001668a --- /dev/null +++ b/generators/system/eive-system.yml @@ -0,0 +1,464 @@ +# nml -> normal +# brd -> board +# ss -> subsystem +# ass -> assembly +# ctrl -> controller +# dh -> device handler +# dft -> default +# All uppermost system components are automatically subsystems +system: + eps: + pcdu: + id: 0x442000a1 + acu: + id: 0x44250003 + pdu1: + id: 0x44250001 + pdu2: + id: 0x44250002 + tcs: + tcs-ctrl: + tcs-brd-ass: + id: 0x73000003 + acs: + acs-brd-ass: + id: 0x73000001 + sus-brd-ass: + id: 0x73000002 + acs-ctrl: + rw: + mgt: + str: + payload: + scex-dh: + ploc-ss: + cam-switcher: + pl-pcdu-dh: + com: + syrlinks-dh: + + +modes: + # If nothing is specified for a particular mode, use default configuration + default: + system: + # The power system is/should always be on. We can't even turn it off + eps: nml + pcdu: nml + acu: nml + pdu1: nml + pdu2: nml + tcs: nml + tcs-brd-ass: nml + tcs-ctrl: nml + payload: off + scex: off + ploc-ss: off + cam-ss: off + pl-pcdu-dh: off + com: nml + syrlinks-dh: nml + acs: + mgt: nml + acs-brd-ass: nml + sus-brd-ass: nml + detumble: + system: + acs: detumble + acs-ctrl: detumble + # Requires MGM and Gyros + rw: off + str: off + safe: + system: + acs: safe + acs-ctrl: safe + rw: off + str: off + submodes: + cold: 1 + system: + # Inherit rest of mode table from default submode + tcs: + tcs-ctrl: heat + idle: + system: + acs: idle + acs-ctrl: idle + rw: nml + str: off + submodes: + charge: 1 + system: + # Inherit rest of mode table from default submode + acs: + rw: off + target-gs: + system: + acs: target-pt + acs-ctrl: target-pt + rw: nml + str: nml + target-gs-pl-dac: + system: + acs: target-pt + acs-ctrl: target-pt + rw: nml + str: nml + payload: dac + cam-switcher: off + scex: off + ploc-ss: nml + pl-pcdu-dh: on + target-gs-pl-cam: + system: + acs: target-pt + acs-ctrl: target-pt + rw: nml + str: nml + payload: cam + cam-switcher: on + scex: off + ploc-ss: nml + submode: dac-off + pl-pcdu-dh: on + target-gs-pl-data: + system: + acs: target-pt + acs-ctrl: target-pt + rw: nml + str: nml + payload: pl-data + cam-switcher: off + scex: off + ploc-ss: nml + submode: dac-off + pl-pcdu-ss: on + earth-obsv: + acs: target-pt + acs-ctrl: target-pt + rw: nml + str: nml + payload: earth-obsv + cam-switcher: on + scex: off + ploc-ss: off + pl-pcdu-ss: off + scex: + acs: target-pt + acs-ctrl: target-pt + rw: nml + str: nml + payload: scex + cam-switcher: off + scex: on + ploc-ss: off + pl-pcdu-ss: off + +sequences: + default: + acs: + off: + fallback: none + seq-id: off + target: + name: off-target + tab-id: 0x61000000 + modes: ignore + trans: + 0: + name: off-trans + tab-id: 0x61000001 + modes: + acs-ctrl: off + acs-brd-ass: off + sus-brd-ass: off + rw: off + mgt: off + str: off + detumble: + seq-id: 1 + target: + name: detumble-target + tab-id: 0x61000100 + modes: + acs-ctrl: detumble + mgt: nml + sus-brd-ass: nml + acs-brd-ass: nml + rw: ignore + str: ignore + trans: + 0: + name: detumble-trans-0 + tab-id: 0x61000101 + modes: + acs-ctrl: ignore + mgt: nml + rw: off + str: off + sus-brd-ass: nml + acs-brd-ass: nml + 1: + name: detumble-trans-1 + tab-id: 0x61000102 + modes: + acs-ctrl: detumble + mgt: ignore + acs-brd-ass: ignore + sus-brd-ass: ignore + rw: ignore + str: ignore + safe: + seq-id: 2 + target: + name: safe-target + tab-id: 0x61000200 + modes: + acs-ctrl: safe + mgt: nml + acs-brd-ass: nml + sus-brd-ass: nml + rw: ignore + str: ignore + trans: + 0: + name: safe-trans-1 + tab-id: 0x61000201 + modes: + acs-ctrl: ignore + mgt: nml + acs-brd-ass: nml + sus-brd-ass: nml + rw: off + str: off + 1: + name: safe-trans-2 + tab-id: 0x61000202 + modes: + acs-ctrl: safe + mgt: ignore + acs-brd-ass: ignore + sus-brd-ass: ignore + rw: ignore + str: ignore + idle: + seq-id: 3 + target: + name: idle-target + tab-id: 0x61000300 + modes: + acs-ctrl: idle + mgt: nml + acs-brd-ass: nml + sus-brd-ass: nml + rw: nml + str: ignore + trans: + 0: + name: idle-trans-0 + tab-id: 0x61000301 + modes: + acs-ctrl: ignore + mgt: nml + acs-brd-ass: nml + sus-brd-ass: nml + rw: nml + str: off + 1: + name: idle-trans-1 + tab-id: 0x61000302 + modes: + acs-ctrl: idle + mgt: ignore + acs-brd-ass: ignore + sus-brd-ass: ignore + rw: ignore + str: ignore + idle-charge: + derive: idle + seq-id: 4 + target: + name: idle-charge-target + tab-id: 0x61000400 + modes: + acs-ctrl: idle + submode: charge + trans: + 0: + name: idle-charge-trans-0 + tab-id: 0x61000401 + modes: + rw: off + 1: + name: idle-charge-trans-1 + tab-id: 0x61000402 + modes: + acs-ctrl: idle + submode: charge + target-pt: + seq-id: 5 + target: + name: target-pt-target + tab-id: 0x61000500 + modes: + acs-ctrl: target-pt + mgt: nml + acs-brd-ass: nml + sus-brd-ass: nml + rw: nml + str: nml + trans: + 0: + name: target-pt-trans-0 + tab-id: 0x61000501 + modes: + acs-ctrl: ignore + mgt: nml + acs-brd-ass: nml + sus-brd-ass: nml + rw: nml + str: nml + 1: + name: target-pt-trans-1 + tab-id: 0x61000502 + modes: + acs-ctrl: target-pt + mgt: ignore + acs-brd-ass: ignore + sus-brd-ass: ignore + rw: ignore + str: ignore + payload: + off: + seq-id: 0 + target: + name: off-target + tab-id: 0x70000000 + modes: ignore + trans: + 0: + name: off-trans-0 + tab-id: 0x70000001 + modes: + ploc-switcher: off + cam-switcher: off + scex-dh: off + ploc-ss: off + pl-pcdu-dh: off + dac: + seq-id: 1 + target: + name: target-dac + tab-id: 0x70000100 + modes: + ploc-switcher: on + cam-switcher: off + scex-dh: off + ploc-ss: dac + pl-pcdu-dh: nml + submode: all-on + trans: + 0: + name: dac-trans-0 + tab-id: 0x70000101 + modes: + ploc-switcher: on + cam-switcher: off + scex-dh: off + ploc-ss: on + submode: dac-on + pl-pcdu-dh: ignore + cam: + seq-id: 2 + target: + name: target-cam + tab-id: 0x70000200 + modes: + ploc-switcher: on + cam-switcher: on + scex-dh: off + ploc-ss: on + submode: dac-off + pl-pcdu-dh: nml + submode: all-on + trans: + 0: + name: cam-trans-0 + tab-id: 0x70000201 + modes: + ploc-switcher: on + cam-switcher: off + scex-dh: off + ploc-ss: on + submode: dac-off + pl-pcdu-dh: ignore + pl-data: + seq-id: 3 + target: + name: target-pl-data + tab-id: 0x70000300 + modes: + ploc-switcher: on + cam-switcher: off + scex-dh: off + ploc-ss: on + submode: dac-off + pl-pcdu-dh: nml + submode: all-on + earth-obsv: + seq-id: 4 + target: + name: target-earth-obsv + tab-id: 0x70000400 + modes: + ploc-switcher: on + cam-switcher: on + scex-dh: off + ploc-ss: on + submode: dac-off + pl-pcdu-dh: off + trans: + 0: + name: trans-earth-obsv-0 + tab-id: 0x70000401 + modes: + ploc-switcher: on + cam-switcher: on + scex-dh: off + ploc-ss: on + submode: dac-off + pl-pcdu-dh: off + scex: + seq-id: 4 + target: scex + name: target-scex + tab-id: 0x70000500 + modes: + ploc-switcher: off + cam-switcher: off + scex-dh: on + ploc-ss: off + pl-pcdu-dh: off + trans: + 0: + name: trans-scex-0 + tab-id: 0x70000501 + modes: + ploc-switcher: off + cam-switcher: off + scex-dh: on + ploc-ss: off + pl-pcdu-dh: off + system: + detumble: + seq-id: 0 + target: + name: target-detumble + tab-id: 0x73000000 + modes: + acs: detumble + tcs: nml + com: nml + payload: ignore + eps: nml \ No newline at end of file diff --git a/hooks/post-checkout b/hooks/post-checkout new file mode 100755 index 0000000..bfddad4 --- /dev/null +++ b/hooks/post-checkout @@ -0,0 +1,6 @@ +#!/bin/bash +# +# update submodules after checkout + +git submodule init +git submodule update diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..7a627d1 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,19 @@ +add_subdirectory(utility) +add_subdirectory(callbacks) +add_subdirectory(boardtest) +add_subdirectory(ipcore) +add_subdirectory(com) +add_subdirectory(acs) +add_subdirectory(tcs) +add_subdirectory(payload) + +if(EIVE_ADD_LINUX_FSFWCONFIG) + add_subdirectory(fsfwconfig) +endif() + +# Dependency on proprietary library +if(TGT_BSP MATCHES "arm/q7s") + add_subdirectory(power) +endif() + +target_sources(${OBSW_NAME} PUBLIC ObjectFactory.cpp scheduling.cpp) diff --git a/linux/ObjectFactory.cpp b/linux/ObjectFactory.cpp new file mode 100644 index 0000000..4a78d75 --- /dev/null +++ b/linux/ObjectFactory.cpp @@ -0,0 +1,355 @@ +#include "ObjectFactory.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OBSWConfig.h" +#include "devConf.h" +#include "devices/gpioIds.h" +#include "mission/system/acs/acsModeTree.h" +#include "mission/system/payload/payloadModeTree.h" +#include "mission/system/power/epsModeTree.h" + +void ObjectFactory::createSunSensorComponents(GpioIF* gpioComIF, SpiComIF* spiComIF, + PowerSwitchIF& pwrSwitcher, std::string spiDev, + bool swap0And6) { + using namespace gpio; + new SusPolling(objects::SUS_POLLING_TASK, *spiComIF, *gpioComIF); + GpioCookie* gpioCookieSus = new GpioCookie(); + GpioCallback* susgpio = nullptr; + + susgpio = new GpioCallback("Chip select SUS 0", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_0, susgpio); + susgpio = new GpioCallback("Chip select SUS 1", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_1, susgpio); + susgpio = new GpioCallback("Chip select SUS 2", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_2, susgpio); + susgpio = new GpioCallback("Chip select SUS 3", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_3, susgpio); + susgpio = new GpioCallback("Chip select SUS 4", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_4, susgpio); + susgpio = new GpioCallback("Chip select SUS 5", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_5, susgpio); + susgpio = new GpioCallback("Chip select SUS 6", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_6, susgpio); + susgpio = new GpioCallback("Chip select SUS 7", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_7, susgpio); + susgpio = new GpioCallback("Chip select SUS 8", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_8, susgpio); + susgpio = new GpioCallback("Chip select SUS 9", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_9, susgpio); + susgpio = new GpioCallback("Chip select SUS 10", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_10, susgpio); + susgpio = new GpioCallback("Chip select SUS 11", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + gpioCookieSus->addGpio(gpioIds::CS_SUS_11, susgpio); + + gpioChecker(gpioComIF->addGpios(gpioCookieSus), "Sun Sensors"); + +#if OBSW_ADD_SUN_SENSORS == 1 + SusFdir* fdir = nullptr; + std::array susHandlers = {}; + SpiCookie* spiCookie = + new SpiCookie(addresses::SUS_0, gpioIds::CS_SUS_0, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[0] = + new SusHandler(objects::SUS_0_N_LOC_XFYFZM_PT_XF, 0, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_0_N_LOC_XFYFZM_PT_XF); + susHandlers[0]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_1, gpioIds::CS_SUS_1, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[1] = + new SusHandler(objects::SUS_1_N_LOC_XBYFZM_PT_XB, 1, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_1_N_LOC_XBYFZM_PT_XB); + susHandlers[1]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_2, gpioIds::CS_SUS_2, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[2] = + new SusHandler(objects::SUS_2_N_LOC_XFYBZB_PT_YB, 2, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_2_N_LOC_XFYBZB_PT_YB); + susHandlers[2]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_3, gpioIds::CS_SUS_3, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[3] = + new SusHandler(objects::SUS_3_N_LOC_XFYBZF_PT_YF, 3, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_3_N_LOC_XFYBZF_PT_YF); + susHandlers[3]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_4, gpioIds::CS_SUS_4, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[4] = + new SusHandler(objects::SUS_4_N_LOC_XMYFZF_PT_ZF, 4, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_4_N_LOC_XMYFZF_PT_ZF); + susHandlers[4]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_5, gpioIds::CS_SUS_5, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[5] = + new SusHandler(objects::SUS_5_N_LOC_XFYMZB_PT_ZB, 5, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_5_N_LOC_XFYMZB_PT_ZB); + susHandlers[5]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_6, gpioIds::CS_SUS_6, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[6] = + new SusHandler(objects::SUS_6_R_LOC_XFYBZM_PT_XF, 6, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_6_R_LOC_XFYBZM_PT_XF); + susHandlers[6]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_7, gpioIds::CS_SUS_7, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[7] = + new SusHandler(objects::SUS_7_R_LOC_XBYBZM_PT_XB, 7, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_7_R_LOC_XBYBZM_PT_XB); + susHandlers[7]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_8, gpioIds::CS_SUS_8, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[8] = + new SusHandler(objects::SUS_8_R_LOC_XBYBZB_PT_YB, 8, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_8_R_LOC_XBYBZB_PT_YB); + susHandlers[8]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_9, gpioIds::CS_SUS_9, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[9] = + new SusHandler(objects::SUS_9_R_LOC_XBYBZB_PT_YF, 9, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_9_R_LOC_XBYBZB_PT_YF); + susHandlers[9]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_10, gpioIds::CS_SUS_10, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[10] = + new SusHandler(objects::SUS_10_N_LOC_XMYBZF_PT_ZF, 10, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_10_N_LOC_XMYBZF_PT_ZF); + susHandlers[10]->setCustomFdir(fdir); + + spiCookie = new SpiCookie(addresses::SUS_11, gpioIds::CS_SUS_11, susMax1227::MAX_CMD_SIZE, + spi::SUS_MAX_1227_MODE, spi::SUS_MAX1227_SPI_FREQ); + spiCookie->setMutexParams(MutexIF::TimeoutType::WAITING, spi::SUS_CS_TIMEOUT); + susHandlers[11] = + new SusHandler(objects::SUS_11_R_LOC_XBYMZB_PT_ZB, 11, objects::SUS_POLLING_TASK, spiCookie); + fdir = new SusFdir(objects::SUS_11_R_LOC_XBYMZB_PT_ZB); + susHandlers[11]->setCustomFdir(fdir); + + for (auto& sus : susHandlers) { + if (sus != nullptr) { +#if OBSW_TEST_SUS == 1 + sus->setStartUpImmediately(); + sus->setToGoToNormalMode(true); +#endif +#if OBSW_DEBUG_SUS == 1 + sus->enablePeriodicPrintout(true, 3); +#endif + } + } + std::array susDhbs; + for (unsigned i = 0; i < susDhbs.size(); i++) { + susDhbs[i] = susHandlers[i]; + } + createSusAssy(pwrSwitcher, susDhbs); +#endif /* OBSW_ADD_SUN_SENSORS == 1 */ +} + +void ObjectFactory::createRtdComponents(std::string spiDev, GpioIF* gpioComIF, + PowerSwitchIF* pwrSwitcher, SpiComIF* comIF) { + using namespace gpio; + GpioCookie* rtdGpioCookie = new GpioCookie; + + GpioCallback* gpioRtdIc0 = new GpioCallback("Chip select RTD IC0", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_0, gpioRtdIc0); + GpioCallback* gpioRtdIc1 = new GpioCallback("Chip select RTD IC1", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_1, gpioRtdIc1); + GpioCallback* gpioRtdIc2 = new GpioCallback("Chip select RTD IC2", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_2, gpioRtdIc2); + GpioCallback* gpioRtdIc3 = new GpioCallback("Chip select RTD IC3", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_3, gpioRtdIc3); + GpioCallback* gpioRtdIc4 = new GpioCallback("Chip select RTD IC4", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_4, gpioRtdIc4); + GpioCallback* gpioRtdIc5 = new GpioCallback("Chip select RTD IC5", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_5, gpioRtdIc5); + GpioCallback* gpioRtdIc6 = new GpioCallback("Chip select RTD IC6", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_6, gpioRtdIc6); + GpioCallback* gpioRtdIc7 = new GpioCallback("Chip select RTD IC7", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_7, gpioRtdIc7); + GpioCallback* gpioRtdIc8 = new GpioCallback("Chip select RTD IC8", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_8, gpioRtdIc8); + GpioCallback* gpioRtdIc9 = new GpioCallback("Chip select RTD IC9", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_9, gpioRtdIc9); + GpioCallback* gpioRtdIc10 = new GpioCallback("Chip select RTD IC10", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_10, gpioRtdIc10); + GpioCallback* gpioRtdIc11 = new GpioCallback("Chip select RTD IC11", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_11, gpioRtdIc11); + GpioCallback* gpioRtdIc12 = new GpioCallback("Chip select RTD IC12", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_12, gpioRtdIc12); + GpioCallback* gpioRtdIc13 = new GpioCallback("Chip select RTD IC13", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_13, gpioRtdIc13); + GpioCallback* gpioRtdIc14 = new GpioCallback("Chip select RTD IC14", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_14, gpioRtdIc14); + GpioCallback* gpioRtdIc15 = new GpioCallback("Chip select RTD IC15", Direction::OUT, Levels::HIGH, + &gpioCallbacks::spiCsDecoderCallback, gpioComIF); + rtdGpioCookie->addGpio(gpioIds::RTD_IC_15, gpioRtdIc15); + + gpioChecker(gpioComIF->addGpios(rtdGpioCookie), "RTDs"); + +#if OBSW_ADD_RTD_DEVICES == 1 + using namespace EiveMax31855; + // ! NOTE ! + // The chip selects for device 9 and 11 are swapped here. It is strongly suspected the cables + // for those devices were swapped during integration. This is probably the easiest way to + // fix the issue. + std::array, NUM_RTDS> cookieArgs = {{ + {addresses::RTD_IC_0, gpioIds::RTD_IC_0}, + {addresses::RTD_IC_1, gpioIds::RTD_IC_1}, + {addresses::RTD_IC_2, gpioIds::RTD_IC_2}, + {addresses::RTD_IC_3, gpioIds::RTD_IC_3}, + {addresses::RTD_IC_4, gpioIds::RTD_IC_4}, + {addresses::RTD_IC_5, gpioIds::RTD_IC_5}, + {addresses::RTD_IC_6, gpioIds::RTD_IC_6}, + {addresses::RTD_IC_7, gpioIds::RTD_IC_7}, + {addresses::RTD_IC_8, gpioIds::RTD_IC_8}, + {addresses::RTD_IC_11, gpioIds::RTD_IC_11}, + {addresses::RTD_IC_10, gpioIds::RTD_IC_10}, + {addresses::RTD_IC_9, gpioIds::RTD_IC_9}, + {addresses::RTD_IC_12, gpioIds::RTD_IC_12}, + {addresses::RTD_IC_13, gpioIds::RTD_IC_13}, + {addresses::RTD_IC_14, gpioIds::RTD_IC_14}, + {addresses::RTD_IC_15, gpioIds::RTD_IC_15}, + }}; + // HSPD: Heatspreader + + std::array rtdCookies = {}; + std::array rtds = {}; + RtdFdir* rtdFdir = nullptr; + + TcsBoardAssembly* tcsBoardAss = + ObjectFactory::createTcsBoardAssy(*pwrSwitcher, tcs::TCS_BOARD_SHORTLY_UNAVAILABLE); + + // Create special low level reader communication interface + new Max31865RtdPolling(objects::SPI_RTD_COM_IF, comIF, gpioComIF); + for (uint8_t idx = 0; idx < NUM_RTDS; idx++) { + rtdCookies[idx] = new SpiCookie(cookieArgs[idx].first, cookieArgs[idx].second, + MAX31865::MAX_REPLY_SIZE, spi::RTD_MODE, spi::RTD_SPEED); + rtdCookies[idx]->setMutexParams(MutexIF::TimeoutType::WAITING, spi::RTD_CS_TIMEOUT); + Max31865ReaderCookie* rtdLowLevelCookie = + new Max31865ReaderCookie(RTD_INFOS[idx].first, idx, RTD_INFOS[idx].second, rtdCookies[idx]); + rtds[idx] = + new Max31865EiveHandler(RTD_INFOS[idx].first, objects::SPI_RTD_COM_IF, rtdLowLevelCookie); + rtds[idx]->setDeviceInfo(idx, RTD_INFOS[idx].second); + ReturnValue_t result = rtds[idx]->connectModeTreeParent(*tcsBoardAss); + if (result != returnvalue::OK) { + sif::error << "Connecting RTD " << static_cast(idx) << " to RTD Assembly failed" + << std::endl; + } + rtdFdir = new RtdFdir(RTD_INFOS[idx].first); + rtds[idx]->setCustomFdir(rtdFdir); +#if OBSW_DEBUG_RTD == 1 + rtds[idx]->setDebugMode(true, 5); +#endif +#if OBSW_TEST_RTD == 1 + rtds[idx]->setInstantNormal(true); + rtds[idx]->setStartUpImmediately(); +#endif + } + +#endif // OBSW_ADD_RTD_DEVICES == 1 +} + +void ObjectFactory::createScexComponents(std::string uartDev, PowerSwitchIF* pwrSwitcher, + SdCardMountedIF& mountedIF, bool onImmediately, + std::optional switchId) { + auto* cookie = new SerialCookie(objects::SCEX, uartDev, serial::SCEX_BAUD, 4096); + cookie->setTwoStopBits(); + // cookie->setParityEven(); + auto scexUartReader = new ScexUartReader(objects::SCEX_UART_READER); + auto scexHandler = new ScexDeviceHandler(objects::SCEX, *scexUartReader, cookie, mountedIF); + if (onImmediately) { + scexHandler->setStartUpImmediately(); + } + if (switchId) { + scexHandler->setPowerSwitcher(*pwrSwitcher, switchId.value()); + } + scexHandler->connectModeTreeParent(satsystem::payload::SUBSYSTEM); +} + +AcsController* ObjectFactory::createAcsController(bool connectSubsystem, bool enableHkSets, + SdCardMountedIF& mountedIF) { + auto acsCtrl = new AcsController(objects::ACS_CONTROLLER, enableHkSets, mountedIF); + if (connectSubsystem) { + acsCtrl->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); + } + return acsCtrl; +} + +PowerController* ObjectFactory::createPowerController(bool connectSubsystem, bool enableHkSets) { + auto pwrCtrl = new PowerController(objects::POWER_CONTROLLER, enableHkSets); + if (connectSubsystem) { + pwrCtrl->connectModeTreeParent(satsystem::eps::EPS_SUBSYSTEM); + } + return pwrCtrl; +} + +void ObjectFactory::gpioChecker(ReturnValue_t result, std::string output) { + if (result != returnvalue::OK) { + sif::error << "ObjectFactory: Adding GPIOs failed for " << output << std::endl; + } +} diff --git a/linux/ObjectFactory.h b/linux/ObjectFactory.h new file mode 100644 index 0000000..b813d82 --- /dev/null +++ b/linux/ObjectFactory.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class GpioIF; +class SpiComIF; +class PowerSwitchIF; +class AcsController; +class PowerController; + +namespace ObjectFactory { + +void createSunSensorComponents(GpioIF* gpioComIF, SpiComIF* spiComIF, PowerSwitchIF& pwrSwitcher, + std::string spiDev, bool swap0And6); +void createRtdComponents(std::string spiDev, GpioIF* gpioComIF, PowerSwitchIF* pwrSwitcher, + SpiComIF* comIF); + +void createScexComponents(std::string uartDev, PowerSwitchIF* pwrSwitcher, + SdCardMountedIF& mountedIF, bool onImmediately, + std::optional switchId); + +void gpioChecker(ReturnValue_t result, std::string output); + +AcsController* createAcsController(bool connectSubsystem, bool enableHkSets, + SdCardMountedIF& mountedIF); +PowerController* createPowerController(bool connectSubsystem, bool enableHkSets); + +} // namespace ObjectFactory diff --git a/linux/acs/AcsBoardPolling.cpp b/linux/acs/AcsBoardPolling.cpp new file mode 100644 index 0000000..6919907 --- /dev/null +++ b/linux/acs/AcsBoardPolling.cpp @@ -0,0 +1,837 @@ +#include "AcsBoardPolling.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devices/gpioIds.h" + +using namespace returnvalue; + +AcsBoardPolling::AcsBoardPolling(object_id_t objectId, SpiComIF& lowLevelComIF, GpioIF& gpioIF) + : SystemObject(objectId), spiComIF(lowLevelComIF), gpioIF(gpioIF) { + semaphore = SemaphoreFactory::instance()->createBinarySemaphore(); + semaphore->acquire(); + ipcLock = MutexFactory::instance()->createMutex(); +} + +ReturnValue_t AcsBoardPolling::performOperation(uint8_t operationCode) { + while (true) { + ipcLock->lockMutex(LOCK_TYPE, LOCK_TIMEOUT); + state = InternalState::IDLE; + ipcLock->unlockMutex(); + semaphore->acquire(); + // Give all tasks or the PST some time to submit all consecutive requests. + TaskFactory::delayTask(2); + { + // Measured to take 0-1 ms in debug build. + // Stopwatch watch; + gyroAdisHandler(gyro0Adis); + gyroAdisHandler(gyro2Adis); + gyroL3gHandler(gyro1L3g); + gyroL3gHandler(gyro3L3g); + mgmRm3100Handler(mgm1Rm3100); + mgmRm3100Handler(mgm3Rm3100); + mgmLis3Handler(mgm0Lis3); + mgmLis3Handler(mgm2Lis3); + } + // To prevent task being not reactivated by tardy tasks + TaskFactory::delayTask(20); + } + return returnvalue::OK; +} + +ReturnValue_t AcsBoardPolling::initialize() { return returnvalue::OK; } + +ReturnValue_t AcsBoardPolling::initializeInterface(CookieIF* cookie) { + SpiCookie* spiCookie = dynamic_cast(cookie); + if (spiCookie == nullptr) { + return returnvalue::FAILED; + } + switch (spiCookie->getChipSelectPin()) { + case (gpioIds::MGM_0_LIS3_CS): { + mgm0Lis3.cookie = spiCookie; + break; + } + case (gpioIds::MGM_1_RM3100_CS): { + mgm1Rm3100.cookie = spiCookie; + break; + } + case (gpioIds::MGM_2_LIS3_CS): { + mgm2Lis3.cookie = spiCookie; + break; + } + case (gpioIds::MGM_3_RM3100_CS): { + mgm3Rm3100.cookie = spiCookie; + break; + } + case (gpioIds::GYRO_0_ADIS_CS): { + gyro0Adis.cookie = spiCookie; + break; + } + case (gpioIds::GYRO_1_L3G_CS): { + gyro1L3g.cookie = spiCookie; + break; + } + case (gpioIds::GYRO_2_ADIS_CS): { + gyro2Adis.cookie = spiCookie; + break; + } + case (gpioIds::GYRO_3_L3G_CS): { + gyro3L3g.cookie = spiCookie; + break; + } + default: { + sif::error << "AcsBoardPollingTask: invalid spi cookie" << std::endl; + } + } + return spiComIF.initializeInterface(cookie); +} + +ReturnValue_t AcsBoardPolling::sendMessage(CookieIF* cookie, const uint8_t* sendData, + size_t sendLen) { + SpiCookie* spiCookie = dynamic_cast(cookie); + if (spiCookie == nullptr) { + return returnvalue::FAILED; + } + auto handleAdisRequest = [&](GyroAdis& adis) { + if (sendLen != sizeof(acs::Adis1650XRequest)) { + sif::error << "AcsBoardPolling: invalid adis request send length"; + adis.replyResult = returnvalue::FAILED; + return returnvalue::FAILED; + } + auto* req = reinterpret_cast(sendData); + if (req->mode != adis.mode) { + if (req->mode == acs::SimpleSensorMode::NORMAL) { + adis.type = req->type; + adis.decRate = req->cfg.decRateReg; + // The initial countdown is handled by the device handler now. + // adis.countdown.setTimeout(adis1650x::START_UP_TIME); + if (adis.type == adis1650x::Type::ADIS16507) { + adis.ownReply.data.accelScaling = adis1650x::ACCELEROMETER_RANGE_16507; + } else if (adis.type == adis1650x::Type::ADIS16505) { + adis.ownReply.data.accelScaling = adis1650x::ACCELEROMETER_RANGE_16505; + } else { + sif::warning << "AcsBoardPolling: Unknown ADIS type" << std::endl; + } + adis.replyResult = returnvalue::FAILED; + adis.performStartup = true; + } else if (req->mode == acs::SimpleSensorMode::OFF) { + adis.performStartup = false; + adis.ownReply.cfgWasSet = false; + adis.ownReply.dataWasSet = false; + adis.replyResult = returnvalue::OK; + } + adis.mode = req->mode; + } + return returnvalue::OK; + }; + auto handleL3gRequest = [&](GyroL3g& gyro) { + if (sendLen != sizeof(acs::GyroL3gRequest)) { + sif::error << "AcsBoardPolling: invalid l3g request send length"; + gyro.replyResult = returnvalue::FAILED; + return returnvalue::FAILED; + } + auto* req = reinterpret_cast(sendData); + if (req->mode != gyro.mode) { + if (req->mode == acs::SimpleSensorMode::NORMAL) { + std::memcpy(gyro.sensorCfg, req->ctrlRegs, 5); + gyro.performStartup = true; + gyro.replyResult = returnvalue::FAILED; + } else { + gyro.ownReply.cfgWasSet = false; + gyro.replyResult = returnvalue::OK; + } + gyro.mode = req->mode; + } + return returnvalue::OK; + }; + auto handleLis3Request = [&](MgmLis3& mgm) { + if (sendLen != sizeof(acs::MgmLis3Request)) { + sif::error << "AcsBoardPolling: invalid lis3 request send length"; + mgm.replyResult = returnvalue::FAILED; + return returnvalue::FAILED; + } + auto* req = reinterpret_cast(sendData); + if (req->mode != mgm.mode) { + if (req->mode == acs::SimpleSensorMode::NORMAL) { + mgm.performStartup = true; + mgm.replyResult = returnvalue::FAILED; + } else { + mgm.ownReply.dataWasSet = false; + mgm.replyResult = returnvalue::OK; + mgm.ownReply.temperatureWasSet = false; + } + mgm.mode = req->mode; + } + return returnvalue::OK; + }; + auto handleRm3100Request = [&](MgmRm3100& mgm) { + if (sendLen != sizeof(acs::MgmRm3100Request)) { + sif::error << "AcsBoardPolling: invalid rm3100 request send length"; + mgm.replyResult = returnvalue::FAILED; + return returnvalue::FAILED; + } + auto* req = reinterpret_cast(sendData); + if (req->mode != mgm.mode) { + if (req->mode == acs::SimpleSensorMode::NORMAL) { + mgm.performStartup = true; + mgm.replyResult = returnvalue::FAILED; + } else { + mgm.ownReply.dataWasRead = false; + mgm.replyResult = returnvalue::OK; + } + mgm.mode = req->mode; + } + return returnvalue::OK; + }; + { + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + switch (spiCookie->getChipSelectPin()) { + case (gpioIds::MGM_0_LIS3_CS): { + handleLis3Request(mgm0Lis3); + break; + } + case (gpioIds::MGM_1_RM3100_CS): { + handleRm3100Request(mgm1Rm3100); + break; + } + case (gpioIds::MGM_2_LIS3_CS): { + handleLis3Request(mgm2Lis3); + break; + } + case (gpioIds::MGM_3_RM3100_CS): { + handleRm3100Request(mgm3Rm3100); + break; + } + case (gpioIds::GYRO_0_ADIS_CS): { + handleAdisRequest(gyro0Adis); + break; + } + case (gpioIds::GYRO_2_ADIS_CS): { + handleAdisRequest(gyro2Adis); + break; + } + case (gpioIds::GYRO_1_L3G_CS): { + handleL3gRequest(gyro1L3g); + break; + } + case (gpioIds::GYRO_3_L3G_CS): { + handleL3gRequest(gyro3L3g); + break; + } + } + if (state == InternalState::IDLE) { + state = InternalState::IS_BUSY; + } + } + semaphore->release(); + return returnvalue::OK; +} + +ReturnValue_t AcsBoardPolling::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } + +ReturnValue_t AcsBoardPolling::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t AcsBoardPolling::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, + size_t* size) { + SpiCookie* spiCookie = dynamic_cast(cookie); + if (spiCookie == nullptr) { + return returnvalue::FAILED; + } + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + auto handleAdisReply = [&](GyroAdis& gyro) { + std::memcpy(&gyro.readerReply, &gyro.ownReply, sizeof(acs::Adis1650XReply)); + *buffer = reinterpret_cast(&gyro.readerReply); + *size = sizeof(acs::Adis1650XReply); + return gyro.replyResult; + }; + auto handleL3gReply = [&](GyroL3g& gyro) { + std::memcpy(&gyro.readerReply, &gyro.ownReply, sizeof(acs::GyroL3gReply)); + *buffer = reinterpret_cast(&gyro.readerReply); + *size = sizeof(acs::GyroL3gReply); + return gyro.replyResult; + }; + auto handleRm3100Reply = [&](MgmRm3100& mgm) { + std::memcpy(&mgm.readerReply, &mgm.ownReply, sizeof(acs::MgmRm3100Reply)); + *buffer = reinterpret_cast(&mgm.readerReply); + *size = sizeof(acs::MgmRm3100Reply); + return mgm.replyResult; + }; + auto handleLis3Reply = [&](MgmLis3& mgm) { + std::memcpy(&mgm.readerReply, &mgm.ownReply, sizeof(acs::MgmLis3Reply)); + *buffer = reinterpret_cast(&mgm.readerReply); + *size = sizeof(acs::MgmLis3Reply); + return mgm.replyResult; + }; + switch (spiCookie->getChipSelectPin()) { + case (gpioIds::MGM_0_LIS3_CS): { + return handleLis3Reply(mgm0Lis3); + } + case (gpioIds::MGM_1_RM3100_CS): { + return handleRm3100Reply(mgm1Rm3100); + } + case (gpioIds::MGM_2_LIS3_CS): { + return handleLis3Reply(mgm2Lis3); + } + case (gpioIds::MGM_3_RM3100_CS): { + return handleRm3100Reply(mgm3Rm3100); + } + case (gpioIds::GYRO_0_ADIS_CS): { + return handleAdisReply(gyro0Adis); + } + case (gpioIds::GYRO_2_ADIS_CS): { + return handleAdisReply(gyro2Adis); + } + case (gpioIds::GYRO_1_L3G_CS): { + return handleL3gReply(gyro1L3g); + } + case (gpioIds::GYRO_3_L3G_CS): { + return handleL3gReply(gyro3L3g); + } + } + return returnvalue::OK; +} + +void AcsBoardPolling::gyroL3gHandler(GyroL3g& l3g) { + ReturnValue_t result; + acs::SimpleSensorMode mode = acs::SimpleSensorMode::OFF; + bool gyroPerformStartup = false; + { + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + mode = l3g.mode; + gyroPerformStartup = l3g.performStartup; + } + if (mode == acs::SimpleSensorMode::NORMAL) { + if (gyroPerformStartup) { + cmdBuf[0] = l3gd20h::CTRL_REG_1 | l3gd20h::AUTO_INCREMENT_MASK; + std::memcpy(cmdBuf.data() + 1, l3g.sensorCfg, 5); + result = spiComIF.sendMessage(l3g.cookie, cmdBuf.data(), 6); + if (result != returnvalue::OK) { + l3g.replyResult = result; + } + // Ignore useless reply and red config + cmdBuf[0] = l3gd20h::CTRL_REG_1 | l3gd20h::AUTO_INCREMENT_MASK | l3gd20h::READ_MASK; + std::memset(cmdBuf.data() + 1, 0, 5); + result = spiComIF.sendMessage(l3g.cookie, cmdBuf.data(), 6); + if (result != returnvalue::OK) { + l3g.replyResult = result; + } + result = spiComIF.readReceivedMessage(l3g.cookie, &rawReply, &dummy); + if (result != returnvalue::OK) { + l3g.replyResult = result; + } + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + // Cross check configuration as verification that communication is working + for (uint8_t idx = 0; idx < 5; idx++) { + if (rawReply[idx + 1] != l3g.sensorCfg[idx]) { + sif::warning << "AcsBoardPolling: l3g config check missmatch" << std::endl; + l3g.replyResult = returnvalue::FAILED; + return; + } + } + l3g.replyResult = returnvalue::OK; + l3g.performStartup = false; + l3g.ownReply.cfgWasSet = true; + l3g.ownReply.sensitivity = l3gd20h::ctrlReg4ToSensitivity(l3g.sensorCfg[3]); + } + cmdBuf[0] = l3gd20h::READ_START | l3gd20h::AUTO_INCREMENT_MASK | l3gd20h::READ_MASK; + std::memset(cmdBuf.data() + 1, 0, l3gd20h::READ_LEN); + result = spiComIF.sendMessage(l3g.cookie, cmdBuf.data(), l3gd20h::READ_LEN + 1); + if (result != returnvalue::OK) { + l3g.replyResult = returnvalue::FAILED; + return; + } + result = spiComIF.readReceivedMessage(l3g.cookie, &rawReply, &dummy); + if (result != returnvalue::OK) { + l3g.replyResult = returnvalue::FAILED; + return; + } + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + // The regular read function always returns the full sensor config as well. Use that + // to verify communications. + for (uint8_t idx = 0; idx < 5; idx++) { + if (rawReply[idx + 1] != l3g.sensorCfg[idx]) { + sif::warning << "AcsBoardPolling: l3g config check missmatch" << std::endl; + l3g.replyResult = returnvalue::FAILED; + return; + } + } + l3g.replyResult = returnvalue::OK; + l3g.ownReply.statusReg = rawReply[l3gd20h::STATUS_IDX]; + l3g.ownReply.angVelocities[0] = (rawReply[l3gd20h::OUT_X_H] << 8) | rawReply[l3gd20h::OUT_X_L]; + l3g.ownReply.angVelocities[1] = (rawReply[l3gd20h::OUT_Y_H] << 8) | rawReply[l3gd20h::OUT_Y_L]; + l3g.ownReply.angVelocities[2] = (rawReply[l3gd20h::OUT_Z_H] << 8) | rawReply[l3gd20h::OUT_Z_L]; + l3g.ownReply.tempOffsetRaw = rawReply[l3gd20h::TEMPERATURE_IDX]; + } +} + +ReturnValue_t AcsBoardPolling::writeAdisReg(SpiCookie& cookie) { + ReturnValue_t result = returnvalue::OK; + int retval = 0; + // Prepare transfer + int fileDescriptor = 0; + std::string device = spiComIF.getSpiDev(); + UnixFileGuard fileHelper(device, fileDescriptor, O_RDWR, "SpiComIF::sendMessage"); + if (fileHelper.getOpenResult() != returnvalue::OK) { + return spi::OPENING_FILE_FAILED; + } + spi::SpiModes spiMode = spi::SpiModes::MODE_0; + uint32_t spiSpeed = 0; + cookie.getSpiParameters(spiMode, spiSpeed, nullptr); + spiComIF.setSpiSpeedAndMode(fileDescriptor, spiMode, spiSpeed); + cookie.assignWriteBuffer(cmdBuf.data()); + cookie.setTransferSize(2); + + gpioId_t gpioId = cookie.getChipSelectPin(); + MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING; + uint32_t timeoutMs = 0; + MutexIF* mutex = spiComIF.getCsMutex(); + cookie.getMutexParams(timeoutType, timeoutMs); + if (mutex == nullptr) { + sif::warning << "GyroADIS16507Handler::spiSendCallback: " + "Mutex or GPIO interface invalid" + << std::endl; + return returnvalue::FAILED; + } + + size_t idx = 0; + spi_ioc_transfer* transferStruct = cookie.getTransferStructHandle(); + uint64_t origTx = transferStruct->tx_buf; + uint64_t origRx = transferStruct->rx_buf; + for (idx = 0; idx < 4; idx += 2) { + result = mutex->lockMutex(timeoutType, timeoutMs); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "AcsBoardPolling: Failed to lock mutex" << std::endl; +#endif + return result; + } + // Pull SPI CS low. For now, no support for active high given + if (gpioId != gpio::NO_GPIO) { + gpioIF.pullLow(gpioId); + } + + // Execute transfer + // Initiate a full duplex SPI transfer. + retval = ioctl(fileDescriptor, SPI_IOC_MESSAGE(1), cookie.getTransferStructHandle()); + if (retval < 0) { + utility::handleIoctlError("SpiComIF::sendMessage: ioctl error."); + result = spi::FULL_DUPLEX_TRANSFER_FAILED; + } +#if FSFW_HAL_SPI_WIRETAPPING == 1 + comIf->performSpiWiretapping(cookie); +#endif /* FSFW_LINUX_SPI_WIRETAPPING == 1 */ + + if (gpioId != gpio::NO_GPIO) { + gpioIF.pullHigh(gpioId); + } + mutex->unlockMutex(); + + transferStruct->tx_buf += 2; + transferStruct->rx_buf += 2; + if (idx < 4) { + usleep(adis1650x::STALL_TIME_MICROSECONDS); + } + } + transferStruct->tx_buf = origTx; + transferStruct->rx_buf = origRx; + cookie.setTransferSize(0); + return returnvalue::OK; +} + +ReturnValue_t AcsBoardPolling::readAdisCfg(SpiCookie& cookie, size_t transferLen) { + ReturnValue_t result = returnvalue::OK; + int retval = 0; + // Prepare transfer + int fileDescriptor = 0; + std::string device = spiComIF.getSpiDev(); + UnixFileGuard fileHelper(device, fileDescriptor, O_RDWR, "SpiComIF::sendMessage"); + if (fileHelper.getOpenResult() != returnvalue::OK) { + return spi::OPENING_FILE_FAILED; + } + spi::SpiModes spiMode = spi::SpiModes::MODE_0; + uint32_t spiSpeed = 0; + cookie.getSpiParameters(spiMode, spiSpeed, nullptr); + spiComIF.setSpiSpeedAndMode(fileDescriptor, spiMode, spiSpeed); + cookie.assignWriteBuffer(cmdBuf.data()); + cookie.setTransferSize(2); + + gpioId_t gpioId = cookie.getChipSelectPin(); + MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING; + uint32_t timeoutMs = 0; + MutexIF* mutex = spiComIF.getCsMutex(); + cookie.getMutexParams(timeoutType, timeoutMs); + if (mutex == nullptr) { + sif::warning << "GyroADIS16507Handler::spiSendCallback: " + "Mutex or GPIO interface invalid" + << std::endl; + return returnvalue::FAILED; + } + + size_t idx = 0; + spi_ioc_transfer* transferStruct = cookie.getTransferStructHandle(); + uint64_t origTx = transferStruct->tx_buf; + uint64_t origRx = transferStruct->rx_buf; + while (idx < transferLen) { + result = mutex->lockMutex(timeoutType, timeoutMs); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "AcsBoardPolling: Failed to lock mutex" << std::endl; +#endif + return result; + } + // Pull SPI CS low. For now, no support for active high given + if (gpioId != gpio::NO_GPIO) { + gpioIF.pullLow(gpioId); + } + + // Execute transfer + // Initiate a full duplex SPI transfer. + retval = ioctl(fileDescriptor, SPI_IOC_MESSAGE(1), cookie.getTransferStructHandle()); + if (retval < 0) { + utility::handleIoctlError("SpiComIF::sendMessage: ioctl error."); + result = spi::FULL_DUPLEX_TRANSFER_FAILED; + } +#if FSFW_HAL_SPI_WIRETAPPING == 1 + comIf->performSpiWiretapping(cookie); +#endif /* FSFW_LINUX_SPI_WIRETAPPING == 1 */ + + if (gpioId != gpio::NO_GPIO) { + gpioIF.pullHigh(gpioId); + } + mutex->unlockMutex(); + + idx += 2; + transferStruct->tx_buf += 2; + transferStruct->rx_buf += 2; + if (idx < transferLen) { + usleep(adis1650x::STALL_TIME_MICROSECONDS); + } + } + transferStruct->tx_buf = origTx; + transferStruct->rx_buf = origRx; + cookie.setTransferSize(transferLen); + return returnvalue::OK; +} + +void AcsBoardPolling::gyroAdisHandler(GyroAdis& gyro) { + ReturnValue_t result; + acs::SimpleSensorMode mode = acs::SimpleSensorMode::OFF; + bool mustPerformStartup = false; + uint16_t decRate = 0; + { + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + mode = gyro.mode; + decRate = gyro.decRate; + mustPerformStartup = gyro.performStartup; + } + if (mode == acs::SimpleSensorMode::OFF) { + return; + } + if (mustPerformStartup) { + adis1650x::prepareWriteRegCommand(adis1650x::DEC_RATE_REG, decRate, cmdBuf.data(), + cmdBuf.size()); + writeAdisReg(*gyro.cookie); + uint8_t regList[6]; + // Read configuration + regList[0] = adis1650x::DIAG_STAT_REG; + regList[1] = adis1650x::FILTER_CTRL_REG; + regList[2] = adis1650x::RANG_MDL_REG; + regList[3] = adis1650x::MSC_CTRL_REG; + regList[4] = adis1650x::DEC_RATE_REG; + regList[5] = adis1650x::PROD_ID_REG; + size_t transferLen = + adis1650x::prepareReadCommand(regList, sizeof(regList), cmdBuf.data(), cmdBuf.size()); + result = readAdisCfg(*gyro.cookie, transferLen); + if (result != returnvalue::OK) { + gyro.replyResult = result; + return; + } + result = spiComIF.readReceivedMessage(gyro.cookie, &rawReply, &dummy); + if (result != returnvalue::OK or rawReply == nullptr) { + gyro.replyResult = result; + return; + } + uint16_t prodId = (rawReply[12] << 8) | rawReply[13]; + if (((gyro.type == adis1650x::Type::ADIS16505) and (prodId != adis1650x::PROD_ID_16505)) or + ((gyro.type == adis1650x::Type::ADIS16507) and (prodId != adis1650x::PROD_ID_16507))) { + sif::warning << "AcsPollingTask: Invalid ADIS product ID " << prodId << std::endl; + gyro.replyResult = returnvalue::FAILED; + return; + } + uint16_t decRateReadBack = (rawReply[10] << 8) | rawReply[11]; + if (decRateReadBack != decRate) { + sif::warning << "AcsPollingTask: DEC rate configuration failed. Read " << decRateReadBack + << ", expected " << decRate << std::endl; + gyro.replyResult = returnvalue::FAILED; + } + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + gyro.ownReply.cfgWasSet = true; + gyro.ownReply.cfg.diagStat = (rawReply[2] << 8) | rawReply[3]; + gyro.ownReply.cfg.filterSetting = (rawReply[4] << 8) | rawReply[5]; + gyro.ownReply.cfg.rangMdl = (rawReply[6] << 8) | rawReply[7]; + gyro.ownReply.cfg.mscCtrlReg = (rawReply[8] << 8) | rawReply[9]; + gyro.ownReply.cfg.decRateReg = decRateReadBack; + gyro.ownReply.cfg.prodId = prodId; + gyro.ownReply.data.sensitivity = adis1650x::rangMdlToSensitivity(gyro.ownReply.cfg.rangMdl); + gyro.performStartup = false; + gyro.replyResult = returnvalue::OK; + } + // Read regular registers + std::memcpy(cmdBuf.data(), adis1650x::BURST_READ_ENABLE.data(), + adis1650x::BURST_READ_ENABLE.size()); + std::memset(cmdBuf.data() + 2, 0, 10 * 2); + result = spiComIF.sendMessage(gyro.cookie, cmdBuf.data(), adis1650x::SENSOR_READOUT_SIZE); + if (result != returnvalue::OK) { + gyro.replyResult = returnvalue::FAILED; + return; + } + result = spiComIF.readReceivedMessage(gyro.cookie, &rawReply, &dummy); + if (result != returnvalue::OK or rawReply == nullptr) { + gyro.replyResult = returnvalue::FAILED; + return; + } + uint16_t checksum = (rawReply[20] << 8) | rawReply[21]; + + // Now verify the read checksum with the expected checksum according to datasheet p. 20 + uint16_t calcChecksum = 0; + for (size_t idx = 2; idx < 20; idx++) { + calcChecksum += rawReply[idx]; + } + if (checksum != calcChecksum) { + sif::warning << "AcsPollingTask: Invalid ADIS reply checksum" << std::endl; + gyro.replyResult = returnvalue::FAILED; + return; + } + + auto burstMode = adis1650x::burstModeFromMscCtrl(gyro.ownReply.cfg.mscCtrlReg); + if (burstMode != adis1650x::BurstModes::BURST_16_BURST_SEL_0) { + sif::error << "GyroADIS1650XHandler::interpretDeviceReply: Analysis for select burst mode" + " not implemented!" + << std::endl; + gyro.replyResult = returnvalue::FAILED; + return; + } + + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + gyro.replyResult = returnvalue::OK; + gyro.ownReply.dataWasSet = true; + gyro.ownReply.cfg.diagStat = rawReply[2] << 8 | rawReply[3]; + gyro.ownReply.data.angVelocities[0] = (rawReply[4] << 8) | rawReply[5]; + gyro.ownReply.data.angVelocities[1] = (rawReply[6] << 8) | rawReply[7]; + gyro.ownReply.data.angVelocities[2] = (rawReply[8] << 8) | rawReply[9]; + + gyro.ownReply.data.accelerations[0] = (rawReply[10] << 8) | rawReply[11]; + gyro.ownReply.data.accelerations[1] = (rawReply[12] << 8) | rawReply[13]; + gyro.ownReply.data.accelerations[2] = (rawReply[14] << 8) | rawReply[15]; + + gyro.ownReply.data.temperatureRaw = (rawReply[16] << 8) | rawReply[17]; +} + +void AcsBoardPolling::mgmLis3Handler(MgmLis3& mgm) { + ReturnValue_t result; + acs::SimpleSensorMode mode = acs::SimpleSensorMode::OFF; + bool mustPerformStartup = false; + { + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + mode = mgm.mode; + mustPerformStartup = mgm.performStartup; + } + if (mode == acs::SimpleSensorMode::NORMAL) { + if (mustPerformStartup) { + // To check valid communication, read back identification + // register which should always be the same value. + cmdBuf[0] = mgmLis3::readCommand(mgmLis3::IDENTIFY_DEVICE_REG_ADDR); + cmdBuf[1] = 0x00; + result = spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), 2); + if (result != OK) { + mgm.replyResult = result; + return; + } + result = spiComIF.readReceivedMessage(mgm.cookie, &rawReply, &dummy); + if (result != OK) { + mgm.replyResult = result; + return; + } + if (rawReply[1] != mgmLis3::DEVICE_ID) { + sif::error << "AcsPollingTask: invalid MGM lis3 device ID" << std::endl; + mgm.replyResult = result; + return; + } + mgm.cfg[0] = mgmLis3::CTRL_REG1_DEFAULT; + mgm.cfg[1] = mgmLis3::CTRL_REG2_DEFAULT; + mgm.cfg[2] = mgmLis3::CTRL_REG3_DEFAULT; + mgm.cfg[3] = mgmLis3::CTRL_REG4_DEFAULT; + mgm.cfg[4] = mgmLis3::CTRL_REG5_DEFAULT; + cmdBuf[0] = mgmLis3::writeCommand(mgmLis3::CTRL_REG1, true); + std::memcpy(cmdBuf.data() + 1, mgm.cfg, 5); + result = spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), 6); + if (result != OK) { + mgm.replyResult = result; + return; + } + // Done here. We can always read back config and data during periodic handling + mgm.performStartup = false; + mgm.replyResult = returnvalue::OK; + } + cmdBuf[0] = mgmLis3::readCommand(mgmLis3::CTRL_REG1, true); + std::memset(cmdBuf.data() + 1, 0, mgmLis3::NR_OF_DATA_AND_CFG_REGISTERS); + result = + spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), mgmLis3::NR_OF_DATA_AND_CFG_REGISTERS + 1); + if (result != returnvalue::OK) { + mgm.replyResult = result; + return; + } + result = spiComIF.readReceivedMessage(mgm.cookie, &rawReply, &dummy); + if (result != returnvalue::OK) { + mgm.replyResult = result; + return; + } + // Verify communication by re-checking config + if (rawReply[1] != mgm.cfg[0] or rawReply[2] != mgm.cfg[1] or rawReply[3] != mgm.cfg[2] or + rawReply[4] != mgm.cfg[3] or rawReply[5] != mgm.cfg[4]) { + mgm.replyResult = returnvalue::FAILED; + return; + } + { + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + mgm.ownReply.dataWasSet = true; + mgm.ownReply.sensitivity = mgmLis3::getSensitivityFactor(mgmLis3::getSensitivity(mgm.cfg[1])); + mgm.ownReply.mgmValuesRaw[0] = + (rawReply[mgmLis3::X_HIGHBYTE_IDX] << 8) | rawReply[mgmLis3::X_LOWBYTE_IDX]; + mgm.ownReply.mgmValuesRaw[1] = + (rawReply[mgmLis3::Y_HIGHBYTE_IDX] << 8) | rawReply[mgmLis3::Y_LOWBYTE_IDX]; + mgm.ownReply.mgmValuesRaw[2] = + (rawReply[mgmLis3::Z_HIGHBYTE_IDX] << 8) | rawReply[mgmLis3::Z_LOWBYTE_IDX]; + } + // Read tempetature + cmdBuf[0] = mgmLis3::readCommand(mgmLis3::TEMP_LOWBYTE, true); + result = spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), 3); + if (result != returnvalue::OK) { + mgm.replyResult = result; + return; + } + result = spiComIF.readReceivedMessage(mgm.cookie, &rawReply, &dummy); + if (result != returnvalue::OK) { + mgm.replyResult = result; + return; + } + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + mgm.replyResult = returnvalue::OK; + mgm.ownReply.temperatureWasSet = true; + mgm.ownReply.temperatureRaw = (rawReply[2] << 8) | rawReply[1]; + } +} + +void AcsBoardPolling::mgmRm3100Handler(MgmRm3100& mgm) { + ReturnValue_t result; + acs::SimpleSensorMode mode = acs::SimpleSensorMode::OFF; + bool mustPerformStartup = false; + { + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + mode = mgm.mode; + mustPerformStartup = mgm.performStartup; + } + if (mode == acs::SimpleSensorMode::NORMAL) { + if (mustPerformStartup) { + // Configure CMM first + cmdBuf[0] = mgmRm3100::CMM_REGISTER; + cmdBuf[1] = mgmRm3100::CMM_VALUE; + result = spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), 2); + if (result != OK) { + mgm.replyResult = result; + return; + } + // Read back register + cmdBuf[0] = mgmRm3100::CMM_REGISTER | mgmRm3100::READ_MASK; + cmdBuf[1] = 0; + result = spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), 2); + if (result != OK) { + mgm.replyResult = result; + return; + } + result = spiComIF.readReceivedMessage(mgm.cookie, &rawReply, &dummy); + if (result != OK) { + mgm.replyResult = result; + return; + } + // For some reason, bit 1 is sometimes set on the reply, even if it is not set for the + // command.. Ignore it for now by clearing it. + rawReply[1] &= ~(1 << 1); + if (rawReply[1] != mgmRm3100::CMM_VALUE) { + sif::error << "AcsBoardPolling: MGM RM3100 read back CMM invalid" << std::endl; + mgm.replyResult = result; + return; + } + // Configure TMRC register + cmdBuf[0] = mgmRm3100::TMRC_REGISTER; + // hardcoded for now + cmdBuf[1] = mgm.tmrcValue; + result = spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), 2); + if (result != OK) { + mgm.replyResult = result; + return; + } + // Read back and verify value + cmdBuf[0] = mgmRm3100::TMRC_REGISTER | mgmRm3100::READ_MASK; + cmdBuf[1] = 0; + result = spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), 2); + if (result != OK) { + mgm.replyResult = result; + return; + } + result = spiComIF.readReceivedMessage(mgm.cookie, &rawReply, &dummy); + if (result != OK) { + mgm.replyResult = result; + return; + } + if (rawReply[1] != mgm.tmrcValue) { + sif::error << "AcsBoardPolling: MGM RM3100 read back TMRC invalid" << std::endl; + mgm.replyResult = result; + return; + } + mgm.performStartup = false; + mgm.replyResult = returnvalue::OK; + } + // Regular read operation + cmdBuf[0] = mgmRm3100::MEASUREMENT_REG_START | mgmRm3100::READ_MASK; + std::memset(cmdBuf.data() + 1, 0, 9); + result = spiComIF.sendMessage(mgm.cookie, cmdBuf.data(), 10); + if (result != OK) { + mgm.replyResult = result; + return; + } + result = spiComIF.readReceivedMessage(mgm.cookie, &rawReply, &dummy); + if (result != OK) { + mgm.replyResult = result; + return; + } + MutexGuard mg(ipcLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + for (uint8_t idx = 0; idx < 3; idx++) { + // Hardcoded, but note that the gain depends on the cycle count + // value which is configurable! + mgm.ownReply.scaleFactors[idx] = 1.0 / mgmRm3100::DEFAULT_GAIN; + } + mgm.ownReply.dataWasRead = true; + mgm.replyResult = returnvalue::OK; + // Bitshift trickery to account for 24 bit signed value. + mgm.ownReply.mgmValuesRaw[0] = + ((rawReply[1] << 24) | (rawReply[2] << 16) | (rawReply[3] << 8)) >> 8; + mgm.ownReply.mgmValuesRaw[1] = + ((rawReply[4] << 24) | (rawReply[5] << 16) | (rawReply[6] << 8)) >> 8; + mgm.ownReply.mgmValuesRaw[2] = + ((rawReply[7] << 24) | (rawReply[8] << 16) | (rawReply[9] << 8)) >> 8; + } +} diff --git a/linux/acs/AcsBoardPolling.h b/linux/acs/AcsBoardPolling.h new file mode 100644 index 0000000..5b4d039 --- /dev/null +++ b/linux/acs/AcsBoardPolling.h @@ -0,0 +1,94 @@ +#ifndef LINUX_DEVICES_ACSBOARDPOLLING_H_ +#define LINUX_DEVICES_ACSBOARDPOLLING_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +class AcsBoardPolling : public SystemObject, + public ExecutableObjectIF, + public DeviceCommunicationIF { + public: + AcsBoardPolling(object_id_t objectId, SpiComIF& lowLevelComIF, GpioIF& gpioIF); + + ReturnValue_t performOperation(uint8_t operationCode) override; + ReturnValue_t initialize() override; + + private: + enum class InternalState { IDLE, IS_BUSY } state = InternalState::IDLE; + MutexIF* ipcLock; + static constexpr MutexIF::TimeoutType LOCK_TYPE = MutexIF::TimeoutType::WAITING; + static constexpr uint32_t LOCK_TIMEOUT = 20; + static constexpr char LOCK_CTX[] = "AcsBoardPolling"; + SemaphoreIF* semaphore; + std::array cmdBuf; + + struct DevBase { + SpiCookie* cookie = nullptr; + bool performStartup = false; + acs::SimpleSensorMode mode = acs::SimpleSensorMode::OFF; + ReturnValue_t replyResult = returnvalue::OK; + }; + + struct GyroAdis : public DevBase { + adis1650x::Type type; + uint16_t decRate; + Countdown countdown; + acs::Adis1650XReply ownReply; + acs::Adis1650XReply readerReply; + }; + GyroAdis gyro0Adis{}; + GyroAdis gyro2Adis{}; + + struct GyroL3g : public DevBase { + uint8_t sensorCfg[5]; + acs::GyroL3gReply ownReply; + acs::GyroL3gReply readerReply; + }; + GyroL3g gyro1L3g{}; + GyroL3g gyro3L3g{}; + + struct MgmRm3100 : public DevBase { + uint8_t tmrcValue = mgmRm3100::TMRC_DEFAULT_37HZ_VALUE; + acs::MgmRm3100Reply ownReply; + acs::MgmRm3100Reply readerReply; + }; + MgmRm3100 mgm1Rm3100; + MgmRm3100 mgm3Rm3100; + + struct MgmLis3 : public DevBase { + uint8_t cfg[5]{}; + acs::MgmLis3Reply ownReply; + acs::MgmLis3Reply readerReply; + }; + MgmLis3 mgm0Lis3; + MgmLis3 mgm2Lis3; + + uint8_t* rawReply = nullptr; + size_t dummy = 0; + + SpiComIF& spiComIF; + GpioIF& gpioIF; + + ReturnValue_t initializeInterface(CookieIF* cookie) override; + ReturnValue_t sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF* cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF* cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) override; + + void gyroL3gHandler(GyroL3g& l3g); + void gyroAdisHandler(GyroAdis& gyro); + void mgmLis3Handler(MgmLis3& mgm); + void mgmRm3100Handler(MgmRm3100& mgm); + // This fumction configures the register as specified on p.21 of the datasheet. + ReturnValue_t writeAdisReg(SpiCookie& cookie); + // Special readout: 16us stall time between small 2 byte transfers. + ReturnValue_t readAdisCfg(SpiCookie& spiCookie, size_t transferLen); +}; + +#endif /* LINUX_DEVICES_ACSBOARDPOLLING_H_ */ diff --git a/linux/acs/CMakeLists.txt b/linux/acs/CMakeLists.txt new file mode 100644 index 0000000..5b5b5ac --- /dev/null +++ b/linux/acs/CMakeLists.txt @@ -0,0 +1,11 @@ +target_sources(${OBSW_NAME} PUBLIC AcsBoardPolling.cpp ImtqPollingTask.cpp + RwPollingTask.cpp SusPolling.cpp) + +# Dependency on proprietary library +if(TGT_BSP MATCHES "arm/q7s") + target_sources(${OBSW_NAME} PUBLIC StrComHandler.cpp) +endif() + +if(EIVE_BUILD_GPSD_GPS_HANDLER) + target_sources(${OBSW_NAME} PRIVATE GpsHyperionLinuxController.cpp) +endif() diff --git a/linux/acs/GPSDefinitions.h b/linux/acs/GPSDefinitions.h new file mode 100644 index 0000000..82025a2 --- /dev/null +++ b/linux/acs/GPSDefinitions.h @@ -0,0 +1,119 @@ +#ifndef MISSION_ACS_ARCHIVE_GPSDEFINITIONS_H_ +#define MISSION_ACS_ARCHIVE_GPSDEFINITIONS_H_ + +#include "eive/eventSubsystemIds.h" +#include "fsfw/datapoollocal/StaticLocalDataSet.h" +#include "fsfw/devicehandlers/DeviceHandlerIF.h" + +namespace GpsHyperion { + +enum FixMode : uint8_t { NOT_SEEN = 0, NO_FIX = 1, FIX_2D = 2, FIX_3D = 3 }; + +enum GnssChip : uint8_t { A_SIDE = 0, B_SIDE = 1 }; + +static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::GPS_HANDLER; +//! [EXPORT] : [COMMENT] Fix has changed. P1: New fix. P2: Missed fix changes +//! 0: Not seen, 1: No Fix, 2: 2D-Fix, 3: 3D-Fix +static constexpr Event GPS_FIX_CHANGE = event::makeEvent(SUBSYSTEM_ID, 0, severity::INFO); +//! [EXPORT] : [COMMENT] Could not get fix in maximum allowed time. Trying to reset both GNSS +//! devices. P1: Maximum allowed time to get a fix after the GPS was switched on. +static constexpr Event CANT_GET_FIX = event::makeEvent(SUBSYSTEM_ID, 1, severity::MEDIUM); +//! [EXPORT] : [COMMENT] Failed to reset an GNNS Device. P1: Board-Side. +static constexpr Event RESET_FAIL = event::makeEvent(SUBSYSTEM_ID, 2, severity::HIGH); + +static constexpr DeviceCommandId_t GPS_REPLY = 0; +static constexpr DeviceCommandId_t TRIGGER_RESET_PIN_GNSS = 5; + +enum SetIds : uint32_t { + CORE_DATASET, + SKYVIEW_DATASET, +}; + +enum GpsPoolIds : lp_id_t { + LATITUDE, + LONGITUDE, + ALTITUDE, + SPEED, + FIX_MODE, + SATS_IN_USE, + SATS_IN_VIEW, + UNIX_SECONDS, + YEAR, + MONTH, + DAY, + HOURS, + MINUTES, + SECONDS, + SKYVIEW_UNIX_SECONDS, + PRN_ID, + AZIMUTH, + ELEVATION, + SIGNAL2NOISE, + USED, +}; + +static constexpr uint8_t CORE_DATASET_ENTRIES = 14; +static constexpr uint8_t SKYVIEW_ENTRIES = 6; + +static constexpr uint8_t MAX_SATELLITES = 30; + +} // namespace GpsHyperion + +class GpsPrimaryDataset : public StaticLocalDataSet { + public: + GpsPrimaryDataset(object_id_t gpsId) + : StaticLocalDataSet(sid_t(gpsId, GpsHyperion::CORE_DATASET)) { + setAllVariablesReadOnly(); + } + + lp_var_t latitude = lp_var_t(sid.objectId, GpsHyperion::LATITUDE, this); + lp_var_t longitude = lp_var_t(sid.objectId, GpsHyperion::LONGITUDE, this); + lp_var_t altitude = lp_var_t(sid.objectId, GpsHyperion::ALTITUDE, this); + lp_var_t speed = lp_var_t(sid.objectId, GpsHyperion::SPEED, this); + lp_var_t fixMode = lp_var_t(sid.objectId, GpsHyperion::FIX_MODE, this); + lp_var_t satInUse = lp_var_t(sid.objectId, GpsHyperion::SATS_IN_USE, this); + lp_var_t satInView = lp_var_t(sid.objectId, GpsHyperion::SATS_IN_VIEW, this); + lp_var_t year = lp_var_t(sid.objectId, GpsHyperion::YEAR, this); + lp_var_t month = lp_var_t(sid.objectId, GpsHyperion::MONTH, this); + lp_var_t day = lp_var_t(sid.objectId, GpsHyperion::DAY, this); + lp_var_t hours = lp_var_t(sid.objectId, GpsHyperion::HOURS, this); + lp_var_t minutes = lp_var_t(sid.objectId, GpsHyperion::MINUTES, this); + lp_var_t seconds = lp_var_t(sid.objectId, GpsHyperion::SECONDS, this); + lp_var_t unixSeconds = + lp_var_t(sid.objectId, GpsHyperion::UNIX_SECONDS, this); + + private: + friend class GpsHyperionLinuxController; + friend class GpsCtrlDummy; + GpsPrimaryDataset(HasLocalDataPoolIF* hkOwner) + : StaticLocalDataSet(hkOwner, GpsHyperion::CORE_DATASET) {} +}; + +class SkyviewDataset : public StaticLocalDataSet { + public: + SkyviewDataset(object_id_t gpsId) + : StaticLocalDataSet(sid_t(gpsId, GpsHyperion::SKYVIEW_DATASET)) { + setAllVariablesReadOnly(); + } + + lp_var_t unixSeconds = + lp_var_t(sid.objectId, GpsHyperion::SKYVIEW_UNIX_SECONDS, this); + lp_vec_t prn_id = + lp_vec_t(sid.objectId, GpsHyperion::PRN_ID, this); + lp_vec_t azimuth = + lp_vec_t(sid.objectId, GpsHyperion::AZIMUTH, this); + lp_vec_t elevation = + lp_vec_t(sid.objectId, GpsHyperion::ELEVATION, this); + lp_vec_t signal2noise = + lp_vec_t(sid.objectId, GpsHyperion::SIGNAL2NOISE, this); + lp_vec_t used = + lp_vec_t(sid.objectId, GpsHyperion::USED, this); + + private: + friend class GpsHyperionLinuxController; + friend class GpsCtrlDummy; + SkyviewDataset(HasLocalDataPoolIF* hkOwner) + : StaticLocalDataSet(hkOwner, GpsHyperion::SKYVIEW_DATASET) {} +}; + +#endif /* MISSION_ACS_ARCHIVE_GPSDEFINITIONS_H_ */ diff --git a/linux/acs/GpsHyperionLinuxController.cpp b/linux/acs/GpsHyperionLinuxController.cpp new file mode 100644 index 0000000..9c4bd54 --- /dev/null +++ b/linux/acs/GpsHyperionLinuxController.cpp @@ -0,0 +1,463 @@ +#include "GpsHyperionLinuxController.h" + +#include +#include + +#include "OBSWConfig.h" +#include "fsfw/FSFW.h" +#include "fsfw/datapool/PoolReadGuard.h" +#include "fsfw/timemanager/Clock.h" +#include "linux/utility/utility.h" +#include "mission/utility/compileTime.h" + +#if FSFW_DEV_HYPERION_GPS_CREATE_NMEA_CSV == 1 +#include +#include +#endif +#include +#include + +GpsHyperionLinuxController::GpsHyperionLinuxController(object_id_t objectId, object_id_t parentId, + bool enableHkSets, bool debugHyperionGps) + : ExtendedControllerBase(objectId), + gpsSet(this), + skyviewSet(this), + enableHkSets(enableHkSets), + debugHyperionGps(debugHyperionGps) {} + +GpsHyperionLinuxController::~GpsHyperionLinuxController() { + gps_stream(&gps, WATCH_DISABLE, nullptr); + gps_close(&gps); +} + +LocalPoolDataSetBase *GpsHyperionLinuxController::getDataSetHandle(sid_t sid) { + switch (sid.ownerSetId) { + case GpsHyperion::CORE_DATASET: + return &gpsSet; + case GpsHyperion::SKYVIEW_DATASET: + return &skyviewSet; + default: + return nullptr; + } + return nullptr; +} + +ReturnValue_t GpsHyperionLinuxController::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t *msToReachTheMode) { + if (mode == MODE_ON) { + maxTimeToReachFix.resetTimer(); + gainedNewFix.timeOut(); + } else if (mode == MODE_NORMAL) { + return HasModesIF::INVALID_MODE; + } + if (mode == MODE_OFF) { + maxTimeToReachFix.timeOut(); + gainedNewFix.timeOut(); + PoolReadGuard pg(&gpsSet); + gpsSet.setValidity(false, true); + // The ctrl is off, so it cannot detect the data from the devices. + handleFixChangedEvent(GpsHyperion::FixMode::NOT_SEEN); + gpsSet.fixMode.value = GpsHyperion::FixMode::NOT_SEEN; + oneShotSwitches.reset(); + } + return returnvalue::OK; +} + +ReturnValue_t GpsHyperionLinuxController::executeAction(ActionId_t actionId, + MessageQueueId_t commandedBy, + const uint8_t *data, size_t size) { + switch (actionId) { + case (GpsHyperion::TRIGGER_RESET_PIN_GNSS): { + if (resetCallback != nullptr) { + PoolReadGuard pg(&gpsSet); + // Set HK entries invalid + gpsSet.setValidity(false, true); + ReturnValue_t result = resetCallback(data, size, resetCallbackArgs); + if (result != returnvalue::OK) { + return result; + } + return HasActionsIF::EXECUTION_FINISHED; + } + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + } + return HasActionsIF::INVALID_ACTION_ID; +} + +ReturnValue_t GpsHyperionLinuxController::initializeLocalDataPool( + localpool::DataPool &localDataPoolMap, LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(GpsHyperion::ALTITUDE, new PoolEntry({0.0})); + localDataPoolMap.emplace(GpsHyperion::LONGITUDE, new PoolEntry({0.0})); + localDataPoolMap.emplace(GpsHyperion::LATITUDE, new PoolEntry({0.0})); + localDataPoolMap.emplace(GpsHyperion::SPEED, new PoolEntry({0.0})); + localDataPoolMap.emplace(GpsHyperion::YEAR, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::MONTH, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::DAY, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::HOURS, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::MINUTES, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::SECONDS, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::UNIX_SECONDS, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::SATS_IN_USE, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::SATS_IN_VIEW, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::FIX_MODE, new PoolEntry()); + poolManager.subscribeForRegularPeriodicPacket({gpsSet.getSid(), enableHkSets, 60.0}); + localDataPoolMap.emplace(GpsHyperion::SKYVIEW_UNIX_SECONDS, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::PRN_ID, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::AZIMUTH, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::ELEVATION, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::SIGNAL2NOISE, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::USED, new PoolEntry()); + poolManager.subscribeForRegularPeriodicPacket({skyviewSet.getSid(), false, 120.0}); + return returnvalue::OK; +} + +void GpsHyperionLinuxController::setResetPinTriggerFunction(gpioResetFunction_t resetCallback, + void *args) { + this->resetCallback = resetCallback; + resetCallbackArgs = args; +} + +ReturnValue_t GpsHyperionLinuxController::performOperation(uint8_t opCode) { + handleQueue(); + poolManager.performHkOperation(); + + while (true) { +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "GPS CTRL"); +#endif + bool callAgainImmediately = readGpsDataFromGpsd(); + if (not callAgainImmediately) { + handleQueue(); + poolManager.performHkOperation(); + TaskFactory::delayTask(250); + } + } + // Should never be reached. + return returnvalue::OK; +} + +ReturnValue_t GpsHyperionLinuxController::initialize() { + ReturnValue_t result = ExtendedControllerBase::initialize(); + if (result != returnvalue::OK) { + return result; + } + auto openError = [&](const char *type, int error) { + // Opening failed +#if FSFW_VERBOSE_LEVEL >= 1 + sif::warning << "GPSHyperionHandler::readGpsDataFromGpsd: Opening GPSMM " << type + << " failed | Error " << error << " | " << gps_errstr(error) << std::endl; +#endif + }; + if (readMode == ReadModes::SOCKET) { + int retval = gps_open("localhost", DEFAULT_GPSD_PORT, &gps); + if (retval != 0) { + openError("Socket", retval); + return ObjectManager::CHILD_INIT_FAILED; + } + gps_stream(&gps, WATCH_ENABLE | WATCH_JSON, nullptr); + } else if (readMode == ReadModes::SHM) { + int retval = gps_open(GPSD_SHARED_MEMORY, "", &gps); + if (retval != 0) { + openError("SHM", retval); + return ObjectManager::CHILD_INIT_FAILED; + } + } + return result; +} + +ReturnValue_t GpsHyperionLinuxController::handleCommandMessage(CommandMessage *message) { + return ExtendedControllerBase::handleCommandMessage(message); +} + +void GpsHyperionLinuxController::performControlOperation() {} + +bool GpsHyperionLinuxController::readGpsDataFromGpsd() { + auto readError = [&]() { + if (oneShotSwitches.gpsReadFailedSwitch) { + oneShotSwitches.gpsReadFailedSwitch = false; + sif::warning << "GPSHyperionHandler::readGpsDataFromGpsd: Reading GPS data failed | " + "Error " + << errno << " | " << gps_errstr(errno) << std::endl; + } + }; + // GPS is off, no point in reading data from GPSD. + if (mode == MODE_OFF) { + return false; + } + unsigned int readIdx = 0; + if (readMode == ReadModes::SOCKET) { + // Poll the GPS. + while (gps_waiting(&gps, 0)) { + int retval = gps_read(&gps); + if (retval < 0) { + readError(); + return false; + } + readIdx++; + if (readIdx >= 40) { + sif::warning << "GpsHyperionLinuxController: Received " << readIdx + << " GPSD message consecutively" << std::endl; + break; + } + } + if (readIdx > 0) { + oneShotSwitches.gpsReadFailedSwitch = true; + handleGpsReadData(); + } + } else if (readMode == ReadModes::SHM) { + sif::error << "GpsHyperionLinuxController::readGpsDataFromGpsdPermanentLoop: " + "SHM read not implemented" + << std::endl; + } + return false; +} + +ReturnValue_t GpsHyperionLinuxController::handleGpsReadData() { + bool modeIsSet = true; + if (MODE_SET != (MODE_SET & gps.set)) { + if (mode != MODE_OFF) { + modeIsSet = false; + } else { + // GPS ctrl is off anyway, so do other handling + return returnvalue::FAILED; + } + } + ReturnValue_t result = handleCoreTelemetry(modeIsSet); + if (result != returnvalue::OK) { + return result; + } + result = handleSkyviewTelemetry(); + return result; +} + +ReturnValue_t GpsHyperionLinuxController::handleCoreTelemetry(bool modeIsSet) { + PoolReadGuard pg(&gpsSet); + if (pg.getReadResult() != returnvalue::OK) { +#if FSFW_VERBOSE_LEVEL >= 1 + sif::warning << "GPSHyperionHandler::readGpsDataFromGpsd: Reading dataset failed" << std::endl; +#endif + return returnvalue::FAILED; + } + + bool validFix = false; + uint8_t newFix = 0; + if (modeIsSet) { + // 0: Not seen, 1: No fix, 2: 2D-Fix, 3: 3D-Fix + if (gps.fix.mode == GpsHyperion::FixMode::FIX_2D or + gps.fix.mode == GpsHyperion::FixMode::FIX_3D) { + validFix = true; + maxTimeToReachFix.resetTimer(); + } + newFix = gps.fix.mode; + } + if (gpsSet.fixMode.value != newFix) { + handleFixChangedEvent(newFix); + } + gpsSet.fixMode = newFix; + gpsSet.fixMode.setValid(modeIsSet); + // We are supposed to be on and functioning, but no fix was found + if (not validFix) { + if (maxTimeToReachFix.hasTimedOut()) { + // Set HK entries invalid + gpsSet.setValidity(false, true); + if (oneShotSwitches.cantGetFixSwitch) { + sif::warning << "GpsHyperionLinuxController: No fix detected in allowed " + << maxTimeToReachFix.getTimeoutMs() / 1000 << " seconds" << std::endl; + triggerEvent(GpsHyperion::CANT_GET_FIX, maxTimeToReachFix.getTimeoutMs()); + oneShotSwitches.cantGetFixSwitch = false; + // Try resetting the devices + if (resetCallback != nullptr) { + uint8_t chip = GpsHyperion::GnssChip::A_SIDE; + ReturnValue_t result = resetCallback(&chip, 1, resetCallbackArgs); + if (result != returnvalue::OK) { + triggerEvent(GpsHyperion::RESET_FAIL, chip); + } + chip = GpsHyperion::GnssChip::B_SIDE; + result = resetCallback(&chip, 1, resetCallbackArgs); + if (result != returnvalue::OK) { + triggerEvent(GpsHyperion::RESET_FAIL, chip); + } + } + } + } + } + + // Only set on specific messages, so only set a valid flag to invalid + // if not set for more than a full message set (10 messages here) + if (SATELLITE_SET == (SATELLITE_SET & gps.set)) { + gpsSet.satInUse.value = gps.satellites_used; + gpsSet.satInView.value = gps.satellites_visible; + if (not gpsSet.satInUse.isValid()) { + gpsSet.satInUse.setValid(true); + gpsSet.satInView.setValid(true); + } + satNotSetCounter = 0; + } else { + if (satNotSetCounter < 10) { + satNotSetCounter++; + } else { + gpsSet.satInUse.value = 0; + gpsSet.satInUse.setValid(false); + gpsSet.satInView.value = 0; + gpsSet.satInView.setValid(false); + } + } + + // LATLON is set for every message, no need for a counter + bool latValid = false; + bool longValid = false; + if (modeIsSet) { + if (LATLON_SET == (LATLON_SET & gps.set)) { + if (std::isfinite(gps.fix.latitude)) { + // Negative latitude -> South direction + gpsSet.latitude.value = gps.fix.latitude; + // As specified in gps.h: Only valid if mode >= 2 + if (gps.fix.mode >= GpsHyperion::FixMode::FIX_2D) { + latValid = true; + } + } + + if (std::isfinite(gps.fix.longitude)) { + // Negative longitude -> West direction + gpsSet.longitude.value = gps.fix.longitude; + // As specified in gps.h: Only valid if mode >= 2 + if (gps.fix.mode >= GpsHyperion::FixMode::FIX_2D) { + longValid = true; + } + } + } + } + gpsSet.latitude.setValid(latValid); + gpsSet.longitude.setValid(longValid); + + // ALTITUDE is set for every message, no need for a counter + bool altitudeValid = false; + if (modeIsSet) { + if (ALTITUDE_SET == (ALTITUDE_SET & gps.set) && std::isfinite(gps.fix.altitude)) { + gpsSet.altitude.value = gps.fix.altitude; + // As specified in gps.h: Only valid if mode == 3 + if (gps.fix.mode == GpsHyperion::FixMode::FIX_3D) { + altitudeValid = true; + } + } + } + gpsSet.altitude.setValid(altitudeValid); + + // SPEED is set for every message, no need for a counter + bool speedValid = false; + if (modeIsSet) { + if (SPEED_SET == (SPEED_SET & gps.set) && std::isfinite(gps.fix.speed)) { + gpsSet.speed.value = gps.fix.speed; + speedValid = true; + } + } + gpsSet.speed.setValid(speedValid); + + // TIME is set for every message, no need for a counter + bool timeValid = false; + if (TIME_SET == (TIME_SET & gps.set)) { + // To prevent totally incorrect times from being declared valid. + if (gpsSet.satInView.isValid() and gpsSet.satInView.value >= 1) { + timeValid = true; + } + timeval time = {}; +#if LIBGPS_VERSION_MINOR <= 17 + gpsSet.unixSeconds.value = std::floor(gps.fix.time); + double fractionalPart = gps.fix.time - gpsSet.unixSeconds.value; + time.tv_usec = fractionalPart * 1000.0 * 1000.0; +#else + gpsSet.unixSeconds.value = gps.fix.time.tv_sec; + time.tv_usec = gps.fix.time.tv_nsec / 1000; +#endif + time.tv_sec = gpsSet.unixSeconds.value; + // If the time is totally wrong (e.g. year 2000 after system reset because we do not have a RTC + // and no time file available) we set it with the roughly valid time from the GPS. + // NTP might only work if the time difference between sys time and current time is not too + // large. + overwriteTimeIfNotSane(time, validFix); + Clock::TimeOfDay_t timeOfDay = {}; + Clock::convertTimevalToTimeOfDay(&time, &timeOfDay); + gpsSet.year = timeOfDay.year; + gpsSet.month = timeOfDay.month; + gpsSet.day = timeOfDay.day; + gpsSet.hours = timeOfDay.hour; + gpsSet.minutes = timeOfDay.minute; + gpsSet.seconds = timeOfDay.second; + } + gpsSet.unixSeconds.setValid(timeValid); + gpsSet.year.setValid(timeValid); + gpsSet.month.setValid(timeValid); + gpsSet.day.setValid(timeValid); + gpsSet.hours.setValid(timeValid); + gpsSet.minutes.setValid(timeValid); + gpsSet.seconds.setValid(timeValid); + + if (debugHyperionGps) { + sif::info << "-- Hyperion GPS Data --" << std::endl; +#if LIBGPS_VERSION_MINOR <= 17 + time_t timeRaw = gpsSet.unixSeconds.value; +#else + time_t timeRaw = gps.fix.time.tv_sec; +#endif + std::tm *time = gmtime(&timeRaw); + std::cout << "Time: " << std::put_time(time, "%c %Z") << std::endl; + std::cout << "Visible satellites: " << gps.satellites_visible << std::endl; + std::cout << "Satellites used: " << gps.satellites_used << std::endl; + std::cout << "Fix (0:Not Seen|1:No Fix|2:2D|3:3D): " << gps.fix.mode << std::endl; + std::cout << "Latitude: " << gps.fix.latitude << std::endl; + std::cout << "Longitude: " << gps.fix.longitude << std::endl; +#if LIBGPS_VERSION_MINOR <= 17 + std::cout << "Altitude(MSL): " << gps.fix.altitude << std::endl; +#else + std::cout << "Altitude(MSL): " << gps.fix.altMSL << std::endl; +#endif + std::cout << "Speed(m/s): " << gps.fix.speed << std::endl; + std::time_t t = std::time(nullptr); + std::tm tm = *std::gmtime(&t); + std::cout << "C Time: " << std::put_time(&tm, "%c") << std::endl; + } + return returnvalue::OK; +} + +ReturnValue_t GpsHyperionLinuxController::handleSkyviewTelemetry() { + PoolReadGuard pg(&skyviewSet); + if (pg.getReadResult() != returnvalue::OK) { + return returnvalue::FAILED; + } + skyviewSet.unixSeconds.value = gps.skyview_time; + for (int sat = 0; sat < GpsHyperion::MAX_SATELLITES; sat++) { + skyviewSet.prn_id.value[sat] = gps.skyview[sat].PRN; + skyviewSet.azimuth.value[sat] = gps.skyview[sat].azimuth; + skyviewSet.elevation.value[sat] = gps.skyview[sat].elevation; + skyviewSet.signal2noise.value[sat] = gps.skyview[sat].ss; + skyviewSet.used.value[sat] = gps.skyview[sat].used; + } + return returnvalue::OK; +} + +void GpsHyperionLinuxController::overwriteTimeIfNotSane(timeval time, bool validFix) { + if (not timeInit and validFix) { + if (not utility::timeSanityCheck()) { +#if OBSW_VERBOSE_LEVEL >= 1 + time_t timeRaw = time.tv_sec; + std::tm *timeTm = std::gmtime(&timeRaw); + sif::info << "Overwriting invalid system time from GPS data directly: " + << std::put_time(timeTm, "%c %Z") << std::endl; +#endif + // For some reason, the clock needs to be somewhat correct for NTP to work. Really dumb.. + Clock::setClock(&time); + } + timeInit = true; + } +} + +void GpsHyperionLinuxController::handleFixChangedEvent(uint8_t newFix) { + if (gainedNewFix.hasTimedOut()) { + triggerEvent(GpsHyperion::GPS_FIX_CHANGE, newFix, fixChangeCounter); + fixChangeCounter = 0; + gainedNewFix.resetTimer(); + return; + } + fixChangeCounter++; + gainedNewFix.resetTimer(); +} diff --git a/linux/acs/GpsHyperionLinuxController.h b/linux/acs/GpsHyperionLinuxController.h new file mode 100644 index 0000000..b8c3ed7 --- /dev/null +++ b/linux/acs/GpsHyperionLinuxController.h @@ -0,0 +1,99 @@ +#ifndef MISSION_DEVICES_GPSHYPERIONHANDLER_H_ +#define MISSION_DEVICES_GPSHYPERIONHANDLER_H_ + +#include +#include +#include +#include +#include +#include + +#ifdef FSFW_OSAL_LINUX +#include +#include +#endif + +/** + * @brief Device handler for the Hyperion HT-GPS200 device + * @details + * Flight manual: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/Hyperion_HT-GPS200 + * This device handler can only be used on Linux system where the gpsd daemon with shared memory + * export is running. + */ +class GpsHyperionLinuxController : public ExtendedControllerBase { + public: + // 15 minutes + static constexpr uint32_t MAX_SECONDS_TO_REACH_FIX = 60 * 15; + + enum ReadModes { SHM = 0, SOCKET = 1 }; + + GpsHyperionLinuxController(object_id_t objectId, object_id_t parentId, bool enableHkSets, + bool debugHyperionGps = false); + virtual ~GpsHyperionLinuxController(); + + using gpioResetFunction_t = ReturnValue_t (*)(const uint8_t* actionData, size_t len, void* args); + + ReturnValue_t performOperation(uint8_t opCode) override; + void setResetPinTriggerFunction(gpioResetFunction_t resetCallback, void* args); + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + void performControlOperation() override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + ReturnValue_t initialize() override; + + protected: + gpioResetFunction_t resetCallback = nullptr; + void* resetCallbackArgs = nullptr; + + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + ReturnValue_t handleGpsReadData(); + ReturnValue_t handleCoreTelemetry(bool modeIsSet); + ReturnValue_t handleSkyviewTelemetry(); + + private: + GpsPrimaryDataset gpsSet; + SkyviewDataset skyviewSet; + gps_data_t gps = {}; + bool enableHkSets = false; + const char* currentClientBuf = nullptr; + ReadModes readMode = ReadModes::SOCKET; + Countdown maxTimeToReachFix = Countdown(MAX_SECONDS_TO_REACH_FIX * 1000); + Countdown gainedNewFix = Countdown(60 * 2 * 1000); + uint32_t fixChangeCounter = 0; + bool timeInit = false; + uint8_t satNotSetCounter = 0; + +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter = 0; +#endif + + struct OneShotSwitches { + void reset() { + gpsReadFailedSwitch = true; + cantGetFixSwitch = true; + } + bool gpsReadFailedSwitch = true; + bool cantGetFixSwitch = true; + + } oneShotSwitches; + + bool debugHyperionGps = false; + + // Returns true if the function should be called again or false if other + // controller handling can be done. + bool readGpsDataFromGpsd(); + // If the time is totally wrong (e.g. year 2000 after system reset because we do not have a RTC) + // we set it with the roughly valid time from the GPS. For some reason, NTP might only work + // if the time difference between sys time and current time is not too large + void overwriteTimeIfNotSane(timeval time, bool validFix); + + void handleFixChangedEvent(uint8_t newFix); +}; + +#endif /* MISSION_DEVICES_GPSHYPERIONHANDLER_H_ */ diff --git a/linux/acs/ImtqPollingTask.cpp b/linux/acs/ImtqPollingTask.cpp new file mode 100644 index 0000000..efa6573 --- /dev/null +++ b/linux/acs/ImtqPollingTask.cpp @@ -0,0 +1,520 @@ +#include "ImtqPollingTask.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "fsfw/FSFW.h" + +ImtqPollingTask::ImtqPollingTask(object_id_t imtqPollingTask, std::atomic_uint16_t& i2cFatalErrors) + : SystemObject(imtqPollingTask), i2cFatalErrors(i2cFatalErrors) { + semaphore = SemaphoreFactory::instance()->createBinarySemaphore(); + semaphore->acquire(); + ipcLock = MutexFactory::instance()->createMutex(); + bufLock = MutexFactory::instance()->createMutex(); +} + +ReturnValue_t ImtqPollingTask::performOperation(uint8_t operationCode) { + while (true) { + ipcLock->lockMutex(); + state = InternalState::IDLE; + ipcLock->unlockMutex(); + semaphore->acquire(); + + comStatus = returnvalue::OK; + // Stopwatch watch; + switch (currentRequest.requestType) { + case imtq::RequestType::MEASURE_NO_ACTUATION: { + // Measured to take 24 ms for debug and release builds. + // Stopwatch watch; + handleMeasureStep(); + break; + } + case imtq::RequestType::ACTUATE: { + handleActuateStep(); + break; + } + default: { + break; + } + }; + } + return returnvalue::OK; +} + +void ImtqPollingTask::handleMeasureStep() { + size_t replyLen = 0; + uint8_t* replyPtr; + ImtqRepliesDefault replies(replyBuf.data()); + // If some startup handling is added later, set configured after it was done once. + if (performStartup) { + // Set integration time for the MGM. + cmdBuf[0] = imtq::CC::SET_PARAM; + size_t dummy = 0; + SerializeAdapter::serialize(&imtq::param::INTEGRATION_TIME_SELECT, cmdBuf.data() + 1, &dummy, + cmdBuf.size(), SerializeIF::Endianness::LITTLE); + cmdBuf[3] = currentRequest.integrationTimeSel; + cmdLen = 4; + ReturnValue_t result = performI2cFullRequest(replyBuf.data(), 5); + if (result != returnvalue::OK) { + comStatus = imtq::STARTUP_CFG_ERROR; + } + if (replyBuf[0] != imtq::CC::SET_PARAM) { + sif::error << "ImtqPollingTask: First byte of reply not equal to sent CC" << std::endl; + comStatus = imtq::STARTUP_CFG_ERROR; + } + if (replyBuf[4] != currentRequest.integrationTimeSel) { + sif::error << "ImtqPollingTask: Integration time configuration failed" << std::endl; + comStatus = imtq::STARTUP_CFG_ERROR; + } + currentIntegrationTimeMs = + imtq::integrationTimeFromSelectValue(currentRequest.integrationTimeSel); + performStartup = false; + } + replies.setConfigured(); + + // Can be used later to verify correct timing (e.g. all data has been read) + clearReadFlagsDefault(replies); + auto i2cCmdExecMeasure = [&](imtq::CC::CC cc) { + ccToReplyPtrMeasure(replies, cc, &replyPtr, replyLen); + return i2cCmdExecDefault(cc, replyPtr, replyLen, imtq::MGM_MEASUREMENT_LOW_LEVEL_ERROR); + }; + + cmdLen = 1; + cmdBuf[0] = imtq::CC::GET_SYSTEM_STATE; + if (i2cCmdExecMeasure(imtq::CC::GET_SYSTEM_STATE) != returnvalue::OK) { + return; + } + + ignoreNextActuateRequest = + (replies.getSystemState()[2] == static_cast(imtq::mode::SELF_TEST)); + if (ignoreNextActuateRequest) { + // Do not command anything until self-test is done. + return; + } + + if (currentRequest.specialRequest != imtq::SpecialRequest::NONE) { + auto executeSelfTest = [&](imtq::selfTest::Axis axis) { + cmdBuf[0] = imtq::CC::SELF_TEST_CMD; + cmdBuf[1] = axis; + return i2cCmdExecMeasure(imtq::CC::SELF_TEST_CMD); + }; + // If a self-test is already ongoing, ignore the request. + if (replies.getSystemState()[2] != static_cast(imtq::mode::SELF_TEST)) { + switch (currentRequest.specialRequest) { + case (imtq::SpecialRequest::DO_SELF_TEST_POS_X): { + executeSelfTest(imtq::selfTest::Axis::X_POSITIVE); + break; + } + case (imtq::SpecialRequest::DO_SELF_TEST_NEG_X): { + executeSelfTest(imtq::selfTest::Axis::X_NEGATIVE); + break; + } + case (imtq::SpecialRequest::DO_SELF_TEST_POS_Y): { + executeSelfTest(imtq::selfTest::Axis::Y_POSITIVE); + break; + } + case (imtq::SpecialRequest::DO_SELF_TEST_NEG_Y): { + executeSelfTest(imtq::selfTest::Axis::Y_NEGATIVE); + break; + } + case (imtq::SpecialRequest::DO_SELF_TEST_POS_Z): { + executeSelfTest(imtq::selfTest::Axis::Z_POSITIVE); + break; + } + case (imtq::SpecialRequest::DO_SELF_TEST_NEG_Z): { + executeSelfTest(imtq::selfTest::Axis::Z_NEGATIVE); + break; + } + case (imtq::SpecialRequest::GET_SELF_TEST_RESULT): { + cmdBuf[0] = imtq::CC::GET_SELF_TEST_RESULT; + i2cCmdExecMeasure(imtq::CC::GET_SELF_TEST_RESULT); + break; + } + default: { + // Should never happen + break; + } + } + // We are done. Only request self test or results here. + return; + } + } + + // The I2C IP core on EIVE sometimes glitches out. Send start MTM measurement twice. + cmdBuf[0] = imtq::CC::START_MTM_MEASUREMENT; + if (i2cCmdExecMeasure(imtq::CC::START_MTM_MEASUREMENT) != returnvalue::OK) { + return; + } + cmdBuf[0] = imtq::CC::START_MTM_MEASUREMENT; + if (i2cCmdExecMeasure(imtq::CC::START_MTM_MEASUREMENT) != returnvalue::OK) { + return; + } + // Takes a bit of time to take measurements. Subtract a bit because of the delay of previous + // commands. + TaskFactory::delayTask(currentIntegrationTimeMs + MGM_READ_BUFFER_TIME_MS); + + cmdBuf[0] = imtq::CC::GET_RAW_MTM_MEASUREMENT; + if (i2cCmdExecMeasure(imtq::CC::GET_RAW_MTM_MEASUREMENT) != returnvalue::OK) { + return; + } + bool mgmMeasurementTooOld = false; + // See p.39 of the iMTQ user manual. If the NEW bit of the STAT bitfield is not set, we probably + // have old data. Which can be really bad for ACS. And everything. + if ((replyPtr[2] >> 7) == 0) { + replyPtr[0] = false; + mgmMeasurementTooOld = true; + } + + cmdBuf[0] = imtq::CC::GET_ENG_HK_DATA; + if (i2cCmdExecMeasure(imtq::CC::GET_ENG_HK_DATA) != returnvalue::OK) { + return; + } + + cmdBuf[0] = imtq::CC::GET_CAL_MTM_MEASUREMENT; + if (i2cCmdExecMeasure(imtq::CC::GET_CAL_MTM_MEASUREMENT) != returnvalue::OK) { + return; + } + if (mgmMeasurementTooOld) { + sif::error << "IMTQ: MGM measurement too old" << std::endl; + } + return; +} + +void ImtqPollingTask::handleActuateStep() { + uint8_t* replyPtr = nullptr; + size_t replyLen = 0; + // No point when self-test mode is active. + if (ignoreNextActuateRequest) { + return; + } + ImtqRepliesWithTorque replies(replyBufActuation.data()); + // Can be used later to verify correct timing (e.g. all data has been read) + clearReadFlagsWithTorque(replies); + auto i2cCmdExecActuate = [&](imtq::CC::CC cc) { + ccToReplyPtrActuate(replies, cc, &replyPtr, replyLen); + return i2cCmdExecDefault(cc, replyPtr, replyLen, imtq::ACTUATE_CMD_LOW_LEVEL_ERROR); + }; + buildDipoleCommand(); + if (i2cCmdExecActuate(imtq::CC::START_ACTUATION_DIPOLE) != returnvalue::OK) { + return; + } + + TaskFactory::delayTask(10); + + cmdLen = 1; + // The I2C IP core on EIVE sometimes glitches out. Send start MTM measurement twice. + cmdBuf[0] = imtq::CC::START_MTM_MEASUREMENT; + if (i2cCmdExecActuate(imtq::CC::START_MTM_MEASUREMENT) != returnvalue::OK) { + return; + } + cmdBuf[0] = imtq::CC::START_MTM_MEASUREMENT; + if (i2cCmdExecActuate(imtq::CC::START_MTM_MEASUREMENT) != returnvalue::OK) { + return; + } + + TaskFactory::delayTask(currentIntegrationTimeMs + MGM_READ_BUFFER_TIME_MS); + + cmdBuf[0] = imtq::CC::GET_RAW_MTM_MEASUREMENT; + if (i2cCmdExecActuate(imtq::CC::GET_RAW_MTM_MEASUREMENT) != returnvalue::OK) { + return; + } + bool measurementWasTooOld = false; + // See p.39 of the iMTQ user manual. If the NEW bit of the STAT bitfield is not set, we probably + // have old data. Which can be really bad for ACS. And everything. + if ((replyPtr[2] >> 7) == 0) { + measurementWasTooOld = true; + replyPtr[0] = false; + } + + cmdBuf[0] = imtq::CC::GET_ENG_HK_DATA; + if (i2cCmdExecActuate(imtq::CC::GET_ENG_HK_DATA) != returnvalue::OK) { + return; + } + + if (measurementWasTooOld) { + sif::error << "IMTQ: MGM measurement too old" << std::endl; + } + return; +} + +ReturnValue_t ImtqPollingTask::initialize() { return returnvalue::OK; } + +ReturnValue_t ImtqPollingTask::initializeInterface(CookieIF* cookie) { + i2cCookie = dynamic_cast(cookie); + if (i2cCookie == nullptr) { + sif::error << "ImtqPollingTask::initializeInterface: Invalid I2C cookie" << std::endl; + return returnvalue::FAILED; + } + i2cDev = i2cCookie->getDeviceFile().c_str(); + i2cAddr = i2cCookie->getAddress(); + return returnvalue::OK; +} + +ReturnValue_t ImtqPollingTask::sendMessage(CookieIF* cookie, const uint8_t* sendData, + size_t sendLen) { + const auto* imtqReq = reinterpret_cast(sendData); + if (sendLen != sizeof(imtq::Request)) { + return returnvalue::FAILED; + } + { + MutexGuard mg(ipcLock); + if (state != InternalState::IDLE) { + return returnvalue::FAILED; + } + state = InternalState::IS_BUSY; + if (currentRequest.mode != imtqReq->mode) { + if (imtqReq->mode == acs::SimpleSensorMode::NORMAL) { + performStartup = true; + } + } + std::memcpy(¤tRequest, imtqReq, sendLen); + } + semaphore->release(); + + return returnvalue::OK; +} + +ReturnValue_t ImtqPollingTask::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } + +ReturnValue_t ImtqPollingTask::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { + return returnvalue::OK; +} + +void ImtqPollingTask::ccToReplyPtrMeasure(ImtqRepliesDefault& replies, imtq::CC::CC cc, + uint8_t** replyBuf, size_t& replyLen) { + replyLen = imtq::getReplySize(cc); + switch (cc) { + case (imtq::CC::CC::GET_ENG_HK_DATA): { + *replyBuf = replies.engHk; + break; + } + case (imtq::CC::CC::SOFTWARE_RESET): { + *replyBuf = replies.swReset; + break; + } + case (imtq::CC::CC::GET_SYSTEM_STATE): { + *replyBuf = replies.systemState; + break; + } + case (imtq::CC::CC::START_MTM_MEASUREMENT): { + *replyBuf = replies.startMtmMeasurement; + break; + } + case (imtq::CC::CC::GET_RAW_MTM_MEASUREMENT): { + *replyBuf = replies.rawMgmMeasurement; + break; + } + case (imtq::CC::CC::GET_CAL_MTM_MEASUREMENT): { + *replyBuf = replies.calibMgmMeasurement; + break; + } + default: { + *replyBuf = replies.specialRequestReply; + break; + } + } +} + +void ImtqPollingTask::ccToReplyPtrActuate(ImtqRepliesWithTorque& replies, imtq::CC::CC cc, + uint8_t** replyBuf, size_t& replyLen) { + replyLen = imtq::getReplySize(cc); + switch (cc) { + case (imtq::CC::CC::START_ACTUATION_DIPOLE): { + *replyBuf = replies.dipoleActuation; + break; + } + case (imtq::CC::CC::GET_ENG_HK_DATA): { + *replyBuf = replies.engHk; + break; + } + case (imtq::CC::CC::START_MTM_MEASUREMENT): { + *replyBuf = replies.startMtmMeasurement; + break; + } + case (imtq::CC::CC::GET_RAW_MTM_MEASUREMENT): { + *replyBuf = replies.rawMgmMeasurement; + break; + } + default: { + *replyBuf = nullptr; + replyLen = 0; + break; + } + } +} +size_t ImtqPollingTask::getExchangeBufLen(imtq::SpecialRequest specialRequest) { + size_t baseLen = ImtqRepliesDefault::BASE_LEN; + switch (specialRequest) { + case (imtq::SpecialRequest::NONE): + case (imtq::SpecialRequest::DO_SELF_TEST_POS_X): + case (imtq::SpecialRequest::DO_SELF_TEST_NEG_X): + case (imtq::SpecialRequest::DO_SELF_TEST_POS_Y): + case (imtq::SpecialRequest::DO_SELF_TEST_NEG_Y): + case (imtq::SpecialRequest::DO_SELF_TEST_POS_Z): + case (imtq::SpecialRequest::DO_SELF_TEST_NEG_Z): { + break; + } + case (imtq::SpecialRequest::GET_SELF_TEST_RESULT): { + baseLen += imtq::replySize::SELF_TEST_RESULTS; + break; + } + } + return baseLen; +} + +void ImtqPollingTask::buildDipoleCommand() { + cmdBuf[0] = imtq::CC::CC::START_ACTUATION_DIPOLE; + uint8_t* serPtr = cmdBuf.data() + 1; + size_t serLen = 0; + for (uint8_t idx = 0; idx < 3; idx++) { + SerializeAdapter::serialize(¤tRequest.dipoles[idx], &serPtr, &serLen, cmdBuf.size(), + SerializeIF::Endianness::LITTLE); + } + SerializeAdapter::serialize(¤tRequest.torqueDuration, &serPtr, &serLen, cmdBuf.size(), + SerializeIF::Endianness::LITTLE); + // sif::debug << "Dipole X: " << dipoles[0] << std::endl; + // sif::debug << "Torqeu Dur: " << torqueDuration << std::endl; + cmdLen = 1 + serLen; +} + +ReturnValue_t ImtqPollingTask::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, + size_t* size) { + imtq::Request currentRequest; + { + MutexGuard mg(ipcLock); + std::memcpy(¤tRequest, &this->currentRequest, sizeof(currentRequest)); + } + + size_t replyLen = 0; + { + MutexGuard mg(bufLock); + if (currentRequest.requestType == imtq::RequestType::MEASURE_NO_ACTUATION) { + replyLen = getExchangeBufLen(currentRequest.specialRequest); + memcpy(exchangeBuf.data(), replyBuf.data(), replyLen); + } else if (currentRequest.requestType == imtq::RequestType::ACTUATE) { + replyLen = ImtqRepliesWithTorque::BASE_LEN; + memcpy(exchangeBuf.data(), replyBufActuation.data(), replyLen); + } else { + *size = 0; + } + } + { + MutexGuard mg(ipcLock); + this->currentRequest.requestType = imtq::RequestType::DO_NOTHING; + } + *buffer = exchangeBuf.data(); + *size = replyLen; + return comStatus; +} + +void ImtqPollingTask::clearReadFlagsDefault(ImtqRepliesDefault& replies) { + replies.calibMgmMeasurement[0] = false; + replies.rawMgmMeasurement[0] = false; + replies.systemState[0] = false; + replies.specialRequestReply[0] = false; + replies.engHk[0] = false; +} + +ReturnValue_t ImtqPollingTask::i2cCmdExecDefault(imtq::CC::CC cc, uint8_t* replyPtr, + size_t replyLen, ReturnValue_t comErrIfFails) { + ReturnValue_t res = performI2cFullRequest(replyPtr + 1, replyLen); + if (res != returnvalue::OK) { + sif::error << "IMTQ: I2C transaction for command 0x" << std::hex << std::setw(2) << cc + << " failed" << std::dec << std::endl; + comStatus = comErrIfFails; + return returnvalue::FAILED; + } + if (replyPtr[1] != cc) { + sif::warning << "IMTQ: Unexpected CC 0x" << std::hex << std::setw(2) + << static_cast(replyPtr[1]) << " for command 0x" << cc << std::dec + << std::endl; + comStatus = comErrIfFails; + return returnvalue::FAILED; + } + replyPtr[0] = true; + return returnvalue::OK; +} + +void ImtqPollingTask::clearReadFlagsWithTorque(ImtqRepliesWithTorque& replies) { + replies.dipoleActuation[0] = false; + replies.engHk[0] = false; + replies.rawMgmMeasurement[0] = false; + replies.startMtmMeasurement[0] = false; +} + +ReturnValue_t ImtqPollingTask::performI2cFullRequest(uint8_t* reply, size_t replyLen) { + int fd = 0; + if (cmdLen == 0 or reply == nullptr) { + sif::error << "ImtqPollingTask: Command lenght is zero or reply PTR is invalid" << std::endl; + return returnvalue::FAILED; + } + + { + UnixFileGuard fileHelper(i2cDev, fd, O_RDWR, "ImtqPollingTask::performI2cFullRequest"); + if (fileHelper.getOpenResult() != returnvalue::OK) { + return fileHelper.getOpenResult(); + } + if (ioctl(fd, I2C_SLAVE, i2cAddr) < 0) { + sif::warning << "Opening IMTQ slave device failed with code " << errno << ": " + << strerror(errno) << std::endl; + if (errno == EBUSY) { + i2cFatalErrors++; + } + } + + int written = write(fd, cmdBuf.data(), cmdLen); + if (written < 0) { + sif::error << "IMTQ: Failed to send with error code " << errno + << ". Error description: " << strerror(errno) << std::endl; + // This is a weird issue which sometimes occurs on debug builds. All I2C buses are busy + // for all writes, + if (errno == EBUSY) { + i2cFatalErrors++; + } + return returnvalue::FAILED; + } else if (static_cast(written) != cmdLen) { + sif::error << "IMTQ: Could not write all bytes" << std::endl; + return returnvalue::FAILED; + } + } +#if FSFW_HAL_I2C_WIRETAPPING == 1 + sif::info << "Sent I2C data to bus " << deviceFile << ":" << std::endl; + arrayprinter::print(sendData, sendLen); +#endif + + // wait 1 ms like specified in the datasheet. This is the time the IMTQ needs + // to prepare a reply. + usleep(1000); + + { + UnixFileGuard fileHelper(i2cDev, fd, O_RDWR, "ImtqPollingTask::performI2cFullRequest"); + if (fileHelper.getOpenResult() != returnvalue::OK) { + return fileHelper.getOpenResult(); + } + if (ioctl(fd, I2C_SLAVE, i2cAddr) < 0) { + sif::warning << "Opening IMTQ slave device failed with code " << errno << ": " + << strerror(errno) << std::endl; + } + MutexGuard mg(bufLock); + int readLen = read(fd, reply, replyLen); + if (readLen != static_cast(replyLen)) { + if (readLen < 0) { + sif::warning << "IMTQ: Reading failed with error code " << errno << " | " << strerror(errno) + << std::endl; + } else { + sif::warning << "IMTQ: Read only" << readLen << " from " << replyLen << " bytes" + << std::endl; + } + } + } + if (reply[0] == 0xff or reply[1] == 0xff) { + sif::warning << "IMTQ: No reply available after 1 millisecond"; + return NO_REPLY_AVAILABLE; + } + return returnvalue::OK; +} diff --git a/linux/acs/ImtqPollingTask.h b/linux/acs/ImtqPollingTask.h new file mode 100644 index 0000000..914343d --- /dev/null +++ b/linux/acs/ImtqPollingTask.h @@ -0,0 +1,74 @@ +#ifndef LINUX_DEVICES_IMTQPOLLINGTASK_H_ +#define LINUX_DEVICES_IMTQPOLLINGTASK_H_ + +#include +#include +#include + +#include + +#include "fsfw/devicehandlers/DeviceCommunicationIF.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "mission/acs/imtqHelpers.h" + +class ImtqPollingTask : public SystemObject, + public ExecutableObjectIF, + public DeviceCommunicationIF { + public: + ImtqPollingTask(object_id_t imtqPollingTask, std::atomic_uint16_t& i2cFatalErrors); + + ReturnValue_t performOperation(uint8_t operationCode) override; + ReturnValue_t initialize() override; + + private: + //! [EXPORT] : [SKIP] + static constexpr ReturnValue_t NO_REPLY_AVAILABLE = returnvalue::makeCode(2, 0); + + enum class InternalState { IDLE, IS_BUSY } state = InternalState::IDLE; + + SemaphoreIF* semaphore; + ReturnValue_t comStatus = returnvalue::OK; + MutexIF* ipcLock; + MutexIF* bufLock; + std::atomic_uint16_t& i2cFatalErrors; + I2cCookie* i2cCookie = nullptr; + const char* i2cDev = nullptr; + address_t i2cAddr = 0; + uint32_t currentIntegrationTimeMs = 10; + // Required in addition to integration time, otherwise old data might be read. + static constexpr uint32_t MGM_READ_BUFFER_TIME_MS = 6; + bool ignoreNextActuateRequest = false; + bool performStartup = false; + + imtq::Request currentRequest{}; + + std::array cmdBuf; + std::array replyBuf; + std::array replyBufActuation; + std::array exchangeBuf; + size_t cmdLen = 0; + + // DeviceCommunicationIF overrides + ReturnValue_t initializeInterface(CookieIF* cookie) override; + ReturnValue_t sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF* cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF* cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) override; + + void ccToReplyPtrMeasure(ImtqRepliesDefault& replies, imtq::CC::CC cc, uint8_t** replyBuf, + size_t& replyLen); + void ccToReplyPtrActuate(ImtqRepliesWithTorque& replies, imtq::CC::CC cc, uint8_t** replyBuf, + size_t& replyLen); + void clearReadFlagsDefault(ImtqRepliesDefault& replies); + void clearReadFlagsWithTorque(ImtqRepliesWithTorque& replies); + size_t getExchangeBufLen(imtq::SpecialRequest specialRequest); + void buildDipoleCommand(); + void handleMeasureStep(); + void handleActuateStep(); + ReturnValue_t i2cCmdExecDefault(imtq::CC::CC cc, uint8_t* replyPtr, size_t replyLen, + ReturnValue_t comErrIfFails); + ReturnValue_t performI2cFullRequest(uint8_t* reply, size_t replyLen); +}; + +#endif /* LINUX_DEVICES_IMTQPOLLINGTASK_H_ */ diff --git a/linux/acs/RwPollingTask.cpp b/linux/acs/RwPollingTask.cpp new file mode 100644 index 0000000..126ad0e --- /dev/null +++ b/linux/acs/RwPollingTask.cpp @@ -0,0 +1,533 @@ +#include "RwPollingTask.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devConf.h" +#include "mission/acs/defs.h" +#include "mission/acs/rwHelpers.h" + +RwPollingTask::RwPollingTask(object_id_t objectId, const char* spiDev, GpioIF& gpioIF) + : SystemObject(objectId), spiDev(spiDev), gpioIF(gpioIF) { + semaphore = SemaphoreFactory::instance()->createBinarySemaphore(); + semaphore->acquire(); + ipcLock = MutexFactory::instance()->createMutex(); + spiLock = MutexFactory::instance()->createMutex(); +} + +ReturnValue_t RwPollingTask::performOperation(uint8_t operationCode) { + for (unsigned i = 0; i < 4; i++) { + if (rwCookies[i] == nullptr) { + sif::error << "Invalid RW cookie at index" << i << std::endl; + return returnvalue::FAILED; + } + } + while (true) { + ipcLock->lockMutex(); + state = InternalState::IDLE; + ipcLock->unlockMutex(); + semaphore->acquire(); + // This loop takes 50 ms on a debug build. + // Stopwatch watch; + // Give all device handlers some time to submit requests. + TaskFactory::delayTask(5); + int fd = 0; + for (auto& skip : skipCommandingForRw) { + skip = false; + } + setAllReadFlagsFalse(); + ReturnValue_t result = openSpi(O_RDWR, fd); + if (result != returnvalue::OK) { + continue; + } + acs::SimpleSensorMode currentMode; + rws::SpecialRwRequest specialRequest; + + for (unsigned idx = 0; idx < rwCookies.size(); idx++) { + { + MutexGuard mg(ipcLock); + currentMode = rwRequests[idx].mode; + specialRequest = rwRequests[idx].specialRequest; + skipSetSpeedReply[idx] = rwRequests[idx].setSpeed; + } + if (currentMode == acs::SimpleSensorMode::OFF) { + skipCommandingForRw[idx] = true; + } else if (specialRequest == rws::SpecialRwRequest::RESET_MCU) { + prepareSimpleCommand(rws::RESET_MCU); + // No point in commanding that specific RW for the cycle. + skipCommandingForRw[idx] = true; + writeOneRwCmd(idx, fd); + } else if (skipSetSpeedReply[idx]) { + prepareSetSpeedCmd(idx); + if (writeOneRwCmd(idx, fd) != returnvalue::OK) { + continue; + } + } + } + closeSpi(fd); + if (readAllRws(rws::SET_SPEED) != returnvalue::OK) { + continue; + } + prepareSimpleCommand(rws::GET_LAST_RESET_STATUS); + if (writeAndReadAllRws(rws::GET_LAST_RESET_STATUS) != returnvalue::OK) { + continue; + } + prepareSimpleCommand(rws::GET_RW_STATUS); + if (writeAndReadAllRws(rws::GET_RW_STATUS) != returnvalue::OK) { + continue; + } + prepareSimpleCommand(rws::GET_TEMPERATURE); + if (writeAndReadAllRws(rws::GET_TEMPERATURE) != returnvalue::OK) { + continue; + } + prepareSimpleCommand(rws::CLEAR_LAST_RESET_STATUS); + if (writeAndReadAllRws(rws::CLEAR_LAST_RESET_STATUS) != returnvalue::OK) { + continue; + } + handleSpecialRequests(); + } + + return returnvalue::OK; +} + +ReturnValue_t RwPollingTask::initialize() { return returnvalue::OK; } + +ReturnValue_t RwPollingTask::initializeInterface(CookieIF* cookie) { + // We don't need to set the speed because a SPI core is used, but the mode has to be set once + // correctly for all RWs + if (not modeAndSpeedWasSet) { + int fd = open(spiDev, O_RDWR); + if (fd < 0) { + sif::error << "could not open RW SPI bus" << std::endl; + return returnvalue::FAILED; + } + spi::SpiModes mode = spi::RW_MODE; + int retval = ioctl(fd, SPI_IOC_WR_MODE, reinterpret_cast(&mode)); + if (retval != 0) { + utility::handleIoctlError("SpiComIF::setSpiSpeedAndMode: Setting SPI mode failed"); + } + + retval = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi::RW_SPEED); + if (retval != 0) { + utility::handleIoctlError("SpiComIF::setSpiSpeedAndMode: Setting SPI speed failed"); + } + close(fd); + modeAndSpeedWasSet = true; + } + + auto* rwCookie = dynamic_cast(cookie); + if (rwCookie == nullptr) { + sif::error << "RwPollingTask::initializeInterface: Wrong cookie" << std::endl; + return returnvalue::FAILED; + } + rwCookies[rwCookie->rwIdx] = rwCookie; + + return returnvalue::OK; +} + +ReturnValue_t RwPollingTask::sendMessage(CookieIF* cookie, const uint8_t* sendData, + size_t sendLen) { + if (sendData == nullptr or sendLen != sizeof(rws::RwRequest)) { + return DeviceHandlerIF::INVALID_DATA; + } + const rws::RwRequest* rwRequest = reinterpret_cast(sendData); + uint8_t rwIdx = rwRequest->rwIdx; + { + MutexGuard mg(ipcLock); + std::memcpy(&rwRequests[rwIdx], rwRequest, sizeof(rws::RwRequest)); + if (state == InternalState::IDLE) { + state = InternalState::IS_BUSY; + semaphore->release(); + } + } + return returnvalue::OK; +} + +ReturnValue_t RwPollingTask::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } + +ReturnValue_t RwPollingTask::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t RwPollingTask::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) { + RwCookie* rwCookie = dynamic_cast(cookie); + if (rwCookie == nullptr or rwCookie->bufLock == nullptr) { + return returnvalue::FAILED; + } + { + MutexGuard mg(rwCookie->bufLock); + memcpy(rwCookie->exchangeBuf.data(), rwCookie->replyBuf.data(), rwCookie->replyBuf.size()); + } + *buffer = rwCookie->exchangeBuf.data(); + *size = rwCookie->exchangeBuf.size(); + return returnvalue::OK; +} + +ReturnValue_t RwPollingTask::writeAndReadAllRws(DeviceCommandId_t id) { + // Stopwatch watch; + ReturnValue_t result = returnvalue::OK; + + int fd = 0; + result = openSpi(O_RDWR, fd); + if (result != returnvalue::OK) { + return result; + } + for (unsigned idx = 0; idx < rwCookies.size(); idx++) { + if (skipCommandingForRw[idx]) { + continue; + } + result = sendOneMessage(fd, *rwCookies[idx]); + if (result != returnvalue::OK) { + closeSpi(fd); + return returnvalue::FAILED; + } + } + + closeSpi(fd); + return readAllRws(id); +} + +ReturnValue_t RwPollingTask::openSpi(int flags, int& fd) { + fd = open(spiDev, flags); + if (fd < 0) { + sif::error << "RwPollingTask::openSpi: Failed to open device file" << std::endl; + return spi::OPENING_FILE_FAILED; + } + + return returnvalue::OK; +} + +ReturnValue_t RwPollingTask::readNextReply(RwCookie& rwCookie, uint8_t* replyBuf, + size_t maxReplyLen) { + ReturnValue_t result = returnvalue::OK; + int fd = 0; + gpioId_t gpioId = rwCookie.getChipSelectPin(); + uint8_t byteRead = 0; + result = openSpi(O_RDWR, fd); + if (result != returnvalue::OK) { + return result; + } + pullCsLow(gpioId, gpioIF); + bool lastByteWasFrameMarker = false; + Countdown cd(2000); + size_t readIdx = 0; + + while (true) { + lastByteWasFrameMarker = false; + if (read(fd, &byteRead, 1) != 1) { + sif::error << "RwPollingTask: Read failed. " << strerror(errno) << std::endl; + pullCsHigh(gpioId, gpioIF); + closeSpi(fd); + return rws::SPI_READ_FAILURE; + } + if (byteRead == rws::FRAME_DELIMITER) { + lastByteWasFrameMarker = true; + } + // Start of frame detected. + if (byteRead != rws::FRAME_DELIMITER and not lastByteWasFrameMarker) { + break; + } + + if (readIdx % 100 == 0 && cd.hasTimedOut()) { + pullCsHigh(gpioId, gpioIF); + closeSpi(fd); + return rws::SPI_READ_FAILURE; + } + readIdx++; + } + +#if FSFW_HAL_SPI_WIRETAPPING == 1 + sif::info << "RW start marker detected" << std::endl; +#endif + + size_t decodedFrameLen = 0; + MutexGuard mg(rwCookie.bufLock); + + while (decodedFrameLen < maxReplyLen) { + // First byte already read in + if (decodedFrameLen != 0) { + byteRead = 0; + if (read(fd, &byteRead, 1) != 1) { + sif::error << "RwPollingTask: Read failed" << std::endl; + result = rws::SPI_READ_FAILURE; + break; + } + } + + if (byteRead == rws::FRAME_DELIMITER) { + // Reached end of frame + break; + } else if (byteRead == 0x7D) { + if (read(fd, &byteRead, 1) != 1) { + sif::error << "RwPollingTask: Read failed" << std::endl; + result = rws::SPI_READ_FAILURE; + break; + } + if (byteRead == 0x5E) { + *(replyBuf + decodedFrameLen) = 0x7E; + decodedFrameLen++; + continue; + } else if (byteRead == 0x5D) { + *(replyBuf + decodedFrameLen) = 0x7D; + decodedFrameLen++; + continue; + } else { + sif::error << "RwPollingTask: Invalid substitute" << std::endl; + result = rws::INVALID_SUBSTITUTE; + break; + } + } else { + *(replyBuf + decodedFrameLen) = byteRead; + decodedFrameLen++; + continue; + } + + // Check end marker. + /** + * There might be the unlikely case that each byte in a get-telemetry reply has been + * replaced by its substitute. Then the next byte must correspond to the end sign 0x7E. + * Otherwise there might be something wrong. + */ + if (decodedFrameLen == maxReplyLen) { + if (read(fd, &byteRead, 1) != 1) { + sif::error << "rwSpiCallback::spiCallback: Failed to read last byte" << std::endl; + result = rws::SPI_READ_FAILURE; + break; + } + if (byteRead != rws::FRAME_DELIMITER) { + sif::error << "rwSpiCallback::spiCallback: Missing end sign " + << static_cast(rws::FRAME_DELIMITER) << std::endl; + decodedFrameLen--; + result = rws::MISSING_END_SIGN; + break; + } + } + result = returnvalue::OK; + } + + pullCsHigh(gpioId, gpioIF); + closeSpi(fd); + return result; +} + +ReturnValue_t RwPollingTask::writeOneRwCmd(uint8_t rwIdx, int fd) { + ReturnValue_t result = sendOneMessage(fd, *rwCookies[rwIdx]); + if (result != returnvalue::OK) { + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t RwPollingTask::readAllRws(DeviceCommandId_t id) { + // SPI dev will be opened in readNextReply on demand. + for (unsigned idx = 0; idx < rwCookies.size(); idx++) { + if (((id == rws::SET_SPEED) and !skipSetSpeedReply[idx]) or skipCommandingForRw[idx]) { + continue; + } + uint8_t* replyBuf; + size_t maxReadLen = idAndIdxToReadBuffer(id, idx, &replyBuf); + ReturnValue_t result = readNextReply(*rwCookies[idx], replyBuf + 1, maxReadLen); + if (result == returnvalue::OK) { + // The first byte is always a flag which shows whether the value was read + // properly at least once. + replyBuf[0] = true; + } + } + // SPI is closed in readNextReply as well. + return returnvalue::OK; +} + +size_t RwPollingTask::idAndIdxToReadBuffer(DeviceCommandId_t id, uint8_t rwIdx, uint8_t** ptr) { + uint8_t* rawStart = rwCookies[rwIdx]->replyBuf.data(); + RwReplies replies(rawStart); + switch (id) { + case (rws::GET_RW_STATUS): { + *ptr = replies.rwStatusReply; + break; + } + case (rws::SET_SPEED): { + *ptr = replies.setSpeedReply; + break; + } + case (rws::CLEAR_LAST_RESET_STATUS): { + *ptr = replies.clearLastResetStatusReply; + break; + } + case (rws::GET_LAST_RESET_STATUS): { + *ptr = replies.getLastResetStatusReply; + break; + } + case (rws::GET_TEMPERATURE): { + *ptr = replies.readTemperatureReply; + break; + } + case (rws::GET_TM): { + *ptr = replies.hkDataReply; + break; + } + case (rws::INIT_RW_CONTROLLER): { + *ptr = replies.initRwControllerReply; + break; + } + default: { + sif::error << "no reply buffer for rw command " << id << std::endl; + *ptr = replies.dummyPointer; + return 0; + } + } + return rws::idToPacketLen(id); +} + +void RwPollingTask::fillSpecialRequestArray() { + for (unsigned idx = 0; idx < rwCookies.size(); idx++) { + if (skipCommandingForRw[idx]) { + specialRequestIds[idx] = DeviceHandlerIF::NO_COMMAND_ID; + continue; + } + switch (rwRequests[idx].specialRequest) { + case (rws::SpecialRwRequest::GET_TM): { + specialRequestIds[idx] = rws::GET_TM; + break; + } + case (rws::SpecialRwRequest::INIT_RW_CONTROLLER): { + specialRequestIds[idx] = rws::INIT_RW_CONTROLLER; + break; + } + default: { + specialRequestIds[idx] = DeviceHandlerIF::NO_COMMAND_ID; + } + } + } +} + +void RwPollingTask::handleSpecialRequests() { + int fd = 0; + fillSpecialRequestArray(); + ReturnValue_t result = openSpi(O_RDWR, fd); + if (result != returnvalue::OK) { + return; + } + for (unsigned idx = 0; idx < rwCookies.size(); idx++) { + if (specialRequestIds[idx] == DeviceHandlerIF::NO_COMMAND_ID) { + continue; + } + prepareSimpleCommand(specialRequestIds[idx]); + writeOneRwCmd(idx, fd); + } + closeSpi(fd); + for (unsigned idx = 0; idx < rwCookies.size(); idx++) { + if (specialRequestIds[idx] == DeviceHandlerIF::NO_COMMAND_ID) { + continue; + } + uint8_t* replyBuf; + size_t maxReadLen = idAndIdxToReadBuffer(specialRequestIds[idx], idx, &replyBuf); + // Skip first byte for actual read buffer: Valid byte + result = readNextReply(*rwCookies[idx], replyBuf + 1, maxReadLen); + if (result == returnvalue::OK) { + // The first byte is always a flag which shows whether the value was read + // properly at least once. + replyBuf[0] = true; + } + } +} + +void RwPollingTask::setAllReadFlagsFalse() { + for (auto& rwCookie : rwCookies) { + RwReplies replies(rwCookie->replyBuf.data()); + replies.getLastResetStatusReply[0] = false; + replies.clearLastResetStatusReply[0] = false; + replies.hkDataReply[0] = false; + replies.readTemperatureReply[0] = false; + replies.rwStatusReply[0] = false; + replies.setSpeedReply[0] = false; + replies.initRwControllerReply[0] = false; + } +} + +void RwPollingTask::closeSpi(int fd) { close(fd); } + +ReturnValue_t RwPollingTask::sendOneMessage(int fd, RwCookie& rwCookie) { + gpioId_t gpioId = rwCookie.getChipSelectPin(); + if (spiLock == nullptr) { + sif::warning << "RwPollingTask: Mutex or GPIO interface invalid" << std::endl; + return returnvalue::FAILED; + } + // Add datalinklayer like specified in the datasheet. + size_t lenToSend = 0; + rws::encodeHdlc(writeBuffer.data(), writeLen, encodedBuffer.data(), lenToSend); + pullCsLow(gpioId, gpioIF); + if (write(fd, encodedBuffer.data(), lenToSend) != static_cast(lenToSend)) { + sif::error << "RwPollingTask: Write failed!" << std::endl; + pullCsHigh(gpioId, gpioIF); + return rws::SPI_WRITE_FAILURE; + } + pullCsHigh(gpioId, gpioIF); + return returnvalue::OK; +} + +ReturnValue_t RwPollingTask::pullCsLow(gpioId_t gpioId, GpioIF& gpioIF) { + ReturnValue_t result = spiLock->lockMutex(TIMEOUT_TYPE, TIMEOUT_MS); + if (result != returnvalue::OK) { + sif::warning << "RwPollingTask::pullCsLow: Failed to lock mutex" << std::endl; + return result; + } + // Pull SPI CS low. For now, no support for active high given + if (gpioId != gpio::NO_GPIO) { + result = gpioIF.pullLow(gpioId); + if (result != returnvalue::OK) { + sif::error << "RwPollingTask::pullCsLow: Failed to pull chip select low" << std::endl; + return result; + } + } + return returnvalue::OK; +} + +void RwPollingTask::pullCsHigh(gpioId_t gpioId, GpioIF& gpioIF) { + if (gpioId != gpio::NO_GPIO) { + if (gpioIF.pullHigh(gpioId) != returnvalue::OK) { + sif::error << "closeSpi: Failed to pull chip select high" << std::endl; + } + } + if (spiLock->unlockMutex() != returnvalue::OK) { + sif::error << "RwPollingTask::pullCsHigh: Failed to unlock mutex" << std::endl; + ; + } +} + +void RwPollingTask::prepareSimpleCommand(DeviceCommandId_t id) { + writeBuffer[0] = static_cast(id); + uint16_t crc = CRC::crc16ccitt(writeBuffer.data(), 1, 0xFFFF); + writeBuffer[1] = static_cast(crc & 0xFF); + writeBuffer[2] = static_cast(crc >> 8 & 0xFF); + writeLen = 3; +} + +ReturnValue_t RwPollingTask::prepareSetSpeedCmd(uint8_t rwIdx) { + writeBuffer[0] = static_cast(rws::SET_SPEED); + uint8_t* serPtr = writeBuffer.data() + 1; + int32_t speedToSet = 0; + uint16_t rampTimeToSet = 10; + { + MutexGuard mg(ipcLock); + speedToSet = rwRequests[rwIdx].currentRwSpeed; + rampTimeToSet = rwRequests[rwIdx].currentRampTime; + } + size_t serLen = 1; + SerializeAdapter::serialize(&speedToSet, &serPtr, &serLen, writeBuffer.size(), + SerializeIF::Endianness::LITTLE); + SerializeAdapter::serialize(&rampTimeToSet, &serPtr, &serLen, writeBuffer.size(), + SerializeIF::Endianness::LITTLE); + + uint16_t crc = CRC::crc16ccitt(writeBuffer.data(), 7, 0xFFFF); + writeBuffer[7] = static_cast(crc & 0xFF); + writeBuffer[8] = static_cast((crc >> 8) & 0xFF); + writeLen = 9; + return returnvalue::OK; +} diff --git a/linux/acs/RwPollingTask.h b/linux/acs/RwPollingTask.h new file mode 100644 index 0000000..f06bc62 --- /dev/null +++ b/linux/acs/RwPollingTask.h @@ -0,0 +1,90 @@ +#ifndef LINUX_DEVICES_RWPOLLINGTASK_H_ +#define LINUX_DEVICES_RWPOLLINGTASK_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mission/acs/rwHelpers.h" + +class RwCookie : public SpiCookie { + friend class RwPollingTask; + + public: + static constexpr size_t REPLY_BUF_LEN = 524; + RwCookie(uint8_t rwIdx, address_t spiAddress, gpioId_t chipSelect, const size_t maxSize, + spi::SpiModes spiMode, uint32_t spiSpeed) + : SpiCookie(spiAddress, chipSelect, maxSize, spiMode, spiSpeed), rwIdx(rwIdx) { + bufLock = MutexFactory::instance()->createMutex(); + } + + private: + std::array replyBuf{}; + std::array exchangeBuf{}; + MutexIF* bufLock; + uint8_t rwIdx; +}; + +class RwPollingTask : public SystemObject, public ExecutableObjectIF, public DeviceCommunicationIF { + public: + RwPollingTask(object_id_t objectId, const char* spiDev, GpioIF& gpioIF); + + ReturnValue_t performOperation(uint8_t operationCode) override; + ReturnValue_t initialize() override; + + private: + enum class InternalState { IDLE, IS_BUSY } state = InternalState::IDLE; + SemaphoreIF* semaphore; + bool debugMode = false; + bool modeAndSpeedWasSet = false; + MutexIF* ipcLock; + MutexIF* spiLock; + const char* spiDev; + GpioIF& gpioIF; + std::array skipCommandingForRw; + std::array skipSetSpeedReply; + std::array specialRequestIds; + std::array rwCookies; + std::array rwRequests{}; + std::array writeBuffer; + std::array encodedBuffer; + + size_t writeLen = 0; + static constexpr MutexIF::TimeoutType TIMEOUT_TYPE = MutexIF::TimeoutType::WAITING; + static constexpr uint32_t TIMEOUT_MS = 20; + static constexpr uint8_t MAX_RETRIES_REPLY = 5; + + // DeviceCommunicationIF overrides + ReturnValue_t initializeInterface(CookieIF* cookie) override; + ReturnValue_t sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF* cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF* cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) override; + + ReturnValue_t writeAndReadAllRws(DeviceCommandId_t id); + ReturnValue_t writeOneRwCmd(uint8_t rwIdx, int fd); + ReturnValue_t readAllRws(DeviceCommandId_t id); + + ReturnValue_t sendOneMessage(int fd, RwCookie& rwCookie); + ReturnValue_t readNextReply(RwCookie& rwCookie, uint8_t* replyBuf, size_t maxReplyLen); + void handleSpecialRequests(); + + ReturnValue_t openSpi(int flags, int& fd); + ReturnValue_t pullCsLow(gpioId_t gpioId, GpioIF& gpioIF); + void prepareSimpleCommand(DeviceCommandId_t id); + ReturnValue_t prepareSetSpeedCmd(uint8_t rwIdx); + + size_t idAndIdxToReadBuffer(DeviceCommandId_t id, uint8_t rwIdx, uint8_t** readPtr); + void fillSpecialRequestArray(); + void setAllReadFlagsFalse(); + + void pullCsHigh(gpioId_t gpioId, GpioIF& gpioIF); + void closeSpi(int); +}; + +#endif /* LINUX_DEVICES_RWPOLLINGTASK_H_ */ diff --git a/linux/acs/StrComHandler.cpp b/linux/acs/StrComHandler.cpp new file mode 100644 index 0000000..65a1b5a --- /dev/null +++ b/linux/acs/StrComHandler.cpp @@ -0,0 +1,838 @@ +#include "StrComHandler.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "OBSWConfig.h" +#include "eive/definitions.h" +#include "fsfw/timemanager/Countdown.h" +#include "mission/utility/Filenaming.h" +#include "mission/utility/ProgressPrinter.h" +#include "mission/utility/Timestamp.h" + +extern "C" { +#include +} + +using namespace returnvalue; + +static constexpr bool PACKET_WIRETAPPING = false; + +StrComHandler::StrComHandler(object_id_t objectId) : SystemObject(objectId) { + lock = MutexFactory::instance()->createMutex(); + semaphore.acquire(); +} + +StrComHandler::~StrComHandler() {} + +ReturnValue_t StrComHandler::initialize() { +#ifdef XIPHOS_Q7S + sdcMan = SdCardManager::instance(); + if (sdcMan == nullptr) { + sif::warning << "StrHelper::initialize: Invalid SD Card Manager" << std::endl; + return returnvalue::FAILED; + } +#endif + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::performOperation(uint8_t operationCode) { + ReturnValue_t result = returnvalue::OK; + while (true) { + lock->lockMutex(); + state = InternalState::SLEEPING; + lock->unlockMutex(); + semaphore.acquire(); + switch (state) { + case InternalState::POLL_ONE_REPLY: { + // Stopwatch watch; + replyTimeout.setTimeout(400); + readOneReply(static_cast(state)); + { + MutexGuard mg(lock); + replyWasReceived = true; + } + break; + } + case InternalState::UPLOAD_IMAGE: { + replyTimeout.setTimeout(200); + resetReplyHandlingState(); + result = performImageUpload(); + if (result == returnvalue::OK) { + triggerEvent(IMAGE_UPLOAD_SUCCESSFUL); + } else { + triggerEvent(IMAGE_UPLOAD_FAILED); + } + break; + } + case InternalState::DOWNLOAD_IMAGE: { + replyTimeout.setTimeout(200); + resetReplyHandlingState(); + result = performImageDownload(); + if (result == returnvalue::OK) { + triggerEvent(IMAGE_DOWNLOAD_SUCCESSFUL); + } else { + triggerEvent(IMAGE_DOWNLOAD_FAILED); + } + break; + } + case InternalState::FLASH_READ: { + replyTimeout.setTimeout(200); + resetReplyHandlingState(); + result = performFlashRead(); + if (result == returnvalue::OK) { + triggerEvent(FLASH_READ_SUCCESSFUL); + } else { + triggerEvent(FLASH_READ_FAILED); + } + break; + } + case InternalState::FIRMWARE_UPDATE: { + replyTimeout.setTimeout(2000); + resetReplyHandlingState(); + result = performFirmwareUpdate(); + if (result == returnvalue::OK) { + triggerEvent(FIRMWARE_UPDATE_SUCCESSFUL); + } else { + triggerEvent(FIRMWARE_UPDATE_FAILED); + } + break; + } + default: + sif::debug << "StrHelper::performOperation: Invalid state" << std::endl; + break; + } + } +} + +ReturnValue_t StrComHandler::startImageUpload(std::string fullname) { + { + MutexGuard mg(lock); + if (state != InternalState::SLEEPING) { + return BUSY; + } + } +#ifdef XIPHOS_Q7S + ReturnValue_t result = checkPath(fullname); + if (result != returnvalue::OK) { + return result; + } +#endif + uploadImage.uploadFile = fullname; + if (not std::filesystem::exists(fullname)) { + return FILE_NOT_EXISTS; + } + { + MutexGuard mg(lock); + replyWasReceived = false; + state = InternalState::UPLOAD_IMAGE; + } + semaphore.release(); + terminate = false; + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::startImageDownload(std::string path) { + { + MutexGuard mg(lock); + if (state != InternalState::SLEEPING) { + return BUSY; + } + } +#ifdef XIPHOS_Q7S + ReturnValue_t result = checkPath(path); + if (result != returnvalue::OK) { + return result; + } +#endif + if (not std::filesystem::exists(path)) { + return PATH_NOT_EXISTS; + } + downloadImage.path = path; + { + MutexGuard mg(lock); + replyWasReceived = false; + state = InternalState::DOWNLOAD_IMAGE; + } + terminate = false; + semaphore.release(); + return returnvalue::OK; +} + +void StrComHandler::stopProcess() { terminate = true; } + +void StrComHandler::setDownloadImageName(std::string filename) { + downloadImage.filename = filename; +} + +void StrComHandler::setFlashReadFilename(std::string filename) { flashRead.filename = filename; } + +ReturnValue_t StrComHandler::startFirmwareUpdate(std::string fullname, + startracker::FirmwareTarget target) { + { + MutexGuard mg(lock); + if (state != InternalState::SLEEPING) { + return BUSY; + } + } +#ifdef XIPHOS_Q7S + ReturnValue_t result = checkPath(fullname); + if (result != returnvalue::OK) { + return result; + } +#endif + flashWrite.fullname = fullname; + if (not std::filesystem::exists(flashWrite.fullname)) { + return FILE_NOT_EXISTS; + } + if (target == startracker::FirmwareTarget::MAIN) { + flashWrite.firstRegion = static_cast(startracker::FirmwareRegions::FIRST_MAIN); + flashWrite.lastRegion = static_cast(startracker::FirmwareRegions::LAST_MAIN); + } else if (target == startracker::FirmwareTarget::BACKUP) { + flashWrite.firstRegion = static_cast(startracker::FirmwareRegions::FIRST_BACKUP); + flashWrite.lastRegion = static_cast(startracker::FirmwareRegions::LAST_BACKUP); + } + { + MutexGuard mg(lock); + replyWasReceived = false; + state = InternalState::FIRMWARE_UPDATE; + } + semaphore.release(); + terminate = false; + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::startFlashRead(std::string path, uint8_t startRegion, + uint32_t length) { + { + MutexGuard mg(lock); + if (state != InternalState::SLEEPING) { + return BUSY; + } + } +#ifdef XIPHOS_Q7S + ReturnValue_t result = checkPath(path); + if (result != returnvalue::OK) { + return result; + } +#endif + flashRead.path = path; + if (not std::filesystem::exists(flashRead.path)) { + return FILE_NOT_EXISTS; + } + flashRead.startRegion = startRegion; + flashRead.size = length; + { + MutexGuard mg(lock); + replyWasReceived = false; + state = InternalState::FLASH_READ; + } + semaphore.release(); + terminate = false; + return returnvalue::OK; +} + +void StrComHandler::disableTimestamping() { timestamping = false; } + +void StrComHandler::enableTimestamping() { timestamping = true; } + +ReturnValue_t StrComHandler::performImageDownload() { +#ifdef XIPHOS_Q7S + if (not sdcMan->getActiveSdCard()) { + return HasFileSystemIF::FILESYSTEM_INACTIVE; + } +#endif + ReturnValue_t result; +#if OBSW_DEBUG_STARTRACKER == 1 + ProgressPrinter progressPrinter("Image download", ImageDownload::LAST_POSITION); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + struct DownloadActionRequest downloadReq; + uint32_t size = 0; + uint32_t retries = 0; + size_t replySize = 0; + std::string image = Filenaming::generateAbsoluteFilename(downloadImage.path, + downloadImage.filename, timestamping); + std::ofstream file(image, std::ios_base::out); + if (not std::filesystem::exists(image)) { + return FILE_CREATION_FAILED; + } + downloadReq.position = 0; + while (downloadReq.position < ImageDownload::LAST_POSITION) { + if (terminate) { + file.close(); + return returnvalue::OK; + } + prv_arc_pack_download_action_req(&downloadReq, cmdBuf.data(), &size); + result = sendAndRead(size, downloadReq.position); + if (result != returnvalue::OK) { + if (retries < CONFIG_MAX_DOWNLOAD_RETRIES) { + serial::flushRxBuf(serialPort); + retries++; + continue; + } + file.close(); + return result; + } + result = checkActionReply(replySize, "downloading image"); + if (result != returnvalue::OK) { + if (retries < CONFIG_MAX_DOWNLOAD_RETRIES) { + serial::flushRxBuf(serialPort); + retries++; + continue; + } + file.close(); + return result; + } + result = checkReplyPosition(downloadReq.position); + if (result != returnvalue::OK) { + if (retries < CONFIG_MAX_DOWNLOAD_RETRIES) { + serial::flushRxBuf(serialPort); + retries++; + continue; + } + file.close(); + return result; + } + file.write(reinterpret_cast(replyPtr + IMAGE_DATA_OFFSET), CHUNK_SIZE); +#if OBSW_DEBUG_STARTRACKER == 1 + progressPrinter.print(downloadReq.position); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + downloadReq.position++; + retries = 0; + } + file.close(); + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::performImageUpload() { + ReturnValue_t result = returnvalue::OK; + uint32_t size = 0; + uint32_t imageSize = 0; + struct UploadActionRequest uploadReq; + uploadReq.position = 0; + size_t writtenBytes = 0; +#ifdef XIPHOS_Q7S + if (not sdcMan->getActiveSdCard()) { + return HasFileSystemIF::FILESYSTEM_INACTIVE; + } +#endif + std::memset(&uploadReq.data, 0, sizeof(uploadReq.data)); + if (not std::filesystem::exists(uploadImage.uploadFile)) { + triggerEvent(STR_HELPER_FILE_NOT_EXISTS, static_cast(state)); + return returnvalue::FAILED; + } + std::ifstream file(uploadImage.uploadFile, std::ifstream::binary); + if (file.bad()) { + return HasFileSystemIF::GENERIC_FILE_ERROR; + } + + // Set position of next character to end of file input stream + file.seekg(0, file.end); + // tellg returns position of character in input stream + imageSize = file.tellg(); +#if OBSW_DEBUG_STARTRACKER == 1 + ProgressPrinter progressPrinter("Image upload", imageSize); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + size_t fullChunks = imageSize / SIZE_IMAGE_PART; + size_t remainder = imageSize % SIZE_IMAGE_PART; + for (size_t idx = 0; idx < fullChunks; idx++) { + if (terminate) { + return returnvalue::OK; + } + file.seekg(uploadReq.position * SIZE_IMAGE_PART, file.beg); + file.read(reinterpret_cast(uploadReq.data), SIZE_IMAGE_PART); + prv_arc_pack_upload_action_req(&uploadReq, cmdBuf.data(), &size); + result = sendAndRead(size, uploadReq.position); + if (result != returnvalue::OK) { + return returnvalue::FAILED; + } + result = checkActionReply(replyLen, "sky image upload"); + if (result != returnvalue::OK) { + return result; + } +#if OBSW_DEBUG_STARTRACKER == 1 + progressPrinter.print((uploadReq.position + 1) * SIZE_IMAGE_PART); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + uploadReq.position++; + writtenBytes += SIZE_IMAGE_PART; + + // This does a bit of delaying roughly every second + if (uploadReq.position % 50 == 0) { + // Some grace time for other tasks + TaskFactory::delayTask(2); + } + } + if (remainder > 0) { + std::memset(uploadReq.data, 0, sizeof(uploadReq.data)); + file.seekg(fullChunks * SIZE_IMAGE_PART, file.beg); + file.read(reinterpret_cast(uploadReq.data), remainder); + file.close(); + prv_arc_pack_upload_action_req(&uploadReq, cmdBuf.data(), &size); + result = sendAndRead(size, uploadReq.position); + if (result != returnvalue::OK) { + return returnvalue::FAILED; + } + result = checkActionReply(replyLen, "sky image upload"); + if (result != returnvalue::OK) { + return result; + } + } +#if OBSW_DEBUG_STARTRACKER == 1 + progressPrinter.print((uploadReq.position + 1) * SIZE_IMAGE_PART); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::performFirmwareUpdate() { + using namespace startracker; + ReturnValue_t result = returnvalue::OK; + result = unlockAndEraseRegions(flashWrite.firstRegion, flashWrite.lastRegion); + if (result != returnvalue::OK) { + return result; + } + result = performFlashWrite(); + return result; +} + +ReturnValue_t StrComHandler::performFlashWrite() { +#ifdef XIPHOS_Q7S + if (not sdcMan->getActiveSdCard()) { + return HasFileSystemIF::FILESYSTEM_INACTIVE; + } +#endif + ReturnValue_t result = returnvalue::OK; + uint32_t size = 0; + uint32_t bytesWrittenInRegion = 0; + size_t totalBytesWritten = 0; + uint32_t fileSize = 0; + + struct WriteActionRequest req; + if (not std::filesystem::exists(flashWrite.fullname)) { + triggerEvent(STR_HELPER_FILE_NOT_EXISTS, static_cast(state)); + return returnvalue::FAILED; + } + std::ifstream file(flashWrite.fullname, std::ifstream::binary); + if (file.bad()) { + return returnvalue::FAILED; + } + file.seekg(0, file.end); + fileSize = file.tellg(); + if (fileSize > FLASH_REGION_SIZE * (flashWrite.lastRegion - flashWrite.firstRegion)) { + sif::warning << "StrHelper::performFlashWrite: Invalid file" << std::endl; + return returnvalue::FAILED; + } +#if OBSW_DEBUG_STARTRACKER == 1 + ProgressPrinter progressPrinter("Flash write", fileSize); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + uint32_t fileChunks = fileSize / CHUNK_SIZE; + bytesWrittenInRegion = 0; + req.region = flashWrite.firstRegion; + req.length = CHUNK_SIZE; + + auto writeNextSegment = [&](uint32_t chunkIdx) { + file.seekg(chunkIdx * CHUNK_SIZE, file.beg); + file.read(reinterpret_cast(req.data), CHUNK_SIZE); + if (bytesWrittenInRegion + CHUNK_SIZE > FLASH_REGION_SIZE) { + req.region++; + bytesWrittenInRegion = 0; + } + req.address = bytesWrittenInRegion; + prv_arc_pack_write_action_req(&req, cmdBuf.data(), &size); + result = sendAndRead(size, req.address); + if (result != returnvalue::OK) { + return result; + } + result = checkActionReply(replyLen, "firmware image upload"); + if (result != returnvalue::OK) { + return result; + } + totalBytesWritten += CHUNK_SIZE; + bytesWrittenInRegion += CHUNK_SIZE; +#if OBSW_DEBUG_STARTRACKER == 1 + progressPrinter.print(chunkIdx * CHUNK_SIZE); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + return result; + }; + + for (uint32_t idx = 0; idx < fileChunks; idx++) { + if (terminate) { + return returnvalue::OK; + } + result = writeNextSegment(idx); + if (result != returnvalue::OK) { + return result; + } + if (idx % 50 == 0) { + // Some grace time for other tasks + TaskFactory::delayTask(2); + } + } + uint32_t remainingBytes = fileSize - fileChunks * CHUNK_SIZE; + if (remainingBytes > 0) { + file.seekg(fileChunks * CHUNK_SIZE, file.beg); + file.read(reinterpret_cast(req.data), remainingBytes); + file.close(); + if (bytesWrittenInRegion + CHUNK_SIZE > FLASH_REGION_SIZE) { + req.region++; + bytesWrittenInRegion = 0; + } + req.address = bytesWrittenInRegion; + req.length = remainingBytes; + totalBytesWritten += CHUNK_SIZE; + bytesWrittenInRegion += remainingBytes; + prv_arc_pack_write_action_req(&req, cmdBuf.data(), &size); + result = sendAndRead(size, req.address); + if (result != returnvalue::OK) { + return result; + } + result = checkActionReply(replyLen, "flash write"); + if (result != returnvalue::OK) { + return result; + } + } +#if OBSW_DEBUG_STARTRACKER == 1 + progressPrinter.print(fileSize); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::performFlashRead() { +#ifdef XIPHOS_Q7S + if (not sdcMan->getActiveSdCard()) { + return HasFileSystemIF::FILESYSTEM_INACTIVE; + } +#endif + ReturnValue_t result; +#if OBSW_DEBUG_STARTRACKER == 1 + ProgressPrinter progressPrinter("Flash read", flashRead.size); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + struct ReadActionRequest req; + uint32_t bytesRead = 0; + uint32_t size = 0; + uint32_t retries = 0; + Timestamp timestamp; + std::string fullname = + Filenaming::generateAbsoluteFilename(flashRead.path, flashRead.filename, timestamping); + std::ofstream file(fullname, std::ios_base::app | std::ios_base::out); + if (not std::filesystem::exists(fullname)) { + return FILE_CREATION_FAILED; + } + req.region = flashRead.startRegion; + req.address = 0; + while (bytesRead < flashRead.size) { + if (terminate) { + return returnvalue::OK; + } + if ((flashRead.size - bytesRead) < CHUNK_SIZE) { + req.length = flashRead.size - bytesRead; + } else { + req.length = CHUNK_SIZE; + } + prv_arc_pack_read_action_req(&req, cmdBuf.data(), &size); + result = sendAndRead(size, req.address); + if (result != returnvalue::OK) { + if (retries < CONFIG_MAX_DOWNLOAD_RETRIES) { + serial::flushRxBuf(serialPort); + retries++; + continue; + } + file.close(); + return result; + } + result = checkActionReply(replyLen, "flash read"); + if (result != returnvalue::OK) { + if (retries < CONFIG_MAX_DOWNLOAD_RETRIES) { + serial::flushRxBuf(serialPort); + retries++; + continue; + } + file.close(); + return result; + } + file.write(reinterpret_cast(replyPtr + FLASH_READ_DATA_OFFSET), req.length); + bytesRead += req.length; + req.address += req.length; + if (req.address >= FLASH_REGION_SIZE) { + req.address = 0; + req.region++; + } + retries = 0; +#if OBSW_DEBUG_STARTRACKER == 1 + progressPrinter.print(bytesRead); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + } + file.close(); + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::sendAndRead(size_t size, uint32_t failParameter) { + ReturnValue_t result = returnvalue::OK; + + const uint8_t* sendData; + size_t txFrameLen = 0; + datalinkLayer.encodeFrame(cmdBuf.data(), size, &sendData, txFrameLen); + int writeResult = write(serialPort, sendData, txFrameLen); + if (writeResult < 0) { + sif::warning << "StrHelper::sendAndRead: Failed to send packet" << std::endl; + triggerEvent(STR_HELPER_SENDING_PACKET_FAILED, result, failParameter); + return returnvalue::FAILED; + } + + return readOneReply(failParameter); +} + +ReturnValue_t StrComHandler::checkActionReply(size_t replySize, const char* context) { + uint8_t type = startracker::getReplyFrameType(replyPtr); + if (type != TMTC_ACTIONREPLY) { + sif::warning << "StrHelper::checkActionReply: Received reply with invalid type ID" << std::endl; + return INVALID_TYPE_ID; + } + uint8_t status = startracker::getStatusField(replyPtr); + if (status != ArcsecDatalinkLayer::STATUS_OK) { + sif::warning << "StrHelper::checkActionReply: Status failure for " << context << ": " + << static_cast(status) << std::endl; + return STATUS_ERROR; + } + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::checkReplyPosition(uint32_t expectedPosition) { + uint32_t receivedPosition = 0; + std::memcpy(&receivedPosition, replyPtr + POS_OFFSET, sizeof(receivedPosition)); + if (receivedPosition != expectedPosition) { + triggerEvent(POSITION_MISMATCH, receivedPosition); + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +#ifdef XIPHOS_Q7S +ReturnValue_t StrComHandler::checkPath(std::string name) { + if (name.substr(0, sizeof(config::SD_0_MOUNT_POINT)) == std::string(config::SD_0_MOUNT_POINT)) { + if (!sdcMan->isSdCardUsable(sd::SLOT_0)) { + sif::warning << "StrHelper::checkPath: SD card 0 not mounted" << std::endl; + return SD_NOT_MOUNTED; + } + } else if (name.substr(0, sizeof(config::SD_1_MOUNT_POINT)) == + std::string(config::SD_1_MOUNT_POINT)) { + if (!sdcMan->isSdCardUsable(sd::SLOT_0)) { + sif::warning << "StrHelper::checkPath: SD card 1 not mounted" << std::endl; + return SD_NOT_MOUNTED; + } + } + return returnvalue::OK; +} +#endif + +ReturnValue_t StrComHandler::initializeInterface(CookieIF* cookie) { + if (cookie == nullptr) { + return returnvalue::FAILED; + } + SerialCookie* serCookie = dynamic_cast(cookie); + if (serCookie == nullptr) { + return DeviceCommunicationIF::INVALID_COOKIE_TYPE; + } + // comCookie = serCookie; + std::string devname = serCookie->getDeviceFile(); + /* Get file descriptor */ + serialPort = open(devname.c_str(), O_RDWR); + if (serialPort < 0) { + sif::warning << "StrComHandler: open call failed with error [" << errno << ", " + << strerror(errno) << std::endl; + return returnvalue::FAILED; + } + // Setting up UART parameters + tty.c_cflag &= ~PARENB; // Clear parity bit + serial::setStopbits(tty, serCookie->getStopBits()); + serial::setBitsPerWord(tty, BitsPerWord::BITS_8); + tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control + serial::enableRead(tty); + serial::ignoreCtrlLines(tty); + + // Use non-canonical mode and clear echo flag + tty.c_lflag &= ~(ICANON | ECHO); + + // Non-blocking mode, use polling + tty.c_cc[VTIME] = 0; + tty.c_cc[VMIN] = 0; + + serial::setBaudrate(tty, serCookie->getBaudrate()); + if (tcsetattr(serialPort, TCSANOW, &tty) != 0) { + sif::warning << "ScexUartReader::initializeInterface: tcsetattr call failed with error [" + << errno << ", " << strerror(errno) << std::endl; + } + // Flush received and unread data + tcflush(serialPort, TCIOFLUSH); + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::sendMessage(CookieIF* cookie, const uint8_t* sendData, + size_t sendLen) { + { + MutexGuard mg(lock); + if (state != InternalState::SLEEPING) { + return BUSY; + } + } + // Ensure consistent state. + resetReplyHandlingState(); + + const uint8_t* txFrame; + size_t frameLen; + datalinkLayer.encodeFrame(sendData, sendLen, &txFrame, frameLen); + if (PACKET_WIRETAPPING) { + sif::debug << "Sending STR frame" << std::endl; + arrayprinter::print(txFrame, frameLen); + } + ssize_t bytesWritten = write(serialPort, txFrame, frameLen); + if (bytesWritten != static_cast(frameLen)) { + sif::warning << "StrComHandler: Sending packet failed" << std::endl; + return returnvalue::FAILED; + } + // Hacky, but the alternatives look bleak. The raw data contains the information we need + // and there are not too many special cases. + if (sendData[0] == TMTC_ACTIONREQ) { + // 1 is a firmware boot request and 7 is a reboot request. For both, no reply is expected. + if (sendData[1] == 7 or sendData[1] == 1) { + return returnvalue::OK; + } + } + { + MutexGuard mg(lock); + state = InternalState::POLL_ONE_REPLY; + } + // Unlock task to perform reply reading. + semaphore.release(); + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } + +ReturnValue_t StrComHandler::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t StrComHandler::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) { + bool replyWasReceived = false; + { + MutexGuard mg(lock); + if (state != InternalState::SLEEPING) { + return BUSY; + } + replyWasReceived = this->replyWasReceived; + } + if (not replyWasReceived) { + *size = 0; + return returnvalue::OK; + } + if (replyResult == returnvalue::OK) { + *buffer = const_cast(replyPtr); + *size = replyLen; + } + replyLen = 0; + return replyResult; +} + +ReturnValue_t StrComHandler::unlockAndEraseRegions(uint32_t from, uint32_t to) { + ReturnValue_t result = returnvalue::OK; +#if OBSW_DEBUG_STARTRACKER == 1 + ProgressPrinter progressPrinter("Unlock and erase", to - from); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + struct UnlockActionRequest unlockReq; + struct EraseActionRequest eraseReq; + uint32_t size = 0; + for (uint32_t idx = from; idx < to; idx++) { + unlockReq.region = idx; + unlockReq.code = startracker::region_secrets::SECRETS[idx]; + prv_arc_pack_unlock_action_req(&unlockReq, cmdBuf.data(), &size); + result = sendAndRead(size, unlockReq.region); + if (result != returnvalue::OK) { + return result; + } + result = checkActionReply(replyLen, "unlocking region"); + if (result != returnvalue::OK) { + sif::warning << "StrHelper::unlockAndEraseRegions: Failed to unlock region with id " + << static_cast(unlockReq.region) << std::endl; + return result; + } + eraseReq.region = idx; + prv_arc_pack_erase_action_req(&eraseReq, cmdBuf.data(), &size); + result = sendAndRead(size, eraseReq.region); + if (result != returnvalue::OK) { + } + result = checkActionReply(replyLen, "erasing region"); + if (result != returnvalue::OK) { + sif::warning << "StrHelper::unlockAndEraseRegions: Failed to erase region with id " + << static_cast(eraseReq.region) << std::endl; + return result; + } +#if OBSW_DEBUG_STARTRACKER == 1 + progressPrinter.print(idx - from); +#endif /* OBSW_DEBUG_STARTRACKER == 1 */ + } + return result; +} + +ReturnValue_t StrComHandler::handleSerialReception() { + ssize_t bytesRead = read(serialPort, reinterpret_cast(recBuf.data()), + static_cast(recBuf.size())); + if (bytesRead == 0) { + return NO_SERIAL_DATA_READ; + } else if (bytesRead < 0) { + sif::warning << "StrComHandler: read call failed with error [" << errno << ", " + << strerror(errno) << "]" << std::endl; + return FAILED; + } else if (bytesRead >= static_cast(recBuf.size())) { + sif::error << "StrComHandler: Receive buffer too small for " << bytesRead << " bytes" + << std::endl; + return FAILED; + } else if (bytesRead > 0) { + if (PACKET_WIRETAPPING) { + sif::info << "Received " << bytesRead << " bytes from the STR" << std::endl; + arrayprinter::print(recBuf.data(), bytesRead); + } + datalinkLayer.feedData(recBuf.data(), bytesRead); + } + return OK; +} + +ReturnValue_t StrComHandler::readOneReply(uint32_t failParameter) { + ReturnValue_t result; + uint32_t nextDelayMs = 1; + replyTimeout.resetTimer(); + while (true) { + handleSerialReception(); + result = datalinkLayer.checkRingBufForFrame(&replyPtr, replyLen); + if (result == returnvalue::OK) { + if (PACKET_WIRETAPPING) { + sif::debug << "Received STR reply frame" << std::endl; + arrayprinter::print(replyPtr, replyLen); + } + return returnvalue::OK; + } else if (result != ArcsecDatalinkLayer::DEC_IN_PROGRESS) { + triggerEvent(STR_HELPER_DEC_ERROR, result, failParameter); + return DECODING_ERROR; + } + if (replyTimeout.hasTimedOut()) { + triggerEvent(STR_COM_REPLY_TIMEOUT, failParameter, replyTimeout.getTimeoutMs()); + return RECEPTION_TIMEOUT; + } + TaskFactory::delayTask(nextDelayMs); + if (nextDelayMs < 32) { + nextDelayMs *= 2; + } + } +} + +void StrComHandler::resetReplyHandlingState() { + serial::flushRxBuf(serialPort); + datalinkLayer.reset(); +} diff --git a/linux/acs/StrComHandler.h b/linux/acs/StrComHandler.h new file mode 100644 index 0000000..0fc1162 --- /dev/null +++ b/linux/acs/StrComHandler.h @@ -0,0 +1,380 @@ +#ifndef BSP_Q7S_DEVICES_STRHELPER_H_ +#define BSP_Q7S_DEVICES_STRHELPER_H_ + +#include + +#include + +#include "OBSWConfig.h" +#include "mission/acs/str/strHelpers.h" + +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/SdCardManager.h" +#endif + +#include "fsfw/devicehandlers/CookieIF.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/osal/linux/BinarySemaphore.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw_hal/linux/serial/SerialComIF.h" + +/** + * @brief Helper class for the star tracker handler to accelerate large data transfers. + * + * @author J. Meier + */ +class StrComHandler : public SystemObject, public DeviceCommunicationIF, public ExecutableObjectIF { + public: + static const uint8_t INTERFACE_ID = CLASS_ID::STR_HELPER; + + //! [EXPORT] : [COMMENT] SD card specified in path string not mounted + static const ReturnValue_t SD_NOT_MOUNTED = MAKE_RETURN_CODE(1); + //! [EXPORT] : [COMMENT] Specified file does not exist on filesystem + static const ReturnValue_t FILE_NOT_EXISTS = MAKE_RETURN_CODE(2); + //! [EXPORT] : [COMMENT] Specified path does not exist + static const ReturnValue_t PATH_NOT_EXISTS = MAKE_RETURN_CODE(3); + //! [EXPORT] : [COMMENT] Failed to create download image or read flash file + static const ReturnValue_t FILE_CREATION_FAILED = MAKE_RETURN_CODE(4); + //! [EXPORT] : [COMMENT] Region in flash write/read reply does not match expected region + static const ReturnValue_t REGION_MISMATCH = MAKE_RETURN_CODE(5); + //! [EXPORT] : [COMMENT] Address in flash write/read reply does not match expected address + static const ReturnValue_t ADDRESS_MISMATCH = MAKE_RETURN_CODE(6); + //! [EXPORT] : [COMMENT] Length in flash write/read reply does not match expected length + static const ReturnValue_t LENGTH_MISMATCH = MAKE_RETURN_CODE(7); + //! [EXPORT] : [COMMENT] Status field in reply signals error + static const ReturnValue_t STATUS_ERROR = MAKE_RETURN_CODE(8); + //! [EXPORT] : [COMMENT] Reply has invalid type ID (should be of action reply type) + static const ReturnValue_t INVALID_TYPE_ID = MAKE_RETURN_CODE(9); + static const ReturnValue_t RECEPTION_TIMEOUT = MAKE_RETURN_CODE(10); + static const ReturnValue_t DECODING_ERROR = MAKE_RETURN_CODE(11); + + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::STR_HELPER; + + //! [EXPORT] : [COMMENT] Image upload failed + static const Event IMAGE_UPLOAD_FAILED = MAKE_EVENT(0, severity::LOW); + //! [EXPORT] : [COMMENT] Image download failed + static const Event IMAGE_DOWNLOAD_FAILED = MAKE_EVENT(1, severity::LOW); + //! [EXPORT] : [COMMENT] Uploading image to star tracker was successfulop + static const Event IMAGE_UPLOAD_SUCCESSFUL = MAKE_EVENT(2, severity::LOW); + //! [EXPORT] : [COMMENT] Image download was successful + static const Event IMAGE_DOWNLOAD_SUCCESSFUL = MAKE_EVENT(3, severity::LOW); + //! [EXPORT] : [COMMENT] Finished flash write procedure successfully + static const Event FLASH_WRITE_SUCCESSFUL = MAKE_EVENT(4, severity::LOW); + //! [EXPORT] : [COMMENT] Finished flash read procedure successfully + static const Event FLASH_READ_SUCCESSFUL = MAKE_EVENT(5, severity::LOW); + //! [EXPORT] : [COMMENT] Flash read procedure failed + static const Event FLASH_READ_FAILED = MAKE_EVENT(6, severity::LOW); + //! [EXPORT] : [COMMENT] Firmware update was successful + static const Event FIRMWARE_UPDATE_SUCCESSFUL = MAKE_EVENT(7, severity::LOW); + //! [EXPORT] : [COMMENT] Firmware update failed + static const Event FIRMWARE_UPDATE_FAILED = MAKE_EVENT(8, severity::LOW); + //! [EXPORT] : [COMMENT] Failed to read communication interface reply data + //! P1: Return code of failed communication interface read call + //! P1: Upload/download position for which the read call failed + static const Event STR_HELPER_READING_REPLY_FAILED = MAKE_EVENT(9, severity::LOW); + //! [EXPORT] : [COMMENT] Unexpected stop of decoding sequence + //! P1: Return code of failed communication interface read call + //! P1: Upload/download position for which the read call failed + static const Event STR_HELPER_COM_ERROR = MAKE_EVENT(10, severity::LOW); + //! [EXPORT] : [COMMENT] Star tracker did not send a valid reply for a certain timeout. + //! P1: Position of upload or download packet for which the packet wa sent. P2: Timeout + static const Event STR_COM_REPLY_TIMEOUT = MAKE_EVENT(11, severity::LOW); + //! [EXPORT] : [COMMENT] Error during decoding of received reply occurred + //! P1: Return value of decoding function + //! P2: Position of upload/download packet, or address of flash write/read request + static const Event STR_HELPER_DEC_ERROR = MAKE_EVENT(13, severity::LOW); + //! [EXPORT] : [COMMENT] Position mismatch + //! P1: The expected position and thus the position for which the image upload/download failed + static const Event POSITION_MISMATCH = MAKE_EVENT(14, severity::LOW); + //! [EXPORT] : [COMMENT] Specified file does not exist + //! P1: Internal state of str helper + static const Event STR_HELPER_FILE_NOT_EXISTS = MAKE_EVENT(15, severity::LOW); + //! [EXPORT] : [COMMENT] Sending packet to star tracker failed + //! P1: Return code of communication interface sendMessage function + //! P2: Position of upload/download packet, or address of flash write/read request for which + //! sending failed + static const Event STR_HELPER_SENDING_PACKET_FAILED = MAKE_EVENT(16, severity::LOW); + //! [EXPORT] : [COMMENT] Communication interface requesting reply failed + //! P1: Return code of failed request + //! P1: Upload/download position, or address of flash write/read request for which transmission + //! failed + static const Event STR_HELPER_REQUESTING_MSG_FAILED = MAKE_EVENT(17, severity::LOW); + + StrComHandler(object_id_t objectId); + virtual ~StrComHandler(); + + ReturnValue_t initialize() override; + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + + /** + * @brief Starts sequence to upload image to star tracker + * + * @param uploadImage_ Name including absolute path of the image to upload. Must be previously + * transferred to the OBC with the CFDP protocol. + */ + ReturnValue_t startImageUpload(std::string uploadImage_); + + /** + * @brief Calling this function initiates the download of an image from the star tracker. + * + * @param path Path where downloaded image will be stored + */ + ReturnValue_t startImageDownload(std::string path); + + /** + * @brief Will start the firmware update + * + * @param fullname Full name including absolute path of file containing firmware + * update. + */ + ReturnValue_t startFirmwareUpdate(std::string fullname, startracker::FirmwareTarget target); + + /** + * @brief Starts the flash read procedure + * + * @param path Path where file with read flash data will be created + * @param startRegion Region form where to start reading + * @param length Number of bytes to read from flash + */ + ReturnValue_t startFlashRead(std::string path, uint8_t startRegion, uint32_t length); + + /** + * @brief Can be used to interrupt a running data transfer. + */ + void stopProcess(); + + /** + * @brief Changes the dafault name of downloaded images + */ + void setDownloadImageName(std::string filename); + + /** + * @brief Sets the name of the file which will be created to store the data read from flash + */ + void setFlashReadFilename(std::string filename); + + /** + * @brief Disables timestamp generation when new file is created + */ + void disableTimestamping(); + + /** + * @brief Enables timestamp generation when new file is created + */ + void enableTimestamping(); + + private: + //! [EXPORT] : [SKIP] + static constexpr ReturnValue_t NO_SERIAL_DATA_READ = MAKE_RETURN_CODE(128); + + // Size of one image part which can be sent per action request + static const size_t SIZE_IMAGE_PART = 1024; + static const uint32_t FLASH_REGION_SIZE = 0x20000; + + struct ImageDownload { + static const uint32_t LAST_POSITION = 4096; + }; + + static const uint32_t MAX_POLLS = 10000; + + static const uint8_t ACTION_DATA_OFFSET = 3; + static const uint8_t POS_OFFSET = 3; + static const uint8_t IMAGE_DATA_OFFSET = 6; + static const uint8_t FLASH_READ_DATA_OFFSET = 9; + static const uint8_t REGION_OFFSET = 3; + static const uint8_t ADDRESS_OFFSET = 4; + static const size_t CHUNK_SIZE = 1024; + static const size_t CONFIG_MAX_DOWNLOAD_RETRIES = 3; + static const uint32_t FLASH_ERASE_DELAY = 500; + + enum class InternalState { + SLEEPING, + POLL_ONE_REPLY, + UPLOAD_IMAGE, + DOWNLOAD_IMAGE, + FLASH_READ, + FIRMWARE_UPDATE + }; + + InternalState state = InternalState::SLEEPING; + + ArcsecDatalinkLayer datalinkLayer; + + MutexIF *lock; + BinarySemaphore semaphore; + + Countdown replyTimeout = Countdown(20); + + struct UploadImage { + // Name including absolute path of image to upload + std::string uploadFile; + }; + UploadImage uploadImage; + + struct DownloadImage { + // Path where the downloaded image will be stored + std::string path; + // Default name of downloaded image, can be changed via command + std::string filename = "image.bin"; + }; + DownloadImage downloadImage; + + struct FlashWrite { + // File which contains data to write when executing the flash write command + std::string fullname; + // The first region to write to + uint8_t firstRegion = 0; + // Maximum region the flash write command is allowed to write to + uint8_t lastRegion = 0; + // Will be set with the flash write command and specifies the start address where to write the + // flash data to + uint32_t address = 0; + }; + FlashWrite flashWrite; + + struct FlashRead { + // Path where the file containing the read data will be stored + std::string path = ""; + // Default name of file containing the data read from flash, can be changed via command + std::string filename = "flashread.bin"; + // Will be set with the flash read command + uint8_t startRegion = 0; + // Number of bytes to read from flash + uint32_t size = 0; + }; + FlashRead flashRead; + +#ifdef XIPHOS_Q7S + SdCardManager *sdcMan = nullptr; +#endif + + std::array cmdBuf{}; + std::array recBuf{}; + + bool replyWasReceived = false; + const uint8_t *replyPtr = nullptr; + size_t replyLen = 0; + ReturnValue_t replyResult = returnvalue::OK; + + bool terminate = false; + +#ifdef EGSE + bool timestamping = false; +#else + bool timestamping = true; +#endif + + int serialPort = 0; + struct termios tty = {}; + + // Queue id of raw data receiver + MessageQueueId_t rawDataReceiver = MessageQueueIF::NO_QUEUE; + + ReturnValue_t initializeInterface(CookieIF *cookie) override; + ReturnValue_t sendMessage(CookieIF *cookie, const uint8_t *sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF *cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF *cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF *cookie, uint8_t **buffer, size_t *size) override; + + ReturnValue_t handleSerialReception(); + + /** + * @brief Performs image uploading + */ + ReturnValue_t performImageUpload(); + + /** + * @brief Performs firmware update + * + * @return returnvalue::OK if successful, otherwise error return value + */ + ReturnValue_t performFirmwareUpdate(); + + /** + * @brief Performs download of last taken image from the star tracker. + * + * @details Download is split over multiple packets transporting each a maximum of 1024 bytes. + * In case the download of one position fails, the same packet will be again + * requested. If the download of the packet fails CONFIG_MAX_DOWNLOAD_RETRIES times, + * the download will be stopped. + */ + ReturnValue_t performImageDownload(); + + /** + * @brief Handles flash write procedure + * + * @param ID of first region to write to + * + * @return returnvalue::OK if successful, otherwise returnvalue::FAILED + */ + ReturnValue_t performFlashWrite(); + + /** + * @brief Sends a sequence of commands to the star tracker to read larger parts from the + * flash memory. + */ + ReturnValue_t performFlashRead(); + + /** + * @brief Sends packet to the star tracker and reads reply by using the communication + * interface + * @details + * The reply frame is stored in the data link layer helper. A pointer to the start of the frame + * is assigned to the @replyPtr member of this class. The frame length will be assigned to + * the @replyLen member. + * @param size Size of data beforehand written to the commandBuffer + * @param parameter Parameter 2 of trigger event function + * + * @return returnvalue::OK if successful, otherwise returnvalue::FAILED + */ + ReturnValue_t sendAndRead(size_t size, uint32_t parameter); + + /** + * @brief Checks the header (type id and status fields) of the action reply + * + * @return returnvalue::OK if reply confirms success of packet transfer, otherwise REUTRN_FAILED + */ + ReturnValue_t checkActionReply(size_t replySize, const char *context); + + /** + * @brief Checks the position field in a star tracker upload/download reply. + * + * @param expectedPosition Value of expected position + * + * @return returnvalue::OK if received position matches expected position, otherwise + * returnvalue::FAILED + */ + ReturnValue_t checkReplyPosition(uint32_t expectedPosition); + +#ifdef XIPHOS_Q7S + /** + * @brief Checks if a path points to an sd card and whether the SD card is monuted. + * + * @return SD_NOT_MOUNTED id SD card is not mounted, otherwise returnvalue::OK + */ + ReturnValue_t checkPath(std::string name); +#endif + + /** + * @brief Unlocks a range of flash regions + * + * @param from First region in range to unlock + * @param to Last region in range to unlock + * + */ + ReturnValue_t unlockAndEraseRegions(uint32_t from, uint32_t to); + + /** + * The reply frame is stored in the data link layer helper. A pointer to the start of the frame + * is assigned to the @replyPtr member of this class. The frame length will be assigned to + * the @replyLen member. + * @param failParameter + * @return + */ + ReturnValue_t readOneReply(uint32_t failParameter); + + void resetReplyHandlingState(); +}; + +#endif /* BSP_Q7S_DEVICES_STRHELPER_H_ */ diff --git a/linux/acs/SusPolling.cpp b/linux/acs/SusPolling.cpp new file mode 100644 index 0000000..a285d26 --- /dev/null +++ b/linux/acs/SusPolling.cpp @@ -0,0 +1,226 @@ +#include "SusPolling.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace returnvalue; + +SusPolling::SusPolling(object_id_t objectId, SpiComIF& spiComIF, GpioIF& gpioIF) + : SystemObject(objectId), spiComIF(spiComIF), gpioIF(gpioIF) { + semaphore = SemaphoreFactory::instance()->createBinarySemaphore(); + semaphore->acquire(); + ipcLock = MutexFactory::instance()->createMutex(); +} + +ReturnValue_t SusPolling::performOperation(uint8_t operationCode) { + while (true) { + ipcLock->lockMutex(); + state = InternalState::IDLE; + ipcLock->unlockMutex(); + semaphore->acquire(); + // Give SUS handlers a chance to submit all requests. + TaskFactory::delayTask(2); + { + // Takes 4-5 ms in debug mode. + // Stopwatch watch; + handleSusPolling(); + } + // Protection against tardy tasks unlocking the thread again immediately. + TaskFactory::delayTask(20); + } + return OK; +} + +ReturnValue_t SusPolling::initialize() { return OK; } + +ReturnValue_t SusPolling::initializeInterface(CookieIF* cookie) { + auto* spiCookie = dynamic_cast(cookie); + if (spiCookie == nullptr) { + return FAILED; + } + int susIdx = addressToIndex(spiCookie->getSpiAddress()); + if (susIdx < 0) { + return FAILED; + } + susDevs[susIdx].cookie = spiCookie; + return spiComIF.initializeInterface(cookie); +} + +ReturnValue_t SusPolling::sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) { + auto* spiCookie = dynamic_cast(cookie); + if (spiCookie == nullptr) { + return FAILED; + } + int susIdx = addressToIndex(spiCookie->getSpiAddress()); + if (susIdx < 0) { + return FAILED; + } + if (sendLen != sizeof(acs::SusRequest)) { + return FAILED; + } + const auto* susReq = reinterpret_cast(sendData); + MutexGuard mg(ipcLock); + if (susDevs[susIdx].mode != susReq->mode) { + if (susReq->mode == acs::SimpleSensorMode::NORMAL) { + susDevs[susIdx].performStartup = true; + susDevs[susIdx].replyResult = returnvalue::FAILED; + } else { + susDevs[susIdx].ownReply.cfgWasSet = false; + susDevs[susIdx].ownReply.dataWasSet = false; + // We are off now, but DHB wants a proper reply. + susDevs[susIdx].replyResult = returnvalue::OK; + } + susDevs[susIdx].mode = susReq->mode; + } + if (state == InternalState::IDLE) { + state = InternalState::IS_BUSY; + semaphore->release(); + } + return OK; +} + +ReturnValue_t SusPolling::getSendSuccess(CookieIF* cookie) { return OK; } + +ReturnValue_t SusPolling::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { return OK; } + +ReturnValue_t SusPolling::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) { + auto* spiCookie = dynamic_cast(cookie); + if (spiCookie == nullptr) { + return FAILED; + } + int susIdx = addressToIndex(spiCookie->getSpiAddress()); + if (susIdx < 0) { + return FAILED; + } + if (susDevs[susIdx].replyResult != returnvalue::OK) { + return susDevs[susIdx].replyResult; + } + MutexGuard mg(ipcLock); + std::memcpy(&susDevs[susIdx].readerReply, &susDevs[susIdx].ownReply, sizeof(acs::SusReply)); + *buffer = reinterpret_cast(&susDevs[susIdx].readerReply); + *size = sizeof(acs::SusReply); + return susDevs[susIdx].replyResult; +} + +ReturnValue_t SusPolling::handleSusPolling() { + ReturnValue_t result; + acs::SimpleSensorMode modes[12]; + bool performStartups[12]{}; + bool cfgsWereSet[12]{}; + uint8_t idx = 0; + { + MutexGuard mg(ipcLock); + for (idx = 0; idx < 12; idx++) { + modes[idx] = susDevs[idx].mode; + performStartups[idx] = susDevs[idx].performStartup; + } + } + for (idx = 0; idx < 12; idx++) { + if (modes[idx] == acs::SimpleSensorMode::NORMAL) { + if (performStartups[idx]) { + // Startup handling. + cmdBuf[0] = susMax1227::SETUP_INT_CLOKED; + result = spiComIF.sendMessage(susDevs[idx].cookie, cmdBuf.data(), 1); + if (result != OK) { + susDevs[idx].replyResult = result; + continue; + } + MutexGuard mg(ipcLock); + susDevs[idx].ownReply.cfgWasSet = true; + cfgsWereSet[idx] = true; + susDevs[idx].performStartup = true; + } + } + } + for (idx = 0; idx < 12; idx++) { + if (modes[idx] == acs::SimpleSensorMode::NORMAL and cfgsWereSet[idx]) { + // Regular sensor polling. + cmdBuf[0] = max1227::buildResetByte(true); + cmdBuf[1] = susMax1227::CONVERSION; + result = spiComIF.sendMessage(susDevs[idx].cookie, cmdBuf.data(), 2); + if (result != OK) { + susDevs[idx].replyResult = result; + continue; + } + } + } + + // Internal conversion time is 3.5 us + usleep(4); + + for (idx = 0; idx < 12; idx++) { + if (modes[idx] == acs::SimpleSensorMode::NORMAL and cfgsWereSet[idx]) { + std::memset(cmdBuf.data(), 0, susMax1227::SIZE_READ_INT_CONVERSIONS); + result = spiComIF.sendMessage(susDevs[idx].cookie, cmdBuf.data(), + susMax1227::SIZE_READ_INT_CONVERSIONS); + if (result != OK) { + susDevs[idx].replyResult = result; + continue; + } + result = spiComIF.readReceivedMessage(susDevs[idx].cookie, &rawReply, &dummy); + if (result != OK) { + susDevs[idx].replyResult = result; + continue; + } + MutexGuard mg(ipcLock); + susDevs[idx].ownReply.tempRaw = ((rawReply[0] & 0x0f) << 8) | rawReply[1]; + susDevs[idx].replyResult = returnvalue::OK; + for (unsigned chIdx = 0; chIdx < 6; chIdx++) { + susDevs[idx].ownReply.channelsRaw[chIdx] = + (rawReply[chIdx * 2 + 2] << 8) | rawReply[chIdx * 2 + 3]; + } + susDevs[idx].ownReply.dataWasSet = true; + } + } + return OK; +} + +int SusPolling::addressToIndex(address_t addr) { + switch (addr) { + case (addresses::SUS_0): + return 0; + break; + case (addresses::SUS_1): + return 1; + break; + case (addresses::SUS_2): + return 2; + break; + case (addresses::SUS_3): + return 3; + break; + case (addresses::SUS_4): + return 4; + break; + case (addresses::SUS_5): + return 5; + break; + case (addresses::SUS_6): + return 6; + break; + case (addresses::SUS_7): + return 7; + break; + case (addresses::SUS_8): + return 8; + break; + case (addresses::SUS_9): + return 9; + break; + case (addresses::SUS_10): + return 10; + break; + case (addresses::SUS_11): + return 11; + break; + default: { + return -1; + } + } +} diff --git a/linux/acs/SusPolling.h b/linux/acs/SusPolling.h new file mode 100644 index 0000000..470f11d --- /dev/null +++ b/linux/acs/SusPolling.h @@ -0,0 +1,52 @@ +#ifndef LINUX_DEVICES_SUSPOLLING_H_ +#define LINUX_DEVICES_SUSPOLLING_H_ + +#include +#include +#include +#include +#include + +#include "devices/addresses.h" +#include "mission/acs/acsBoardPolling.h" + +class SusPolling : public SystemObject, public ExecutableObjectIF, public DeviceCommunicationIF { + public: + SusPolling(object_id_t objectId, SpiComIF& spiComIF, GpioIF& gpioIF); + + ReturnValue_t performOperation(uint8_t operationCode) override; + ReturnValue_t initialize() override; + + private: + enum class InternalState { IDLE, IS_BUSY } state = InternalState::IDLE; + + struct SusDev { + SpiCookie* cookie = nullptr; + bool performStartup = false; + acs::SimpleSensorMode mode = acs::SimpleSensorMode::OFF; + ReturnValue_t replyResult = returnvalue::OK; + acs::SusReply ownReply{}; + acs::SusReply readerReply{}; + }; + + MutexIF* ipcLock; + SemaphoreIF* semaphore; + uint8_t* rawReply = nullptr; + size_t dummy = 0; + SpiComIF& spiComIF; + GpioIF& gpioIF; + + std::array susDevs; + std::array cmdBuf; + + ReturnValue_t initializeInterface(CookieIF* cookie) override; + ReturnValue_t sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF* cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF* cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) override; + + ReturnValue_t handleSusPolling(); + static int addressToIndex(address_t addr); +}; + +#endif /* LINUX_DEVICES_SUSPOLLING_H_ */ diff --git a/linux/boardtest/CMakeLists.txt b/linux/boardtest/CMakeLists.txt new file mode 100644 index 0000000..0ec5512 --- /dev/null +++ b/linux/boardtest/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(${OBSW_NAME} PRIVATE LibgpiodTest.cpp I2cTestClass.cpp + SpiTestClass.cpp UartTestClass.cpp) diff --git a/linux/boardtest/I2cTestClass.cpp b/linux/boardtest/I2cTestClass.cpp new file mode 100644 index 0000000..1b9f82f --- /dev/null +++ b/linux/boardtest/I2cTestClass.cpp @@ -0,0 +1,101 @@ +#include "I2cTestClass.h" + +#include +#include +#include +#include + +#include "fsfw/globalfunctions/arrayprinter.h" +#include "fsfw/serviceinterface.h" + +I2cTestClass::I2cTestClass(object_id_t objectId, std::string i2cdev) + : TestTask(objectId), i2cdev(i2cdev) { + mode = TestModes::BPX_BATTERY; +} + +ReturnValue_t I2cTestClass::initialize() { + if (mode == TestModes::BPX_BATTERY) { + battInit(); + } + return returnvalue::OK; +} + +ReturnValue_t I2cTestClass::performPeriodicAction() { + if (mode == TestModes::BPX_BATTERY) { + battPeriodic(); + } + return returnvalue::OK; +} + +void I2cTestClass::battInit() { + sif::info << "I2cTestClass: BPX Initialization" << std::endl; + UnixFileGuard fileHelper(i2cdev, bpxInfo.fd, O_RDWR, "I2cTestClass::sendMessage"); + if (fileHelper.getOpenResult() != returnvalue::OK) { + sif::error << "Opening I2C device" << i2cdev << " failed" << std::endl; + return; + } + if (ioctl(bpxInfo.fd, I2C_SLAVE, bpxInfo.addr) < 0) { + sif::error << "Failed to acquire bus access and/or talk to slave" << std::endl; + } + cmdBuf[0] = bpxBat::PORT_PING; + cmdBuf[1] = 0x42; + sendLen = 2; + ReturnValue_t result = i2cWrite(bpxInfo.fd, cmdBuf.data(), sendLen); + if (result != returnvalue::OK) { + return; + } + // Receive back port, error byte and ping reply + recvLen = 3; + result = i2cRead(bpxInfo.fd, replyBuf.data(), recvLen); + if (result != returnvalue::OK) { + return; + } + sif::info << "Ping reply:" << std::endl; + arrayprinter::print(replyBuf.data(), recvLen); + if (replyBuf[2] != 0x42) { + sif::warning << "Received ping reply not expected value 0x42" << std::endl; + } +} + +void I2cTestClass::battPeriodic() { + UnixFileGuard fileHelper(i2cdev, bpxInfo.fd, O_RDWR, "I2cTestClass::sendMessage"); + if (fileHelper.getOpenResult() != returnvalue::OK) { + sif::error << "Opening I2C device" << i2cdev << " failed" << std::endl; + return; + } + if (ioctl(bpxInfo.fd, I2C_SLAVE, bpxInfo.addr) < 0) { + sif::error << "Failed to acquire bus access and/or talk to slave" << std::endl; + } + cmdBuf[0] = bpxBat::PORT_GET_HK; + sendLen = 1; + ReturnValue_t result = i2cWrite(bpxInfo.fd, cmdBuf.data(), sendLen); + if (result != returnvalue::OK) { + return; + } + // Receive back HK set + recvLen = 23; + result = i2cRead(bpxInfo.fd, replyBuf.data(), recvLen); + if (result != returnvalue::OK) { + return; + } + sif::info << "HK reply:" << std::endl; + arrayprinter::print(replyBuf.data(), recvLen); +} + +ReturnValue_t I2cTestClass::i2cWrite(int fd, uint8_t* data, size_t len) { + if (write(fd, data, len) != static_cast(len)) { + sif::error << "Failed to write to I2C bus" << std::endl; + sif::error << "Error " << errno << ": " << strerror(errno) << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t I2cTestClass::i2cRead(int fd, uint8_t* data, size_t len) { + if (read(fd, data, len) != static_cast(len)) { + sif::error << "Failed to read from I2C bus" << std::endl; + sif::error << "Error " << errno << ": " << strerror(errno) << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} diff --git a/linux/boardtest/I2cTestClass.h b/linux/boardtest/I2cTestClass.h new file mode 100644 index 0000000..299d74c --- /dev/null +++ b/linux/boardtest/I2cTestClass.h @@ -0,0 +1,39 @@ +#ifndef LINUX_BOARDTEST_I2CTESTCLASS_H_ +#define LINUX_BOARDTEST_I2CTESTCLASS_H_ + +#include +#include + +#include +#include + +class I2cTestClass : public TestTask { + public: + I2cTestClass(object_id_t objectId, std::string i2cdev); + + ReturnValue_t initialize() override; + ReturnValue_t performPeriodicAction() override; + + private: + enum TestModes { NONE, BPX_BATTERY }; + struct I2cInfo { + int addr = 0; + int fd = 0; + }; + + TestModes mode = TestModes::NONE; + void battInit(); + void battPeriodic(); + + I2cInfo bpxInfo = {.addr = 0x07, .fd = 0}; + std::string i2cdev; + size_t sendLen = 0; + size_t recvLen = 0; + std::array cmdBuf = {}; + std::array replyBuf = {}; + + ReturnValue_t i2cWrite(int fd, uint8_t* data, size_t len); + ReturnValue_t i2cRead(int fd, uint8_t* data, size_t len); +}; + +#endif /* LINUX_BOARDTEST_I2CTESTCLASS_H_ */ diff --git a/linux/boardtest/LibgpiodTest.cpp b/linux/boardtest/LibgpiodTest.cpp new file mode 100644 index 0000000..a1cbdc1 --- /dev/null +++ b/linux/boardtest/LibgpiodTest.cpp @@ -0,0 +1,127 @@ +#include "LibgpiodTest.h" + +#include +#include +#include + +#include "devices/gpioIds.h" + +LibgpiodTest::LibgpiodTest(object_id_t objectId, object_id_t gpioIfobjectId, GpioCookie* gpioCookie) + : TestTask(objectId) { + gpioInterface = ObjectManager::instance()->get(gpioIfobjectId); + if (gpioInterface == nullptr) { + sif::error << "LibgpiodTest::LibgpiodTest: Invalid Gpio interface." << std::endl; + } + gpioInterface->addGpios(gpioCookie); + testCase = TestCases::BLINK; +} + +LibgpiodTest::~LibgpiodTest() {} + +ReturnValue_t LibgpiodTest::performPeriodicAction() { + gpio::Levels gpioState; + ReturnValue_t result; + + switch (testCase) { + case (TestCases::READ): { + result = gpioInterface->readGpio(gpioIds::TEST_ID_0, gpioState); + if (result != returnvalue::OK) { + sif::warning << "LibgpiodTest::performPeriodicAction: Failed to read gpio " << std::endl; + return returnvalue::FAILED; + } else { + sif::debug << "LibgpiodTest::performPeriodicAction: MIO 0 state = " + << static_cast(gpioState) << std::endl; + } + break; + } + case (TestCases::LOOPBACK): { + break; + } + case (TestCases::BLINK): { + result = gpioInterface->readGpio(gpioIds::TEST_ID_0, gpioState); + if (result != returnvalue::OK) { + sif::warning << "LibgpiodTest::performPeriodicAction: Failed to read gpio " << std::endl; + return returnvalue::FAILED; + } + if (gpioState == gpio::Levels::HIGH) { + result = gpioInterface->pullLow(gpioIds::TEST_ID_0); + if (result != returnvalue::OK) { + sif::warning << "LibgpiodTest::performPeriodicAction: Could not pull GPIO low!" + << std::endl; + return returnvalue::FAILED; + } + } else if (gpioState == gpio::Levels::LOW) { + result = gpioInterface->pullHigh(gpioIds::TEST_ID_0); + if (result != returnvalue::OK) { + sif::warning << "LibgpiodTest::performPeriodicAction: Could not pull GPIO high!" + << std::endl; + return returnvalue::FAILED; + } + } else { + sif::warning << "LibgpiodTest::performPeriodicAction: Invalid GPIO state" << std::endl; + } + + break; + } + default: + sif::debug << "LibgpiodTest::performPeriodicAction: Invalid test case" << std::endl; + break; + } + + return returnvalue::OK; +} + +ReturnValue_t LibgpiodTest::performOneShotAction() { + gpio::Levels gpioState; + ReturnValue_t result; + + switch (testCase) { + case (TestCases::READ): { + break; + } + case (TestCases::BLINK): { + break; + } + case (TestCases::LOOPBACK): { + result = gpioInterface->pullHigh(gpioIds::TEST_ID_0); + if (result == returnvalue::OK) { + sif::info << "LibgpiodTest::performOneShotAction: " + "GPIO pulled high successfully for loopback test" + << std::endl; + } else { + sif::warning << "LibgpiodTest::performOneShotAction: Could not pull GPIO high!" + << std::endl; + return returnvalue::OK; + } + result = gpioInterface->readGpio(gpioIds::TEST_ID_1, gpioState); + if (result == returnvalue::OK and gpioState == gpio::Levels::HIGH) { + sif::info << "LibgpiodTest::performOneShotAction: " + "GPIO state read successfully and is high" + << std::endl; + } else { + sif::warning << "LibgpiodTest::performOneShotAction: GPIO read and is not high!" + << std::endl; + return returnvalue::OK; + } + + result = gpioInterface->pullLow(gpioIds::TEST_ID_0); + if (result == returnvalue::OK) { + sif::info << "LibgpiodTest::performOneShotAction: " + "GPIO pulled low successfully for loopback test" + << std::endl; + } + result = gpioInterface->readGpio(gpioIds::TEST_ID_1, gpioState); + if (result == returnvalue::OK and gpioState == gpio::Levels::LOW) { + sif::info << "LibgpiodTest::performOneShotAction: " + "GPIO state read successfully and is low" + << std::endl; + } else { + sif::warning << "LibgpiodTest::performOneShotAction: GPIO read and is not low!" + << std::endl; + return returnvalue::OK; + } + break; + } + } + return returnvalue::OK; +} diff --git a/linux/boardtest/LibgpiodTest.h b/linux/boardtest/LibgpiodTest.h new file mode 100644 index 0000000..f7aada0 --- /dev/null +++ b/linux/boardtest/LibgpiodTest.h @@ -0,0 +1,31 @@ +#ifndef TEST_TESTTASKS_LIBGPIODTEST_H_ +#define TEST_TESTTASKS_LIBGPIODTEST_H_ + +#include +#include +#include + +#include "test/TestTask.h" + +/** + * @brief Test for the GPIO read implementation of the LinuxLibgpioIF. + * @author J. Meier + */ +class LibgpiodTest : public TestTask { + public: + enum TestCases { READ = 0, LOOPBACK = 1, BLINK }; + + TestCases testCase; + + LibgpiodTest(object_id_t objectId, object_id_t gpioIfobjectId, GpioCookie* gpioCookie); + virtual ~LibgpiodTest(); + + protected: + ReturnValue_t performOneShotAction() override; + ReturnValue_t performPeriodicAction() override; + + private: + GpioIF* gpioInterface; +}; + +#endif /* TEST_TESTTASKS_LIBGPIODTEST_H_ */ diff --git a/linux/boardtest/SpiTestClass.cpp b/linux/boardtest/SpiTestClass.cpp new file mode 100644 index 0000000..1b9b4c6 --- /dev/null +++ b/linux/boardtest/SpiTestClass.cpp @@ -0,0 +1,908 @@ +#include "SpiTestClass.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(XIPHOS_Q7S) +#include "busConf.h" +#endif +#include + +#include "devices/gpioIds.h" + +SpiTestClass::SpiTestClass(object_id_t objectId, GpioIF *gpioIF) + : TestTask(objectId), gpioIF(gpioIF) { + if (gpioIF == nullptr) { + sif::error << "SpiTestClass::SpiTestClass: Invalid GPIO ComIF!" << std::endl; + } + testMode = TestModes::MAX1227; + spiTransferStruct[0].rx_buf = reinterpret_cast<__u64>(recvBuffer.data()); + setSendBuffer(); +} + +ReturnValue_t SpiTestClass::performOneShotAction() { + switch (testMode) { + case (TestModes::NONE): { + break; + } + case (TestModes::MGM_LIS3MDL): { + performLis3MdlTest(mgm0Lis3mdlChipSelect); + break; + } + case (TestModes::MGM_RM3100): { + performRm3100Test(mgm1Rm3100ChipSelect); + break; + } + case (TestModes::GYRO_L3GD20H): { + performL3gTest(gyro1L3gd20ChipSelect); + break; + } + case (TestModes::MAX1227): { + performOneShotMax1227Test(); + break; + } + } + return returnvalue::OK; +} + +ReturnValue_t SpiTestClass::performPeriodicAction() { + switch (testMode) { + case (TestModes::MAX1227): { + performPeriodicMax1227Test(); + break; + } + default: + break; + } + return returnvalue::OK; +} + +void SpiTestClass::performRm3100Test(uint8_t mgmId) { + /* Configure all SPI chip selects and pull them high */ + acsInit(); + + /* Adapt accordingly */ + if (mgmId != mgm1Rm3100ChipSelect and mgmId != mgm3Rm3100ChipSelect) { + sif::warning << "SpiTestClass::performRm3100Test: Invalid MGM ID!" << std::endl; + } + gpioId_t currentGpioId = 0; + uint8_t chipSelectPin = mgmId; + if (chipSelectPin == mgm1Rm3100ChipSelect) { + currentGpioId = gpioIds::MGM_1_RM3100_CS; + } else { + currentGpioId = gpioIds::MGM_3_RM3100_CS; + } + uint32_t rm3100speed = 976'000; + uint8_t rm3100revidReg = 0x36; + spi::SpiModes rm3100mode = spi::SpiModes::MODE_3; + +#ifdef RASPBERRY_PI + std::string deviceName = "/dev/spidev0.0"; +#else + std::string deviceName = "/dev/spidev2.0"; +#endif + int fileDescriptor = 0; + + UnixFileGuard fileHelper(deviceName, fileDescriptor, O_RDWR, "SpiComIF::initializeInterface"); + if (fileHelper.getOpenResult()) { + sif::error << "SpiTestClass::performRm3100Test: File descriptor could not be opened!" + << std::endl; + return; + } + setSpiSpeedAndMode(fileDescriptor, rm3100mode, rm3100speed); + + uint8_t revId = readRegister(fileDescriptor, currentGpioId, rm3100revidReg); + sif::info << "SpiTestClass::performRm3100Test: Revision ID 0b" << std::bitset<8>(revId) + << std::endl; + + /* Write configuration to CMM register */ + writeRegister(fileDescriptor, currentGpioId, 0x01, 0x75); + uint8_t cmmRegister = readRm3100Register(fileDescriptor, currentGpioId, 0x01); + sif::info << "SpiTestClass::performRm3100Test: CMM register value: " << std::hex << "0x" + << static_cast(cmmRegister) << std::dec << std::endl; + + /* Read the cycle count registers */ + uint8_t cycleCountsRaw[6]; + readMultipleRegisters(fileDescriptor, currentGpioId, 0x04, cycleCountsRaw, 6); + + uint16_t cycleCountX = cycleCountsRaw[0] << 8 | cycleCountsRaw[1]; + uint16_t cycleCountY = cycleCountsRaw[2] << 8 | cycleCountsRaw[3]; + uint16_t cycleCountZ = cycleCountsRaw[4] << 8 | cycleCountsRaw[5]; + + sif::info << "Cycle count X: " << cycleCountX << std::endl; + sif::info << "Cycle count Y: " << cycleCountY << std::endl; + sif::info << "Cycle count z: " << cycleCountZ << std::endl; + + writeRegister(fileDescriptor, currentGpioId, 0x0B, 0x96); + uint8_t tmrcReg = readRm3100Register(fileDescriptor, currentGpioId, 0x0B); + sif::info << "SpiTestClass::performRm3100Test: TMRC register value: " << std::hex << "0x" + << static_cast(tmrcReg) << std::dec << std::endl; + + TaskFactory::delayTask(10); + uint8_t statusReg = readRm3100Register(fileDescriptor, currentGpioId, 0x34); + sif::info << "SpiTestClass::performRm3100Test: Status Register 0b" << std::bitset<8>(statusReg) + << std::endl; + /* This means that data is not ready */ + if ((statusReg & 0b1000'0000) == 0) { + sif::warning << "SpiTestClass::performRm3100Test: Data not ready!" << std::endl; + TaskFactory::delayTask(10); + statusReg = readRm3100Register(fileDescriptor, currentGpioId, 0x34); + if ((statusReg & 0b1000'0000) == 0) { + return; + } + } + + uint32_t rm3100DefaultCycleCout = 0xC8; + /* Gain scales lineary with cycle count and is 38 for cycle count 100 */ + float rm3100Gain = rm3100DefaultCycleCout / 100.0 * 38.0; + float scaleFactor = 1 / rm3100Gain; + uint8_t rawValues[9]; + readMultipleRegisters(fileDescriptor, currentGpioId, 0x24, rawValues, 9); + + /* The sensor generates 24 bit signed values */ + int32_t rawX = ((rawValues[0] << 24) | (rawValues[1] << 16) | (rawValues[2] << 8)) >> 8; + int32_t rawY = ((rawValues[3] << 24) | (rawValues[4] << 16) | (rawValues[5] << 8)) >> 8; + int32_t rawZ = ((rawValues[6] << 24) | (rawValues[7] << 16) | (rawValues[8] << 8)) >> 8; + + float fieldStrengthX = rawX * scaleFactor; + float fieldStrengthY = rawY * scaleFactor; + float fieldStrengthZ = rawZ * scaleFactor; + + sif::info << "RM3100 measured field strengths in microtesla:" << std::endl; + sif::info << "Field Strength X: " << fieldStrengthX << " uT" << std::endl; + sif::info << "Field Strength Y: " << fieldStrengthY << " uT" << std::endl; + sif::info << "Field Strength Z: " << fieldStrengthZ << " uT" << std::endl; +} + +void SpiTestClass::performLis3MdlTest(uint8_t lis3Id) { + /* Configure all SPI chip selects and pull them high */ + acsInit(); + + /* Adapt accordingly */ + if (lis3Id != mgm0Lis3mdlChipSelect and lis3Id != mgm2Lis3mdlChipSelect) { + sif::warning << "SpiTestClass::performLis3MdlTest: Invalid MGM ID!" << std::endl; + } + gpioId_t currentGpioId = 0; + uint8_t chipSelectPin = lis3Id; + uint8_t whoAmIReg = 0b0000'1111; + uint8_t whoAmIRegExpectedVal = 0b0011'1101; + if (chipSelectPin == mgm0Lis3mdlChipSelect) { + currentGpioId = gpioIds::MGM_0_LIS3_CS; + } else { + currentGpioId = gpioIds::MGM_2_LIS3_CS; + } + uint32_t spiSpeed = 10'000'000; + spi::SpiModes spiMode = spi::SpiModes::MODE_0; +#ifdef RASPBERRY_PI + std::string deviceName = "/dev/spidev0.0"; +#else + std::string deviceName = "/dev/spidev2.0"; +#endif + int fileDescriptor = 0; + + UnixFileGuard fileHelper(deviceName, fileDescriptor, O_RDWR, "SpiComIF::initializeInterface"); + if (fileHelper.getOpenResult()) { + sif::error << "SpiTestClass::performLis3Mdl3100Test: File descriptor could not be opened!" + << std::endl; + return; + } + setSpiSpeedAndMode(fileDescriptor, spiMode, spiSpeed); + spiTransferStruct[0].delay_usecs = 0; + + uint8_t whoAmIRegVal = readStmRegister(fileDescriptor, currentGpioId, whoAmIReg, false); + sif::info << "SpiTestClass::performLis3MdlTest: WHO AM I register 0b" + << std::bitset<8>(whoAmIRegVal) << std::endl; + if (whoAmIRegVal != whoAmIRegExpectedVal) { + sif::warning << "SpiTestClass::performLis3MdlTest: WHO AM I register invalid!" << std::endl; + } +} + +void SpiTestClass::performL3gTest(uint8_t l3gId) { + /* Configure all SPI chip selects and pull them high */ + acsInit(); + + gpioId_t currentGpioId = 0; + uint8_t chipSelectPin = l3gId; + uint8_t whoAmIReg = 0b0000'1111; + uint8_t whoAmIRegExpectedVal = 0b1101'0111; + + if (chipSelectPin == gyro1L3gd20ChipSelect) { + currentGpioId = gpioIds::GYRO_1_L3G_CS; + } else { + currentGpioId = gpioIds::GYRO_3_L3G_CS; + } + uint32_t spiSpeed = 3'900'000; + spi::SpiModes spiMode = spi::SpiModes::MODE_3; +#ifdef RASPBERRY_PI + std::string deviceName = "/dev/spidev0.0"; +#else + std::string deviceName = "/dev/spidev2.0"; +#endif + int fileDescriptor = 0; + + UnixFileGuard fileHelper(deviceName, fileDescriptor, O_RDWR, "SpiComIF::initializeInterface"); + if (fileHelper.getOpenResult()) { + sif::error << "SpiTestClass::performLis3Mdl3100Test: File descriptor could not be opened!" + << std::endl; + return; + } + setSpiSpeedAndMode(fileDescriptor, spiMode, spiSpeed); + uint8_t whoAmIRegVal = readStmRegister(fileDescriptor, currentGpioId, whoAmIReg, false); + sif::info << "SpiTestClass::performLis3MdlTest: WHO AM I register 0b" + << std::bitset<8>(whoAmIRegVal) << std::endl; + if (whoAmIRegVal != whoAmIRegExpectedVal) { + sif::warning << "SpiTestClass::performL3gTest: Read WHO AM I register invalid!" << std::endl; + } + + uint8_t ctrlReg1Addr = 0b0010'0000; + { + uint8_t commandRegs[5]; + commandRegs[0] = 0b0000'1111; + commandRegs[1] = 0x0; + commandRegs[2] = 0x0; + /* Configure big endian data format */ + commandRegs[3] = 0b0100'0000; + commandRegs[4] = 0x0; + writeMultipleStmRegisters(fileDescriptor, currentGpioId, ctrlReg1Addr, commandRegs, + sizeof(commandRegs)); + uint8_t readRegs[5]; + readMultipleRegisters(fileDescriptor, currentGpioId, ctrlReg1Addr, readRegs, sizeof(readRegs)); + for (uint8_t idx = 0; idx < sizeof(readRegs); idx++) { + if (readRegs[idx] != commandRegs[0]) { + sif::warning << "SpiTestClass::performL3gTest: Read control register " + << static_cast(idx + 1) << " not equal to configured value" << std::endl; + } + } + } + + uint8_t readOutBuffer[14]; + readMultipleStmRegisters(fileDescriptor, currentGpioId, ctrlReg1Addr, readOutBuffer, + sizeof(readOutBuffer)); + + uint8_t statusReg = readOutBuffer[7]; + sif::info << "SpiTestClass::performL3gTest: Status Register 0b" << std::bitset<8>(statusReg) + << std::endl; + + uint16_t l3gRange = 245; + float scaleFactor = static_cast(l3gRange) / INT16_MAX; + /* The sensor spits out little endian */ + int16_t angVelocRawX = (readOutBuffer[8] << 8) | readOutBuffer[9]; + int16_t angVelocRawY = (readOutBuffer[10] << 8) | readOutBuffer[11]; + int16_t angVelocRawZ = (readOutBuffer[12] << 8) | readOutBuffer[13]; + + float angVelocX = scaleFactor * angVelocRawX; + float angVelocY = scaleFactor * angVelocRawY; + float angVelocZ = scaleFactor * angVelocRawZ; + + sif::info << "Angular velocities for the L3GD20H in degrees per second:" << std::endl; + sif::info << "X: " << angVelocX << std::endl; + sif::info << "Y: " << angVelocY << std::endl; + sif::info << "Z: " << angVelocZ << std::endl; +} + +void SpiTestClass::performOneShotMax1227Test() { + using namespace max1227; + adcCfg.testRadSensorExtConvWithDelay = false; + adcCfg.testRadSensorIntConv = false; + + bool setAllSusOn = false; + bool susIntConv = false; + bool susExtConv = false; + if (setAllSusOn) { + for (uint8_t idx = 0; idx < 12; idx++) { + adcCfg.testSus[idx].doTest = true; + } + } else { + for (uint8_t idx = 0; idx < 12; idx++) { + adcCfg.testSus[idx].doTest = false; + } + } + + if (susIntConv) { + for (uint8_t idx = 0; idx < 12; idx++) { + adcCfg.testSus[idx].intConv = true; + } + } + if (susExtConv) { + for (uint8_t idx = 0; idx < 12; idx++) { + adcCfg.testSus[idx].extConv = true; + } + } + + adcCfg.plPcduAdcExtConv = true; + adcCfg.plPcduAdcIntConv = false; + // Is problematic, don't know why + adcCfg.plPcduAdcExtConvAsOne = false; + performMax1227Test(); +} + +void SpiTestClass::performPeriodicMax1227Test() { + using namespace max1227; + performMax1227Test(); +} + +void SpiTestClass::performMax1227Test() { + std::string deviceName = ""; +#ifdef XIPHOS_Q7S + deviceName = q7s::SPI_DEFAULT_DEV; +#elif defined(RASPBERRY_PI) +#elif defined(EGSE) +#elif defined(TE0720_1CFA) +#endif + int fd = 0; + UnixFileGuard fileHelper(deviceName, fd, O_RDWR, "SpiComIF::initializeInterface"); + if (fileHelper.getOpenResult()) { + sif::error << "SpiTestClass::performLis3Mdl3100Test: File descriptor could not be opened!" + << std::endl; + return; + } + uint32_t spiSpeed = 976'000; + spi::SpiModes spiMode = spi::SpiModes::MODE_3; + setSpiSpeedAndMode(fd, spiMode, spiSpeed); + + max1227RadSensorTest(fd); + int idx = 0; + bool firstTest = true; + for (auto &susCfg : adcCfg.testSus) { + if (susCfg.doTest) { + if (firstTest) { + firstTest = false; + sif::info << "---------- SUS ADC Values -----------" << std::endl; + } + sif::info << "SUS " << std::setw(2) << idx << ": "; + max1227SusTest(fd, susCfg); + } + idx++; + } + max1227PlPcduTest(fd); +} + +void SpiTestClass::max1227RadSensorTest(int fd) { + using namespace max1227; + if (adcCfg.testRadSensorExtConvWithDelay) { + sendBuffer[0] = max1227::buildResetByte(true); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::CS_RAD_SENSOR); + usleep(200); + sendBuffer[0] = max1227::buildSetupByte(ClkSel::EXT_CONV_EXT_TIMED, RefSel::INT_REF_WITH_WAKEUP, + DiffSel::NONE_0); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::CS_RAD_SENSOR); + size_t tmpSz = spiTransferStruct[0].len; + max1227::prepareExternallyClockedRead0ToN(sendBuffer.data(), 7, tmpSz); + size_t tmpLen = tmpSz; + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::CS_RAD_SENSOR); + std::memcpy(sendBuffer.data(), sendBuffer.data() + 1, tmpLen - 1); + spiTransferStruct[0].len = tmpLen - 1; + usleep(65); + transfer(fd, gpioIds::CS_RAD_SENSOR); + arrayprinter::print(recvBuffer.data(), 13, OutputType::HEX); + uint16_t adcRaw[8] = {}; + adcRaw[0] = (recvBuffer[0] << 8) | recvBuffer[1]; + adcRaw[1] = (recvBuffer[2] << 8) | recvBuffer[3]; + adcRaw[2] = (recvBuffer[4] << 8) | recvBuffer[5]; + adcRaw[3] = (recvBuffer[6] << 8) | recvBuffer[7]; + adcRaw[4] = (recvBuffer[8] << 8) | recvBuffer[9]; + adcRaw[5] = (recvBuffer[10] << 8) | recvBuffer[11]; + adcRaw[6] = (recvBuffer[12] << 8) | recvBuffer[13]; + adcRaw[7] = (recvBuffer[14] << 8) | recvBuffer[15]; + arrayprinter::print(recvBuffer.data(), 17, OutputType::HEX); + for (int idx = 0; idx < 8; idx++) { + sif::info << "ADC raw " << idx << ": " << adcRaw[idx] << std::endl; + } + tmpSz = spiTransferStruct[0].len; + max1227::prepareExternallyClockedTemperatureRead(sendBuffer.data(), tmpSz); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::CS_RAD_SENSOR); + usleep(65); + spiTransferStruct[0].len = 24; + std::memmove(sendBuffer.data(), sendBuffer.data() + 1, 24); + transfer(fd, gpioIds::CS_RAD_SENSOR); + int16_t tempRaw = ((recvBuffer[22] & 0x0f) << 8) | recvBuffer[23]; + float temp = max1227::getTemperature(tempRaw); + sif::info << "Temperature: " << temp << std::endl; + } + if (adcCfg.testRadSensorIntConv) { + sendBuffer[0] = max1227::buildResetByte(false); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::CS_RAD_SENSOR); + usleep(5); + // Now use internal conversion + sendBuffer[0] = max1227::buildSetupByte(ClkSel::INT_CONV_INT_TIMED_CNVST_AS_AIN, + RefSel::INT_REF_NO_WAKEUP, DiffSel::NONE_0); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::CS_RAD_SENSOR); + usleep(10); + sendBuffer[0] = buildConvByte(ScanModes::CHANNELS_0_TO_N, 7, true); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::CS_RAD_SENSOR); + + usleep(65); + spiTransferStruct[0].len = 18; + // Shift out zeros + shiftOutZeros(); + transfer(fd, gpioIds::CS_RAD_SENSOR); + setSendBuffer(); + + arrayprinter::print(recvBuffer.data(), 14); + uint16_t adcRaw[8] = {}; + int16_t tempRaw = ((recvBuffer[0] & 0x0f) << 8) | recvBuffer[1]; + sif::info << "Temperature: " << tempRaw * 0.125 << " C" << std::endl; + adcRaw[0] = (recvBuffer[2] << 8) | recvBuffer[3]; + adcRaw[1] = (recvBuffer[4] << 8) | recvBuffer[5]; + adcRaw[2] = (recvBuffer[6] << 8) | recvBuffer[7]; + adcRaw[3] = (recvBuffer[8] << 8) | recvBuffer[9]; + adcRaw[4] = (recvBuffer[10] << 8) | recvBuffer[11]; + adcRaw[5] = (recvBuffer[12] << 8) | recvBuffer[13]; + adcRaw[6] = (recvBuffer[14] << 8) | recvBuffer[15]; + adcRaw[7] = (recvBuffer[16] << 8) | recvBuffer[17]; + for (int idx = 0; idx < 8; idx++) { + sif::info << "ADC raw " << idx << ": " << adcRaw[idx] << std::endl; + } + } +} + +void SpiTestClass::max1227SusTest(int fd, SusTestCfg &cfg) { + using namespace max1227; + if (cfg.extConv) { + sendBuffer[0] = max1227::buildResetByte(false); + spiTransferStruct[0].len = 1; + transfer(fd, cfg.gpioId); + + usleep(65); + sendBuffer[0] = max1227::buildSetupByte(ClkSel::EXT_CONV_EXT_TIMED, RefSel::INT_REF_NO_WAKEUP, + DiffSel::NONE_0); + spiTransferStruct[0].len = 1; + transfer(fd, cfg.gpioId); + + size_t tmpSz = spiTransferStruct[0].len; + max1227::prepareExternallyClockedRead0ToN(sendBuffer.data(), 5, tmpSz); + transfer(fd, cfg.gpioId); + uint16_t adcRaw[6] = {}; + adcRaw[0] = (recvBuffer[1] << 8) | recvBuffer[2]; + adcRaw[1] = (recvBuffer[3] << 8) | recvBuffer[4]; + adcRaw[2] = (recvBuffer[5] << 8) | recvBuffer[6]; + adcRaw[3] = (recvBuffer[7] << 8) | recvBuffer[8]; + adcRaw[4] = (recvBuffer[9] << 8) | recvBuffer[10]; + adcRaw[5] = (recvBuffer[11] << 8) | recvBuffer[12]; + sif::info << "Ext Conv [" << std::hex << std::setw(3); + for (int idx = 0; idx < 5; idx++) { + sif::info << adcRaw[idx]; + if (idx < 6) { + sif::info << ","; + } + } + sif::info << std::dec << "]" << std::endl; // | Temperature: " << temp << " C" << std::endl; + } + if (cfg.intConv) { + sendBuffer[0] = max1227::buildResetByte(false); + spiTransferStruct[0].len = 1; + transfer(fd, cfg.gpioId); + usleep(65); + // Now use internal conversion + sendBuffer[0] = max1227::buildSetupByte(ClkSel::INT_CONV_INT_TIMED_CNVST_AS_AIN, + RefSel::INT_REF_NO_WAKEUP, DiffSel::NONE_0); + spiTransferStruct[0].len = 1; + transfer(fd, cfg.gpioId); + usleep(10); + sendBuffer[0] = buildConvByte(ScanModes::CHANNELS_0_TO_N, 5, true); + spiTransferStruct[0].len = 1; + transfer(fd, cfg.gpioId); + + usleep(65); + spiTransferStruct[0].len = 14; + // Shift out zeros + shiftOutZeros(); + transfer(fd, cfg.gpioId); + setSendBuffer(); + // arrayprinter::print(recvBuffer.data(), 14); + float temp = static_cast(((recvBuffer[0] & 0x0f) << 8) | recvBuffer[1]) * 0.125; + uint16_t adcRaw[6] = {}; + adcRaw[0] = (recvBuffer[2] << 8) | recvBuffer[3]; + adcRaw[1] = (recvBuffer[4] << 8) | recvBuffer[5]; + adcRaw[2] = (recvBuffer[6] << 8) | recvBuffer[7]; + adcRaw[3] = (recvBuffer[8] << 8) | recvBuffer[9]; + adcRaw[4] = (recvBuffer[10] << 8) | recvBuffer[11]; + adcRaw[5] = (recvBuffer[12] << 8) | recvBuffer[13]; + sif::info << "Int Conv [" << std::hex << std::setw(3); + for (int idx = 0; idx < 6; idx++) { + sif::info << adcRaw[idx]; + if (idx < 5) { + sif::info << ","; + } + } + sif::info << std::dec << "] | T[C] " << temp << std::endl; + } +} + +void SpiTestClass::max1227PlPcduTest(int fd) { + using namespace max1227; + if ((adcCfg.plPcduAdcExtConv or adcCfg.plPcduAdcIntConv or adcCfg.plPcduAdcExtConvAsOne) and + adcCfg.vbatSwitch) { + // This enables the ADC + ReturnValue_t result = gpioIF->pullHigh(gpioIds::PLPCDU_ENB_VBAT0); + if (result != returnvalue::OK) { + return; + } + result = gpioIF->pullHigh(gpioIds::PLPCDU_ENB_VBAT1); + if (result != returnvalue::OK) { + return; + } + adcCfg.vbatSwitch = false; + // Takes a bit of time until the ADC is usable + TaskFactory::delayTask(50); + sendBuffer[0] = max1227::buildResetByte(false); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::PLPCDU_ADC_CS); + } + if (adcCfg.plPcduAdcExtConv) { + sendBuffer[0] = max1227::buildSetupByte(ClkSel::EXT_CONV_EXT_TIMED, RefSel::INT_REF_NO_WAKEUP, + DiffSel::NONE_0); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::PLPCDU_ADC_CS); + uint8_t n = 11; + size_t tmpSz = spiTransferStruct[0].len; + max1227::prepareExternallyClockedRead0ToN(sendBuffer.data(), n, tmpSz); + spiTransferStruct[0].len = tmpSz; + size_t dummy = 0; + max1227::prepareExternallyClockedTemperatureRead(sendBuffer.data() + spiTransferStruct[0].len, + dummy); + // + 1 to account for temp conversion byte + spiTransferStruct[0].len += 1; + transfer(fd, gpioIds::PLPCDU_ADC_CS); + uint16_t adcRaw[n + 1] = {}; + for (uint8_t idx = 0; idx < n + 1; idx++) { + adcRaw[idx] = (recvBuffer[idx * 2 + 1] << 8) | recvBuffer[idx * 2 + 2]; + } + spiTransferStruct[0].len = 24; + // Shift out zeros + shiftOutZeros(); + transfer(fd, gpioIds::PLPCDU_ADC_CS); + setSendBuffer(); + int16_t tempRaw = ((recvBuffer[22] & 0x0f) << 8) | recvBuffer[23]; + sif::info << "PL PCDU ADC ext conv [" << std::hex << std::setfill('0'); + for (int idx = 0; idx < n + 1; idx++) { + sif::info << std::setw(3) << adcRaw[idx]; + if (idx < n) { + sif::info << ","; + } + } + sif::info << "]" << std::endl; + sif::info << "Temperature: " << max1227::getTemperature(tempRaw) << " C" << std::endl; + } + if (adcCfg.plPcduAdcExtConvAsOne) { + sendBuffer[0] = max1227::buildSetupByte(ClkSel::EXT_CONV_EXT_TIMED, RefSel::INT_REF_NO_WAKEUP, + DiffSel::NONE_0); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::PLPCDU_ADC_CS); + uint8_t n = 11; + size_t tmpLen = spiTransferStruct[0].len; + max1227::prepareExternallyClockedRead0ToN(sendBuffer.data(), n, tmpLen); + max1227::prepareExternallyClockedTemperatureRead(sendBuffer.data() + spiTransferStruct[0].len, + tmpLen); + spiTransferStruct[0].len = tmpLen; + transfer(fd, gpioIds::PLPCDU_ADC_CS); + uint16_t adcRaw[n + 1] = {}; + for (uint8_t idx = 0; idx < n + 1; idx++) { + adcRaw[idx] = (recvBuffer[idx * 2 + 1] << 8) | recvBuffer[idx * 2 + 2]; + } + int16_t tempRaw = ((recvBuffer[spiTransferStruct[0].len - 2] & 0x0f) << 8) | + recvBuffer[spiTransferStruct[0].len - 1]; + sif::info << "PL PCDU ADC ext conv [" << std::hex << std::setfill('0'); + for (int idx = 0; idx < n + 1; idx++) { + sif::info << std::setw(3) << adcRaw[idx]; + if (idx < n) { + sif::info << ","; + } + } + sif::info << "]" << std::endl; + sif::info << "Temperature: " << max1227::getTemperature(tempRaw) << " C" << std::endl; + } + if (adcCfg.plPcduAdcIntConv) { + sendBuffer[0] = max1227::buildResetByte(true); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::PLPCDU_ADC_CS); + // Now use internal conversion + sendBuffer[0] = max1227::buildSetupByte(ClkSel::INT_CONV_INT_TIMED_CNVST_AS_AIN, + RefSel::INT_REF_NO_WAKEUP, DiffSel::NONE_0); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::PLPCDU_ADC_CS); + usleep(10); + uint8_t n = 11; + sendBuffer[0] = buildConvByte(ScanModes::CHANNELS_0_TO_N, n, true); + spiTransferStruct[0].len = 1; + transfer(fd, gpioIds::PLPCDU_ADC_CS); + + usleep(65); + spiTransferStruct[0].len = 26; + // Shift out zeros + shiftOutZeros(); + transfer(fd, gpioIds::PLPCDU_ADC_CS); + setSendBuffer(); + uint16_t adcRaw[n + 1] = {}; + int16_t tempRaw = ((recvBuffer[0] & 0x0f) << 8) | recvBuffer[1]; + sif::info << "PL PCDU ADC int conv [" << std::hex << std::setfill('0'); + for (int idx = 0; idx < n + 1; idx++) { + adcRaw[idx] = (recvBuffer[idx * 2 + 2] << 8) | recvBuffer[idx * 2 + 3]; + sif::info << std::setw(3) << adcRaw[idx]; + if (idx < n) { + sif::info << ","; + } + } + sif::info << "]" << std::endl; + sif::info << "Temperature: " << max1227::getTemperature(tempRaw) << " C" << std::endl; + } +} + +void SpiTestClass::acsInit() { + using namespace gpio; + GpioCookie *gpioCookie = new GpioCookie(); + +#ifdef RASPBERRY_PI + GpiodRegularByChip *gpio = nullptr; + std::string rpiGpioName = "gpiochip0"; + gpio = new GpiodRegularByChip(rpiGpioName, mgm0Lis3mdlChipSelect, "MGM_0_LIS3", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::MGM_0_LIS3_CS, gpio); + + gpio = new GpiodRegularByChip(rpiGpioName, mgm1Rm3100ChipSelect, "MGM_1_RM3100", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::MGM_1_RM3100_CS, gpio); + + gpio = new GpiodRegularByChip(rpiGpioName, gyro0AdisChipSelect, "GYRO_0_ADIS", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::GYRO_0_ADIS_CS, gpio); + + gpio = new GpiodRegularByChip(rpiGpioName, gyro1L3gd20ChipSelect, "GYRO_1_L3G", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::GYRO_1_L3G_CS, gpio); + + gpio = new GpiodRegularByChip(rpiGpioName, gyro3L3gd20ChipSelect, "GYRO_2_L3G", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::GYRO_3_L3G_CS, gpio); + + gpio = new GpiodRegularByChip(rpiGpioName, mgm2Lis3mdlChipSelect, "MGM_2_LIS3", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::MGM_2_LIS3_CS, gpio); + + gpio = new GpiodRegularByChip(rpiGpioName, mgm3Rm3100ChipSelect, "MGM_3_RM3100", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::MGM_3_RM3100_CS, gpio); +#elif defined(XIPHOS_Q7S) + GpiodRegularByLineName *gpio = nullptr; + gpio = new GpiodRegularByLineName(q7s::gpioNames::MGM_0_CS, "MGM_0_LIS3", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::MGM_0_LIS3_CS, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::MGM_1_CS, "MGM_1_RM3100", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::MGM_1_RM3100_CS, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::MGM_2_CS, "MGM_2_LIS3", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::MGM_2_LIS3_CS, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::MGM_1_CS, "MGM_3_RM3100", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::MGM_3_RM3100_CS, gpio); + + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_0_ADIS_CS, "GYRO_0_ADIS", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::GYRO_0_ADIS_CS, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_1_L3G_CS, "GYRO_1_L3G", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::GYRO_1_L3G_CS, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_2_ADIS_CS, "GYRO_2_ADIS", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::GYRO_2_ADIS_CS, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_3_L3G_CS, "GYRO_3_L3G", Direction::OUT, + Levels::HIGH); + gpioCookie->addGpio(gpioIds::GYRO_3_L3G_CS, gpio); + + // Enable pins must be pulled low for regular operations + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_0_ENABLE, "GYRO_0_ENABLE", Direction::OUT, + Levels::LOW); + gpioCookie->addGpio(gpioIds::GYRO_0_ENABLE, gpio); + gpio = new GpiodRegularByLineName(q7s::gpioNames::GYRO_0_ENABLE, "GYRO_2_ENABLE", Direction::OUT, + Levels::LOW); + gpioCookie->addGpio(gpioIds::GYRO_2_ENABLE, gpio); +#endif + if (gpioIF != nullptr) { + gpioIF->addGpios(gpioCookie); + } +} + +void SpiTestClass::setSpiSpeedAndMode(int spiFd, spi::SpiModes mode, uint32_t speed) { + int modeUnix = 0; + switch (mode) { + case (spi::SpiModes::MODE_0): { + modeUnix = SPI_MODE_0; + break; + } + case (spi::SpiModes::MODE_1): { + modeUnix = SPI_MODE_1; + break; + } + case (spi::SpiModes::MODE_2): { + modeUnix = SPI_MODE_2; + break; + } + case (spi::SpiModes::MODE_3): { + modeUnix = SPI_MODE_3; + break; + } + } + + int retval = ioctl(spiFd, SPI_IOC_WR_MODE, &modeUnix); // reinterpret_cast(&mode)); + if (retval != 0) { + utility::handleIoctlError("SpiTestClass::performRm3100Test: Setting SPI mode failed!"); + } + + retval = ioctl(spiFd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); + if (retval != 0) { + utility::handleIoctlError("SpiTestClass::performRm3100Test: Setting SPI speed failed!"); + } +} + +void SpiTestClass::writeRegister(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t value) { + spiTransferStruct[0].len = 2; + sendBuffer[0] = reg; + sendBuffer[1] = value; + + if (gpioIF != nullptr and chipSelect != gpio::NO_GPIO) { + gpioIF->pullLow(chipSelect); + } + int retval = ioctl(fd, SPI_IOC_MESSAGE(1), &spiTransferStruct); + if (retval < 0) { + utility::handleIoctlError("SpiTestClass::writeRegister: Write failed"); + } + if (gpioIF != nullptr and chipSelect != gpio::NO_GPIO) { + gpioIF->pullHigh(chipSelect); + } +} + +void SpiTestClass::writeStmRegister(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t value, + bool autoIncrement) { + if (autoIncrement) { + reg |= STM_AUTO_INCR_MASK; + } + writeRegister(fd, chipSelect, reg, value); +} + +void SpiTestClass::writeMultipleStmRegisters(int fd, gpioId_t chipSelect, uint8_t reg, + uint8_t *values, size_t len) { + if (values == nullptr) { + return; + } + + reg |= STM_AUTO_INCR_MASK; + /* Clear read mask */ + reg &= ~STM_READ_MASK; + writeMultipleRegisters(fd, chipSelect, reg, values, len); +} + +void SpiTestClass::writeMultipleRegisters(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t *values, + size_t len) { + if (values == nullptr) { + return; + } + + sendBuffer[0] = reg; + std::memcpy(sendBuffer.data() + 1, values, len); + spiTransferStruct[0].len = len + 1; + + if (gpioIF != nullptr and chipSelect != gpio::NO_GPIO) { + gpioIF->pullLow(chipSelect); + } + int retval = ioctl(fd, SPI_IOC_MESSAGE(1), &spiTransferStruct); + if (retval < 0) { + utility::handleIoctlError("SpiTestClass::readRegister: Read failed"); + } + if (gpioIF != nullptr and chipSelect != gpio::NO_GPIO) { + gpioIF->pullHigh(chipSelect); + } +} + +uint8_t SpiTestClass::readRm3100Register(int fd, gpioId_t chipSelect, uint8_t reg) { + return readStmRegister(fd, chipSelect, reg, false); +} + +void SpiTestClass::readMultipleStmRegisters(int fd, gpioId_t chipSelect, uint8_t reg, + uint8_t *reply, size_t len) { + reg |= STM_AUTO_INCR_MASK; + readMultipleRegisters(fd, chipSelect, reg, reply, len); +} + +void SpiTestClass::shiftOutZeros() { spiTransferStruct[0].tx_buf = 0; } + +void SpiTestClass::setSendBuffer() { + spiTransferStruct[0].tx_buf = reinterpret_cast<__u64>(sendBuffer.data()); +} + +void SpiTestClass::readMultipleRegisters(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t *reply, + size_t len) { + if (reply == nullptr) { + return; + } + + spiTransferStruct[0].len = len + 1; + sendBuffer[0] = reg | STM_READ_MASK; + + for (uint8_t idx = 0; idx < len; idx++) { + sendBuffer[idx + 1] = 0; + } + + if (gpioIF != nullptr and chipSelect != gpio::NO_GPIO) { + gpioIF->pullLow(chipSelect); + } + int retval = ioctl(fd, SPI_IOC_MESSAGE(1), &spiTransferStruct); + if (retval < 0) { + utility::handleIoctlError("SpiTestClass::readRegister: Read failed"); + } + if (gpioIF != nullptr and chipSelect != gpio::NO_GPIO) { + gpioIF->pullHigh(chipSelect); + } + std::memcpy(reply, recvBuffer.data() + 1, len); +} + +uint8_t SpiTestClass::readStmRegister(int fd, gpioId_t chipSelect, uint8_t reg, + bool autoIncrement) { + reg |= STM_READ_MASK; + if (autoIncrement) { + reg |= STM_AUTO_INCR_MASK; + } + return readRegister(fd, chipSelect, reg); +} + +uint8_t SpiTestClass::readRegister(int fd, gpioId_t chipSelect, uint8_t reg) { + spiTransferStruct[0].len = 2; + sendBuffer[0] = reg; + sendBuffer[1] = 0; + + if (gpioIF != nullptr and chipSelect != gpio::NO_GPIO) { + gpioIF->pullLow(chipSelect); + } + int retval = ioctl(fd, SPI_IOC_MESSAGE(1), &spiTransferStruct); + if (retval < 0) { + utility::handleIoctlError("SpiTestClass::readRegister: Read failed"); + } + if (gpioIF != nullptr and chipSelect != gpio::NO_GPIO) { + gpioIF->pullHigh(chipSelect); + } + return recvBuffer[1]; +} + +ReturnValue_t SpiTestClass::transfer(int fd, gpioId_t chipSelect = gpio::NO_GPIO) { + int retval = 0; + ReturnValue_t result = returnvalue::OK; + if (chipSelect != gpio::NO_GPIO) { + result = gpioIF->pullLow(chipSelect); + if (result != returnvalue::OK) { + return result; + } + } + + retval = ioctl(fd, SPI_IOC_MESSAGE(1), &spiTransferStruct); + if (retval < 0) { + utility::handleIoctlError("SpiTestClass::transfer: ioctl failed"); + return returnvalue::FAILED; + } + + if (chipSelect != gpio::NO_GPIO) { + result = gpioIF->pullHigh(chipSelect); + if (result != returnvalue::OK) { + return result; + } + } + return returnvalue::OK; +} diff --git a/linux/boardtest/SpiTestClass.h b/linux/boardtest/SpiTestClass.h new file mode 100644 index 0000000..cdecd7f --- /dev/null +++ b/linux/boardtest/SpiTestClass.h @@ -0,0 +1,127 @@ +#ifndef LINUX_BOARDTEST_SPITESTCLASS_H_ +#define LINUX_BOARDTEST_SPITESTCLASS_H_ + +#include "OBSWConfig.h" + +#ifdef XIPHOS_Q7S +#include "busConf.h" +#endif + +#ifdef RASPBERRY_PI +#include +#endif + +#include +#include +#include + +#include + +#include "devices/gpioIds.h" + +struct SusTestCfg { + SusTestCfg(bool doTest, gpioId_t gpioId) : gpioId(gpioId) {} + bool doTest = false; + const gpioId_t gpioId; + bool intConv = true; + bool extConv = false; +}; + +struct Max1227TestCfg { + bool testRadSensorExtConvWithDelay = false; + bool testRadSensorIntConv = false; + bool plPcduAdcExtConv = false; + bool plPcduAdcExtConvAsOne = false; + bool plPcduAdcIntConv = false; + bool vbatSwitch = true; + + SusTestCfg testSus[12] = { + {false, gpioIds::CS_SUS_0}, {false, gpioIds::CS_SUS_1}, {false, gpioIds::CS_SUS_2}, + {false, gpioIds::CS_SUS_3}, {false, gpioIds::CS_SUS_4}, {false, gpioIds::CS_SUS_5}, + {false, gpioIds::CS_SUS_6}, {false, gpioIds::CS_SUS_7}, {false, gpioIds::CS_SUS_8}, + {false, gpioIds::CS_SUS_9}, {false, gpioIds::CS_SUS_10}, {false, gpioIds::CS_SUS_11}, + }; +}; +class SpiTestClass : public TestTask { + public: + enum TestModes { NONE, MGM_LIS3MDL, MGM_RM3100, GYRO_L3GD20H, MAX1227 }; + + TestModes testMode; + + SpiTestClass(object_id_t objectId, GpioIF* gpioIF); + + ReturnValue_t performOneShotAction() override; + ReturnValue_t performPeriodicAction() override; + + private: + GpioIF* gpioIF; + Max1227TestCfg adcCfg = {}; + + std::array recvBuffer; + std::array sendBuffer; + struct spi_ioc_transfer spiTransferStruct[6] = {}; + + void performRm3100Test(uint8_t mgmId); + void performLis3MdlTest(uint8_t lis3Id); + void performL3gTest(uint8_t l3gId); + void performOneShotMax1227Test(); + void performPeriodicMax1227Test(); + void performMax1227Test(); + + /* ACS board specific code which pulls all GPIOs high */ + void acsInit(); + + /* ACS board specific variables */ +#ifdef RASPBERRY_PI + uint8_t mgm0Lis3mdlChipSelect = gpio::MGM_0_BCM_PIN; + uint8_t mgm1Rm3100ChipSelect = gpio::MGM_1_BCM_PIN; + uint8_t mgm2Lis3mdlChipSelect = gpio::MGM_2_BCM_PIN; + uint8_t mgm3Rm3100ChipSelect = gpio::MGM_3_BCM_PIN; + + uint8_t gyro0AdisChipSelect = gpio::GYRO_0_BCM_PIN; + uint8_t gyro1L3gd20ChipSelect = gpio::GYRO_1_BCM_PIN; + uint8_t gyro2AdisChipSelect = gpio::GYRO_2_BCM_PIN; + uint8_t gyro3L3gd20ChipSelect = gpio::GYRO_3_BCM_PIN; +#else + + uint8_t mgm0Lis3mdlChipSelect = 0; + uint8_t mgm1Rm3100ChipSelect = 0; + uint8_t gyro0AdisResetLine = 0; + uint8_t gyro0AdisChipSelect = 0; + uint8_t gyro1L3gd20ChipSelect = 0; + uint8_t gyro2L3gd20ChipSelect = 0; + uint8_t mgm2Lis3mdlChipSelect = 0; + uint8_t mgm3Rm3100ChipSelect = 0; +#endif + + static constexpr uint8_t STM_READ_MASK = 0b1000'0000; + static constexpr uint8_t RM3100_READ_MASK = STM_READ_MASK; + static constexpr uint8_t STM_AUTO_INCR_MASK = 0b0100'0000; + + void shiftOutZeros(); + void setSendBuffer(); + + void max1227RadSensorTest(int fd); + void max1227SusTest(int fd, SusTestCfg& cfg); + void max1227PlPcduTest(int fd); + + void setSpiSpeedAndMode(int spiFd, spi::SpiModes mode, uint32_t speed); + + void writeStmRegister(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t value, + bool autoIncrement); + void writeMultipleStmRegisters(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t* values, + size_t len); + void writeMultipleRegisters(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t* values, + size_t len); + void writeRegister(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t value); + ReturnValue_t transfer(int fd, gpioId_t chipSelect); + + uint8_t readRm3100Register(int fd, gpioId_t chipSelect, uint8_t reg); + uint8_t readStmRegister(int fd, gpioId_t chipSelect, uint8_t reg, bool autoIncrement); + uint8_t readRegister(int fd, gpioId_t chipSelect, uint8_t reg); + void readMultipleStmRegisters(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t* reply, + size_t len); + void readMultipleRegisters(int fd, gpioId_t chipSelect, uint8_t reg, uint8_t* reply, size_t len); +}; + +#endif /* LINUX_BOARDTEST_SPITESTCLASS_H_ */ diff --git a/linux/boardtest/UartTestClass.cpp b/linux/boardtest/UartTestClass.cpp new file mode 100644 index 0000000..6522a90 --- /dev/null +++ b/linux/boardtest/UartTestClass.cpp @@ -0,0 +1,402 @@ +#include "UartTestClass.h" + +#include // Error integer and strerror() function +#include // Contains file controls like O_RDWR +#include +#include +#include +#include +#include +#include +#include // write(), read(), close() + +#include +#include + +#include "OBSWConfig.h" +#include "eive/objects.h" +#include "fsfw/globalfunctions/CRC.h" +#include "fsfw/globalfunctions/DleEncoder.h" +#include "fsfw/globalfunctions/arrayprinter.h" +#include "fsfw/serviceinterface.h" + +#define GPS_REPLY_WIRETAPPING 0 + +#ifndef RPI_TEST_GPS_HANDLER +#define RPI_TEST_GPS_HANDLER 0 +#endif + +using namespace returnvalue; + +UartTestClass::UartTestClass(object_id_t objectId) : TestTask(objectId) { + mode = TestModes::SCEX; + scexMode = ScexModes::SIMPLE; + // No one-cell and all-cell support implemented yet + currCmd = scex::Cmds::PING; + if (scexMode == ScexModes::SIMPLE) { + auto encodingBuf = new std::array; + DleParser::BufPair encodingBufPair{encodingBuf->data(), encodingBuf->size()}; + auto decodedBuf = new std::array; + DleParser::BufPair decodingBufPair{decodedBuf->data(), decodedBuf->size()}; + // TODO: Code changes but this test class has not, might not work like this anymore + dleParser = new ScexDleParser(*(new SimpleRingBuffer(4096, true)), dleEncoder, encodingBufPair, + decodingBufPair); + } else { + reader = new ScexUartReader(objects::SCEX_UART_READER); + } +} + +ReturnValue_t UartTestClass::initialize() { + if (mode == TestModes::GPS) { + gpsInit(); + } else if (mode == TestModes::SCEX) { + scexInit(); + } + return returnvalue::OK; +} + +ReturnValue_t UartTestClass::performOneShotAction() { return returnvalue::OK; } + +ReturnValue_t UartTestClass::performPeriodicAction() { + if (mode == TestModes::GPS) { + gpsPeriodic(); + } else if (mode == TestModes::SCEX) { + scexPeriodic(); + } + return returnvalue::OK; +} + +void UartTestClass::gpsInit() { +#if RPI_TEST_GPS_HANDLER == 1 + int result = lwgps_init(&gpsData); + if (result == 0) { + sif::warning << "UartTestClass::gpsInit: lwgps_init error: " << result << std::endl; + } + + /* Get file descriptor */ + serialPort = open("/dev/serial0", O_RDWR); + if (serialPort < 0) { + sif::warning << "UartTestClass::gpsInit: open call failed with error [" << errno << ", " + << strerror(errno) << std::endl; + } + /* Setting up UART parameters */ + tty.c_cflag &= ~PARENB; // Clear parity bit + tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication + tty.c_cflag &= ~CSIZE; // Clear all the size bits + tty.c_cflag |= CS8; // 8 bits per byte + tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control + tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) + // Use canonical mode for GPS device + tty.c_lflag |= ICANON; + tty.c_lflag &= ~ECHO; // Disable echo + tty.c_lflag &= ~ECHOE; // Disable erasure + tty.c_lflag &= ~ECHONL; // Disable new-line echo + tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP + tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | + ICRNL); // Disable any special handling of received bytes + tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) + tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed + + // Non-blocking mode + tty.c_cc[VTIME] = 0; + tty.c_cc[VMIN] = 0; + + cfsetispeed(&tty, B9600); + cfsetospeed(&tty, B9600); + if (tcsetattr(serialPort, TCSANOW, &tty) != 0) { + sif::warning << "UartTestClass::gpsInit: tcsetattr call failed with error [" << errno << ", " + << strerror(errno) << std::endl; + ; + } + // Flush received and unread data. Those are old NMEA strings which are not relevant anymore + tcflush(serialPort, TCIFLUSH); +#endif +} + +void UartTestClass::gpsPeriodic() { +#if RPI_TEST_GPS_HANDLER == 1 + int bytesRead = 0; + do { + bytesRead = read(serialPort, reinterpret_cast(recBuf.data()), + static_cast(recBuf.size())); + if (bytesRead < 0) { + sif::warning << "UartTestClass::gpsPeriodic: read call failed with error [" << errno << ", " + << strerror(errno) << "]" << std::endl; + break; + } else if (bytesRead >= static_cast(recBuf.size())) { + sif::debug << "UartTestClass::gpsPeriodic: " + "recv buffer might not be large enough" + << std::endl; + } else if (bytesRead > 0) { + // pass data to lwgps for processing +#if GPS_REPLY_WIRETAPPING == 1 + sif::info << recBuf.data() << std::endl; +#endif + int result = lwgps_process(&gpsData, recBuf.data(), bytesRead); + if (result == 0) { + sif::warning << "UartTestClass::gpsPeriodic: lwgps_process error" << std::endl; + } + recvCnt++; + if (recvCnt == 6) { + recvCnt = 0; + sif::info << "GPS Data" << std::endl; + // Print messages + printf("Valid status: %d\n", gpsData.is_valid); + printf("Latitude: %f degrees\n", gpsData.latitude); + printf("Longitude: %f degrees\n", gpsData.longitude); + printf("Altitude: %f meters\n", gpsData.altitude); + } + } + } while (bytesRead > 0); +#endif +} + +void UartTestClass::scexInit() { + if (scexMode == ScexModes::SIMPLE) { + scexSimpleInit(); + } else { + if (reader == nullptr) { + sif::warning << "UartTestClass::scexInit: Reader invalid" << std::endl; + return; + } +#if defined(RASPBERRY_PI) + std::string devname = "/dev/serial0"; +#else + std::string devname = "/dev/ul-scex"; +#endif + uartCookie = new SerialCookie(this->getObjectId(), devname, UartBaudRate::RATE_57600, 4096); + reader->setDebugMode(false); + ReturnValue_t result = reader->initializeInterface(uartCookie); + if (result != OK) { + sif::warning << "UartTestClass::scexInit: Initializing SCEX reader " + "UART IF failed" + << std::endl; + } + } +} + +void UartTestClass::scexPeriodic() { + using namespace std; + using namespace scex; + + if (scexMode == ScexModes::SIMPLE) { + scexSimplePeriodic(); + } else { + if (reader == nullptr) { + return; + } + if (not cmdSent) { + size_t len = 0; + prepareScexCmd(currCmd, false, cmdBuf.data(), &len); + reader->sendMessage(uartCookie, cmdBuf.data(), len); + cmdSent = true; + cmdDone = false; + } + if (cmdSent and not cmdDone) { + uint8_t* decodedPacket = nullptr; + size_t len = 0; + do { + ReturnValue_t result = reader->readReceivedMessage(uartCookie, &decodedPacket, &len); + if (len == 0) { + break; + } + ScexHelper helper; + const uint8_t* helperPtr = decodedPacket; + result = helper.deSerialize(&helperPtr, &len); + if (result == ScexHelper::INVALID_CRC) { + sif::warning << "UartTestClass::scexPeriodic: CRC invalid" << std::endl; + } + sif::info << helper << endl; + + // ping + // if ping cmd + if (helper.getCmd() == PING) { + ofstream out("/tmp/scex-ping.bin", ofstream::binary); + if (out.bad()) { + sif::warning << "bad" << std::endl; + } + out << helper; + } + // fram + if (helper.getCmd() == FRAM) { + if (not fileNameSet) { + fileId = random_string(6); + fileName = "/tmp/scex-fram_" + fileId + ".bin"; + fileNameSet = true; + } + if (helper.getPacketCounter() == 1) { + // countdown starten + finishCountdown.resetTimer(); + ofstream out(fileName, + ofstream::binary); // neues file anlegen + } else { + ofstream out(fileName, + ofstream::binary | ofstream::app); // an bestehendes file appenden + out << helper; + } + + if (finishCountdown.hasTimedOut()) { + triggerEvent(scex::EXPERIMENT_TIMEDOUT, currCmd, 0); + reader->finish(); + sif::warning << "UartTestClass::scexPeriodic: Reader timeout" << endl; + cmdDone = true; + fileNameSet = false; + } + } + + if (helper.getPacketCounter() == helper.getTotalPacketCounter()) { + reader->finish(); + sif::info << "UartTestClass::scexPeriodic: Reader is finished" << endl; + cmdDone = true; + fileNameSet = false; + if (helper.getCmd() == scex::Cmds::PING) { + cmdSent = false; + fileNameSet = true; // to not generate everytime new file + } + } + } while (len > 0); + } + } +} + +void UartTestClass::scexSimpleInit() { +#if defined(RASPBERRY_PI) + std::string devname = "/dev/serial0"; +#else + std::string devname = "/dev/ul-scex"; +#endif + /* Get file descriptor */ + serialPort = open(devname.c_str(), O_RDWR); + if (serialPort < 0) { + sif::warning << "UartTestClass::scexSimpleInit: Open call failed with error [" << errno << ", " + << strerror(errno) << std::endl; + return; + } + // Setting up UART parameters + tty.c_cflag &= ~PARENB; // Clear parity bit + tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication + tty.c_cflag &= ~CSIZE; // Clear all the size bits + tty.c_cflag |= CS8; // 8 bits per byte + tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control + tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) + + // Use non-canonical mode and clear echo flag + tty.c_lflag &= ~(ICANON | ECHO); + + // Non-blocking mode, read until either line is 0.1 second idle or maximum of 255 bytes are + // received in one go + tty.c_cc[VTIME] = 0; // In units of 0.1 seconds + tty.c_cc[VMIN] = 0; // Read up to 255 bytes + + // Q7S UART Lite has fixed baud rate. For other linux systems, set baud rate here. +#if !defined(XIPHOS_Q7S) + if (cfsetispeed(&tty, B57600) != 0) { + sif::warning << "UartTestClass::scexSimpleInit: Setting baud rate failed" << std::endl; + } +#endif + + if (tcsetattr(serialPort, TCSANOW, &tty) != 0) { + sif::warning << "UartTestClass::scexSimpleInit: tcsetattr call failed with error [" << errno + << ", " << strerror(errno) << std::endl; + } + // Flush received and unread data + tcflush(serialPort, TCIOFLUSH); +} + +void UartTestClass::scexSimplePeriodic() { + using namespace scex; + ReturnValue_t result = OK; + if (not cmdSent) { + // Flush received and unread data + tcflush(serialPort, TCIFLUSH); + uint8_t tmpCmdBuf[32] = {}; + size_t len = 0; + sif::info << "UartTestClass::scexSimplePeriodic: Sending command to SCEX" << std::endl; + prepareScexCmd(currCmd, false, tmpCmdBuf, &len); + result = dleEncoder.encode(tmpCmdBuf, len, cmdBuf.data(), cmdBuf.size(), &encodedLen, true); + if (result != OK) { + sif::warning << "UartTestClass::scexSimplePeriodic: Encoding failed" << std::endl; + return; + } + if (result != 0) { + return; + }; + size_t bytesWritten = write(serialPort, cmdBuf.data(), encodedLen); + if (bytesWritten != encodedLen) { + sif::warning + << "UartTestClass::scexSimplePeriodic: Sending command to solar experiment failed" + << std::endl; + } + cmdSent = true; + cmdDone = false; + } + if (not cmdDone) { + // Read back reply immediately + int bytesRead = 0; + do { + bytesRead = read(serialPort, reinterpret_cast(recBuf.data()), + static_cast(recBuf.size())); + if (bytesRead == 0) { + sif::warning << "UartTestClass::scexSimplePeriodic: Reading SCEX: Timeout or no bytes read" + << std::endl; + } else if (bytesRead < 0) { + sif::warning << "UartTestClass::scexSimplePeriodic: read call failed with error [" << errno + << ", " << strerror(errno) << "]" << std::endl; + break; + } else if (bytesRead >= static_cast(recBuf.size())) { + sif::debug << "UartTestClass::scexSimplePeriodic: recv buffer might not be large " + "enough, bytes read:" + << bytesRead << std::endl; + } else if (bytesRead > 0) { + dleParser->passData(recBuf.data(), bytesRead); + if (currCmd == Cmds::PING) { + cmdDone = true; + cmdSent = false; + } + } + } while (bytesRead > 0); + } +} + +int UartTestClass::prepareScexCmd(scex::Cmds cmd, bool tempCheck, uint8_t* cmdBuf, size_t* len) { + using namespace scex; + // Send command + cmdBuf[0] = scex::createCmdByte(cmd, false); + // These two fields are the packet counter and the total packet count. Those are 1 and 1 for each + // telecommand so far + cmdBuf[1] = 1; + cmdBuf[2] = 1; + uint16_t userDataLen = 0; + cmdBuf[3] = (userDataLen >> 8) & 0xff; + cmdBuf[4] = userDataLen & 0xff; + uint16_t crc = CRC::crc16ccitt(cmdBuf, 5); + cmdBuf[5] = (crc >> 8) & 0xff; + cmdBuf[6] = crc & 0xff; + *len = 7; + return 0; +} + +void UartTestClass::handleFoundDlePacket(uint8_t* packet, size_t len) { + sif::info << "UartTestClass::handleFoundDlePacket: Detected DLE encoded packet with decoded size " + << len << std::endl; +} + +std::string UartTestClass::random_string(std::string::size_type length) { + static auto& chrs = + "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + thread_local static std::mt19937 rg{std::random_device{}()}; + thread_local static std::uniform_int_distribution pick(0, + sizeof(chrs) - 2); + + std::string s; + + s.reserve(length); + + while (length--) s += chrs[pick(rg)]; + + return s; +} diff --git a/linux/boardtest/UartTestClass.h b/linux/boardtest/UartTestClass.h new file mode 100644 index 0000000..d03706e --- /dev/null +++ b/linux/boardtest/UartTestClass.h @@ -0,0 +1,74 @@ +#ifndef LINUX_BOARDTEST_UARTTESTCLASS_H_ +#define LINUX_BOARDTEST_UARTTESTCLASS_H_ + +#include +#include +#include +#include +#include +#include +#include // Contains POSIX terminal control definitions + +#include + +// #include "lwgps/lwgps.h" +#include "test/TestTask.h" + +class ScexUartReader; +class ScexDleParser; + +class UartTestClass : public TestTask { + public: + UartTestClass(object_id_t objectId); + + ReturnValue_t initialize() override; + ReturnValue_t performOneShotAction() override; + ReturnValue_t performPeriodicAction() override; + + private: + enum TestModes { + GPS, + // Solar Cell Experiment + SCEX + }; + + enum ScexModes { SIMPLE, READER_TASK } scexMode; + + void gpsInit(); + void gpsPeriodic(); + + void scexInit(); + void scexPeriodic(); + int prepareScexCmd(scex::Cmds cmd, bool tempCheck, uint8_t* cmdBuf, size_t* len); + + void scexSimplePeriodic(); + void scexSimpleInit(); + + static void foundDlePacketHandler(const DleParser::Context& ctx); + void handleFoundDlePacket(uint8_t* packet, size_t len); + std::string random_string(std::string::size_type length); + + std::string fileId = ""; + std::string fileName = ""; + bool fileNameSet = false; + Countdown finishCountdown = Countdown(180 * 1000); + bool cmdSent = false; + bool cmdDone = false; + scex::Cmds currCmd = scex::Cmds::PING; + TestModes mode = TestModes::GPS; + DleEncoder dleEncoder = DleEncoder(); + SerialCookie* uartCookie = nullptr; + size_t encodedLen = 0; + // lwgps_t gpsData = {}; + struct termios tty = {}; + int serialPort = 0; + bool startFound = false; + ScexUartReader* reader = nullptr; + std::array cmdBuf = {}; + std::array recBuf = {}; + ScexDleParser* dleParser; + scex::Cmds cmdHelper = scex::Cmds::INVALID; + uint8_t recvCnt = 0; +}; + +#endif /* LINUX_BOARDTEST_UARTTESTCLASS_H_ */ diff --git a/linux/callbacks/CMakeLists.txt b/linux/callbacks/CMakeLists.txt new file mode 100644 index 0000000..49a71c1 --- /dev/null +++ b/linux/callbacks/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PRIVATE gpioCallbacks.cpp) diff --git a/linux/callbacks/gpioCallbacks.cpp b/linux/callbacks/gpioCallbacks.cpp new file mode 100644 index 0000000..4c05bac --- /dev/null +++ b/linux/callbacks/gpioCallbacks.cpp @@ -0,0 +1,431 @@ +#include "gpioCallbacks.h" + +#include "devices/gpioIds.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "fsfw_hal/common/gpio/GpioCookie.h" +#include "fsfw_hal/common/gpio/GpioIF.h" + +void gpioCallbacks::spiCsDecoderCallback(gpioId_t gpioId, gpio::GpioOperation gpioOp, + gpio::Levels value, void* args) { + GpioIF* gpioIF = reinterpret_cast(args); + if (gpioIF == nullptr) { + sif::debug << "spiCsDecoderCallback: No gpioComIF specified. Call initSpiCsDecoder " + << "to specify gpioComIF" << std::endl; + return; + } + + /* Reading is not supported by the callback function */ + if (gpioOp == gpio::GpioOperation::READ) { + return; + } + + if (value == gpio::Levels::HIGH) { + switch (gpioId) { + case (gpioIds::RTD_IC_0): { + disableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_1): { + disableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_2): { + disableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_3): { + disableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_4): { + disableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_5): { + disableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_6): { + disableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_7): { + disableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_8): { + disableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_9): { + disableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_10): { + disableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_11): { + disableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_12): { + disableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_13): { + disableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_14): { + disableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_15): { + disableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_0): { + disableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_1): { + disableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_2): { + disableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_3): { + disableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_4): { + disableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_5): { + disableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_6): { + disableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_7): { + disableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_8): { + disableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_9): { + disableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_10): { + disableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_11): { + disableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_RW1): { + disableRwDecoder(gpioIF); + break; + } + case (gpioIds::CS_RW2): { + disableRwDecoder(gpioIF); + break; + } + case (gpioIds::CS_RW3): { + disableRwDecoder(gpioIF); + break; + } + case (gpioIds::CS_RW4): { + disableRwDecoder(gpioIF); + break; + } + default: + sif::debug << "spiCsDecoderCallback: Invalid gpio id " << gpioId << std::endl; + } + } else if (value == gpio::Levels::LOW) { + switch (gpioId) { + case (gpioIds::RTD_IC_0): { + selectY7(gpioIF); + enableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_1): { + selectY6(gpioIF); + enableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_2): { + selectY5(gpioIF); + enableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_3): { + selectY4(gpioIF); + enableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_4): { + selectY3(gpioIF); + enableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_5): { + selectY2(gpioIF); + enableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_6): { + selectY1(gpioIF); + enableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_7): { + selectY0(gpioIF); + enableDecoderTcsIc1(gpioIF); + break; + } + case (gpioIds::RTD_IC_8): { + selectY7(gpioIF); + enableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_9): { + selectY6(gpioIF); + enableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_10): { + selectY5(gpioIF); + enableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_11): { + selectY4(gpioIF); + enableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_12): { + selectY3(gpioIF); + enableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_13): { + selectY2(gpioIF); + enableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_14): { + selectY1(gpioIF); + enableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::RTD_IC_15): { + selectY0(gpioIF); + enableDecoderTcsIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_0): { + selectY0(gpioIF); + enableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_1): { + selectY1(gpioIF); + enableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_2): { + selectY2(gpioIF); + enableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_3): { + selectY3(gpioIF); + enableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_4): { + selectY4(gpioIF); + enableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_5): { + selectY5(gpioIF); + enableDecoderInterfaceBoardIc1(gpioIF); + break; + } + case (gpioIds::CS_SUS_6): { + selectY0(gpioIF); + enableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_7): { + selectY1(gpioIF); + enableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_8): { + selectY2(gpioIF); + enableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_9): { + selectY3(gpioIF); + enableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_10): { + selectY4(gpioIF); + enableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_SUS_11): { + selectY5(gpioIF); + enableDecoderInterfaceBoardIc2(gpioIF); + break; + } + case (gpioIds::CS_RW1): { + selectY0(gpioIF); + enableRwDecoder(gpioIF); + break; + } + case (gpioIds::CS_RW2): { + selectY1(gpioIF); + enableRwDecoder(gpioIF); + break; + } + case (gpioIds::CS_RW3): { + selectY2(gpioIF); + enableRwDecoder(gpioIF); + break; + } + case (gpioIds::CS_RW4): { + selectY3(gpioIF); + enableRwDecoder(gpioIF); + break; + } + default: + sif::debug << "spiCsDecoderCallback: Invalid gpio id " << gpioId << std::endl; + } + } else { + sif::debug << "spiCsDecoderCallback: Invalid value. Must be 0 or 1" << std::endl; + } +} + +void gpioCallbacks::enableDecoderTcsIc1(GpioIF* gpioIF) { + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_1); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_2); +} + +void gpioCallbacks::enableDecoderTcsIc2(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_2); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_1); +} + +void gpioCallbacks::enableDecoderInterfaceBoardIc1(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_1); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_2); +} + +void gpioCallbacks::enableDecoderInterfaceBoardIc2(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_1); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_2); +} + +void gpioCallbacks::disableDecoderTcsIc1(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_1); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_2); +} + +void gpioCallbacks::disableDecoderTcsIc2(GpioIF* gpioIF) { + // DO NOT CHANGE THE ORDER HERE + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_2); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_1); +} + +void gpioCallbacks::disableDecoderInterfaceBoardIc1(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_1); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_2); +} + +void gpioCallbacks::disableDecoderInterfaceBoardIc2(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_1); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_2); +} + +void gpioCallbacks::enableRwDecoder(GpioIF* gpioIF) { gpioIF->pullHigh(gpioIds::EN_RW_CS); } + +void gpioCallbacks::disableRwDecoder(GpioIF* gpioIF) { gpioIF->pullLow(gpioIds::EN_RW_CS); } + +void gpioCallbacks::selectY0(GpioIF* gpioIF) { + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_3); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_4); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_5); +} + +void gpioCallbacks::selectY1(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_3); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_4); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_5); +} + +void gpioCallbacks::selectY2(GpioIF* gpioIF) { + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_3); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_4); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_5); +} + +void gpioCallbacks::selectY3(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_3); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_4); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_5); +} + +void gpioCallbacks::selectY4(GpioIF* gpioIF) { + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_3); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_4); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_5); +} + +void gpioCallbacks::selectY5(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_3); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_4); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_5); +} + +void gpioCallbacks::selectY6(GpioIF* gpioIF) { + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_3); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_4); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_5); +} + +void gpioCallbacks::selectY7(GpioIF* gpioIF) { + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_3); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_4); + gpioIF->pullHigh(gpioIds::SPI_MUX_BIT_5); +} + +void gpioCallbacks::disableAllDecoder(GpioIF* gpioIF) { + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_2); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_0); + gpioIF->pullLow(gpioIds::SPI_MUX_BIT_1); + gpioIF->pullLow(gpioIds::EN_RW_CS); +} diff --git a/linux/callbacks/gpioCallbacks.h b/linux/callbacks/gpioCallbacks.h new file mode 100644 index 0000000..fca8960 --- /dev/null +++ b/linux/callbacks/gpioCallbacks.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +class GpioIF; + +namespace gpioCallbacks { + +/** + * @brief This function implements the decoding to multiply gpios by using the decoder + * chips SN74LVC138APWR on the TCS board and the interface board. + */ +void spiCsDecoderCallback(gpioId_t gpioId, gpio::GpioOperation gpioOp, gpio::Levels value, + void* args); + +/** + * @brief This function sets mux bits 1-3 to a state which will only enable the decoder + * on the TCS board which is named to IC1 in the schematic. + */ +void enableDecoderTcsIc1(GpioIF* gpioIF); + +/** + * @brief This function sets mux bits 1-3 to a state which will only enable the decoder + * on the TCS board which is named to IC2 in the schematic. + */ +void enableDecoderTcsIc2(GpioIF* gpioIF); + +/** + * @brief This function sets mux bits 1-3 to a state which will only enable the decoder + * on the inteface board board which is named to IC21 in the schematic. + */ +void enableDecoderInterfaceBoardIc1(GpioIF* gpioIF); + +/** + * @brief This function sets mux bits 1-3 to a state which will only enable the decoder + * on the inteface board board which is named to IC22 in the schematic. + */ +void enableDecoderInterfaceBoardIc2(GpioIF* gpioIF); + +void disableDecoderTcsIc1(GpioIF* gpioIF); +void disableDecoderTcsIc2(GpioIF* gpioIF); +void disableDecoderInterfaceBoardIc1(GpioIF* gpioIF); +void disableDecoderInterfaceBoardIc2(GpioIF* gpioIF); + +/** + * @brief Enables the reaction wheel chip select decoder (IC3). + */ +void enableRwDecoder(GpioIF* gpioIF); +void disableRwDecoder(GpioIF* gpioIF); + +/** + * @brief This function disables all decoder. + */ +void disableAllDecoder(GpioIF* gpioIF); + +/** The following functions enable the appropriate channel of the currently enabled decoder */ +void selectY0(GpioIF* gpioIF); +void selectY1(GpioIF* gpioIF); +void selectY2(GpioIF* gpioIF); +void selectY3(GpioIF* gpioIF); +void selectY4(GpioIF* gpioIF); +void selectY5(GpioIF* gpioIF); +void selectY6(GpioIF* gpioIF); +void selectY7(GpioIF* gpioIF); + +} // namespace gpioCallbacks diff --git a/linux/com/CMakeLists.txt b/linux/com/CMakeLists.txt new file mode 100644 index 0000000..99e0070 --- /dev/null +++ b/linux/com/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PUBLIC SyrlinksComHandler.cpp) diff --git a/linux/com/SyrlinksComHandler.cpp b/linux/com/SyrlinksComHandler.cpp new file mode 100644 index 0000000..35478d1 --- /dev/null +++ b/linux/com/SyrlinksComHandler.cpp @@ -0,0 +1,209 @@ +#include "SyrlinksComHandler.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace returnvalue; + +SyrlinksComHandler::SyrlinksComHandler(object_id_t objectId) + : SystemObject(objectId), ringBuf(2048, true) { + lock = MutexFactory::instance()->createMutex(); + semaphore = SemaphoreFactory::instance()->createBinarySemaphore(); + semaphore->acquire(); +} + +ReturnValue_t SyrlinksComHandler::performOperation(uint8_t opCode) { + while (true) { + lock->lockMutex(); + state = State::SLEEPING; + lock->unlockMutex(); + semaphore->acquire(); + // Stopwatch watch; + readOneReply(); + } + return returnvalue::OK; +} + +ReturnValue_t SyrlinksComHandler::initializeInterface(CookieIF *cookie) { + if (cookie == nullptr) { + return returnvalue::FAILED; + } + SerialCookie *serCookie = dynamic_cast(cookie); + if (serCookie == nullptr) { + return DeviceCommunicationIF::INVALID_COOKIE_TYPE; + } + // comCookie = serCookie; + std::string devname = serCookie->getDeviceFile(); + /* Get file descriptor */ + serialPort = open(devname.c_str(), O_RDWR); + if (serialPort < 0) { + sif::warning << "SyrlinksComHandler: open call failed with error [" << errno << ", " + << strerror(errno) << std::endl; + return returnvalue::FAILED; + } + // Setting up UART parameters + serial::setStopbits(tty, serCookie->getStopBits()); + serial::setParity(tty, serCookie->getParity()); + serial::setBitsPerWord(tty, BitsPerWord::BITS_8); + tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control + serial::enableRead(tty); + serial::ignoreCtrlLines(tty); + + // Use non-canonical mode and clear echo flag + tty.c_lflag &= ~(ICANON | ECHO); + + // Non-blocking mode, use polling + tty.c_cc[VTIME] = 0; + tty.c_cc[VMIN] = 0; + + serial::setBaudrate(tty, serCookie->getBaudrate()); + if (tcsetattr(serialPort, TCSANOW, &tty) != 0) { + sif::warning << "ScexUartReader::initializeInterface: tcsetattr call failed with error [" + << errno << ", " << strerror(errno) << std::endl; + } + // Flush received and unread data + tcflush(serialPort, TCIOFLUSH); + return returnvalue::OK; +} + +ReturnValue_t SyrlinksComHandler::sendMessage(CookieIF *cookie, const uint8_t *sendData, + size_t sendLen) { + { + MutexGuard mg(lock); + if (state != State::SLEEPING) { + return BUSY; + } + } + serial::flushRxBuf(serialPort); + + ssize_t writtenBytes = write(serialPort, sendData, sendLen); + if (writtenBytes != static_cast(sendLen)) { + sif::warning << "StrComHandler: Sending packet failed" << std::endl; + return returnvalue::FAILED; + } + { + MutexGuard mg(lock); + state = State::ACTIVE; + } + semaphore->release(); + return returnvalue::OK; +} + +ReturnValue_t SyrlinksComHandler::getSendSuccess(CookieIF *cookie) { return returnvalue::OK; } + +ReturnValue_t SyrlinksComHandler::requestReceiveMessage(CookieIF *cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t SyrlinksComHandler::handleSerialReception() { + ssize_t bytesRead = read(serialPort, reinterpret_cast(recBuf.data()), + static_cast(recBuf.size())); + if (bytesRead == 0) { + return NO_SERIAL_DATA_READ; + } else if (bytesRead < 0) { + sif::warning << "SyrlinksComHandler: read call failed with error [" << errno << ", " + << strerror(errno) << "]" << std::endl; + return FAILED; + } else if (bytesRead >= static_cast(recBuf.size())) { + sif::error << "SyrlinksComHandler: Receive buffer too small for " << bytesRead << " bytes" + << std::endl; + return FAILED; + } else if (bytesRead > 0) { + // sif::debug << "Received " << bytesRead << " bytes from the Syrlinks" << std::endl; + // arrayprinter::print(recBuf.data(), bytesRead); + ringBuf.writeData(recBuf.data(), bytesRead); + } + return OK; +} + +ReturnValue_t SyrlinksComHandler::readReceivedMessage(CookieIF *cookie, uint8_t **buffer, + size_t *size) { + MutexGuard mg(lock); + if (replyResult != returnvalue::OK) { + ReturnValue_t tmp = replyResult; + replyResult = returnvalue::OK; + return tmp; + } + if (replyLen == 0) { + *size = 0; + return returnvalue::OK; + } + *buffer = ipcBuf.data(); + *size = replyLen; + replyLen = 0; + return returnvalue::OK; +} + +ReturnValue_t SyrlinksComHandler::readOneReply() { + ReturnValue_t result; + uint32_t nextDelayMs = 1; + replyTimeout.resetTimer(); + while (true) { + handleSerialReception(); + result = tryReadingOneSyrlinksReply(); + if (result == returnvalue::OK) { + return returnvalue::OK; + } + if (replyTimeout.hasTimedOut()) { + { + MutexGuard mg(lock); + replyResult = DeviceCommunicationIF::NO_REPLY_RECEIVED; + } + return returnvalue::FAILED; + } + TaskFactory::delayTask(nextDelayMs); + if (nextDelayMs < 32) { + nextDelayMs *= 2; + } + } +} +ReturnValue_t SyrlinksComHandler::tryReadingOneSyrlinksReply() { + size_t bytesToRead = ringBuf.getAvailableReadData(); + if (bytesToRead == 0) { + return NO_PACKET_FOUND; + } + bool startMarkerFound = false; + size_t startIdx = 0; + ringBuf.readData(recBuf.data(), bytesToRead); + for (size_t idx = 0; idx < bytesToRead; idx++) { + if (recBuf[idx] == START_MARKER) { + if (startMarkerFound) { + // Probably lost a packet. Discard broken packet. + sif::warning << "SyrlinksComHandler: Detected 2 consecutive start markers" << std::endl; + ringBuf.deleteData(idx); + } else { + startMarkerFound = true; + startIdx = idx; + } + } + if (recBuf[idx] == END_MARKER) { + if (startMarkerFound) { + { + MutexGuard mg(lock); + replyLen = idx - startIdx + 1; + } + // Copy detected packet to IPC buffer so it can be passed back to the device handler. + if (replyLen > ipcBuf.size()) { + sif::error << "SyrlinksComHandler: Detected reply too large" << std::endl; + ringBuf.deleteData(idx); + return returnvalue::FAILED; + } + // sif::debug << "Detected Syrlinks reply with length " << replyLen << std::endl; + std::memcpy(ipcBuf.data(), recBuf.data() + startIdx, replyLen); + ringBuf.deleteData(idx + 1); + return returnvalue::OK; + } else { + // Probably lost a packet. Discard broken packet. + sif::warning << "SyrlinksComHandler: Detected 2 consecutive end markers" << std::endl; + ringBuf.deleteData(idx + 1); + } + } + } + return NO_PACKET_FOUND; +} diff --git a/linux/com/SyrlinksComHandler.h b/linux/com/SyrlinksComHandler.h new file mode 100644 index 0000000..5c87ff7 --- /dev/null +++ b/linux/com/SyrlinksComHandler.h @@ -0,0 +1,53 @@ +#ifndef LINUX_DEVICES_SYRLINKSCOMHANDLER_H_ +#define LINUX_DEVICES_SYRLINKSCOMHANDLER_H_ + +#include +#include +#include +#include +#include +#include + +class SyrlinksComHandler : public DeviceCommunicationIF, + public ExecutableObjectIF, + public SystemObject { + public: + SyrlinksComHandler(object_id_t objectId); + + private: + static constexpr char START_MARKER = '<'; + static constexpr char END_MARKER = '>'; + + //! [EXPORT] : [SKIP] + static constexpr ReturnValue_t NO_SERIAL_DATA_READ = returnvalue::makeCode(2, 0); + //! [EXPORT] : [SKIP] + static constexpr ReturnValue_t NO_PACKET_FOUND = returnvalue::makeCode(2, 1); + + enum class State { SLEEPING, ACTIVE } state = State::SLEEPING; + + MutexIF *lock; + SemaphoreIF *semaphore; + int serialPort = 0; + struct termios tty{}; + Countdown replyTimeout = Countdown(2000); + std::array recBuf{}; + SimpleRingBuffer ringBuf; + std::array ipcBuf{}; + size_t replyLen = 0; + ReturnValue_t replyResult = returnvalue::OK; + + ReturnValue_t handleSerialReception(); + + ReturnValue_t performOperation(uint8_t opCode) override; + + ReturnValue_t initializeInterface(CookieIF *cookie) override; + ReturnValue_t sendMessage(CookieIF *cookie, const uint8_t *sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF *cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF *cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF *cookie, uint8_t **buffer, size_t *size) override; + + ReturnValue_t readOneReply(); + ReturnValue_t tryReadingOneSyrlinksReply(); +}; + +#endif /* LINUX_DEVICES_SYRLINKSCOMHANDLER_H_ */ diff --git a/linux/fsfwconfig/CMakeLists.txt b/linux/fsfwconfig/CMakeLists.txt new file mode 100644 index 0000000..d5b5e7e --- /dev/null +++ b/linux/fsfwconfig/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources(${OBSW_NAME} PRIVATE ipc/MissionMessageTypes.cpp) + +target_include_directories(${OBSW_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# If a special translation file for object IDs exists, compile it. +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/objects/translateObjects.cpp") + target_sources(${OBSW_NAME} PRIVATE objects/translateObjects.cpp) + target_sources(${UNITTEST_NAME} PRIVATE objects/translateObjects.cpp) +endif() + +# If a special translation file for events exists, compile it. +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/objects/translateObjects.cpp") + target_sources(${OBSW_NAME} PRIVATE events/translateEvents.cpp) + target_sources(${UNITTEST_NAME} PRIVATE events/translateEvents.cpp) +endif() diff --git a/linux/fsfwconfig/FSFWConfig.h.in b/linux/fsfwconfig/FSFWConfig.h.in new file mode 100644 index 0000000..677cf0a --- /dev/null +++ b/linux/fsfwconfig/FSFWConfig.h.in @@ -0,0 +1,83 @@ +#ifndef CONFIG_FSFWCONFIG_H_ +#define CONFIG_FSFWCONFIG_H_ + +#include +#include + +// It is assumed the user has a subsystem and class ID list in some user header files. +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +//! Used to determine whether C++ ostreams are used which can increase +//! the binary size significantly. If this is disabled, +//! the C stdio functions can be used alternatively +#define FSFW_CPP_OSTREAM_ENABLED 1 + +//! More FSFW related printouts depending on level. Useful for development. +#define FSFW_VERBOSE_LEVEL 1 + +//! Can be used to completely disable printouts, even the C stdio ones. +#if FSFW_CPP_OSTREAM_ENABLED == 0 && FSFW_VERBOSE_LEVEL == 0 + #define FSFW_DISABLE_PRINTOUT 0 +#endif + +#define FSFW_USE_PUS_C_TELEMETRY 1 +#define FSFW_USE_PUS_C_TELECOMMANDS 1 + +//! Can be used to disable the ANSI color sequences for C stdio. +#define FSFW_COLORED_OUTPUT 1 + +//! If FSFW_OBJ_EVENT_TRANSLATION is set to one, +//! additional output which requires the translation files translateObjects +//! and translateEvents (and their compiled source files) +#define FSFW_OBJ_EVENT_TRANSLATION 1 + +#if FSFW_OBJ_EVENT_TRANSLATION == 1 +//! Specify whether info events are printed too. +#define FSFW_DEBUG_INFO @FSFW_DEBUG_INFO@ +#include "objects/translateObjects.h" +#include "events/translateEvents.h" +#else +#endif + +//! When using the newlib nano library, C99 support for stdio facilities +//! will not be provided. This define should be set to 1 if this is the case. +#define FSFW_NO_C99_IO 0 + +//! Specify whether a special mode store is used for Subsystem components. +#define FSFW_USE_MODESTORE 0 + +//! Defines if the real time scheduler for linux should be used. +//! If set to 0, this will also disable priority settings for linux +//! as most systems will not allow to set nice values without privileges +//! For embedded linux system set this to 1. +//! If set to 1 the binary needs "cap_sys_nice=eip" privileges to run +#define FSFW_USE_REALTIME_FOR_LINUX 1 + +namespace fsfwconfig { +//! Default timestamp size. The default timestamp will be an eight byte CDC +//! short timestamp. +static constexpr uint8_t FSFW_MISSION_TIMESTAMP_SIZE = 7; + +//! Configure the allocated pool sizes for the event manager. +static constexpr size_t FSFW_EVENTMGMR_MATCHTREE_NODES = 240; +static constexpr size_t FSFW_EVENTMGMT_EVENTIDMATCHERS = 120; +static constexpr size_t FSFW_EVENTMGMR_RANGEMATCHERS = 120; + +//! Defines the FIFO depth of each commanding service base which +//! also determines how many commands a CSB service can handle in one cycle +//! simulataneously. This will increase the required RAM for +//! each CSB service ! +static constexpr uint8_t FSFW_CSB_FIFO_DEPTH = 6; + +static constexpr size_t FSFW_PRINT_BUFFER_SIZE = 124; + +static constexpr size_t FSFW_MAX_TM_PACKET_SIZE = 2048; + +} + +#define FSFW_HAL_SPI_WIRETAPPING 0 +#define FSFW_HAL_I2C_WIRETAPPING 0 +#define FSFW_DEV_HYPERION_GPS_CREATE_NMEA_CSV 0 + +#endif /* CONFIG_FSFWCONFIG_H_ */ diff --git a/linux/fsfwconfig/events/subsystemIdRanges.h b/linux/fsfwconfig/events/subsystemIdRanges.h new file mode 100644 index 0000000..7e23e37 --- /dev/null +++ b/linux/fsfwconfig/events/subsystemIdRanges.h @@ -0,0 +1,19 @@ +#ifndef FSFWCONFIG_EVENTS_SUBSYSTEMIDRANGES_H_ +#define FSFWCONFIG_EVENTS_SUBSYSTEMIDRANGES_H_ + +#include + +#include "eive/eventSubsystemIds.h" +#include "fsfw/events/fwSubsystemIdRanges.h" + +/** + * These IDs are part of the ID for an event thrown by a subsystem. + * Numbers 0-80 are reserved for FSFW Subsystem IDs (framework/events/) + */ +namespace SUBSYSTEM_ID { +enum : uint8_t { + SUBSYSTEM_ID_START = COMMON_SUBSYSTEM_ID_END, +}; +} + +#endif /* FSFWCONFIG_EVENTS_SUBSYSTEMIDRANGES_H_ */ diff --git a/linux/fsfwconfig/events/translateEvents.cpp b/linux/fsfwconfig/events/translateEvents.cpp new file mode 100644 index 0000000..7a8da30 --- /dev/null +++ b/linux/fsfwconfig/events/translateEvents.cpp @@ -0,0 +1,990 @@ +/** + * @brief Auto-generated event translation file. Contains 325 translations. + * @details + * Generated on: 2024-05-06 13:47:38 + */ +#include "translateEvents.h" + +const char *STORE_SEND_WRITE_FAILED_STRING = "STORE_SEND_WRITE_FAILED"; +const char *STORE_WRITE_FAILED_STRING = "STORE_WRITE_FAILED"; +const char *STORE_SEND_READ_FAILED_STRING = "STORE_SEND_READ_FAILED"; +const char *STORE_READ_FAILED_STRING = "STORE_READ_FAILED"; +const char *UNEXPECTED_MSG_STRING = "UNEXPECTED_MSG"; +const char *STORING_FAILED_STRING = "STORING_FAILED"; +const char *TM_DUMP_FAILED_STRING = "TM_DUMP_FAILED"; +const char *STORE_INIT_FAILED_STRING = "STORE_INIT_FAILED"; +const char *STORE_INIT_EMPTY_STRING = "STORE_INIT_EMPTY"; +const char *STORE_CONTENT_CORRUPTED_STRING = "STORE_CONTENT_CORRUPTED"; +const char *STORE_INITIALIZE_STRING = "STORE_INITIALIZE"; +const char *INIT_DONE_STRING = "INIT_DONE"; +const char *DUMP_FINISHED_STRING = "DUMP_FINISHED"; +const char *DELETION_FINISHED_STRING = "DELETION_FINISHED"; +const char *DELETION_FAILED_STRING = "DELETION_FAILED"; +const char *AUTO_CATALOGS_SENDING_FAILED_STRING = "AUTO_CATALOGS_SENDING_FAILED"; +const char *GET_DATA_FAILED_STRING = "GET_DATA_FAILED"; +const char *STORE_DATA_FAILED_STRING = "STORE_DATA_FAILED"; +const char *DEVICE_BUILDING_COMMAND_FAILED_STRING = "DEVICE_BUILDING_COMMAND_FAILED"; +const char *DEVICE_SENDING_COMMAND_FAILED_STRING = "DEVICE_SENDING_COMMAND_FAILED"; +const char *DEVICE_REQUESTING_REPLY_FAILED_STRING = "DEVICE_REQUESTING_REPLY_FAILED"; +const char *DEVICE_READING_REPLY_FAILED_STRING = "DEVICE_READING_REPLY_FAILED"; +const char *DEVICE_INTERPRETING_REPLY_FAILED_STRING = "DEVICE_INTERPRETING_REPLY_FAILED"; +const char *DEVICE_MISSED_REPLY_STRING = "DEVICE_MISSED_REPLY"; +const char *DEVICE_UNKNOWN_REPLY_STRING = "DEVICE_UNKNOWN_REPLY"; +const char *DEVICE_UNREQUESTED_REPLY_STRING = "DEVICE_UNREQUESTED_REPLY"; +const char *INVALID_DEVICE_COMMAND_STRING = "INVALID_DEVICE_COMMAND"; +const char *MONITORING_LIMIT_EXCEEDED_STRING = "MONITORING_LIMIT_EXCEEDED"; +const char *MONITORING_AMBIGUOUS_STRING = "MONITORING_AMBIGUOUS"; +const char *DEVICE_WANTS_HARD_REBOOT_STRING = "DEVICE_WANTS_HARD_REBOOT"; +const char *SWITCH_WENT_OFF_STRING = "SWITCH_WENT_OFF"; +const char *FUSE_CURRENT_HIGH_STRING = "FUSE_CURRENT_HIGH"; +const char *FUSE_WENT_OFF_STRING = "FUSE_WENT_OFF"; +const char *POWER_ABOVE_HIGH_LIMIT_STRING = "POWER_ABOVE_HIGH_LIMIT"; +const char *POWER_BELOW_LOW_LIMIT_STRING = "POWER_BELOW_LOW_LIMIT"; +const char *HEATER_ON_STRING = "HEATER_ON"; +const char *HEATER_OFF_STRING = "HEATER_OFF"; +const char *HEATER_TIMEOUT_STRING = "HEATER_TIMEOUT"; +const char *HEATER_STAYED_ON_STRING = "HEATER_STAYED_ON"; +const char *HEATER_STAYED_OFF_STRING = "HEATER_STAYED_OFF"; +const char *TEMP_SENSOR_HIGH_STRING = "TEMP_SENSOR_HIGH"; +const char *TEMP_SENSOR_LOW_STRING = "TEMP_SENSOR_LOW"; +const char *TEMP_SENSOR_GRADIENT_STRING = "TEMP_SENSOR_GRADIENT"; +const char *COMPONENT_TEMP_LOW_STRING = "COMPONENT_TEMP_LOW"; +const char *COMPONENT_TEMP_HIGH_STRING = "COMPONENT_TEMP_HIGH"; +const char *COMPONENT_TEMP_OOL_LOW_STRING = "COMPONENT_TEMP_OOL_LOW"; +const char *COMPONENT_TEMP_OOL_HIGH_STRING = "COMPONENT_TEMP_OOL_HIGH"; +const char *TEMP_NOT_IN_OP_RANGE_STRING = "TEMP_NOT_IN_OP_RANGE"; +const char *FDIR_CHANGED_STATE_STRING = "FDIR_CHANGED_STATE"; +const char *FDIR_STARTS_RECOVERY_STRING = "FDIR_STARTS_RECOVERY"; +const char *FDIR_TURNS_OFF_DEVICE_STRING = "FDIR_TURNS_OFF_DEVICE"; +const char *MONITOR_CHANGED_STATE_STRING = "MONITOR_CHANGED_STATE"; +const char *VALUE_BELOW_LOW_LIMIT_STRING = "VALUE_BELOW_LOW_LIMIT"; +const char *VALUE_ABOVE_HIGH_LIMIT_STRING = "VALUE_ABOVE_HIGH_LIMIT"; +const char *VALUE_OUT_OF_RANGE_STRING = "VALUE_OUT_OF_RANGE"; +const char *CHANGING_MODE_STRING = "CHANGING_MODE"; +const char *MODE_INFO_STRING = "MODE_INFO"; +const char *FALLBACK_FAILED_STRING = "FALLBACK_FAILED"; +const char *MODE_TRANSITION_FAILED_STRING = "MODE_TRANSITION_FAILED"; +const char *CANT_KEEP_MODE_STRING = "CANT_KEEP_MODE"; +const char *OBJECT_IN_INVALID_MODE_STRING = "OBJECT_IN_INVALID_MODE"; +const char *FORCING_MODE_STRING = "FORCING_MODE"; +const char *MODE_CMD_REJECTED_STRING = "MODE_CMD_REJECTED"; +const char *HEALTH_INFO_STRING = "HEALTH_INFO"; +const char *CHILD_CHANGED_HEALTH_STRING = "CHILD_CHANGED_HEALTH"; +const char *CHILD_PROBLEMS_STRING = "CHILD_PROBLEMS"; +const char *OVERWRITING_HEALTH_STRING = "OVERWRITING_HEALTH"; +const char *TRYING_RECOVERY_STRING = "TRYING_RECOVERY"; +const char *RECOVERY_STEP_STRING = "RECOVERY_STEP"; +const char *RECOVERY_DONE_STRING = "RECOVERY_DONE"; +const char *HANDLE_PACKET_FAILED_STRING = "HANDLE_PACKET_FAILED"; +const char *RF_AVAILABLE_STRING = "RF_AVAILABLE"; +const char *RF_LOST_STRING = "RF_LOST"; +const char *BIT_LOCK_STRING = "BIT_LOCK"; +const char *BIT_LOCK_LOST_STRING = "BIT_LOCK_LOST"; +const char *FRAME_PROCESSING_FAILED_STRING = "FRAME_PROCESSING_FAILED"; +const char *CLOCK_SET_STRING = "CLOCK_SET"; +const char *CLOCK_DUMP_LEGACY_STRING = "CLOCK_DUMP_LEGACY"; +const char *CLOCK_SET_FAILURE_STRING = "CLOCK_SET_FAILURE"; +const char *CLOCK_DUMP_STRING = "CLOCK_DUMP"; +const char *CLOCK_DUMP_BEFORE_SETTING_TIME_STRING = "CLOCK_DUMP_BEFORE_SETTING_TIME"; +const char *CLOCK_DUMP_AFTER_SETTING_TIME_STRING = "CLOCK_DUMP_AFTER_SETTING_TIME"; +const char *TC_DELETION_FAILED_STRING = "TC_DELETION_FAILED"; +const char *TEST_STRING = "TEST"; +const char *CHANGE_OF_SETUP_PARAMETER_STRING = "CHANGE_OF_SETUP_PARAMETER"; +const char *STORE_ERROR_STRING = "STORE_ERROR"; +const char *MSG_QUEUE_ERROR_STRING = "MSG_QUEUE_ERROR"; +const char *SERIALIZATION_ERROR_STRING = "SERIALIZATION_ERROR"; +const char *FILESTORE_ERROR_STRING = "FILESTORE_ERROR"; +const char *FILENAME_TOO_LARGE_ERROR_STRING = "FILENAME_TOO_LARGE_ERROR"; +const char *HANDLING_CFDP_REQUEST_FAILED_STRING = "HANDLING_CFDP_REQUEST_FAILED"; +const char *SAFE_RATE_VIOLATION_STRING = "SAFE_RATE_VIOLATION"; +const char *RATE_RECOVERY_STRING = "RATE_RECOVERY"; +const char *MULTIPLE_RW_INVALID_STRING = "MULTIPLE_RW_INVALID"; +const char *MEKF_INVALID_INFO_STRING = "MEKF_INVALID_INFO"; +const char *MEKF_RECOVERY_STRING = "MEKF_RECOVERY"; +const char *MEKF_AUTOMATIC_RESET_STRING = "MEKF_AUTOMATIC_RESET"; +const char *PTG_CTRL_NO_ATTITUDE_INFORMATION_STRING = "PTG_CTRL_NO_ATTITUDE_INFORMATION"; +const char *SAFE_MODE_CONTROLLER_FAILURE_STRING = "SAFE_MODE_CONTROLLER_FAILURE"; +const char *TLE_TOO_OLD_STRING = "TLE_TOO_OLD"; +const char *TLE_FILE_READ_FAILED_STRING = "TLE_FILE_READ_FAILED"; +const char *PTG_RATE_VIOLATION_STRING = "PTG_RATE_VIOLATION"; +const char *DETUMBLE_TRANSITION_FAILED_STRING = "DETUMBLE_TRANSITION_FAILED"; +const char *SWITCH_CMD_SENT_STRING = "SWITCH_CMD_SENT"; +const char *SWITCH_HAS_CHANGED_STRING = "SWITCH_HAS_CHANGED"; +const char *SWITCHING_Q7S_DENIED_STRING = "SWITCHING_Q7S_DENIED"; +const char *FDIR_REACTION_IGNORED_STRING = "FDIR_REACTION_IGNORED"; +const char *DATASET_READ_FAILED_STRING = "DATASET_READ_FAILED"; +const char *VOLTAGE_OUT_OF_BOUNDS_STRING = "VOLTAGE_OUT_OF_BOUNDS"; +const char *TIMEDELTA_OUT_OF_BOUNDS_STRING = "TIMEDELTA_OUT_OF_BOUNDS"; +const char *POWER_LEVEL_LOW_STRING = "POWER_LEVEL_LOW"; +const char *POWER_LEVEL_CRITICAL_STRING = "POWER_LEVEL_CRITICAL"; +const char *GPIO_PULL_HIGH_FAILED_STRING = "GPIO_PULL_HIGH_FAILED"; +const char *GPIO_PULL_LOW_FAILED_STRING = "GPIO_PULL_LOW_FAILED"; +const char *HEATER_WENT_ON_STRING = "HEATER_WENT_ON"; +const char *HEATER_WENT_OFF_STRING = "HEATER_WENT_OFF"; +const char *SWITCH_ALREADY_ON_STRING = "SWITCH_ALREADY_ON"; +const char *SWITCH_ALREADY_OFF_STRING = "SWITCH_ALREADY_OFF"; +const char *MAIN_SWITCH_TIMEOUT_STRING = "MAIN_SWITCH_TIMEOUT"; +const char *FAULTY_HEATER_WAS_ON_STRING = "FAULTY_HEATER_WAS_ON"; +const char *BURN_PHASE_START_STRING = "BURN_PHASE_START"; +const char *BURN_PHASE_DONE_STRING = "BURN_PHASE_DONE"; +const char *MAIN_SWITCH_ON_TIMEOUT_STRING = "MAIN_SWITCH_ON_TIMEOUT"; +const char *MAIN_SWITCH_OFF_TIMEOUT_STRING = "MAIN_SWITCH_OFF_TIMEOUT"; +const char *DEPL_SA1_GPIO_SWTICH_ON_FAILED_STRING = "DEPL_SA1_GPIO_SWTICH_ON_FAILED"; +const char *DEPL_SA2_GPIO_SWTICH_ON_FAILED_STRING = "DEPL_SA2_GPIO_SWTICH_ON_FAILED"; +const char *DEPL_SA1_GPIO_SWTICH_OFF_FAILED_STRING = "DEPL_SA1_GPIO_SWTICH_OFF_FAILED"; +const char *DEPL_SA2_GPIO_SWTICH_OFF_FAILED_STRING = "DEPL_SA2_GPIO_SWTICH_OFF_FAILED"; +const char *AUTONOMOUS_DEPLOYMENT_COMPLETED_STRING = "AUTONOMOUS_DEPLOYMENT_COMPLETED"; +const char *MEMORY_READ_RPT_CRC_FAILURE_STRING = "MEMORY_READ_RPT_CRC_FAILURE"; +const char *ACK_FAILURE_STRING = "ACK_FAILURE"; +const char *EXE_FAILURE_STRING = "EXE_FAILURE"; +const char *MPSOC_HANDLER_CRC_FAILURE_STRING = "MPSOC_HANDLER_CRC_FAILURE"; +const char *MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH_STRING = "MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH"; +const char *MPSOC_SHUTDOWN_FAILED_STRING = "MPSOC_SHUTDOWN_FAILED"; +const char *SUPV_NOT_ON_STRING = "SUPV_NOT_ON"; +const char *SUPV_REPLY_TIMEOUT_STRING = "SUPV_REPLY_TIMEOUT"; +const char *CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE_STRING = "CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE"; +const char *SELF_TEST_I2C_FAILURE_STRING = "SELF_TEST_I2C_FAILURE"; +const char *SELF_TEST_SPI_FAILURE_STRING = "SELF_TEST_SPI_FAILURE"; +const char *SELF_TEST_ADC_FAILURE_STRING = "SELF_TEST_ADC_FAILURE"; +const char *SELF_TEST_PWM_FAILURE_STRING = "SELF_TEST_PWM_FAILURE"; +const char *SELF_TEST_TC_FAILURE_STRING = "SELF_TEST_TC_FAILURE"; +const char *SELF_TEST_MTM_RANGE_FAILURE_STRING = "SELF_TEST_MTM_RANGE_FAILURE"; +const char *SELF_TEST_COIL_CURRENT_FAILURE_STRING = "SELF_TEST_COIL_CURRENT_FAILURE"; +const char *INVALID_ERROR_BYTE_STRING = "INVALID_ERROR_BYTE"; +const char *ERROR_STATE_STRING = "ERROR_STATE"; +const char *RESET_OCCURED_STRING = "RESET_OCCURED"; +const char *BOOTING_FIRMWARE_FAILED_EVENT_STRING = "BOOTING_FIRMWARE_FAILED_EVENT"; +const char *BOOTING_BOOTLOADER_FAILED_EVENT_STRING = "BOOTING_BOOTLOADER_FAILED_EVENT"; +const char *COM_ERROR_REPLY_RECEIVED_STRING = "COM_ERROR_REPLY_RECEIVED"; +const char *SUPV_MEMORY_READ_RPT_CRC_FAILURE_STRING = "SUPV_MEMORY_READ_RPT_CRC_FAILURE"; +const char *SUPV_UNKNOWN_TM_STRING = "SUPV_UNKNOWN_TM"; +const char *SUPV_UNINIMPLEMENTED_TM_STRING = "SUPV_UNINIMPLEMENTED_TM"; +const char *SUPV_ACK_FAILURE_STRING = "SUPV_ACK_FAILURE"; +const char *SUPV_EXE_FAILURE_STRING = "SUPV_EXE_FAILURE"; +const char *SUPV_CRC_FAILURE_EVENT_STRING = "SUPV_CRC_FAILURE_EVENT"; +const char *SUPV_HELPER_EXECUTING_STRING = "SUPV_HELPER_EXECUTING"; +const char *SUPV_MPSOC_SHUTDOWN_BUILD_FAILED_STRING = "SUPV_MPSOC_SHUTDOWN_BUILD_FAILED"; +const char *SUPV_ACK_UNKNOWN_COMMAND_STRING = "SUPV_ACK_UNKNOWN_COMMAND"; +const char *SUPV_EXE_ACK_UNKNOWN_COMMAND_STRING = "SUPV_EXE_ACK_UNKNOWN_COMMAND"; +const char *SANITIZATION_FAILED_STRING = "SANITIZATION_FAILED"; +const char *MOUNTED_SD_CARD_STRING = "MOUNTED_SD_CARD"; +const char *SEND_MRAM_DUMP_FAILED_STRING = "SEND_MRAM_DUMP_FAILED"; +const char *MRAM_DUMP_FAILED_STRING = "MRAM_DUMP_FAILED"; +const char *MRAM_DUMP_FINISHED_STRING = "MRAM_DUMP_FINISHED"; +const char *INVALID_TC_FRAME_STRING = "INVALID_TC_FRAME"; +const char *INVALID_FAR_STRING = "INVALID_FAR"; +const char *CARRIER_LOCK_STRING = "CARRIER_LOCK"; +const char *BIT_LOCK_PDEC_STRING = "BIT_LOCK_PDEC"; +const char *LOST_CARRIER_LOCK_PDEC_STRING = "LOST_CARRIER_LOCK_PDEC"; +const char *LOST_BIT_LOCK_PDEC_STRING = "LOST_BIT_LOCK_PDEC"; +const char *TOO_MANY_IRQS_STRING = "TOO_MANY_IRQS"; +const char *POLL_SYSCALL_ERROR_PDEC_STRING = "POLL_SYSCALL_ERROR_PDEC"; +const char *WRITE_SYSCALL_ERROR_PDEC_STRING = "WRITE_SYSCALL_ERROR_PDEC"; +const char *PDEC_TRYING_RESET_WITH_INIT_STRING = "PDEC_TRYING_RESET_WITH_INIT"; +const char *PDEC_TRYING_RESET_NO_INIT_STRING = "PDEC_TRYING_RESET_NO_INIT"; +const char *PDEC_RESET_FAILED_STRING = "PDEC_RESET_FAILED"; +const char *OPEN_IRQ_FILE_FAILED_STRING = "OPEN_IRQ_FILE_FAILED"; +const char *PDEC_INIT_FAILED_STRING = "PDEC_INIT_FAILED"; +const char *PDEC_CONFIG_CORRUPTED_STRING = "PDEC_CONFIG_CORRUPTED"; +const char *IMAGE_UPLOAD_FAILED_STRING = "IMAGE_UPLOAD_FAILED"; +const char *IMAGE_DOWNLOAD_FAILED_STRING = "IMAGE_DOWNLOAD_FAILED"; +const char *IMAGE_UPLOAD_SUCCESSFUL_STRING = "IMAGE_UPLOAD_SUCCESSFUL"; +const char *IMAGE_DOWNLOAD_SUCCESSFUL_STRING = "IMAGE_DOWNLOAD_SUCCESSFUL"; +const char *FLASH_WRITE_SUCCESSFUL_STRING = "FLASH_WRITE_SUCCESSFUL"; +const char *FLASH_READ_SUCCESSFUL_STRING = "FLASH_READ_SUCCESSFUL"; +const char *FLASH_READ_FAILED_STRING = "FLASH_READ_FAILED"; +const char *FIRMWARE_UPDATE_SUCCESSFUL_STRING = "FIRMWARE_UPDATE_SUCCESSFUL"; +const char *FIRMWARE_UPDATE_FAILED_STRING = "FIRMWARE_UPDATE_FAILED"; +const char *STR_HELPER_READING_REPLY_FAILED_STRING = "STR_HELPER_READING_REPLY_FAILED"; +const char *STR_HELPER_COM_ERROR_STRING = "STR_HELPER_COM_ERROR"; +const char *STR_COM_REPLY_TIMEOUT_STRING = "STR_COM_REPLY_TIMEOUT"; +const char *STR_HELPER_DEC_ERROR_STRING = "STR_HELPER_DEC_ERROR"; +const char *POSITION_MISMATCH_STRING = "POSITION_MISMATCH"; +const char *STR_HELPER_FILE_NOT_EXISTS_STRING = "STR_HELPER_FILE_NOT_EXISTS"; +const char *STR_HELPER_SENDING_PACKET_FAILED_STRING = "STR_HELPER_SENDING_PACKET_FAILED"; +const char *STR_HELPER_REQUESTING_MSG_FAILED_STRING = "STR_HELPER_REQUESTING_MSG_FAILED"; +const char *MPSOC_FLASH_WRITE_FAILED_STRING = "MPSOC_FLASH_WRITE_FAILED"; +const char *MPSOC_FLASH_WRITE_SUCCESSFUL_STRING = "MPSOC_FLASH_WRITE_SUCCESSFUL"; +const char *MPSOC_SENDING_COMMAND_FAILED_STRING = "MPSOC_SENDING_COMMAND_FAILED"; +const char *MPSOC_HELPER_REQUESTING_REPLY_FAILED_STRING = "MPSOC_HELPER_REQUESTING_REPLY_FAILED"; +const char *MPSOC_HELPER_READING_REPLY_FAILED_STRING = "MPSOC_HELPER_READING_REPLY_FAILED"; +const char *MPSOC_MISSING_ACK_STRING = "MPSOC_MISSING_ACK"; +const char *MPSOC_MISSING_EXE_STRING = "MPSOC_MISSING_EXE"; +const char *MPSOC_ACK_FAILURE_REPORT_STRING = "MPSOC_ACK_FAILURE_REPORT"; +const char *MPSOC_EXE_FAILURE_REPORT_STRING = "MPSOC_EXE_FAILURE_REPORT"; +const char *MPSOC_ACK_INVALID_APID_STRING = "MPSOC_ACK_INVALID_APID"; +const char *MPSOC_EXE_INVALID_APID_STRING = "MPSOC_EXE_INVALID_APID"; +const char *MPSOC_HELPER_SEQ_CNT_MISMATCH_STRING = "MPSOC_HELPER_SEQ_CNT_MISMATCH"; +const char *MPSOC_TM_SIZE_ERROR_STRING = "MPSOC_TM_SIZE_ERROR"; +const char *MPSOC_TM_CRC_MISSMATCH_STRING = "MPSOC_TM_CRC_MISSMATCH"; +const char *MPSOC_FLASH_READ_PACKET_ERROR_STRING = "MPSOC_FLASH_READ_PACKET_ERROR"; +const char *MPSOC_FLASH_READ_FAILED_STRING = "MPSOC_FLASH_READ_FAILED"; +const char *MPSOC_FLASH_READ_SUCCESSFUL_STRING = "MPSOC_FLASH_READ_SUCCESSFUL"; +const char *MPSOC_READ_TIMEOUT_STRING = "MPSOC_READ_TIMEOUT"; +const char *TRANSITION_BACK_TO_OFF_STRING = "TRANSITION_BACK_TO_OFF"; +const char *NEG_V_OUT_OF_BOUNDS_STRING = "NEG_V_OUT_OF_BOUNDS"; +const char *U_DRO_OUT_OF_BOUNDS_STRING = "U_DRO_OUT_OF_BOUNDS"; +const char *I_DRO_OUT_OF_BOUNDS_STRING = "I_DRO_OUT_OF_BOUNDS"; +const char *U_X8_OUT_OF_BOUNDS_STRING = "U_X8_OUT_OF_BOUNDS"; +const char *I_X8_OUT_OF_BOUNDS_STRING = "I_X8_OUT_OF_BOUNDS"; +const char *U_TX_OUT_OF_BOUNDS_STRING = "U_TX_OUT_OF_BOUNDS"; +const char *I_TX_OUT_OF_BOUNDS_STRING = "I_TX_OUT_OF_BOUNDS"; +const char *U_MPA_OUT_OF_BOUNDS_STRING = "U_MPA_OUT_OF_BOUNDS"; +const char *I_MPA_OUT_OF_BOUNDS_STRING = "I_MPA_OUT_OF_BOUNDS"; +const char *U_HPA_OUT_OF_BOUNDS_STRING = "U_HPA_OUT_OF_BOUNDS"; +const char *I_HPA_OUT_OF_BOUNDS_STRING = "I_HPA_OUT_OF_BOUNDS"; +const char *TRANSITION_OTHER_SIDE_FAILED_STRING = "TRANSITION_OTHER_SIDE_FAILED"; +const char *NOT_ENOUGH_DEVICES_DUAL_MODE_STRING = "NOT_ENOUGH_DEVICES_DUAL_MODE"; +const char *POWER_STATE_MACHINE_TIMEOUT_STRING = "POWER_STATE_MACHINE_TIMEOUT"; +const char *SIDE_SWITCH_TRANSITION_NOT_ALLOWED_STRING = "SIDE_SWITCH_TRANSITION_NOT_ALLOWED"; +const char *DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY_STRING = "DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY"; +const char *TRANSITION_OTHER_SIDE_FAILED_12900_STRING = "TRANSITION_OTHER_SIDE_FAILED_12900"; +const char *NOT_ENOUGH_DEVICES_DUAL_MODE_12901_STRING = "NOT_ENOUGH_DEVICES_DUAL_MODE_12901"; +const char *POWER_STATE_MACHINE_TIMEOUT_12902_STRING = "POWER_STATE_MACHINE_TIMEOUT_12902"; +const char *SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903_STRING = "SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903"; +const char *CHILDREN_LOST_MODE_STRING = "CHILDREN_LOST_MODE"; +const char *GPS_FIX_CHANGE_STRING = "GPS_FIX_CHANGE"; +const char *CANT_GET_FIX_STRING = "CANT_GET_FIX"; +const char *RESET_FAIL_STRING = "RESET_FAIL"; +const char *P60_BOOT_COUNT_STRING = "P60_BOOT_COUNT"; +const char *BATT_MODE_STRING = "BATT_MODE"; +const char *BATT_MODE_CHANGED_STRING = "BATT_MODE_CHANGED"; +const char *SUPV_UPDATE_FAILED_STRING = "SUPV_UPDATE_FAILED"; +const char *SUPV_UPDATE_SUCCESSFUL_STRING = "SUPV_UPDATE_SUCCESSFUL"; +const char *SUPV_CONTINUE_UPDATE_FAILED_STRING = "SUPV_CONTINUE_UPDATE_FAILED"; +const char *SUPV_CONTINUE_UPDATE_SUCCESSFUL_STRING = "SUPV_CONTINUE_UPDATE_SUCCESSFUL"; +const char *TERMINATED_UPDATE_PROCEDURE_STRING = "TERMINATED_UPDATE_PROCEDURE"; +const char *SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL_STRING = "SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL"; +const char *SUPV_EVENT_BUFFER_REQUEST_FAILED_STRING = "SUPV_EVENT_BUFFER_REQUEST_FAILED"; +const char *SUPV_EVENT_BUFFER_REQUEST_TERMINATED_STRING = "SUPV_EVENT_BUFFER_REQUEST_TERMINATED"; +const char *SUPV_MEM_CHECK_OK_STRING = "SUPV_MEM_CHECK_OK"; +const char *SUPV_MEM_CHECK_FAIL_STRING = "SUPV_MEM_CHECK_FAIL"; +const char *SUPV_SENDING_COMMAND_FAILED_STRING = "SUPV_SENDING_COMMAND_FAILED"; +const char *SUPV_HELPER_REQUESTING_REPLY_FAILED_STRING = "SUPV_HELPER_REQUESTING_REPLY_FAILED"; +const char *SUPV_HELPER_READING_REPLY_FAILED_STRING = "SUPV_HELPER_READING_REPLY_FAILED"; +const char *SUPV_MISSING_ACK_STRING = "SUPV_MISSING_ACK"; +const char *SUPV_MISSING_EXE_STRING = "SUPV_MISSING_EXE"; +const char *SUPV_ACK_FAILURE_REPORT_STRING = "SUPV_ACK_FAILURE_REPORT"; +const char *SUPV_EXE_FAILURE_REPORT_STRING = "SUPV_EXE_FAILURE_REPORT"; +const char *SUPV_ACK_INVALID_APID_STRING = "SUPV_ACK_INVALID_APID"; +const char *SUPV_EXE_INVALID_APID_STRING = "SUPV_EXE_INVALID_APID"; +const char *ACK_RECEPTION_FAILURE_STRING = "ACK_RECEPTION_FAILURE"; +const char *EXE_RECEPTION_FAILURE_STRING = "EXE_RECEPTION_FAILURE"; +const char *WRITE_MEMORY_FAILED_STRING = "WRITE_MEMORY_FAILED"; +const char *SUPV_REPLY_SIZE_MISSMATCH_STRING = "SUPV_REPLY_SIZE_MISSMATCH"; +const char *SUPV_REPLY_CRC_MISSMATCH_STRING = "SUPV_REPLY_CRC_MISSMATCH"; +const char *SUPV_UPDATE_PROGRESS_STRING = "SUPV_UPDATE_PROGRESS"; +const char *HDLC_FRAME_REMOVAL_ERROR_STRING = "HDLC_FRAME_REMOVAL_ERROR"; +const char *HDLC_CRC_ERROR_STRING = "HDLC_CRC_ERROR"; +const char *TX_ON_STRING = "TX_ON"; +const char *TX_OFF_STRING = "TX_OFF"; +const char *MISSING_PACKET_STRING = "MISSING_PACKET"; +const char *EXPERIMENT_TIMEDOUT_STRING = "EXPERIMENT_TIMEDOUT"; +const char *MULTI_PACKET_COMMAND_DONE_STRING = "MULTI_PACKET_COMMAND_DONE"; +const char *FS_UNUSABLE_STRING = "FS_UNUSABLE"; +const char *SET_CONFIGFILEVALUE_FAILED_STRING = "SET_CONFIGFILEVALUE_FAILED"; +const char *GET_CONFIGFILEVALUE_FAILED_STRING = "GET_CONFIGFILEVALUE_FAILED"; +const char *INSERT_CONFIGFILEVALUE_FAILED_STRING = "INSERT_CONFIGFILEVALUE_FAILED"; +const char *WRITE_CONFIGFILE_FAILED_STRING = "WRITE_CONFIGFILE_FAILED"; +const char *READ_CONFIGFILE_FAILED_STRING = "READ_CONFIGFILE_FAILED"; +const char *ALLOC_FAILURE_STRING = "ALLOC_FAILURE"; +const char *REBOOT_SW_STRING = "REBOOT_SW"; +const char *REBOOT_MECHANISM_TRIGGERED_STRING = "REBOOT_MECHANISM_TRIGGERED"; +const char *REBOOT_HW_STRING = "REBOOT_HW"; +const char *NO_SD_CARD_ACTIVE_STRING = "NO_SD_CARD_ACTIVE"; +const char *VERSION_INFO_STRING = "VERSION_INFO"; +const char *CURRENT_IMAGE_INFO_STRING = "CURRENT_IMAGE_INFO"; +const char *REBOOT_COUNTER_STRING = "REBOOT_COUNTER"; +const char *INDIVIDUAL_BOOT_COUNTS_STRING = "INDIVIDUAL_BOOT_COUNTS"; +const char *TRYING_I2C_RECOVERY_STRING = "TRYING_I2C_RECOVERY"; +const char *I2C_REBOOT_STRING = "I2C_REBOOT"; +const char *PDEC_REBOOT_STRING = "PDEC_REBOOT"; +const char *FIRMWARE_INFO_STRING = "FIRMWARE_INFO"; +const char *ACTIVE_SD_INFO_STRING = "ACTIVE_SD_INFO"; +const char *NO_VALID_SENSOR_TEMPERATURE_STRING = "NO_VALID_SENSOR_TEMPERATURE"; +const char *NO_HEALTHY_HEATER_AVAILABLE_STRING = "NO_HEALTHY_HEATER_AVAILABLE"; +const char *SYRLINKS_OVERHEATING_STRING = "SYRLINKS_OVERHEATING"; +const char *OBC_OVERHEATING_STRING = "OBC_OVERHEATING"; +const char *CAMERA_OVERHEATING_STRING = "CAMERA_OVERHEATING"; +const char *PCDU_SYSTEM_OVERHEATING_STRING = "PCDU_SYSTEM_OVERHEATING"; +const char *HEATER_NOT_OFF_FOR_OFF_MODE_STRING = "HEATER_NOT_OFF_FOR_OFF_MODE"; +const char *MGT_OVERHEATING_STRING = "MGT_OVERHEATING"; +const char *TCS_SWITCHING_HEATER_ON_STRING = "TCS_SWITCHING_HEATER_ON"; +const char *TCS_SWITCHING_HEATER_OFF_STRING = "TCS_SWITCHING_HEATER_OFF"; +const char *TCS_HEATER_MAX_BURN_TIME_REACHED_STRING = "TCS_HEATER_MAX_BURN_TIME_REACHED"; +const char *TX_TIMER_EXPIRED_STRING = "TX_TIMER_EXPIRED"; +const char *BIT_LOCK_TX_ON_STRING = "BIT_LOCK_TX_ON"; +const char *POSSIBLE_FILE_CORRUPTION_STRING = "POSSIBLE_FILE_CORRUPTION"; +const char *FILE_TOO_LARGE_STRING = "FILE_TOO_LARGE"; +const char *BUSY_DUMPING_EVENT_STRING = "BUSY_DUMPING_EVENT"; +const char *DUMP_OK_STORE_DONE_STRING = "DUMP_OK_STORE_DONE"; +const char *DUMP_NOK_STORE_DONE_STRING = "DUMP_NOK_STORE_DONE"; +const char *DUMP_MISC_STORE_DONE_STRING = "DUMP_MISC_STORE_DONE"; +const char *DUMP_HK_STORE_DONE_STRING = "DUMP_HK_STORE_DONE"; +const char *DUMP_CFDP_STORE_DONE_STRING = "DUMP_CFDP_STORE_DONE"; +const char *DUMP_OK_CANCELLED_STRING = "DUMP_OK_CANCELLED"; +const char *DUMP_NOK_CANCELLED_STRING = "DUMP_NOK_CANCELLED"; +const char *DUMP_MISC_CANCELLED_STRING = "DUMP_MISC_CANCELLED"; +const char *DUMP_HK_CANCELLED_STRING = "DUMP_HK_CANCELLED"; +const char *DUMP_CFDP_CANCELLED_STRING = "DUMP_CFDP_CANCELLED"; +const char *TEMPERATURE_ALL_ONES_START_STRING = "TEMPERATURE_ALL_ONES_START"; +const char *TEMPERATURE_ALL_ONES_RECOVERY_STRING = "TEMPERATURE_ALL_ONES_RECOVERY"; +const char *FAULT_HANDLER_TRIGGERED_STRING = "FAULT_HANDLER_TRIGGERED"; + +const char *translateEvents(Event event) { + switch ((event & 0xFFFF)) { + case (2200): + return STORE_SEND_WRITE_FAILED_STRING; + case (2201): + return STORE_WRITE_FAILED_STRING; + case (2202): + return STORE_SEND_READ_FAILED_STRING; + case (2203): + return STORE_READ_FAILED_STRING; + case (2204): + return UNEXPECTED_MSG_STRING; + case (2205): + return STORING_FAILED_STRING; + case (2206): + return TM_DUMP_FAILED_STRING; + case (2207): + return STORE_INIT_FAILED_STRING; + case (2208): + return STORE_INIT_EMPTY_STRING; + case (2209): + return STORE_CONTENT_CORRUPTED_STRING; + case (2210): + return STORE_INITIALIZE_STRING; + case (2211): + return INIT_DONE_STRING; + case (2212): + return DUMP_FINISHED_STRING; + case (2213): + return DELETION_FINISHED_STRING; + case (2214): + return DELETION_FAILED_STRING; + case (2215): + return AUTO_CATALOGS_SENDING_FAILED_STRING; + case (2600): + return GET_DATA_FAILED_STRING; + case (2601): + return STORE_DATA_FAILED_STRING; + case (2800): + return DEVICE_BUILDING_COMMAND_FAILED_STRING; + case (2801): + return DEVICE_SENDING_COMMAND_FAILED_STRING; + case (2802): + return DEVICE_REQUESTING_REPLY_FAILED_STRING; + case (2803): + return DEVICE_READING_REPLY_FAILED_STRING; + case (2804): + return DEVICE_INTERPRETING_REPLY_FAILED_STRING; + case (2805): + return DEVICE_MISSED_REPLY_STRING; + case (2806): + return DEVICE_UNKNOWN_REPLY_STRING; + case (2807): + return DEVICE_UNREQUESTED_REPLY_STRING; + case (2808): + return INVALID_DEVICE_COMMAND_STRING; + case (2809): + return MONITORING_LIMIT_EXCEEDED_STRING; + case (2810): + return MONITORING_AMBIGUOUS_STRING; + case (2811): + return DEVICE_WANTS_HARD_REBOOT_STRING; + case (4300): + return SWITCH_WENT_OFF_STRING; + case (4301): + return FUSE_CURRENT_HIGH_STRING; + case (4302): + return FUSE_WENT_OFF_STRING; + case (4304): + return POWER_ABOVE_HIGH_LIMIT_STRING; + case (4305): + return POWER_BELOW_LOW_LIMIT_STRING; + case (5000): + return HEATER_ON_STRING; + case (5001): + return HEATER_OFF_STRING; + case (5002): + return HEATER_TIMEOUT_STRING; + case (5003): + return HEATER_STAYED_ON_STRING; + case (5004): + return HEATER_STAYED_OFF_STRING; + case (5200): + return TEMP_SENSOR_HIGH_STRING; + case (5201): + return TEMP_SENSOR_LOW_STRING; + case (5202): + return TEMP_SENSOR_GRADIENT_STRING; + case (5901): + return COMPONENT_TEMP_LOW_STRING; + case (5902): + return COMPONENT_TEMP_HIGH_STRING; + case (5903): + return COMPONENT_TEMP_OOL_LOW_STRING; + case (5904): + return COMPONENT_TEMP_OOL_HIGH_STRING; + case (5905): + return TEMP_NOT_IN_OP_RANGE_STRING; + case (7101): + return FDIR_CHANGED_STATE_STRING; + case (7102): + return FDIR_STARTS_RECOVERY_STRING; + case (7103): + return FDIR_TURNS_OFF_DEVICE_STRING; + case (7201): + return MONITOR_CHANGED_STATE_STRING; + case (7202): + return VALUE_BELOW_LOW_LIMIT_STRING; + case (7203): + return VALUE_ABOVE_HIGH_LIMIT_STRING; + case (7204): + return VALUE_OUT_OF_RANGE_STRING; + case (7400): + return CHANGING_MODE_STRING; + case (7401): + return MODE_INFO_STRING; + case (7402): + return FALLBACK_FAILED_STRING; + case (7403): + return MODE_TRANSITION_FAILED_STRING; + case (7404): + return CANT_KEEP_MODE_STRING; + case (7405): + return OBJECT_IN_INVALID_MODE_STRING; + case (7406): + return FORCING_MODE_STRING; + case (7407): + return MODE_CMD_REJECTED_STRING; + case (7506): + return HEALTH_INFO_STRING; + case (7507): + return CHILD_CHANGED_HEALTH_STRING; + case (7508): + return CHILD_PROBLEMS_STRING; + case (7509): + return OVERWRITING_HEALTH_STRING; + case (7510): + return TRYING_RECOVERY_STRING; + case (7511): + return RECOVERY_STEP_STRING; + case (7512): + return RECOVERY_DONE_STRING; + case (7600): + return HANDLE_PACKET_FAILED_STRING; + case (7900): + return RF_AVAILABLE_STRING; + case (7901): + return RF_LOST_STRING; + case (7902): + return BIT_LOCK_STRING; + case (7903): + return BIT_LOCK_LOST_STRING; + case (7905): + return FRAME_PROCESSING_FAILED_STRING; + case (8900): + return CLOCK_SET_STRING; + case (8901): + return CLOCK_DUMP_LEGACY_STRING; + case (8902): + return CLOCK_SET_FAILURE_STRING; + case (8903): + return CLOCK_DUMP_STRING; + case (8904): + return CLOCK_DUMP_BEFORE_SETTING_TIME_STRING; + case (8905): + return CLOCK_DUMP_AFTER_SETTING_TIME_STRING; + case (9100): + return TC_DELETION_FAILED_STRING; + case (9700): + return TEST_STRING; + case (10600): + return CHANGE_OF_SETUP_PARAMETER_STRING; + case (10800): + return STORE_ERROR_STRING; + case (10801): + return MSG_QUEUE_ERROR_STRING; + case (10802): + return SERIALIZATION_ERROR_STRING; + case (10803): + return FILESTORE_ERROR_STRING; + case (10804): + return FILENAME_TOO_LARGE_ERROR_STRING; + case (10805): + return HANDLING_CFDP_REQUEST_FAILED_STRING; + case (11200): + return SAFE_RATE_VIOLATION_STRING; + case (11201): + return RATE_RECOVERY_STRING; + case (11202): + return MULTIPLE_RW_INVALID_STRING; + case (11203): + return MEKF_INVALID_INFO_STRING; + case (11204): + return MEKF_RECOVERY_STRING; + case (11205): + return MEKF_AUTOMATIC_RESET_STRING; + case (11206): + return PTG_CTRL_NO_ATTITUDE_INFORMATION_STRING; + case (11207): + return SAFE_MODE_CONTROLLER_FAILURE_STRING; + case (11208): + return TLE_TOO_OLD_STRING; + case (11209): + return TLE_FILE_READ_FAILED_STRING; + case (11210): + return PTG_RATE_VIOLATION_STRING; + case (11211): + return DETUMBLE_TRANSITION_FAILED_STRING; + case (11300): + return SWITCH_CMD_SENT_STRING; + case (11301): + return SWITCH_HAS_CHANGED_STRING; + case (11302): + return SWITCHING_Q7S_DENIED_STRING; + case (11303): + return FDIR_REACTION_IGNORED_STRING; + case (11304): + return DATASET_READ_FAILED_STRING; + case (11305): + return VOLTAGE_OUT_OF_BOUNDS_STRING; + case (11306): + return TIMEDELTA_OUT_OF_BOUNDS_STRING; + case (11307): + return POWER_LEVEL_LOW_STRING; + case (11308): + return POWER_LEVEL_CRITICAL_STRING; + case (11400): + return GPIO_PULL_HIGH_FAILED_STRING; + case (11401): + return GPIO_PULL_LOW_FAILED_STRING; + case (11402): + return HEATER_WENT_ON_STRING; + case (11403): + return HEATER_WENT_OFF_STRING; + case (11404): + return SWITCH_ALREADY_ON_STRING; + case (11405): + return SWITCH_ALREADY_OFF_STRING; + case (11406): + return MAIN_SWITCH_TIMEOUT_STRING; + case (11407): + return FAULTY_HEATER_WAS_ON_STRING; + case (11500): + return BURN_PHASE_START_STRING; + case (11501): + return BURN_PHASE_DONE_STRING; + case (11502): + return MAIN_SWITCH_ON_TIMEOUT_STRING; + case (11503): + return MAIN_SWITCH_OFF_TIMEOUT_STRING; + case (11504): + return DEPL_SA1_GPIO_SWTICH_ON_FAILED_STRING; + case (11505): + return DEPL_SA2_GPIO_SWTICH_ON_FAILED_STRING; + case (11506): + return DEPL_SA1_GPIO_SWTICH_OFF_FAILED_STRING; + case (11507): + return DEPL_SA2_GPIO_SWTICH_OFF_FAILED_STRING; + case (11508): + return AUTONOMOUS_DEPLOYMENT_COMPLETED_STRING; + case (11601): + return MEMORY_READ_RPT_CRC_FAILURE_STRING; + case (11602): + return ACK_FAILURE_STRING; + case (11603): + return EXE_FAILURE_STRING; + case (11604): + return MPSOC_HANDLER_CRC_FAILURE_STRING; + case (11605): + return MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH_STRING; + case (11606): + return MPSOC_SHUTDOWN_FAILED_STRING; + case (11607): + return SUPV_NOT_ON_STRING; + case (11608): + return SUPV_REPLY_TIMEOUT_STRING; + case (11609): + return CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE_STRING; + case (11701): + return SELF_TEST_I2C_FAILURE_STRING; + case (11702): + return SELF_TEST_SPI_FAILURE_STRING; + case (11703): + return SELF_TEST_ADC_FAILURE_STRING; + case (11704): + return SELF_TEST_PWM_FAILURE_STRING; + case (11705): + return SELF_TEST_TC_FAILURE_STRING; + case (11706): + return SELF_TEST_MTM_RANGE_FAILURE_STRING; + case (11707): + return SELF_TEST_COIL_CURRENT_FAILURE_STRING; + case (11708): + return INVALID_ERROR_BYTE_STRING; + case (11801): + return ERROR_STATE_STRING; + case (11802): + return RESET_OCCURED_STRING; + case (11901): + return BOOTING_FIRMWARE_FAILED_EVENT_STRING; + case (11902): + return BOOTING_BOOTLOADER_FAILED_EVENT_STRING; + case (11903): + return COM_ERROR_REPLY_RECEIVED_STRING; + case (12001): + return SUPV_MEMORY_READ_RPT_CRC_FAILURE_STRING; + case (12002): + return SUPV_UNKNOWN_TM_STRING; + case (12003): + return SUPV_UNINIMPLEMENTED_TM_STRING; + case (12004): + return SUPV_ACK_FAILURE_STRING; + case (12005): + return SUPV_EXE_FAILURE_STRING; + case (12006): + return SUPV_CRC_FAILURE_EVENT_STRING; + case (12007): + return SUPV_HELPER_EXECUTING_STRING; + case (12008): + return SUPV_MPSOC_SHUTDOWN_BUILD_FAILED_STRING; + case (12009): + return SUPV_ACK_UNKNOWN_COMMAND_STRING; + case (12010): + return SUPV_EXE_ACK_UNKNOWN_COMMAND_STRING; + case (12100): + return SANITIZATION_FAILED_STRING; + case (12101): + return MOUNTED_SD_CARD_STRING; + case (12300): + return SEND_MRAM_DUMP_FAILED_STRING; + case (12301): + return MRAM_DUMP_FAILED_STRING; + case (12302): + return MRAM_DUMP_FINISHED_STRING; + case (12401): + return INVALID_TC_FRAME_STRING; + case (12402): + return INVALID_FAR_STRING; + case (12403): + return CARRIER_LOCK_STRING; + case (12404): + return BIT_LOCK_PDEC_STRING; + case (12405): + return LOST_CARRIER_LOCK_PDEC_STRING; + case (12406): + return LOST_BIT_LOCK_PDEC_STRING; + case (12407): + return TOO_MANY_IRQS_STRING; + case (12408): + return POLL_SYSCALL_ERROR_PDEC_STRING; + case (12409): + return WRITE_SYSCALL_ERROR_PDEC_STRING; + case (12410): + return PDEC_TRYING_RESET_WITH_INIT_STRING; + case (12411): + return PDEC_TRYING_RESET_NO_INIT_STRING; + case (12412): + return PDEC_RESET_FAILED_STRING; + case (12413): + return OPEN_IRQ_FILE_FAILED_STRING; + case (12414): + return PDEC_INIT_FAILED_STRING; + case (12415): + return PDEC_CONFIG_CORRUPTED_STRING; + case (12500): + return IMAGE_UPLOAD_FAILED_STRING; + case (12501): + return IMAGE_DOWNLOAD_FAILED_STRING; + case (12502): + return IMAGE_UPLOAD_SUCCESSFUL_STRING; + case (12503): + return IMAGE_DOWNLOAD_SUCCESSFUL_STRING; + case (12504): + return FLASH_WRITE_SUCCESSFUL_STRING; + case (12505): + return FLASH_READ_SUCCESSFUL_STRING; + case (12506): + return FLASH_READ_FAILED_STRING; + case (12507): + return FIRMWARE_UPDATE_SUCCESSFUL_STRING; + case (12508): + return FIRMWARE_UPDATE_FAILED_STRING; + case (12509): + return STR_HELPER_READING_REPLY_FAILED_STRING; + case (12510): + return STR_HELPER_COM_ERROR_STRING; + case (12511): + return STR_COM_REPLY_TIMEOUT_STRING; + case (12513): + return STR_HELPER_DEC_ERROR_STRING; + case (12514): + return POSITION_MISMATCH_STRING; + case (12515): + return STR_HELPER_FILE_NOT_EXISTS_STRING; + case (12516): + return STR_HELPER_SENDING_PACKET_FAILED_STRING; + case (12517): + return STR_HELPER_REQUESTING_MSG_FAILED_STRING; + case (12600): + return MPSOC_FLASH_WRITE_FAILED_STRING; + case (12601): + return MPSOC_FLASH_WRITE_SUCCESSFUL_STRING; + case (12602): + return MPSOC_SENDING_COMMAND_FAILED_STRING; + case (12603): + return MPSOC_HELPER_REQUESTING_REPLY_FAILED_STRING; + case (12604): + return MPSOC_HELPER_READING_REPLY_FAILED_STRING; + case (12605): + return MPSOC_MISSING_ACK_STRING; + case (12606): + return MPSOC_MISSING_EXE_STRING; + case (12607): + return MPSOC_ACK_FAILURE_REPORT_STRING; + case (12608): + return MPSOC_EXE_FAILURE_REPORT_STRING; + case (12609): + return MPSOC_ACK_INVALID_APID_STRING; + case (12610): + return MPSOC_EXE_INVALID_APID_STRING; + case (12611): + return MPSOC_HELPER_SEQ_CNT_MISMATCH_STRING; + case (12612): + return MPSOC_TM_SIZE_ERROR_STRING; + case (12613): + return MPSOC_TM_CRC_MISSMATCH_STRING; + case (12614): + return MPSOC_FLASH_READ_PACKET_ERROR_STRING; + case (12615): + return MPSOC_FLASH_READ_FAILED_STRING; + case (12616): + return MPSOC_FLASH_READ_SUCCESSFUL_STRING; + case (12617): + return MPSOC_READ_TIMEOUT_STRING; + case (12700): + return TRANSITION_BACK_TO_OFF_STRING; + case (12701): + return NEG_V_OUT_OF_BOUNDS_STRING; + case (12702): + return U_DRO_OUT_OF_BOUNDS_STRING; + case (12703): + return I_DRO_OUT_OF_BOUNDS_STRING; + case (12704): + return U_X8_OUT_OF_BOUNDS_STRING; + case (12705): + return I_X8_OUT_OF_BOUNDS_STRING; + case (12706): + return U_TX_OUT_OF_BOUNDS_STRING; + case (12707): + return I_TX_OUT_OF_BOUNDS_STRING; + case (12708): + return U_MPA_OUT_OF_BOUNDS_STRING; + case (12709): + return I_MPA_OUT_OF_BOUNDS_STRING; + case (12710): + return U_HPA_OUT_OF_BOUNDS_STRING; + case (12711): + return I_HPA_OUT_OF_BOUNDS_STRING; + case (12800): + return TRANSITION_OTHER_SIDE_FAILED_STRING; + case (12801): + return NOT_ENOUGH_DEVICES_DUAL_MODE_STRING; + case (12802): + return POWER_STATE_MACHINE_TIMEOUT_STRING; + case (12803): + return SIDE_SWITCH_TRANSITION_NOT_ALLOWED_STRING; + case (12804): + return DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY_STRING; + case (12900): + return TRANSITION_OTHER_SIDE_FAILED_12900_STRING; + case (12901): + return NOT_ENOUGH_DEVICES_DUAL_MODE_12901_STRING; + case (12902): + return POWER_STATE_MACHINE_TIMEOUT_12902_STRING; + case (12903): + return SIDE_SWITCH_TRANSITION_NOT_ALLOWED_12903_STRING; + case (13000): + return CHILDREN_LOST_MODE_STRING; + case (13100): + return GPS_FIX_CHANGE_STRING; + case (13101): + return CANT_GET_FIX_STRING; + case (13102): + return RESET_FAIL_STRING; + case (13200): + return P60_BOOT_COUNT_STRING; + case (13201): + return BATT_MODE_STRING; + case (13202): + return BATT_MODE_CHANGED_STRING; + case (13600): + return SUPV_UPDATE_FAILED_STRING; + case (13601): + return SUPV_UPDATE_SUCCESSFUL_STRING; + case (13602): + return SUPV_CONTINUE_UPDATE_FAILED_STRING; + case (13603): + return SUPV_CONTINUE_UPDATE_SUCCESSFUL_STRING; + case (13604): + return TERMINATED_UPDATE_PROCEDURE_STRING; + case (13605): + return SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL_STRING; + case (13606): + return SUPV_EVENT_BUFFER_REQUEST_FAILED_STRING; + case (13607): + return SUPV_EVENT_BUFFER_REQUEST_TERMINATED_STRING; + case (13608): + return SUPV_MEM_CHECK_OK_STRING; + case (13609): + return SUPV_MEM_CHECK_FAIL_STRING; + case (13616): + return SUPV_SENDING_COMMAND_FAILED_STRING; + case (13617): + return SUPV_HELPER_REQUESTING_REPLY_FAILED_STRING; + case (13618): + return SUPV_HELPER_READING_REPLY_FAILED_STRING; + case (13619): + return SUPV_MISSING_ACK_STRING; + case (13620): + return SUPV_MISSING_EXE_STRING; + case (13621): + return SUPV_ACK_FAILURE_REPORT_STRING; + case (13622): + return SUPV_EXE_FAILURE_REPORT_STRING; + case (13623): + return SUPV_ACK_INVALID_APID_STRING; + case (13624): + return SUPV_EXE_INVALID_APID_STRING; + case (13625): + return ACK_RECEPTION_FAILURE_STRING; + case (13626): + return EXE_RECEPTION_FAILURE_STRING; + case (13627): + return WRITE_MEMORY_FAILED_STRING; + case (13628): + return SUPV_REPLY_SIZE_MISSMATCH_STRING; + case (13629): + return SUPV_REPLY_CRC_MISSMATCH_STRING; + case (13630): + return SUPV_UPDATE_PROGRESS_STRING; + case (13631): + return HDLC_FRAME_REMOVAL_ERROR_STRING; + case (13632): + return HDLC_CRC_ERROR_STRING; + case (13701): + return TX_ON_STRING; + case (13702): + return TX_OFF_STRING; + case (13800): + return MISSING_PACKET_STRING; + case (13801): + return EXPERIMENT_TIMEDOUT_STRING; + case (13802): + return MULTI_PACKET_COMMAND_DONE_STRING; + case (13803): + return FS_UNUSABLE_STRING; + case (13901): + return SET_CONFIGFILEVALUE_FAILED_STRING; + case (13902): + return GET_CONFIGFILEVALUE_FAILED_STRING; + case (13903): + return INSERT_CONFIGFILEVALUE_FAILED_STRING; + case (13904): + return WRITE_CONFIGFILE_FAILED_STRING; + case (13905): + return READ_CONFIGFILE_FAILED_STRING; + case (14000): + return ALLOC_FAILURE_STRING; + case (14001): + return REBOOT_SW_STRING; + case (14002): + return REBOOT_MECHANISM_TRIGGERED_STRING; + case (14003): + return REBOOT_HW_STRING; + case (14004): + return NO_SD_CARD_ACTIVE_STRING; + case (14005): + return VERSION_INFO_STRING; + case (14006): + return CURRENT_IMAGE_INFO_STRING; + case (14007): + return REBOOT_COUNTER_STRING; + case (14008): + return INDIVIDUAL_BOOT_COUNTS_STRING; + case (14010): + return TRYING_I2C_RECOVERY_STRING; + case (14011): + return I2C_REBOOT_STRING; + case (14012): + return PDEC_REBOOT_STRING; + case (14013): + return FIRMWARE_INFO_STRING; + case (14014): + return ACTIVE_SD_INFO_STRING; + case (14100): + return NO_VALID_SENSOR_TEMPERATURE_STRING; + case (14101): + return NO_HEALTHY_HEATER_AVAILABLE_STRING; + case (14102): + return SYRLINKS_OVERHEATING_STRING; + case (14104): + return OBC_OVERHEATING_STRING; + case (14105): + return CAMERA_OVERHEATING_STRING; + case (14106): + return PCDU_SYSTEM_OVERHEATING_STRING; + case (14107): + return HEATER_NOT_OFF_FOR_OFF_MODE_STRING; + case (14108): + return MGT_OVERHEATING_STRING; + case (14109): + return TCS_SWITCHING_HEATER_ON_STRING; + case (14110): + return TCS_SWITCHING_HEATER_OFF_STRING; + case (14111): + return TCS_HEATER_MAX_BURN_TIME_REACHED_STRING; + case (14201): + return TX_TIMER_EXPIRED_STRING; + case (14202): + return BIT_LOCK_TX_ON_STRING; + case (14300): + return POSSIBLE_FILE_CORRUPTION_STRING; + case (14301): + return FILE_TOO_LARGE_STRING; + case (14302): + return BUSY_DUMPING_EVENT_STRING; + case (14305): + return DUMP_OK_STORE_DONE_STRING; + case (14306): + return DUMP_NOK_STORE_DONE_STRING; + case (14307): + return DUMP_MISC_STORE_DONE_STRING; + case (14308): + return DUMP_HK_STORE_DONE_STRING; + case (14309): + return DUMP_CFDP_STORE_DONE_STRING; + case (14310): + return DUMP_OK_CANCELLED_STRING; + case (14311): + return DUMP_NOK_CANCELLED_STRING; + case (14312): + return DUMP_MISC_CANCELLED_STRING; + case (14313): + return DUMP_HK_CANCELLED_STRING; + case (14314): + return DUMP_CFDP_CANCELLED_STRING; + case (14500): + return TEMPERATURE_ALL_ONES_START_STRING; + case (14501): + return TEMPERATURE_ALL_ONES_RECOVERY_STRING; + case (14600): + return FAULT_HANDLER_TRIGGERED_STRING; + default: + return "UNKNOWN_EVENT"; + } + return 0; +} diff --git a/linux/fsfwconfig/events/translateEvents.h b/linux/fsfwconfig/events/translateEvents.h new file mode 100644 index 0000000..9955431 --- /dev/null +++ b/linux/fsfwconfig/events/translateEvents.h @@ -0,0 +1,8 @@ +#ifndef FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ +#define FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ + +#include "fsfw/events/Event.h" + +const char *translateEvents(Event event); + +#endif /* FSFWCONFIG_EVENTS_TRANSLATEEVENTS_H_ */ diff --git a/linux/fsfwconfig/ipc/MissionMessageTypes.cpp b/linux/fsfwconfig/ipc/MissionMessageTypes.cpp new file mode 100644 index 0000000..fa1c487 --- /dev/null +++ b/linux/fsfwconfig/ipc/MissionMessageTypes.cpp @@ -0,0 +1,10 @@ +#include "MissionMessageTypes.h" + +#include + +void messagetypes::clearMissionMessage(CommandMessage* message) { + switch (message->getMessageType()) { + default: + break; + } +} diff --git a/linux/fsfwconfig/ipc/MissionMessageTypes.h b/linux/fsfwconfig/ipc/MissionMessageTypes.h new file mode 100644 index 0000000..78b0d34 --- /dev/null +++ b/linux/fsfwconfig/ipc/MissionMessageTypes.h @@ -0,0 +1,22 @@ +#ifndef FSFWCONFIG_IPC_MISSIONMESSAGETYPES_H_ +#define FSFWCONFIG_IPC_MISSIONMESSAGETYPES_H_ + +#include + +class CommandMessage; + +/** + * Custom command messages are specified here. + * Most messages needed to use FSFW are already located in + * + * @param message Generic Command Message + */ +namespace messagetypes { +enum MESSAGE_TYPE { + MISSION_MESSAGE_TYPE_START = FW_MESSAGES_COUNT, +}; + +void clearMissionMessage(CommandMessage* message); +} // namespace messagetypes + +#endif /* FSFWCONFIG_IPC_MISSIONMESSAGETYPES_H_ */ diff --git a/linux/fsfwconfig/objects/systemObjectList.h b/linux/fsfwconfig/objects/systemObjectList.h new file mode 100644 index 0000000..26124b7 --- /dev/null +++ b/linux/fsfwconfig/objects/systemObjectList.h @@ -0,0 +1,64 @@ +#ifndef LINUX_FSFWCONFIG_OBJECTS_SYSTEMOBJECTLIST_H_ +#define LINUX_FSFWCONFIG_OBJECTS_SYSTEMOBJECTLIST_H_ + +#include + +#include + +#include "eive/objects.h" + +// The objects will be instantiated in the ID order +// For naming scheme see flight manual +/* +https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/EIVE_Project_IDs + +Second byte first four bits is the subsystem: +OBDH 0x0 +ACS 0x1 +EPS 0x2 +PL 0x3 +TCS 0x4 +COM 0x5 + +Second byte last four bits is the bus: +None 0x0 +GPIO 0x1 +SPI 0x2 +UART 0x3 +I2C 0x4 +CAN 0x5 + +Third byte is an assembly counter if there are multiple redundant devices. +Fourth byte is a unique counter. + + */ +namespace objects { +enum sourceObjects : uint32_t { + /* 0x53 reserved for FSFW */ + FW_ADDRESS_START = PUS_SERVICE_1_VERIFICATION, + FW_ADDRESS_END = TIME_STAMPER, + PUS_SERVICE_6 = 0x51000500, + + /* 0x49 ('I') for Communication Interfaces **/ + ARDUINO_COM_IF = 0x49000000, + CSP_COM_IF = 0x49050001, + I2C_COM_IF = 0x49040002, + SPI_MAIN_COM_IF = 0x49020004, + GPIO_IF = 0x49010005, + + /* Custom device handler */ + + /* 0x54 ('T') for test handlers */ + TEST_TASK = 0x54694269, + LIBGPIOD_TEST = 0x54123456, + SPI_TEST = 0x54000010, + UART_TEST = 0x54000020, + I2C_TEST = 0x54000030, + DUMMY_INTERFACE = 0x5400CAFE, + DUMMY_HANDLER = 0x5400AFFE, + P60DOCK_TEST_TASK = 0x00005060, + DUMMY_COM_IF = 0x54000040 +}; +} + +#endif /* LINUX_FSFWCONFIG_OBJECTS_SYSTEMOBJECTLIST_H_ */ diff --git a/linux/fsfwconfig/objects/translateObjects.cpp b/linux/fsfwconfig/objects/translateObjects.cpp new file mode 100644 index 0000000..048a77d --- /dev/null +++ b/linux/fsfwconfig/objects/translateObjects.cpp @@ -0,0 +1,556 @@ +/** + * @brief Auto-generated object translation file. + * @details + * Contains 180 translations. + * Generated on: 2024-05-06 13:47:38 + */ +#include "translateObjects.h" + +const char *P60DOCK_TEST_TASK_STRING = "P60DOCK_TEST_TASK"; +const char *ACS_CONTROLLER_STRING = "ACS_CONTROLLER"; +const char *CORE_CONTROLLER_STRING = "CORE_CONTROLLER"; +const char *POWER_CONTROLLER_STRING = "POWER_CONTROLLER"; +const char *GLOBAL_JSON_CFG_STRING = "GLOBAL_JSON_CFG"; +const char *XIPHOS_WDT_STRING = "XIPHOS_WDT"; +const char *THERMAL_CONTROLLER_STRING = "THERMAL_CONTROLLER"; +const char *MGM_0_LIS3_HANDLER_STRING = "MGM_0_LIS3_HANDLER"; +const char *GYRO_0_ADIS_HANDLER_STRING = "GYRO_0_ADIS_HANDLER"; +const char *SUS_0_N_LOC_XFYFZM_PT_XF_STRING = "SUS_0_N_LOC_XFYFZM_PT_XF"; +const char *SUS_1_N_LOC_XBYFZM_PT_XB_STRING = "SUS_1_N_LOC_XBYFZM_PT_XB"; +const char *SUS_2_N_LOC_XFYBZB_PT_YB_STRING = "SUS_2_N_LOC_XFYBZB_PT_YB"; +const char *SUS_3_N_LOC_XFYBZF_PT_YF_STRING = "SUS_3_N_LOC_XFYBZF_PT_YF"; +const char *SUS_4_N_LOC_XMYFZF_PT_ZF_STRING = "SUS_4_N_LOC_XMYFZF_PT_ZF"; +const char *SUS_5_N_LOC_XFYMZB_PT_ZB_STRING = "SUS_5_N_LOC_XFYMZB_PT_ZB"; +const char *SUS_6_R_LOC_XFYBZM_PT_XF_STRING = "SUS_6_R_LOC_XFYBZM_PT_XF"; +const char *SUS_7_R_LOC_XBYBZM_PT_XB_STRING = "SUS_7_R_LOC_XBYBZM_PT_XB"; +const char *SUS_8_R_LOC_XBYBZB_PT_YB_STRING = "SUS_8_R_LOC_XBYBZB_PT_YB"; +const char *SUS_9_R_LOC_XBYBZB_PT_YF_STRING = "SUS_9_R_LOC_XBYBZB_PT_YF"; +const char *SUS_10_N_LOC_XMYBZF_PT_ZF_STRING = "SUS_10_N_LOC_XMYBZF_PT_ZF"; +const char *SUS_11_R_LOC_XBYMZB_PT_ZB_STRING = "SUS_11_R_LOC_XBYMZB_PT_ZB"; +const char *RW1_STRING = "RW1"; +const char *MGM_1_RM3100_HANDLER_STRING = "MGM_1_RM3100_HANDLER"; +const char *GYRO_1_L3G_HANDLER_STRING = "GYRO_1_L3G_HANDLER"; +const char *RW2_STRING = "RW2"; +const char *MGM_2_LIS3_HANDLER_STRING = "MGM_2_LIS3_HANDLER"; +const char *GYRO_2_ADIS_HANDLER_STRING = "GYRO_2_ADIS_HANDLER"; +const char *RW3_STRING = "RW3"; +const char *MGM_3_RM3100_HANDLER_STRING = "MGM_3_RM3100_HANDLER"; +const char *GYRO_3_L3G_HANDLER_STRING = "GYRO_3_L3G_HANDLER"; +const char *RW4_STRING = "RW4"; +const char *STAR_TRACKER_STRING = "STAR_TRACKER"; +const char *GPS_CONTROLLER_STRING = "GPS_CONTROLLER"; +const char *GPS_0_HEALTH_DEV_STRING = "GPS_0_HEALTH_DEV"; +const char *GPS_1_HEALTH_DEV_STRING = "GPS_1_HEALTH_DEV"; +const char *IMTQ_POLLING_STRING = "IMTQ_POLLING"; +const char *IMTQ_HANDLER_STRING = "IMTQ_HANDLER"; +const char *PCDU_HANDLER_STRING = "PCDU_HANDLER"; +const char *P60DOCK_HANDLER_STRING = "P60DOCK_HANDLER"; +const char *PDU1_HANDLER_STRING = "PDU1_HANDLER"; +const char *PDU2_HANDLER_STRING = "PDU2_HANDLER"; +const char *ACU_HANDLER_STRING = "ACU_HANDLER"; +const char *BPX_BATT_HANDLER_STRING = "BPX_BATT_HANDLER"; +const char *PLPCDU_HANDLER_STRING = "PLPCDU_HANDLER"; +const char *RAD_SENSOR_STRING = "RAD_SENSOR"; +const char *PLOC_UPDATER_STRING = "PLOC_UPDATER"; +const char *PLOC_MEMORY_DUMPER_STRING = "PLOC_MEMORY_DUMPER"; +const char *STR_COM_IF_STRING = "STR_COM_IF"; +const char *PLOC_MPSOC_HELPER_STRING = "PLOC_MPSOC_HELPER"; +const char *AXI_PTME_CONFIG_STRING = "AXI_PTME_CONFIG"; +const char *PTME_CONFIG_STRING = "PTME_CONFIG"; +const char *PTME_VC0_LIVE_TM_STRING = "PTME_VC0_LIVE_TM"; +const char *PTME_VC1_LOG_TM_STRING = "PTME_VC1_LOG_TM"; +const char *PTME_VC2_HK_TM_STRING = "PTME_VC2_HK_TM"; +const char *PTME_VC3_CFDP_TM_STRING = "PTME_VC3_CFDP_TM"; +const char *PLOC_MPSOC_HANDLER_STRING = "PLOC_MPSOC_HANDLER"; +const char *PLOC_SUPERVISOR_HANDLER_STRING = "PLOC_SUPERVISOR_HANDLER"; +const char *PLOC_SUPERVISOR_HELPER_STRING = "PLOC_SUPERVISOR_HELPER"; +const char *PLOC_MPSOC_COMMUNICATION_STRING = "PLOC_MPSOC_COMMUNICATION"; +const char *SCEX_STRING = "SCEX"; +const char *SOLAR_ARRAY_DEPL_HANDLER_STRING = "SOLAR_ARRAY_DEPL_HANDLER"; +const char *HEATER_HANDLER_STRING = "HEATER_HANDLER"; +const char *TMP1075_HANDLER_TCS_0_STRING = "TMP1075_HANDLER_TCS_0"; +const char *TMP1075_HANDLER_TCS_1_STRING = "TMP1075_HANDLER_TCS_1"; +const char *TMP1075_HANDLER_PLPCDU_0_STRING = "TMP1075_HANDLER_PLPCDU_0"; +const char *TMP1075_HANDLER_PLPCDU_1_STRING = "TMP1075_HANDLER_PLPCDU_1"; +const char *TMP1075_HANDLER_IF_BOARD_STRING = "TMP1075_HANDLER_IF_BOARD"; +const char *RTD_0_IC3_PLOC_HEATSPREADER_STRING = "RTD_0_IC3_PLOC_HEATSPREADER"; +const char *RTD_1_IC4_PLOC_MISSIONBOARD_STRING = "RTD_1_IC4_PLOC_MISSIONBOARD"; +const char *RTD_2_IC5_4K_CAMERA_STRING = "RTD_2_IC5_4K_CAMERA"; +const char *RTD_3_IC6_DAC_HEATSPREADER_STRING = "RTD_3_IC6_DAC_HEATSPREADER"; +const char *RTD_4_IC7_STARTRACKER_STRING = "RTD_4_IC7_STARTRACKER"; +const char *RTD_5_IC8_RW1_MX_MY_STRING = "RTD_5_IC8_RW1_MX_MY"; +const char *RTD_6_IC9_DRO_STRING = "RTD_6_IC9_DRO"; +const char *RTD_7_IC10_SCEX_STRING = "RTD_7_IC10_SCEX"; +const char *RTD_8_IC11_X8_STRING = "RTD_8_IC11_X8"; +const char *RTD_9_IC12_HPA_STRING = "RTD_9_IC12_HPA"; +const char *RTD_10_IC13_PL_TX_STRING = "RTD_10_IC13_PL_TX"; +const char *RTD_11_IC14_MPA_STRING = "RTD_11_IC14_MPA"; +const char *RTD_12_IC15_ACU_STRING = "RTD_12_IC15_ACU"; +const char *RTD_13_IC16_PLPCDU_HEATSPREADER_STRING = "RTD_13_IC16_PLPCDU_HEATSPREADER"; +const char *RTD_14_IC17_TCS_BOARD_STRING = "RTD_14_IC17_TCS_BOARD"; +const char *RTD_15_IC18_IMTQ_STRING = "RTD_15_IC18_IMTQ"; +const char *SYRLINKS_HANDLER_STRING = "SYRLINKS_HANDLER"; +const char *SYRLINKS_COM_HANDLER_STRING = "SYRLINKS_COM_HANDLER"; +const char *ARDUINO_COM_IF_STRING = "ARDUINO_COM_IF"; +const char *GPIO_IF_STRING = "GPIO_IF"; +const char *SCEX_UART_READER_STRING = "SCEX_UART_READER"; +const char *SPI_MAIN_COM_IF_STRING = "SPI_MAIN_COM_IF"; +const char *UART_COM_IF_STRING = "UART_COM_IF"; +const char *I2C_COM_IF_STRING = "I2C_COM_IF"; +const char *CSP_COM_IF_STRING = "CSP_COM_IF"; +const char *ACS_BOARD_POLLING_TASK_STRING = "ACS_BOARD_POLLING_TASK"; +const char *RW_POLLING_TASK_STRING = "RW_POLLING_TASK"; +const char *SPI_RTD_COM_IF_STRING = "SPI_RTD_COM_IF"; +const char *SUS_POLLING_TASK_STRING = "SUS_POLLING_TASK"; +const char *CCSDS_PACKET_DISTRIBUTOR_STRING = "CCSDS_PACKET_DISTRIBUTOR"; +const char *PUS_PACKET_DISTRIBUTOR_STRING = "PUS_PACKET_DISTRIBUTOR"; +const char *TCP_TMTC_SERVER_STRING = "TCP_TMTC_SERVER"; +const char *UDP_TMTC_SERVER_STRING = "UDP_TMTC_SERVER"; +const char *TCP_TMTC_POLLING_TASK_STRING = "TCP_TMTC_POLLING_TASK"; +const char *UDP_TMTC_POLLING_TASK_STRING = "UDP_TMTC_POLLING_TASK"; +const char *FILE_SYSTEM_HANDLER_STRING = "FILE_SYSTEM_HANDLER"; +const char *SDC_MANAGER_STRING = "SDC_MANAGER"; +const char *PTME_STRING = "PTME"; +const char *PDEC_HANDLER_STRING = "PDEC_HANDLER"; +const char *CCSDS_HANDLER_STRING = "CCSDS_HANDLER"; +const char *PUS_SERVICE_6_STRING = "PUS_SERVICE_6"; +const char *FSFW_OBJECTS_START_STRING = "FSFW_OBJECTS_START"; +const char *PUS_SERVICE_1_VERIFICATION_STRING = "PUS_SERVICE_1_VERIFICATION"; +const char *PUS_SERVICE_2_DEVICE_ACCESS_STRING = "PUS_SERVICE_2_DEVICE_ACCESS"; +const char *PUS_SERVICE_3_HOUSEKEEPING_STRING = "PUS_SERVICE_3_HOUSEKEEPING"; +const char *PUS_SERVICE_5_EVENT_REPORTING_STRING = "PUS_SERVICE_5_EVENT_REPORTING"; +const char *PUS_SERVICE_8_FUNCTION_MGMT_STRING = "PUS_SERVICE_8_FUNCTION_MGMT"; +const char *PUS_SERVICE_9_TIME_MGMT_STRING = "PUS_SERVICE_9_TIME_MGMT"; +const char *PUS_SERVICE_11_TC_SCHEDULER_STRING = "PUS_SERVICE_11_TC_SCHEDULER"; +const char *PUS_SERVICE_15_TM_STORAGE_STRING = "PUS_SERVICE_15_TM_STORAGE"; +const char *PUS_SERVICE_17_TEST_STRING = "PUS_SERVICE_17_TEST"; +const char *PUS_SERVICE_20_PARAMETERS_STRING = "PUS_SERVICE_20_PARAMETERS"; +const char *PUS_SERVICE_200_MODE_MGMT_STRING = "PUS_SERVICE_200_MODE_MGMT"; +const char *PUS_SERVICE_201_HEALTH_STRING = "PUS_SERVICE_201_HEALTH"; +const char *CFDP_PACKET_DISTRIBUTOR_STRING = "CFDP_PACKET_DISTRIBUTOR"; +const char *HEALTH_TABLE_STRING = "HEALTH_TABLE"; +const char *MODE_STORE_STRING = "MODE_STORE"; +const char *EVENT_MANAGER_STRING = "EVENT_MANAGER"; +const char *INTERNAL_ERROR_REPORTER_STRING = "INTERNAL_ERROR_REPORTER"; +const char *TC_STORE_STRING = "TC_STORE"; +const char *TM_STORE_STRING = "TM_STORE"; +const char *IPC_STORE_STRING = "IPC_STORE"; +const char *TIME_STAMPER_STRING = "TIME_STAMPER"; +const char *VERIFICATION_REPORTER_STRING = "VERIFICATION_REPORTER"; +const char *FSFW_OBJECTS_END_STRING = "FSFW_OBJECTS_END"; +const char *SPI_TEST_STRING = "SPI_TEST"; +const char *UART_TEST_STRING = "UART_TEST"; +const char *I2C_TEST_STRING = "I2C_TEST"; +const char *DUMMY_COM_IF_STRING = "DUMMY_COM_IF"; +const char *DUMMY_HANDLER_STRING = "DUMMY_HANDLER"; +const char *DUMMY_INTERFACE_STRING = "DUMMY_INTERFACE"; +const char *LIBGPIOD_TEST_STRING = "LIBGPIOD_TEST"; +const char *TEST_TASK_STRING = "TEST_TASK"; +const char *HEATER_0_PLOC_PROC_BRD_STRING = "HEATER_0_PLOC_PROC_BRD"; +const char *HEATER_1_PCDU_BRD_STRING = "HEATER_1_PCDU_BRD"; +const char *HEATER_2_ACS_BRD_STRING = "HEATER_2_ACS_BRD"; +const char *HEATER_3_OBC_BRD_STRING = "HEATER_3_OBC_BRD"; +const char *HEATER_4_CAMERA_STRING = "HEATER_4_CAMERA"; +const char *HEATER_5_STR_STRING = "HEATER_5_STR"; +const char *HEATER_6_DRO_STRING = "HEATER_6_DRO"; +const char *HEATER_7_SYRLINKS_STRING = "HEATER_7_SYRLINKS"; +const char *ACS_BOARD_ASS_STRING = "ACS_BOARD_ASS"; +const char *SUS_BOARD_ASS_STRING = "SUS_BOARD_ASS"; +const char *TCS_BOARD_ASS_STRING = "TCS_BOARD_ASS"; +const char *RW_ASSY_STRING = "RW_ASSY"; +const char *CAM_SWITCHER_STRING = "CAM_SWITCHER"; +const char *SYRLINKS_ASSY_STRING = "SYRLINKS_ASSY"; +const char *IMTQ_ASSY_STRING = "IMTQ_ASSY"; +const char *STR_ASSY_STRING = "STR_ASSY"; +const char *TM_FUNNEL_STRING = "TM_FUNNEL"; +const char *PUS_TM_FUNNEL_STRING = "PUS_TM_FUNNEL"; +const char *CFDP_TM_FUNNEL_STRING = "CFDP_TM_FUNNEL"; +const char *CFDP_HANDLER_STRING = "CFDP_HANDLER"; +const char *CFDP_DISTRIBUTOR_STRING = "CFDP_DISTRIBUTOR"; +const char *CFDP_FAULT_HANDLER_STRING = "CFDP_FAULT_HANDLER"; +const char *EIVE_SYSTEM_STRING = "EIVE_SYSTEM"; +const char *ACS_SUBSYSTEM_STRING = "ACS_SUBSYSTEM"; +const char *PL_SUBSYSTEM_STRING = "PL_SUBSYSTEM"; +const char *TCS_SUBSYSTEM_STRING = "TCS_SUBSYSTEM"; +const char *COM_SUBSYSTEM_STRING = "COM_SUBSYSTEM"; +const char *EPS_SUBSYSTEM_STRING = "EPS_SUBSYSTEM"; +const char *MISC_TM_STORE_STRING = "MISC_TM_STORE"; +const char *OK_TM_STORE_STRING = "OK_TM_STORE"; +const char *NOT_OK_TM_STORE_STRING = "NOT_OK_TM_STORE"; +const char *HK_TM_STORE_STRING = "HK_TM_STORE"; +const char *CFDP_TM_STORE_STRING = "CFDP_TM_STORE"; +const char *LIVE_TM_TASK_STRING = "LIVE_TM_TASK"; +const char *LOG_STORE_AND_TM_TASK_STRING = "LOG_STORE_AND_TM_TASK"; +const char *HK_STORE_AND_TM_TASK_STRING = "HK_STORE_AND_TM_TASK"; +const char *CFDP_STORE_AND_TM_TASK_STRING = "CFDP_STORE_AND_TM_TASK"; +const char *DOWNLINK_RAM_STORE_STRING = "DOWNLINK_RAM_STORE"; +const char *THERMAL_TEMP_INSERTER_STRING = "THERMAL_TEMP_INSERTER"; +const char *NO_OBJECT_STRING = "NO_OBJECT"; + +const char *translateObject(object_id_t object) { + switch ((object & 0xFFFFFFFF)) { + case 0x00005060: + return P60DOCK_TEST_TASK_STRING; + case 0x43000002: + return ACS_CONTROLLER_STRING; + case 0x43000003: + return CORE_CONTROLLER_STRING; + case 0x43000004: + return POWER_CONTROLLER_STRING; + case 0x43000006: + return GLOBAL_JSON_CFG_STRING; + case 0x43000007: + return XIPHOS_WDT_STRING; + case 0x43400001: + return THERMAL_CONTROLLER_STRING; + case 0x44120006: + return MGM_0_LIS3_HANDLER_STRING; + case 0x44120010: + return GYRO_0_ADIS_HANDLER_STRING; + case 0x44120032: + return SUS_0_N_LOC_XFYFZM_PT_XF_STRING; + case 0x44120033: + return SUS_1_N_LOC_XBYFZM_PT_XB_STRING; + case 0x44120034: + return SUS_2_N_LOC_XFYBZB_PT_YB_STRING; + case 0x44120035: + return SUS_3_N_LOC_XFYBZF_PT_YF_STRING; + case 0x44120036: + return SUS_4_N_LOC_XMYFZF_PT_ZF_STRING; + case 0x44120037: + return SUS_5_N_LOC_XFYMZB_PT_ZB_STRING; + case 0x44120038: + return SUS_6_R_LOC_XFYBZM_PT_XF_STRING; + case 0x44120039: + return SUS_7_R_LOC_XBYBZM_PT_XB_STRING; + case 0x44120040: + return SUS_8_R_LOC_XBYBZB_PT_YB_STRING; + case 0x44120041: + return SUS_9_R_LOC_XBYBZB_PT_YF_STRING; + case 0x44120042: + return SUS_10_N_LOC_XMYBZF_PT_ZF_STRING; + case 0x44120043: + return SUS_11_R_LOC_XBYMZB_PT_ZB_STRING; + case 0x44120047: + return RW1_STRING; + case 0x44120107: + return MGM_1_RM3100_HANDLER_STRING; + case 0x44120111: + return GYRO_1_L3G_HANDLER_STRING; + case 0x44120148: + return RW2_STRING; + case 0x44120208: + return MGM_2_LIS3_HANDLER_STRING; + case 0x44120212: + return GYRO_2_ADIS_HANDLER_STRING; + case 0x44120249: + return RW3_STRING; + case 0x44120309: + return MGM_3_RM3100_HANDLER_STRING; + case 0x44120313: + return GYRO_3_L3G_HANDLER_STRING; + case 0x44120350: + return RW4_STRING; + case 0x44130001: + return STAR_TRACKER_STRING; + case 0x44130045: + return GPS_CONTROLLER_STRING; + case 0x44130046: + return GPS_0_HEALTH_DEV_STRING; + case 0x44130047: + return GPS_1_HEALTH_DEV_STRING; + case 0x44140013: + return IMTQ_POLLING_STRING; + case 0x44140014: + return IMTQ_HANDLER_STRING; + case 0x442000A1: + return PCDU_HANDLER_STRING; + case 0x44250000: + return P60DOCK_HANDLER_STRING; + case 0x44250001: + return PDU1_HANDLER_STRING; + case 0x44250002: + return PDU2_HANDLER_STRING; + case 0x44250003: + return ACU_HANDLER_STRING; + case 0x44260000: + return BPX_BATT_HANDLER_STRING; + case 0x44300000: + return PLPCDU_HANDLER_STRING; + case 0x443200A5: + return RAD_SENSOR_STRING; + case 0x44330000: + return PLOC_UPDATER_STRING; + case 0x44330001: + return PLOC_MEMORY_DUMPER_STRING; + case 0x44330002: + return STR_COM_IF_STRING; + case 0x44330003: + return PLOC_MPSOC_HELPER_STRING; + case 0x44330004: + return AXI_PTME_CONFIG_STRING; + case 0x44330005: + return PTME_CONFIG_STRING; + case 0x44330006: + return PTME_VC0_LIVE_TM_STRING; + case 0x44330007: + return PTME_VC1_LOG_TM_STRING; + case 0x44330008: + return PTME_VC2_HK_TM_STRING; + case 0x44330009: + return PTME_VC3_CFDP_TM_STRING; + case 0x44330015: + return PLOC_MPSOC_HANDLER_STRING; + case 0x44330016: + return PLOC_SUPERVISOR_HANDLER_STRING; + case 0x44330017: + return PLOC_SUPERVISOR_HELPER_STRING; + case 0x44330018: + return PLOC_MPSOC_COMMUNICATION_STRING; + case 0x44330032: + return SCEX_STRING; + case 0x444100A2: + return SOLAR_ARRAY_DEPL_HANDLER_STRING; + case 0x444100A4: + return HEATER_HANDLER_STRING; + case 0x44420004: + return TMP1075_HANDLER_TCS_0_STRING; + case 0x44420005: + return TMP1075_HANDLER_TCS_1_STRING; + case 0x44420006: + return TMP1075_HANDLER_PLPCDU_0_STRING; + case 0x44420007: + return TMP1075_HANDLER_PLPCDU_1_STRING; + case 0x44420008: + return TMP1075_HANDLER_IF_BOARD_STRING; + case 0x44420016: + return RTD_0_IC3_PLOC_HEATSPREADER_STRING; + case 0x44420017: + return RTD_1_IC4_PLOC_MISSIONBOARD_STRING; + case 0x44420018: + return RTD_2_IC5_4K_CAMERA_STRING; + case 0x44420019: + return RTD_3_IC6_DAC_HEATSPREADER_STRING; + case 0x44420020: + return RTD_4_IC7_STARTRACKER_STRING; + case 0x44420021: + return RTD_5_IC8_RW1_MX_MY_STRING; + case 0x44420022: + return RTD_6_IC9_DRO_STRING; + case 0x44420023: + return RTD_7_IC10_SCEX_STRING; + case 0x44420024: + return RTD_8_IC11_X8_STRING; + case 0x44420025: + return RTD_9_IC12_HPA_STRING; + case 0x44420026: + return RTD_10_IC13_PL_TX_STRING; + case 0x44420027: + return RTD_11_IC14_MPA_STRING; + case 0x44420028: + return RTD_12_IC15_ACU_STRING; + case 0x44420029: + return RTD_13_IC16_PLPCDU_HEATSPREADER_STRING; + case 0x44420030: + return RTD_14_IC17_TCS_BOARD_STRING; + case 0x44420031: + return RTD_15_IC18_IMTQ_STRING; + case 0x445300A3: + return SYRLINKS_HANDLER_STRING; + case 0x445300A4: + return SYRLINKS_COM_HANDLER_STRING; + case 0x49000000: + return ARDUINO_COM_IF_STRING; + case 0x49010005: + return GPIO_IF_STRING; + case 0x49010006: + return SCEX_UART_READER_STRING; + case 0x49020004: + return SPI_MAIN_COM_IF_STRING; + case 0x49030003: + return UART_COM_IF_STRING; + case 0x49040002: + return I2C_COM_IF_STRING; + case 0x49050001: + return CSP_COM_IF_STRING; + case 0x49060004: + return ACS_BOARD_POLLING_TASK_STRING; + case 0x49060005: + return RW_POLLING_TASK_STRING; + case 0x49060006: + return SPI_RTD_COM_IF_STRING; + case 0x49060007: + return SUS_POLLING_TASK_STRING; + case 0x50000100: + return CCSDS_PACKET_DISTRIBUTOR_STRING; + case 0x50000200: + return PUS_PACKET_DISTRIBUTOR_STRING; + case 0x50000300: + return TCP_TMTC_SERVER_STRING; + case 0x50000301: + return UDP_TMTC_SERVER_STRING; + case 0x50000400: + return TCP_TMTC_POLLING_TASK_STRING; + case 0x50000401: + return UDP_TMTC_POLLING_TASK_STRING; + case 0x50000500: + return FILE_SYSTEM_HANDLER_STRING; + case 0x50000550: + return SDC_MANAGER_STRING; + case 0x50000600: + return PTME_STRING; + case 0x50000700: + return PDEC_HANDLER_STRING; + case 0x50000800: + return CCSDS_HANDLER_STRING; + case 0x51000500: + return PUS_SERVICE_6_STRING; + case 0x53000000: + return FSFW_OBJECTS_START_STRING; + case 0x53000001: + return PUS_SERVICE_1_VERIFICATION_STRING; + case 0x53000002: + return PUS_SERVICE_2_DEVICE_ACCESS_STRING; + case 0x53000003: + return PUS_SERVICE_3_HOUSEKEEPING_STRING; + case 0x53000005: + return PUS_SERVICE_5_EVENT_REPORTING_STRING; + case 0x53000008: + return PUS_SERVICE_8_FUNCTION_MGMT_STRING; + case 0x53000009: + return PUS_SERVICE_9_TIME_MGMT_STRING; + case 0x53000011: + return PUS_SERVICE_11_TC_SCHEDULER_STRING; + case 0x53000015: + return PUS_SERVICE_15_TM_STORAGE_STRING; + case 0x53000017: + return PUS_SERVICE_17_TEST_STRING; + case 0x53000020: + return PUS_SERVICE_20_PARAMETERS_STRING; + case 0x53000200: + return PUS_SERVICE_200_MODE_MGMT_STRING; + case 0x53000201: + return PUS_SERVICE_201_HEALTH_STRING; + case 0x53001000: + return CFDP_PACKET_DISTRIBUTOR_STRING; + case 0x53010000: + return HEALTH_TABLE_STRING; + case 0x53010100: + return MODE_STORE_STRING; + case 0x53030000: + return EVENT_MANAGER_STRING; + case 0x53040000: + return INTERNAL_ERROR_REPORTER_STRING; + case 0x534f0100: + return TC_STORE_STRING; + case 0x534f0200: + return TM_STORE_STRING; + case 0x534f0300: + return IPC_STORE_STRING; + case 0x53500010: + return TIME_STAMPER_STRING; + case 0x53500020: + return VERIFICATION_REPORTER_STRING; + case 0x53ffffff: + return FSFW_OBJECTS_END_STRING; + case 0x54000010: + return SPI_TEST_STRING; + case 0x54000020: + return UART_TEST_STRING; + case 0x54000030: + return I2C_TEST_STRING; + case 0x54000040: + return DUMMY_COM_IF_STRING; + case 0x5400AFFE: + return DUMMY_HANDLER_STRING; + case 0x5400CAFE: + return DUMMY_INTERFACE_STRING; + case 0x54123456: + return LIBGPIOD_TEST_STRING; + case 0x54694269: + return TEST_TASK_STRING; + case 0x60000000: + return HEATER_0_PLOC_PROC_BRD_STRING; + case 0x60000001: + return HEATER_1_PCDU_BRD_STRING; + case 0x60000002: + return HEATER_2_ACS_BRD_STRING; + case 0x60000003: + return HEATER_3_OBC_BRD_STRING; + case 0x60000004: + return HEATER_4_CAMERA_STRING; + case 0x60000005: + return HEATER_5_STR_STRING; + case 0x60000006: + return HEATER_6_DRO_STRING; + case 0x60000007: + return HEATER_7_SYRLINKS_STRING; + case 0x73000001: + return ACS_BOARD_ASS_STRING; + case 0x73000002: + return SUS_BOARD_ASS_STRING; + case 0x73000003: + return TCS_BOARD_ASS_STRING; + case 0x73000004: + return RW_ASSY_STRING; + case 0x73000006: + return CAM_SWITCHER_STRING; + case 0x73000007: + return SYRLINKS_ASSY_STRING; + case 0x73000008: + return IMTQ_ASSY_STRING; + case 0x73000009: + return STR_ASSY_STRING; + case 0x73000100: + return TM_FUNNEL_STRING; + case 0x73000101: + return PUS_TM_FUNNEL_STRING; + case 0x73000102: + return CFDP_TM_FUNNEL_STRING; + case 0x73000205: + return CFDP_HANDLER_STRING; + case 0x73000206: + return CFDP_DISTRIBUTOR_STRING; + case 0x73000207: + return CFDP_FAULT_HANDLER_STRING; + case 0x73010000: + return EIVE_SYSTEM_STRING; + case 0x73010001: + return ACS_SUBSYSTEM_STRING; + case 0x73010002: + return PL_SUBSYSTEM_STRING; + case 0x73010003: + return TCS_SUBSYSTEM_STRING; + case 0x73010004: + return COM_SUBSYSTEM_STRING; + case 0x73010005: + return EPS_SUBSYSTEM_STRING; + case 0x73020001: + return MISC_TM_STORE_STRING; + case 0x73020002: + return OK_TM_STORE_STRING; + case 0x73020003: + return NOT_OK_TM_STORE_STRING; + case 0x73020004: + return HK_TM_STORE_STRING; + case 0x73030000: + return CFDP_TM_STORE_STRING; + case 0x73040000: + return LIVE_TM_TASK_STRING; + case 0x73040001: + return LOG_STORE_AND_TM_TASK_STRING; + case 0x73040002: + return HK_STORE_AND_TM_TASK_STRING; + case 0x73040003: + return CFDP_STORE_AND_TM_TASK_STRING; + case 0x73040004: + return DOWNLINK_RAM_STORE_STRING; + case 0x90000003: + return THERMAL_TEMP_INSERTER_STRING; + case 0xFFFFFFFF: + return NO_OBJECT_STRING; + default: + return "UNKNOWN_OBJECT"; + } + return 0; +} diff --git a/linux/fsfwconfig/objects/translateObjects.h b/linux/fsfwconfig/objects/translateObjects.h new file mode 100644 index 0000000..257912f --- /dev/null +++ b/linux/fsfwconfig/objects/translateObjects.h @@ -0,0 +1,8 @@ +#ifndef FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ +#define FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ + +#include + +const char *translateObject(object_id_t object); + +#endif /* FSFWCONFIG_OBJECTS_TRANSLATEOBJECTS_H_ */ diff --git a/linux/fsfwconfig/returnvalues/classIds.h b/linux/fsfwconfig/returnvalues/classIds.h new file mode 100644 index 0000000..f4db7ff --- /dev/null +++ b/linux/fsfwconfig/returnvalues/classIds.h @@ -0,0 +1,21 @@ +#ifndef FSFWCONFIG_RETURNVALUES_CLASSIDS_H_ +#define FSFWCONFIG_RETURNVALUES_CLASSIDS_H_ + +#include + +#include "eive/resultClassIds.h" + +/** + * Source IDs starts at 73 for now + * Framework IDs for ReturnValues run from 0 to 56 + * and are located inside + */ +namespace CLASS_ID { +enum { + CLASS_ID_START = COMMON_CLASS_ID_END, + SCRATCH_BUFFER, // SCBU + CLASS_ID_END // [EXPORT] : [END] +}; +} + +#endif /* FSFWCONFIG_RETURNVALUES_CLASSIDS_H_ */ diff --git a/linux/ipcore/AxiPtmeConfig.cpp b/linux/ipcore/AxiPtmeConfig.cpp new file mode 100644 index 0000000..b21edf5 --- /dev/null +++ b/linux/ipcore/AxiPtmeConfig.cpp @@ -0,0 +1,103 @@ +#include "AxiPtmeConfig.h" + +#include + +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "fsfw_hal/linux/uio/UioMapper.h" + +AxiPtmeConfig::AxiPtmeConfig(object_id_t objectId, std::string axiUio, int mapNum) + : SystemObject(objectId), axiUio(std::move(axiUio)), mapNum(mapNum) { + mutex = MutexFactory::instance()->createMutex(); + if (mutex == nullptr) { + sif::warning << "Failed to create mutex" << std::endl; + } +} + +AxiPtmeConfig::~AxiPtmeConfig() {} + +ReturnValue_t AxiPtmeConfig::initialize() { + UioMapper uioMapper(axiUio, mapNum); + ReturnValue_t result = + uioMapper.getMappedAdress(&baseAddress, UioMapper::Permissions::READ_WRITE); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t AxiPtmeConfig::writeCaduRateReg(uint8_t rateVal) { + ReturnValue_t result = mutex->lockMutex(timeoutType, mutexTimeout); + if (result != returnvalue::OK) { + sif::warning << "AxiPtmeConfig::writeCaduRateReg: Failed to lock mutex" << std::endl; + return returnvalue::FAILED; + } + *(baseAddress + CADU_BITRATE_REG) = static_cast(rateVal); + result = mutex->unlockMutex(); + if (result != returnvalue::OK) { + sif::warning << "AxiPtmeConfig::writeCaduRateReg: Failed to unlock mutex" << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +uint8_t AxiPtmeConfig::readCaduRateReg() { + MutexGuard mg(mutex); + return static_cast(*(baseAddress + CADU_BITRATE_REG)); +} + +void AxiPtmeConfig::enableTxclockManipulator() { + writeBit(COMMON_CONFIG_REG, true, BitPos::EN_TX_CLK_MANIPULATOR); +} + +void AxiPtmeConfig::disableTxclockManipulator() { + writeBit(COMMON_CONFIG_REG, false, BitPos::EN_TX_CLK_MANIPULATOR); +} + +void AxiPtmeConfig::enableTxclockInversion() { + writeBit(COMMON_CONFIG_REG, true, BitPos::INVERT_CLOCK); +} + +void AxiPtmeConfig::disableTxclockInversion() { + writeBit(COMMON_CONFIG_REG, false, BitPos::INVERT_CLOCK); +} + +void AxiPtmeConfig::enableBatPriorityBit() { + writeBit(COMMON_CONFIG_REG, true, BitPos::EN_BAT_PRIORITY); +} + +void AxiPtmeConfig::disableBatPriorityBit() { + writeBit(COMMON_CONFIG_REG, false, BitPos::EN_BAT_PRIORITY); +} + +void AxiPtmeConfig::writeReg(uint32_t regOffset, uint32_t writeVal) { + MutexGuard mg(mutex, timeoutType, mutexTimeout); + *(baseAddress + regOffset / ADRESS_DIVIDER) = writeVal; +} + +uint32_t AxiPtmeConfig::readReg(uint32_t regOffset) { + MutexGuard mg(mutex, timeoutType, mutexTimeout); + return *(baseAddress + regOffset / ADRESS_DIVIDER); +} + +void AxiPtmeConfig::writePollThreshold(AxiPtmeConfig::IdlePollThreshold pollThreshold) { + uint32_t regVal = readCommonCfgReg(); + // Clear bits first + regVal &= ~(0b111 << 3); + regVal |= (static_cast(pollThreshold) << 3); + writeCommonCfgReg(regVal); +} + +AxiPtmeConfig::IdlePollThreshold AxiPtmeConfig::readPollThreshold() { + uint32_t regVal = readCommonCfgReg(); + return static_cast((regVal >> 3) & 0b111); +} + +void AxiPtmeConfig::writeCommonCfgReg(uint32_t value) { writeReg(COMMON_CONFIG_REG, value); } +uint32_t AxiPtmeConfig::readCommonCfgReg() { return readReg(COMMON_CONFIG_REG); } + +void AxiPtmeConfig::writeBit(uint32_t regOffset, bool bitVal, BitPos bitPos) { + uint32_t readVal = readReg(regOffset); + uint32_t writeVal = + (readVal & ~(1 << static_cast(bitPos))) | bitVal << static_cast(bitPos); + writeReg(regOffset, writeVal); +} diff --git a/linux/ipcore/AxiPtmeConfig.h b/linux/ipcore/AxiPtmeConfig.h new file mode 100644 index 0000000..ebdf4d3 --- /dev/null +++ b/linux/ipcore/AxiPtmeConfig.h @@ -0,0 +1,122 @@ +#ifndef LINUX_OBC_AXIPTMECONFIG_H_ +#define LINUX_OBC_AXIPTMECONFIG_H_ + +#include + +#include "fsfw/ipc/MutexIF.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/returnvalues/returnvalue.h" + +/** + * @brief Class providing low level access to the configuration interface of the PTME. + * + * @author J. Meier + */ +class AxiPtmeConfig : public SystemObject { + public: + enum IdlePollThreshold : uint8_t { + ALWAYS = 0b000, + POLL_1 = 0b001, + POLL_4 = 0b010, + POLL_16 = 0b011, + POLL_64 = 0b100, + POLL_256 = 0b101, + POLL_1024 = 0b110, + NEVER = 0b111 + }; + /** + * @brief Constructor + * @param axiUio Device file of UIO belonging to the AXI configuration interface. + * @param mapNum Number of map belonging to axi configuration interface. + */ + AxiPtmeConfig(object_id_t objectId, std::string axiUio, int mapNum); + virtual ~AxiPtmeConfig(); + + virtual ReturnValue_t initialize() override; + /** + * @brief Will write to the bitrate configuration register. Actual generated rate depends on + * frequency of the clock connected to the bit clock input of PTME. + */ + ReturnValue_t writeCaduRateReg(uint8_t rateVal); + uint8_t readCaduRateReg(); + + /** + * @brief Next to functions control the tx clock manipulator component + * + * @details If the tx clock manipulator is enabled the output clock of the PTME is manipulated + * in a way that both high and low periods in the clock signal have equal lengths. + * The default implementation of the PTME generates a clock where the high level is + * only one bit clock period long. This might be too short to match the setup and hold + * times of the S-and transceiver. + * Default: Enables TX clock manipulator + * + */ + void enableTxclockManipulator(); + void disableTxclockManipulator(); + + /** + * @brief The next to functions control whether data will be updated on the rising or falling edge + * of the tx clock. + * Enable inversion will update data on falling edge (not the configuration required by the + * syrlinks) + * Disable clock inversion. Data updated on rising edge. + * Default: Inversion is disabled + */ + void enableTxclockInversion(); + void disableTxclockInversion(); + + void enableBatPriorityBit(); + void disableBatPriorityBit(); + + void writePollThreshold(IdlePollThreshold pollThreshold); + IdlePollThreshold readPollThreshold(); + + private: + // Address of register storing the bitrate configuration parameter + static const uint32_t CADU_BITRATE_REG = 0x0; + // Address of register storing common configuration parameters + static const uint32_t COMMON_CONFIG_REG = 0x4; + static const uint32_t ADRESS_DIVIDER = 4; + + enum class BitPos : uint32_t { EN_TX_CLK_MANIPULATOR = 0, INVERT_CLOCK = 1, EN_BAT_PRIORITY = 2 }; + + std::string axiUio; + std::string uioMap; + int mapNum = 0; + MutexIF* mutex = nullptr; + MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING; + uint32_t mutexTimeout = 20; + + uint32_t* baseAddress = nullptr; + + /** + * @brief Function to write to configuration registers + * + * @param writeVal Value to write + */ + void writeReg(uint32_t regOffset, uint32_t writeVal); + + /** + * @brief Reads value from configuration register + * + * @param regOffset Offset of register from base address to read from + * Qparam readVal Pointer to variable where read value will be written to + */ + uint32_t readReg(uint32_t regOffset); + + uint32_t readCommonCfgReg(); + void writeCommonCfgReg(uint32_t value); + + /** + * @brief Sets one bit in a register + * + * @param regOffset Offset of the register where to set the bit + * @param bitVal The value of the bit to set (1 or 0) + * @param bitPos The position of the bit within the register to set + * + * @return returnvalue::OK if successful, otherwise returnvalue::FAILED + */ + void writeBit(uint32_t regOffset, bool bitVal, BitPos bitPos); +}; + +#endif /* LINUX_OBC_AXIPTMECONFIG_H_ */ diff --git a/linux/ipcore/CMakeLists.txt b/linux/ipcore/CMakeLists.txt new file mode 100644 index 0000000..e45a1fe --- /dev/null +++ b/linux/ipcore/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources( + ${OBSW_NAME} PUBLIC PapbVcInterface.cpp Ptme.cpp PdecHandler.cpp + PdecConfig.cpp PtmeConfig.cpp AxiPtmeConfig.cpp) diff --git a/linux/ipcore/PapbVcInterface.cpp b/linux/ipcore/PapbVcInterface.cpp new file mode 100644 index 0000000..e441436 --- /dev/null +++ b/linux/ipcore/PapbVcInterface.cpp @@ -0,0 +1,147 @@ +#include +#include +#include + +#include +#include + +#include "fsfw/serviceinterface/ServiceInterface.h" + +PapbVcInterface::PapbVcInterface(LinuxLibgpioIF* gpioComIF, gpioId_t papbEmptyId, + std::string uioFile, int mapNum, size_t maxPacketSize) + : gpioComIF(gpioComIF), + papbEmptyId(papbEmptyId), + packetBuf(maxPacketSize), + uioFile(std::move(uioFile)), + mapNum(mapNum) {} + +PapbVcInterface::~PapbVcInterface() {} + +ReturnValue_t PapbVcInterface::initialize() { + UioMapper uioMapper(uioFile, mapNum); + ReturnValue_t result = uioMapper.getMappedAdress(const_cast(&vcBaseReg), + UioMapper::Permissions::READ_WRITE); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PapbVcInterface::write(const uint8_t* data, size_t size, size_t& writtenSize) { + // There are no packets smaller than 4, this is considered a configuration error. + if (size < 4) { + sif::warning << "PapbVcInterface::write: Passed packet smaller than 4 bytes" << std::endl; + return returnvalue::FAILED; + } + // The user must call advance until completion before starting a new packet transfer. + if (writeActiveStatus) { + return IS_BUSY; + } + if (size > packetBuf.capacity()) { + sif::error << "PapbVcInterface: Packet with size " << size << " larger than maximum configured" + << " byte size " << packetBuf.capacity() << std::endl; + return returnvalue::FAILED; + } + std::memcpy(packetBuf.data(), data, size); + currentPacketSize = size; + currentPacketIndex = 0; + if (pollReadyForPacket()) { + startPacketTransfer(ByteWidthCfg::ONE); + } else { + return DirectTmSinkIF::IS_BUSY; + } + return advanceWrite(writtenSize); +} + +void PapbVcInterface::startPacketTransfer(ByteWidthCfg initWidth) { + *vcBaseReg = CONFIG_DATA_INPUT | initWidth; + writeActiveStatus = true; +} + +bool PapbVcInterface::pollReadyForPacket() const { + // Check if PAPB interface is ready to receive data. Use the configuration register for this. + // Bit 5, see PTME ptme_001_01-0-7-r2 Table 31. + uint32_t reg = *vcBaseReg; + return (reg >> 6) & 0b1; +} + +ReturnValue_t PapbVcInterface::advanceWrite(size_t& writtenSize) { + if (!writeActiveStatus) { + return NO_WRITE_ACTIVE; + } + if (not pollReadyForPacket()) { + return IS_BUSY; + } + while (currentPacketIndex < currentPacketSize) { + if (not pollReadyForOctet(MAX_BUSY_POLLS)) { + if (not pollReadyForPacket()) { + return PARTIALLY_WRITTEN; + } + abortPacketTransfer(); + return returnvalue::FAILED; + } + *(vcBaseReg + DATA_REG_OFFSET) = static_cast(packetBuf[currentPacketIndex++]); + writtenSize++; + } + if (not pollReadyForOctet(MAX_BUSY_POLLS)) { + if (not pollReadyForPacket()) { + return PARTIALLY_WRITTEN; + } + abortPacketTransfer(); + return returnvalue::FAILED; + } + completePacketTransfer(); + return returnvalue::OK; +} + +bool PapbVcInterface::writeActive() const { return writeActiveStatus; } + +bool PapbVcInterface::isVcInterfaceBufferEmpty() { + ReturnValue_t result = returnvalue::OK; + gpio::Levels papbEmptyState = gpio::Levels::HIGH; + + result = gpioComIF->readGpio(papbEmptyId, papbEmptyState); + + if (result != returnvalue::OK) { + sif::error << "PapbVcInterface::isVcInterfaceBufferEmpty: Failed to read papb empty signal" + << std::endl; + return true; + } + + if (papbEmptyState == gpio::Levels::HIGH) { + return true; + } + return false; +} + +bool PapbVcInterface::isBusy() const { return not pollReadyForPacket(); } + +void PapbVcInterface::cancelTransfer() { abortPacketTransfer(); } + +inline bool PapbVcInterface::pollReadyForOctet(uint32_t maxCycles) const { + uint32_t reg; + uint32_t idx = 0; + while (idx < maxCycles) { + reg = *vcBaseReg; + // Busy bit. + if (not((reg >> 5) & 0b1)) { + return true; + } + idx++; + } + return false; +} + +void PapbVcInterface::abortPacketTransfer() { + *vcBaseReg = CONFIG_ABORT; + writeActiveStatus = false; + currentPacketIndex = 0; + currentPacketSize = 0; +} + +void PapbVcInterface::completePacketTransfer() { + *vcBaseReg = CONFIG_END; + writeActiveStatus = false; + currentPacketIndex = 0; + currentPacketSize = 0; +} diff --git a/linux/ipcore/PapbVcInterface.h b/linux/ipcore/PapbVcInterface.h new file mode 100644 index 0000000..ddc9c07 --- /dev/null +++ b/linux/ipcore/PapbVcInterface.h @@ -0,0 +1,137 @@ +#ifndef LINUX_OBC_PAPBVCINTERFACE_H_ +#define LINUX_OBC_PAPBVCINTERFACE_H_ + +#include +#include +#include + +#include +#include + +#include "OBSWConfig.h" +#include "fsfw/returnvalues/returnvalue.h" + +/** + * @brief This class handles the transmission of data to a virtual channel of the PTME IP Core + * via the PAPB interface. + * + * @author J. Meier + */ +class PapbVcInterface : public VirtualChannelIF { + public: + /** + * @brief Constructor + * + * @param papbBusyId The ID of the GPIO which is connected to the PAPBBusy_N signal of the + * VcInterface IP Core. A low logic level indicates the VcInterface is not + * ready to receive more data. + * @param papbEmptyId The ID of the GPIO which is connected to the PAPBEmpty signal of the + * VcInterface IP Core. The signal is high when there are no packets in the + * external buffer memory (BRAM). + * @param uioFile UIO file providing access to the PAPB bus + * @param mapNum Map number of UIO map associated with this virtual channel + */ + PapbVcInterface(LinuxLibgpioIF* gpioComIF, gpioId_t papbEmptyId, std::string uioFile, int mapNum, + size_t maxPacketSize); + virtual ~PapbVcInterface(); + + // See interface function documentation for docs on these functions. + + bool isBusy() const override; + + ReturnValue_t write(const uint8_t* data, size_t size, size_t& writtenSize) override; + + ReturnValue_t advanceWrite(size_t& remainingSize) override; + + void cancelTransfer() override; + + bool writeActive() const override; + + ReturnValue_t initialize() override; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::CCSDS_IP_CORE_BRIDGE; + + static const ReturnValue_t PAPB_BUSY = MAKE_RETURN_CODE(0xA0); + + enum ByteWidthCfg : uint32_t { ONE = 0b00, TWO = 0b01, THREE = 0b10, FOUR = 0b11 }; + /** + * Configuration bits: + * bit[1:0]: Size of data (1,2,3 or 4 bytes). 1 Byte <=> b00 + * bit[2]: Set this bit to 1 to abort a transferred packet + * bit[3]: Signals to VcInterface the start of a new telemetry packet + */ + static constexpr uint32_t CONFIG_DATA_INPUT = 0b00001000; + + /** + * Abort a transferred packet. + */ + static constexpr uint32_t CONFIG_ABORT = 0b00000100; + + /** + * Writing this word to the VcInterface base address signals to the virtual channel interface + * that a complete tm packet has been transferred. + */ + static constexpr uint32_t CONFIG_END = 0x0; + + /** + * Writing to this offset within the memory space of a virtual channel will insert data for + * encoding to the external buffer memory of the PTME IP Core. + * The address offset is 0x400 (= 4 * 256) + */ + static const int DATA_REG_OFFSET = 256; + + static constexpr long int FIRST_DELAY_PAPB_POLLING_NS = 10; + static constexpr long int MAX_DELAY_PAPB_POLLING_NS = 40; + static constexpr uint32_t MAX_BUSY_POLLS = 1000; + + LinuxLibgpioIF* gpioComIF = nullptr; + /** High when external buffer memory of virtual channel is empty */ + gpioId_t papbEmptyId = gpio::NO_GPIO; + + std::vector packetBuf; + std::string uioFile; + int mapNum = 0; + bool writeActiveStatus = false; + size_t currentPacketIndex = 0; + size_t currentPacketSize = 0; + mutable struct timespec nextDelay = {.tv_sec = 0, .tv_nsec = 0}; + const struct timespec BETWEEN_POLL_DELAY = {.tv_sec = 0, .tv_nsec = 10}; + + volatile uint32_t* vcBaseReg = nullptr; + + uint32_t vcOffset = 0; + + /** + * @brief This function sends the config byte to the virtual channel of the PTME IP Core + * to initiate a packet transfer. + */ + void startPacketTransfer(ByteWidthCfg initWidth); + + void abortPacketTransfer(); + + /** + * @brief This function sends the config byte to the virtual channel interface of the PTME + * IP Core to signal the end of a packet transfer. + */ + void completePacketTransfer(); + + /** + * @brief This function reads the papb busy signal indicating whether the virtual channel + * interface is ready to receive more data or not. PAPB is ready when + * PAPB_Busy_N == '1'. + * + * @return returnvalue::OK when ready to receive data else PAPB_BUSY. + */ + inline bool pollReadyForPacket() const; + + inline bool pollReadyForOctet(uint32_t maxCycles) const; + + /** + * @brief This function can be used for debugging to check whether there are packets in + * the packet buffer of the virtual channel or not. + */ + bool isVcInterfaceBufferEmpty(); +}; + +#endif /* LINUX_OBC_PAPBVCINTERFACE_H_ */ diff --git a/linux/ipcore/PdecConfig.cpp b/linux/ipcore/PdecConfig.cpp new file mode 100644 index 0000000..1942386 --- /dev/null +++ b/linux/ipcore/PdecConfig.cpp @@ -0,0 +1,217 @@ +#include "PdecConfig.h" + +#include "fsfw/filesystem/HasFileSystemIF.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "pdecconfigdefs.h" + +PdecConfig::PdecConfig() + : localParameterHandler("conf/pdecconfig.json", SdCardManager::instance()) {} + +PdecConfig::~PdecConfig() {} + +void PdecConfig::setMemoryBaseAddress(uint32_t* memoryBaseAddress_) { + memoryBaseAddress = memoryBaseAddress_; +} + +ReturnValue_t PdecConfig::write() { + if (memoryBaseAddress == nullptr) { + sif::error << "PdecConfig::write: Memory base address not set" << std::endl; + return returnvalue::FAILED; + } + ReturnValue_t result = initializePersistentParameters(); + if (result != returnvalue::OK) { + return result; + } + result = writeFrameHeaderFirstWord(); + if (result != returnvalue::OK) { + return result; + } + result = writeFrameHeaderSecondWord(); + if (result != returnvalue::OK) { + return result; + } + writeMapConfig(); + return returnvalue::OK; +} + +ReturnValue_t PdecConfig::initializePersistentParameters() { + ReturnValue_t result = localParameterHandler.initialize(); + if (result == HasFileSystemIF::FILE_DOES_NOT_EXIST) { + result = createPersistentConfig(); + if (result != returnvalue::OK) { + return result; + } + } + return result; +} + +ReturnValue_t PdecConfig::createPersistentConfig() { + ReturnValue_t result = localParameterHandler.addParameter( + pdecconfigdefs::paramkeys::POSITIVE_WINDOW, pdecconfigdefs::defaultvalue::positiveWindow); + if (result != returnvalue::OK) { + sif::error << "PdecConfig::createPersistentConfig: Failed to set positive window" << std::endl; + return result; + } + result = localParameterHandler.addParameter(pdecconfigdefs::paramkeys::NEGATIVE_WINDOW, + pdecconfigdefs::defaultvalue::negativeWindow); + if (result != returnvalue::OK) { + sif::error << "PdecConfig::createPersistentConfig: Failed to set negative window" << std::endl; + return result; + } + return returnvalue::OK; +} + +uint32_t PdecConfig::getImrReg() { + return static_cast(enableNewFarIrq << 2) | + static_cast(enableTcAbortIrq << 1) | static_cast(enableTcNewIrq); +} + +ReturnValue_t PdecConfig::setPositiveWindow(uint8_t pw) { + if (memoryBaseAddress == nullptr) { + sif::error << "PdecConfig::setPositiveWindow: Memory base address not set" << std::endl; + return returnvalue::FAILED; + } + ReturnValue_t result = + localParameterHandler.updateParameter(pdecconfigdefs::paramkeys::POSITIVE_WINDOW, pw); + if (result != returnvalue::OK) { + return result; + } + // Rewrite second config word which contains the positive window parameter + writeFrameHeaderSecondWord(); + return returnvalue::OK; +} + +ReturnValue_t PdecConfig::setNegativeWindow(uint8_t nw) { + if (memoryBaseAddress == nullptr) { + sif::error << "PdecConfig::setPositiveWindow: Memory base address not set" << std::endl; + return returnvalue::FAILED; + } + ReturnValue_t result = + localParameterHandler.updateParameter(pdecconfigdefs::paramkeys::NEGATIVE_WINDOW, nw); + if (result != returnvalue::OK) { + return result; + } + // Rewrite second config word which contains the negative window parameter + writeFrameHeaderSecondWord(); + return returnvalue::OK; +} + +ReturnValue_t PdecConfig::getPositiveWindow(uint8_t& positiveWindow) { + ReturnValue_t result = + localParameterHandler.getValue(pdecconfigdefs::paramkeys::POSITIVE_WINDOW, positiveWindow); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PdecConfig::getNegativeWindow(uint8_t& negativeWindow) { + ReturnValue_t result = + localParameterHandler.getValue(pdecconfigdefs::paramkeys::NEGATIVE_WINDOW, negativeWindow); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PdecConfig::writeFrameHeaderFirstWord() { + uint32_t word = 0; + ReturnValue_t result = createFirstWord(&word); + if (result != returnvalue::OK) { + return result; + } + *(memoryBaseAddress + FRAME_HEADER_OFFSET + OFFSET_FIRST_CONFIG_WORD) = word; + return returnvalue::OK; +} + +ReturnValue_t PdecConfig::writeFrameHeaderSecondWord() { + uint32_t word = 0; + ReturnValue_t result = createSecondWord(&word); + if (result != returnvalue::OK) { + return result; + } + *(memoryBaseAddress + FRAME_HEADER_OFFSET + OFFSET_SECOND_CONFIG_WORD) = word; + return returnvalue::OK; +} + +void PdecConfig::writeMapConfig() { + // Configure all MAP IDs as invalid + for (int idx = 0; idx <= MAX_MAP_ADDR; idx += 4) { + *(memoryBaseAddress + MAP_ADDR_LUT_OFFSET + idx / 4) = + NO_DESTINATION << 24 | NO_DESTINATION << 16 | NO_DESTINATION << 8 | NO_DESTINATION; + } + + // All TCs with MAP ID 7 will be routed to the PM module (can then be read from memory) + uint8_t routeToPm = calcMapAddrEntry(PM_BUFFER); + *(memoryBaseAddress + MAP_ADDR_LUT_OFFSET + 1) = + (NO_DESTINATION << 24) | (NO_DESTINATION << 16) | (NO_DESTINATION << 8) | routeToPm; + + // Write map id clock frequencies + for (int idx = 0; idx <= MAX_MAP_ADDR; idx += 4) { + *(memoryBaseAddress + MAP_CLK_FREQ_OFFSET + idx / 4) = + MAP_CLK_FREQ << 24 | MAP_CLK_FREQ << 16 | MAP_CLK_FREQ << 8 | MAP_CLK_FREQ; + } +} + +uint8_t PdecConfig::calcMapAddrEntry(uint8_t moduleId) { + uint8_t lutEntry = 0; + uint8_t parity = getOddParity(moduleId | (1 << VALID_POSITION)); + lutEntry = (parity << PARITY_POSITION) | (1 << VALID_POSITION) | moduleId; + return lutEntry; +} + +uint8_t PdecConfig::getOddParity(uint8_t number) { + uint8_t parityBit = 0; + uint8_t countBits = 0; + for (unsigned int idx = 0; idx < sizeof(number) * 8; idx++) { + countBits += (number >> idx) & 0x1; + } + parityBit = ~(countBits & 0x1) & 0x1; + return parityBit; +} + +ReturnValue_t PdecConfig::createFirstWord(uint32_t* word) { + *word = 0; + *word |= (VERSION_ID << 30); + + // Setting the bypass flag and the control command flag should not have any + // implication on the operation of the PDEC IP Core + *word |= (BYPASS_FLAG << 29); + *word |= (CONTROL_COMMAND_FLAG << 28); + + *word |= (RESERVED_FIELD_A << 26); + *word |= (SPACECRAFT_ID << 16); + *word |= (VIRTUAL_CHANNEL << 10); + *word |= (DUMMY_BITS << 8); + uint8_t positiveWindow = 0; + ReturnValue_t result = + localParameterHandler.getValue(pdecconfigdefs::paramkeys::POSITIVE_WINDOW, positiveWindow); + if (result != returnvalue::OK) { + return result; + } + *word |= static_cast(positiveWindow); + return returnvalue::OK; +} + +ReturnValue_t PdecConfig::createSecondWord(uint32_t* word) { + uint8_t negativeWindow = 0; + ReturnValue_t result = + localParameterHandler.getValue(pdecconfigdefs::paramkeys::NEGATIVE_WINDOW, negativeWindow); + if (result != returnvalue::OK) { + return result; + } + *word = 0; + *word = 0; + *word |= (static_cast(negativeWindow) << 24); + *word |= (HIGH_AU_MAP_ID << 16); + *word |= (ENABLE_DERANDOMIZER << 8); + return returnvalue::OK; +} + +uint32_t PdecConfig::readbackFirstWord() { + return *(memoryBaseAddress + FRAME_HEADER_OFFSET + OFFSET_FIRST_CONFIG_WORD); +} + +uint32_t PdecConfig::readbackSecondWord() { + return *(memoryBaseAddress + FRAME_HEADER_OFFSET + OFFSET_SECOND_CONFIG_WORD); +} diff --git a/linux/ipcore/PdecConfig.h b/linux/ipcore/PdecConfig.h new file mode 100644 index 0000000..1f2ed9c --- /dev/null +++ b/linux/ipcore/PdecConfig.h @@ -0,0 +1,166 @@ +#ifndef LINUX_OBC_PDECCONFIG_H_ +#define LINUX_OBC_PDECCONFIG_H_ + +#include + +#include "bsp_q7s/fs/SdCardManager.h" +#include "bsp_q7s/memory/LocalParameterHandler.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "pdec.h" + +/** + * @brief This class generates the configuration words for the configuration memory of the PDEC + * IP Cores. + * + * @details Fields are initialized according to specification in PDEC datasheet section 6.11.3.1 + * PROM usage. + * + * @author J. Meier + */ +class PdecConfig { + public: + /** + * @brief Constructor + */ + PdecConfig(); + virtual ~PdecConfig(); + + /** + * @brief Sets the memory base address pointer + */ + void setMemoryBaseAddress(uint32_t* memoryBaseAddress_); + + /** + * @brief Will write the config to the PDEC configuration memory. New config + * becomes active after resetting PDEC. + */ + ReturnValue_t write(); + + /** + * @brief Returns the value to write to the interrupt mask register. This + * value defines which interrupts should be enabled/disabled. + */ + uint32_t getImrReg(); + + ReturnValue_t setPositiveWindow(uint8_t pw); + ReturnValue_t setNegativeWindow(uint8_t nw); + + ReturnValue_t getPositiveWindow(uint8_t& positiveWindow); + ReturnValue_t getNegativeWindow(uint8_t& negativeWindow); + + /** + * @brief Creates the first word of the PDEC configuration + * + * @param word The created word will be written to this pointer + * + * @return OK if successful, otherwise error return value + * + */ + ReturnValue_t createFirstWord(uint32_t* word); + + /** + * @brief Creates the second word of the PDEC configuration + * + * @param word The created word will be written to this pointer + * + * @return OK if successful, otherwise error return value + */ + ReturnValue_t createSecondWord(uint32_t* word); + + /** + * @brief Reads first config word from the config memory + * + * @return The config word + */ + uint32_t readbackFirstWord(); + + /** + * @brief Reads the second config word from the config memory + * + * @return The config word + */ + uint32_t readbackSecondWord(); + + private: + // TC transfer frame configuration parameters + static const uint8_t VERSION_ID = 0; + // BD Frames + static const uint8_t BYPASS_FLAG = 1; + static const uint8_t CONTROL_COMMAND_FLAG = 0; + + static const uint8_t VIRTUAL_CHANNEL = 0; + static const uint8_t RESERVED_FIELD_A = 0; + static const uint16_t SPACECRAFT_ID = 0x3DC; + static const uint16_t DUMMY_BITS = 0; + static const uint8_t HIGH_AU_MAP_ID = 0xF; + static const uint8_t ENABLE_DERANDOMIZER = 1; + + static const uint8_t CONFIG_WORDS_NUM = 2; + + // 0x200 / 4 = 0x80 + static const uint32_t FRAME_HEADER_OFFSET = 0x80; + static const uint32_t OFFSET_FIRST_CONFIG_WORD = 0; + static const uint32_t OFFSET_SECOND_CONFIG_WORD = 1; + + static const uint32_t MAP_ADDR_LUT_OFFSET = 0xA0; + static const uint32_t MAP_CLK_FREQ_OFFSET = 0x90; + // MAP clock frequency. Must be a value between 1 and 13 otherwise the TC segment will be + // discarded + static const uint8_t MAP_CLK_FREQ = 2; + + static const uint8_t MAX_MAP_ADDR = 63; + // Writing this to the map address in the look up table will invalidate a MAP ID. + static const uint8_t NO_DESTINATION = 0; + static const uint8_t VALID_POSITION = 6; + static const uint8_t PARITY_POSITION = 7; + + /** + * TCs with map addresses (also know as Map IDs) assigned to this channel will be stored in + * the PDEC memory. + */ + static const uint8_t PM_BUFFER = 7; + + uint32_t* memoryBaseAddress = nullptr; + + // Pointer to object providing access to persistent configuration parameters + LocalParameterHandler localParameterHandler; + + uint32_t configWords[CONFIG_WORDS_NUM]; + bool enableTcNewIrq = true; + bool enableTcAbortIrq = true; + bool enableNewFarIrq = true; + + ReturnValue_t initializePersistentParameters(); + /** + * @brief If the json file containing the persistent config parameters does + * not exist it will be created here. + */ + ReturnValue_t createPersistentConfig(); + + ReturnValue_t writeFrameHeaderFirstWord(); + ReturnValue_t writeFrameHeaderSecondWord(); + void writeMapConfig(); + + /** + * @brief This function calculates the entry for the configuration of the MAP ID routing. + * + * @param mapAddr The MAP ID to configure + * @param moduleId The destination module where all TCs with the map id mapAddr will be routed + * to. + * + * @details The PDEC has different modules where the TCs can be routed to. A lookup table is + * used which links the MAP ID field to the destination module. The entry for this + * lookup table is created by this function and must be stored in the configuration + * memory region of the PDEC. The entry has a specific format + */ + uint8_t calcMapAddrEntry(uint8_t moduleId); + + /** + * @brief This functions calculates the odd parity of the bits in number. + * + * @param number The number from which to calculate the odd parity. + */ + uint8_t getOddParity(uint8_t number); +}; + +#endif /* LINUX_OBC_PDECCONFIG_H_ */ diff --git a/linux/ipcore/PdecHandler.cpp b/linux/ipcore/PdecHandler.cpp new file mode 100644 index 0000000..fd95285 --- /dev/null +++ b/linux/ipcore/PdecHandler.cpp @@ -0,0 +1,854 @@ +#include "PdecHandler.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "OBSWConfig.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/objectmanager/ObjectManager.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "fsfw/tmtcservices/TmTcMessage.h" +#include "fsfw_hal/linux/uio/UioMapper.h" +#include "linux/ipcore/PdecConfig.h" +#include "pdec.h" + +using namespace pdec; + +// If this is ever shared, protect it with a mutex! +uint32_t PdecHandler::CURRENT_FAR = 0; + +PdecHandler::PdecHandler(object_id_t objectId, object_id_t tcDestinationId, + LinuxLibgpioIF* gpioComIF, gpioId_t pdecReset, UioNames names, + uint32_t cfgMemPhyAddr, uint32_t pdecRamPhyAddr) + : SystemObject(objectId), + tcDestinationId(tcDestinationId), + gpioComIF(gpioComIF), + pdecReset(pdecReset), + actionHelper(this, nullptr), + cfgMemBaseAddr(cfgMemPhyAddr), + pdecRamBaseAddr(pdecRamPhyAddr), + uioNames(names), + paramHelper(this) { + auto mqArgs = MqArgs(objectId, static_cast(this)); + commandQueue = QueueFactory::instance()->createMessageQueue( + QUEUE_SIZE, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); +} + +PdecHandler::~PdecHandler() {} + +ReturnValue_t PdecHandler::initialize() { + tcStore = ObjectManager::instance()->get(objects::TC_STORE); + if (tcStore == nullptr) { + sif::error << "PdecHandler::initialize: Invalid TC store" << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + tcDestination = ObjectManager::instance()->get(tcDestinationId); + + if (tcDestination == nullptr) { + sif::error << "PdecHandler::initialize: Invalid tc destination specified" << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + UioMapper regMapper(uioNames.registers); + ReturnValue_t result = + regMapper.getMappedAdress(®isterBaseAddress, UioMapper::Permissions::READ_WRITE); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + int fd = 0; + if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) { + sif::error << "PdecHandler::initialize: Opening /dev/mem failed" << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + }; + memoryBaseAddress = static_cast( + mmap(0, PDEC_CFG_MEM_SIZE, static_cast(UioMapper::Permissions::READ_WRITE), MAP_SHARED, + fd, cfgMemBaseAddr)); + if (memoryBaseAddress == nullptr) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + pdecConfig.setMemoryBaseAddress(memoryBaseAddress); + + ramBaseAddress = static_cast(mmap(0, PDEC_RAM_SIZE, + static_cast(UioMapper::Permissions::READ_WRITE), + MAP_SHARED, fd, pdecRamBaseAddr)); + if (ramBaseAddress == nullptr) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + if (OP_MODE == Modes::IRQ and uioNames.irq == nullptr) { + sif::error << "Can not use IRQ mode if IRQ UIO name is invalid" << std::endl; + return returnvalue::FAILED; + } + + result = actionHelper.initialize(commandQueue); + if (result != returnvalue::OK) { + return result; + } + + result = paramHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + + return returnvalue::OK; +} + +ReturnValue_t PdecHandler::firstLoop() { + ReturnValue_t result = pdecConfig.write(); + if (result != returnvalue::OK) { + if (result == LocalParameterHandler::SD_NOT_READY) { + return result; + } else { + sif::error << "PdecHandler::firstLoop: Failed to write PDEC config" << std::endl; + } + return returnvalue::FAILED; + } + + result = releasePdec(); + if (result != returnvalue::OK) { + return result; + } + + return postResetOperation(); +} + +ReturnValue_t PdecHandler::performOperation(uint8_t operationCode) { + if (OP_MODE == Modes::POLLED) { + return polledOperation(); + } else if (OP_MODE == Modes::IRQ) { + return irqOperation(); + } + return returnvalue::FAILED; +} + +ReturnValue_t PdecHandler::polledOperation() { + readCommandQueue(); + + switch (state) { + case State::INIT: { + handleInitState(); + break; + } + case State::RUNNING: { + if (newTcReceived()) { + handleNewTc(); + } + doPeriodicWork(); + break; + } + case State::PDEC_RESET: { + triggerEvent(pdec::PDEC_TRYING_RESET_WITH_INIT); + ReturnValue_t result = pdecToReset(); + if (result != returnvalue::OK) { + triggerEvent(PDEC_RESET_FAILED); + } + state = State::INIT; + break; + } + case State::WAIT_FOR_RECOVERY: + break; + default: + sif::error << "PdecHandler::performOperation: Invalid state" << std::endl; + break; + } + + return returnvalue::OK; +} + +// See https://yurovsky.github.io/2014/10/10/linux-uio-gpio-interrupt.html for more information. +ReturnValue_t PdecHandler::irqOperation() { + ReturnValue_t result = returnvalue::OK; + // int fd = -1; + // Used to unmask IRQ + uint32_t info = 1; + + interruptWindowCd.resetTimer(); + + // Clear interrupts with dummy read before unmasking the interrupt. Use a volatile to prevent + // read being optimized away. + volatile uint32_t dummy = *(registerBaseAddress + PDEC_PIR_OFFSET); + + while (true) { + // Default value to unmask IRQ on the write call. + info = 1; + readCommandQueue(); + switch (state) { + case State::INIT: { + result = handleInitState(); + if (result != returnvalue::OK) { + break; + } + openIrqFile(); + if (ptmeResetWithReinitializationPending) { + actionHelper.finish(true, commandedBy, pdec::RESET_PDEC_WITH_REINIITALIZATION); + ptmeResetWithReinitializationPending = false; + } + break; + } + case State::PDEC_RESET: { + triggerEvent(pdec::PDEC_TRYING_RESET_WITH_INIT); + result = pdecToReset(); + if (result != returnvalue::OK) { + triggerEvent(PDEC_RESET_FAILED); + } + usleep(20); + state = State::INIT; + break; + } + case State::RUNNING: { + doPeriodicWork(); + checkAndHandleIrqs(info); + break; + } + case State::WAIT_FOR_RECOVERY: + TaskFactory::delayTask(400); + break; + default: + // Should never happen. + sif::error << "PdecHandler::performOperation: Invalid state" << std::endl; + TaskFactory::delayTask(400); + break; + } + } + // To avoid compiler warning + static_cast(dummy); + return returnvalue::OK; +} + +ReturnValue_t PdecHandler::handleInitState() { + ReturnValue_t result = firstLoop(); + if (result != returnvalue::OK) { + if (result == LocalParameterHandler::SD_NOT_READY) { + if (initTries == MAX_INIT_TRIES) { + sif::error << "PdecHandler::handleInitState: SD card never becomes ready" << std::endl; + initFailedHandler(result); + return result; + } + state = State::INIT; + initTries++; + TaskFactory::delayTask(400); + return result; + } + sif::error << "PDEC: Init failed with reason 0x" << std::hex << std::setw(4) << result + << std::endl; + initFailedHandler(result); + return result; + } + state = State::RUNNING; + return returnvalue::OK; +} + +void PdecHandler::openIrqFile() { + irqFd = open(uioNames.irq, O_RDWR); + if (irqFd < 0) { + sif::error << "PdecHandler::irqOperation: Opening UIO IRQ file" << uioNames.irq << " failed" + << std::endl; + triggerEvent(OPEN_IRQ_FILE_FAILED); + state = State::WAIT_FOR_RECOVERY; + } +} + +ReturnValue_t PdecHandler::checkAndHandleIrqs(uint32_t& info) { + ssize_t nb = write(irqFd, &info, sizeof(info)); + if (nb != static_cast(sizeof(info))) { + sif::error << "PdecHandler::irqOperation: Unmasking IRQ failed" << std::endl; + triggerEvent(WRITE_SYSCALL_ERROR_PDEC, errno); + close(irqFd); + state = State::INIT; + return returnvalue::FAILED; + } + struct pollfd fds = {.fd = irqFd, .events = POLLIN, .revents = 0}; + int ret = poll(&fds, 1, IRQ_TIMEOUT_MS); + if (ret == 0) { + // No TCs for timeout period + genericCheckCd.resetTimer(); + resetIrqLimiters(); + } else if (ret >= 1) { + // Interrupt handling. + nb = read(irqFd, &info, sizeof(info)); + interruptCounter++; + if (nb == static_cast(sizeof(info))) { + uint32_t pisr = *(registerBaseAddress + PDEC_PISR_OFFSET); + if ((pisr & TC_NEW_MASK) == TC_NEW_MASK) { + // handle TC + handleNewTc(); + } + if ((pisr & TC_ABORT_MASK) == TC_ABORT_MASK) { + tcAbortCounter += 1; + } + if ((pisr & NEW_FAR_MASK) == NEW_FAR_MASK) { + // Read FAR here + CURRENT_FAR = readFar(); + checkFrameAna(CURRENT_FAR); + } + // Clear interrupts with dummy read. Volatile is important here to prevent + // compiler opitmizations in release builds! + volatile uint32_t dummy = *(registerBaseAddress + PDEC_PIR_OFFSET); + static_cast(dummy); + + if (genericCheckCd.hasTimedOut()) { + genericCheckCd.resetTimer(); + if (interruptWindowCd.hasTimedOut()) { + if (interruptCounter >= MAX_ALLOWED_IRQS_PER_WINDOW) { + sif::error << "PdecHandler::irqOperation: Possible IRQ storm" << std::endl; + triggerEvent(TOO_MANY_IRQS, MAX_ALLOWED_IRQS_PER_WINDOW); + resetIrqLimiters(); + TaskFactory::delayTask(400); + return returnvalue::FAILED; + } + resetIrqLimiters(); + } + } + } + } else { + sif::error << "PdecHandler::irqOperation: Poll error with errno " << errno << ": " + << strerror(errno) << std::endl; + triggerEvent(POLL_SYSCALL_ERROR_PDEC, errno); + close(irqFd); + state = State::INIT; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void PdecHandler::readCommandQueue(void) { + CommandMessage message; + ReturnValue_t result = returnvalue::FAILED; + + result = commandQueue->receiveMessage(&message); + if (result == returnvalue::OK) { + result = actionHelper.handleActionMessage(&message); + if (result == returnvalue::OK) { + return; + } + result = paramHelper.handleParameterMessage(&message); + if (result == returnvalue::OK) { + return; + } + CommandMessage reply; + reply.setReplyRejected(CommandMessage::UNKNOWN_COMMAND, message.getCommand()); + commandQueue->reply(&reply); + return; + } +} + +MessageQueueId_t PdecHandler::getCommandQueue() const { return commandQueue->getId(); } + +ReturnValue_t PdecHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + using namespace pdec; + switch (actionId) { + case PRINT_CLCW: + printClcw(); + return EXECUTION_FINISHED; + case PRINT_PDEC_MON: + printPdecMon(); + return EXECUTION_FINISHED; + case RESET_PDEC_NO_REINIITALIZATION: { + pdecResetNoInit(); + return EXECUTION_FINISHED; + } + case RESET_PDEC_WITH_REINIITALIZATION: { + initializeReset(); + ptmeResetWithReinitializationPending = true; + this->commandedBy = commandedBy; + return returnvalue::OK; + } + default: + return COMMAND_NOT_IMPLEMENTED; + } +} + +ReturnValue_t PdecHandler::getParameter(uint8_t domainId, uint8_t uniqueIdentifier, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, uint16_t startAtIndex) { + if ((domainId == 0) and (uniqueIdentifier == ParameterId::POSITIVE_WINDOW)) { + uint8_t newVal = 0; + ReturnValue_t result = newValues->getElement(&newVal); + if (result != returnvalue::OK) { + return result; + } + uint8_t positiveWindow = 0; + result = pdecConfig.getPositiveWindow(positiveWindow); + if (result != returnvalue::OK) { + sif::warning << "PdecHandler::getParameter: Failed to get positive window from pdec config" + << std::endl; + return returnvalue::FAILED; + } + parameterWrapper->set(positiveWindow); + result = pdecConfig.setPositiveWindow(newVal); + if (result != returnvalue::OK) { + sif::warning << "PdecHandler::getParameter: Failed to set positive window" << std::endl; + return returnvalue::FAILED; + } + // PDEC needs reset to apply this parameter change + initializeReset(); + return returnvalue::OK; + } else if ((domainId == 0) and (uniqueIdentifier == ParameterId::NEGATIVE_WINDOW)) { + uint8_t newVal = 0; + ReturnValue_t result = newValues->getElement(&newVal); + if (result != returnvalue::OK) { + return result; + } + uint8_t negativeWindow = 0; + result = pdecConfig.getNegativeWindow(negativeWindow); + if (result != returnvalue::OK) { + sif::warning << "PdecHandler::getParameter: Failed to get negative window from pdec config" + << std::endl; + return returnvalue::FAILED; + } + parameterWrapper->set(negativeWindow); + result = pdecConfig.setNegativeWindow(newVal); + if (result != returnvalue::OK) { + sif::warning << "PdecHandler::getParameter: Failed to set negative window" << std::endl; + return returnvalue::FAILED; + } + // PDEC needs reset to apply this parameter change + initializeReset(); + return returnvalue::OK; + } + return returnvalue::OK; +} + +ReturnValue_t PdecHandler::resetFarStatFlag() { + uint32_t pdecFar = readFar(); + if ((pdecFar & FAR_STAT_MASK) != 0) { + sif::warning << "PdecHandler::resetFarStatFlag: FAR register stat bit is not 0." + << " Read value for FAR: 0x" << std::hex << static_cast(pdecFar) + << std::endl; + CURRENT_FAR = pdecFar; + return returnvalue::FAILED; + } +#if OBSW_DEBUG_PDEC_HANDLER == 1 + sif::debug << "PdecHandler::resetFarStatFlag: read FAR with value: 0x" << std::hex << pdecFar + << std::endl; +#endif /* OBSW_DEBUG_PDEC_HANDLER == 1 */ + CURRENT_FAR = pdecFar; + return returnvalue::OK; +} + +ReturnValue_t PdecHandler::releasePdec() { + ReturnValue_t result = returnvalue::OK; + result = gpioComIF->pullHigh(pdecReset); + if (result != returnvalue::OK) { + sif::error << "PdecHandler::releasePdec: Failed to release PDEC reset signal" << std::endl; + } + return result; +} + +ReturnValue_t PdecHandler::pdecToReset() { + ReturnValue_t result = gpioComIF->pullLow(pdecReset); + if (result != returnvalue::OK) { + sif::error << "PdecHandler::pdecToReset: Failed to pull PDEC reset line" + " to low" + << std::endl; + } + return result; +} + +bool PdecHandler::newTcReceived() { + uint32_t pdecFar = readFar(); + + if (pdecFar >> STAT_POSITION != NEW_FAR_RECEIVED) { + CURRENT_FAR = pdecFar; + return false; + } + if (!checkFrameAna(pdecFar)) { + CURRENT_FAR = pdecFar; + return false; + } + return true; +} + +void PdecHandler::doPeriodicWork() { checkLocks(); } + +bool PdecHandler::checkFrameAna(uint32_t pdecFar) { + bool frameValid = false; + FrameAna_t frameAna = static_cast((pdecFar & FRAME_ANA_MASK) >> FRAME_ANA_POSITION); + switch (frameAna) { + case (FrameAna_t::ABANDONED_CLTU): { + triggerEvent(INVALID_TC_FRAME, ABANDONED_CLTU_RETVAL); + sif::warning << "PdecHandler::checkFrameAna: Abondoned CLTU" << std::endl; + break; + } + case (FrameAna_t::FRAME_DIRTY): { + triggerEvent(INVALID_TC_FRAME, FRAME_DIRTY_RETVAL); + checkConfig(); + sif::warning << "PdecHandler::checkFrameAna: Frame dirty" << std::endl; + break; + } + case (FrameAna_t::FRAME_ILLEGAL): { + sif::warning << "PdecHandler::checkFrameAna: Frame illegal for one reason" << std::endl; + handleIReason(pdecFar, FRAME_ILLEGAL_ONE_REASON); + break; + } + case (FrameAna_t::FRAME_ILLEGAL_MULTI_REASON): { + sif::warning << "PdecHandler::checkFrameAna: Frame illegal for multiple reasons" << std::endl; + handleIReason(pdecFar, FRAME_ILLEGAL_MULTIPLE_REASONS); + break; + } + case (FrameAna_t::AD_DISCARDED_LOCKOUT): { + triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_LOCKOUT_RETVAL); + sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because of lockout" + << std::endl; + break; + } + case (FrameAna_t::AD_DISCARDED_WAIT): { + triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_LOCKOUT_RETVAL); + sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because of wait" << std::endl; + break; + } + case (FrameAna_t::AD_DISCARDED_NS_VR): { + triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_NS_VS); + sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because N(S) or V(R)" + << std::endl; + break; + } + case (FrameAna_t::FRAME_ACCEPTED): { +#if OBSW_DEBUG_PDEC_HANDLER == 1 + sif::info << "PdecHandler::checkFrameAna: Accepted TC frame" << std::endl; +#endif + frameValid = true; + break; + } + default: { + sif::debug << "PdecHandler::checkFrameAna: Invalid frame analysis report" << std::endl; + break; + } + } + return frameValid; +} + +void PdecHandler::handleIReason(uint32_t pdecFar, ReturnValue_t parameter1) { + IReason_t ireason = static_cast((pdecFar & IREASON_MASK) >> IREASON_POSITION); + switch (ireason) { + case (IReason_t::NO_REPORT): { + triggerEvent(INVALID_TC_FRAME, parameter1, NO_REPORT_RETVAL); + sif::info << "PdecHandler::handleIReason: No illegal report" << std::endl; + break; + } + case (IReason_t::ERROR_VERSION_NUMBER): { + triggerEvent(INVALID_TC_FRAME, parameter1, ERROR_VERSION_NUMBER_RETVAL); + sif::info << "PdecHandler::handleIReason: Error in version number and reserved A and B " + << "fields" << std::endl; + break; + } + case (IReason_t::ILLEGAL_COMBINATION): { + triggerEvent(INVALID_TC_FRAME, parameter1, ILLEGAL_COMBINATION_RETVAL); + sif::info << "PdecHandler::handleIReason: Illegal combination (AC) of bypass and control " + << "command flags" << std::endl; + break; + } + case (IReason_t::INVALID_SC_ID): { + triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_SC_ID_RETVAL); + sif::info << "PdecHandler::handleIReason: Invalid spacecraft identifier " << std::endl; + break; + } + case (IReason_t::INVALID_VC_ID_MSB): { + triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_VC_ID_MSB_RETVAL); + sif::info << "PdecHandler::handleIReason: VC identifier bit 0 to 4 did not match " + << std::endl; + break; + } + case (IReason_t::INVALID_VC_ID_LSB): { + triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_VC_ID_LSB_RETVAL); + sif::info << "PdecHandler::handleIReason: VC identifier bit 5 did not match " << std::endl; + break; + } + case (IReason_t::NS_NOT_ZERO): { + triggerEvent(INVALID_TC_FRAME, parameter1, NS_NOT_ZERO_RETVAL); + sif::info << "PdecHandler::handleIReason: N(S) of BC or BD frame not set to all zeros" + << std::endl; + break; + } + case (IReason_t::INCORRECT_BC_CC): { + triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_BC_CC); + sif::info << "PdecHandler::handleIReason: Invalid BC control command format" << std::endl; + break; + } + default: { + sif::info << "PdecHandler::handleIReason: Invalid reason id" << std::endl; + break; + } + } +} + +void PdecHandler::checkConfig() { + uint32_t firstWord = 0; + ReturnValue_t result = pdecConfig.createFirstWord(&firstWord); + if (result != returnvalue::OK) { + // This should normally never happen during runtime. So here is just + // output a warning + sif::warning << "PdecHandler::checkConfig: Failed to create first word" << std::endl; + return; + } + uint32_t secondWord = 0; + result = pdecConfig.createSecondWord(&secondWord); + if (result != returnvalue::OK) { + // This should normally never happen during runtime. So here is just + // output a warning + sif::warning << "PdecHandler::checkConfig: Failed to create second word" << std::endl; + return; + } + uint32_t readbackFirstWord = pdecConfig.readbackFirstWord(); + uint32_t readbackSecondWord = pdecConfig.readbackSecondWord(); + if (firstWord != readbackFirstWord or secondWord != readbackSecondWord) { + triggerEvent(PDEC_CONFIG_CORRUPTED, readbackFirstWord, readbackSecondWord); + } +} + +void PdecHandler::handleNewTc() { + ReturnValue_t result = returnvalue::OK; + + uint32_t tcLength = 0; + result = readTc(tcLength); + if (result != returnvalue::OK) { + return; + } +#if OBSW_DEBUG_PDEC_HANDLER == 1 + unsigned int mapId = tcSegment[0] & MAP_ID_MASK; + sif::info << "PdecHandler::handleNewTc: Received TC segment with map ID " << mapId << std::endl; + printTC(tcLength); +#endif /* OBSW_DEBUG_PDEC_HANDLER */ + + store_address_t storeId; + result = tcStore->addData(&storeId, tcSegment + 1, tcLength - 1); + if (result != returnvalue::OK) { + sif::warning << "PdecHandler::handleNewTc: Failed to add received space packet to store" + << std::endl; + return; + } + + TmTcMessage message(storeId); + + result = MessageQueueSenderIF::sendMessage(tcDestination->getRequestQueue(), &message); + if (result != returnvalue::OK) { + sif::warning << "PdecHandler::handleNewTc: Failed to send message to TC destination" + << std::endl; + tcStore->deleteData(storeId); + return; + } + + return; +} + +ReturnValue_t PdecHandler::readTc(uint32_t& tcLength) { + uint32_t tcOffset = (*(registerBaseAddress + PDEC_BPTR_OFFSET) - pdecRamBaseAddr) / 4; + +#if OBSW_DEBUG_PDEC_HANDLER == 1 + sif::debug << "PdecHandler::readTc: TC offset: 0x" << std::hex << tcOffset << std::endl; +#endif /* OBSW_DEBUG_PDEC_HANDLER */ + + tcLength = *(registerBaseAddress + PDEC_SLEN_OFFSET); + +#if OBSW_DEBUG_PDEC_HANDLER == 1 + sif::debug << "PdecHandler::readTc: TC segment length: " << std::dec << tcLength << std::endl; +#endif /* OBSW_DEBUG_PDEC_HANDLER */ + + if (tcLength > MAX_TC_SEGMENT_SIZE) { + sif::warning << "PdecHandler::handleNewTc: Read invalid TC length from PDEC register" + << std::endl; + return returnvalue::FAILED; + } + + uint32_t idx = 0; + uint32_t tcData = 0; + for (idx = 0; idx <= tcLength; idx = idx + 4) { + tcData = *(ramBaseAddress + tcOffset + idx / 4); + if (idx == 0) { + tcSegment[idx] = static_cast((tcData >> 16) & 0xFF); + tcSegment[idx + 1] = static_cast((tcData >> 8) & 0xFF); + tcSegment[idx + 2] = static_cast(tcData & 0xFF); + } else if (tcLength - idx + 1 == 3) { + tcSegment[idx - 1] = static_cast((tcData >> 24) & 0xFF); + tcSegment[idx] = static_cast((tcData >> 16) & 0xFF); + tcSegment[idx + 1] = static_cast((tcData >> 8) & 0xFF); + } else if (tcLength - idx + 1 == 2) { + tcSegment[idx - 1] = static_cast((tcData >> 24) & 0xFF); + tcSegment[idx] = static_cast((tcData >> 16) & 0xFF); + } else if (tcLength - idx + 1 == 1) { + tcSegment[idx - 1] = static_cast((tcData >> 24) & 0xFF); + } else { + tcSegment[idx - 1] = static_cast((tcData >> 24) & 0xFF); + tcSegment[idx] = static_cast((tcData >> 16) & 0xFF); + tcSegment[idx + 1] = static_cast((tcData >> 8) & 0xFF); + tcSegment[idx + 2] = static_cast(tcData & 0xFF); + } + } + + // Backend buffer is handled back to PDEC3 + *(registerBaseAddress + PDEC_BFREE_OFFSET) = 0; + + return returnvalue::OK; +} + +void PdecHandler::printTC(uint32_t tcLength) { + std::stringstream tcSegmentStream; + tcSegmentStream << "TC segment data: 0x"; + for (uint32_t idx = 0; idx < tcLength; idx++) { + tcSegmentStream << std::setfill('0') << std::setw(2) << std::hex + << static_cast(tcSegment[idx]); + } + sif::info << tcSegmentStream.str() << std::endl; +} + +uint32_t PdecHandler::getClcw() { return *(registerBaseAddress + PDEC_CLCW_OFFSET); } + +uint32_t PdecHandler::getPdecMon() { return *(registerBaseAddress + PDEC_MON_OFFSET); } + +void PdecHandler::printClcw() { + uint32_t clcw = getClcw(); + uint8_t type = static_cast((clcw >> 31) & 0x1); + uint8_t versionNo = static_cast((clcw >> 29) & 0x3); + uint8_t status = static_cast((clcw >> 26) & 0x7); + uint8_t cop = static_cast((clcw >> 24) & 0x3); + uint8_t vcId = static_cast((clcw >> 18) & 0x3F); + uint8_t noRf = static_cast((clcw >> 15) & 0x1); + uint8_t noBitLock = static_cast((clcw >> 14) & 0x1); + uint8_t lockoutFlag = static_cast((clcw >> 13) & 0x1); + uint8_t waitFlag = static_cast((clcw >> 12) & 0x1); + uint8_t retransmitFlag = static_cast((clcw >> 11) & 0x1); + uint8_t farmBcnt = static_cast((clcw >> 9) & 0x3); + // Expected frame sequence number in te next AD frame + uint8_t repValue = static_cast(clcw & 0xFF); + sif::info << std::setw(30) << std::left << "CLCW type: " << std::hex << "0x" + << static_cast(type) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW version no: " << std::hex << "0x" + << static_cast(versionNo) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW status: " << std::hex << "0x" + << static_cast(status) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW COP: " << std::hex << "0x" + << static_cast(cop) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW virtual channel ID: " << std::hex << "0x" + << static_cast(vcId) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW no RF: " << std::hex << "0x" + << static_cast(noRf) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW no bit lock: " << std::hex << "0x" + << static_cast(noBitLock) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW lockout flag: " << std::hex << "0x" + << static_cast(lockoutFlag) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW wait flag: " << std::hex << "0x" + << static_cast(waitFlag) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW retransmit flag: " << std::hex << "0x" + << static_cast(retransmitFlag) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW FARM B count: " << std::hex << "0x" + << static_cast(farmBcnt) << std::endl; + sif::info << std::setw(30) << std::left << "CLCW rep value: " << std::hex << "0x" + << static_cast(repValue) << std::endl; +} + +void PdecHandler::printPdecMon() { + uint32_t pdecMon = getPdecMon(); + uint32_t tc0ChannelStatus = (pdecMon & TC0_STATUS_MASK) >> TC0_STATUS_POS; + uint32_t tc1ChannelStatus = (pdecMon & TC1_STATUS_MASK) >> TC1_STATUS_POS; + uint32_t tc2ChannelStatus = (pdecMon & TC2_STATUS_MASK) >> TC2_STATUS_POS; + uint32_t tc3ChannelStatus = (pdecMon & TC3_STATUS_MASK) >> TC3_STATUS_POS; + uint32_t tc4ChannelStatus = (pdecMon & TC4_STATUS_MASK) >> TC4_STATUS_POS; + uint32_t tc5ChannelStatus = (pdecMon & TC5_STATUS_MASK) >> TC5_STATUS_POS; + uint32_t lock = (pdecMon & LOCK_MASK) >> LOCK_POS; + sif::info << std::setw(30) << std::left << "TC0 status: " << getMonStatusString(tc0ChannelStatus) + << std::endl; + sif::info << std::setw(30) << std::left << "TC1 status: " << getMonStatusString(tc1ChannelStatus) + << std::endl; + sif::info << std::setw(30) << std::left << "TC2 status: " << getMonStatusString(tc2ChannelStatus) + << std::endl; + sif::info << std::setw(30) << std::left << "TC3 status: " << getMonStatusString(tc3ChannelStatus) + << std::endl; + sif::info << std::setw(30) << std::left << "TC4 status: " << getMonStatusString(tc4ChannelStatus) + << std::endl; + sif::info << std::setw(30) << std::left << "TC5 status: " << getMonStatusString(tc5ChannelStatus) + << std::endl; + sif::info << std::setw(30) << std::left << "Start sequence lock: " << lock << std::endl; +} + +uint32_t PdecHandler::readFar() { return *(registerBaseAddress + PDEC_FAR_OFFSET); } + +void PdecHandler::resetIrqLimiters() { + interruptWindowCd.resetTimer(); + interruptCounter = 0; +} + +void PdecHandler::checkLocks() { + uint32_t clcw = getClcw(); + if (not(clcw & NO_RF_MASK) && not carrierLock) { + triggerEvent(CARRIER_LOCK); + carrierLock = true; + } else if ((clcw & NO_RF_MASK) && carrierLock) { + carrierLock = false; + triggerEvent(LOST_CARRIER_LOCK_PDEC); + } + if (not(clcw & NO_BITLOCK_MASK) && not bitLock) { + triggerEvent(BIT_LOCK_PDEC); + bitLock = true; + } else if ((clcw & NO_BITLOCK_MASK) && bitLock) { + bitLock = false; + triggerEvent(LOST_BIT_LOCK_PDEC); + } +} + +void PdecHandler::initFailedHandler(ReturnValue_t reason) { + triggerEvent(pdec::PDEC_INIT_FAILED, reason, 0); + if (ptmeResetWithReinitializationPending) { + actionHelper.finish(false, commandedBy, pdec::RESET_PDEC_WITH_REINIITALIZATION, reason); + ptmeResetWithReinitializationPending = false; + } + state = State::WAIT_FOR_RECOVERY; +} + +void PdecHandler::pdecResetNoInit() { + triggerEvent(pdec::PDEC_TRYING_RESET_NO_INIT); + pdecToReset(); + usleep(20); + releasePdec(); + ReturnValue_t result = postResetOperation(); + if (result != returnvalue::OK) { + // What can we really do here? Event was already triggered if this is due to the FAR flag + // not being reset. + sif::error << "PdecHandler::pdecResetNoInit: Post reset operation failed unexpectedly" + << std::endl; + } +} + +ReturnValue_t PdecHandler::postResetOperation() { + // This configuration must be done while the PDEC is not held in reset. + if (OP_MODE == Modes::IRQ) { + // Configure interrupt mask register to enable interrupts + *(registerBaseAddress + PDEC_IMR_OFFSET) = pdecConfig.getImrReg(); + } + ReturnValue_t result = resetFarStatFlag(); + if (result != returnvalue::OK) { + // Requires reconfiguration and reinitialization of PDEC + triggerEvent(INVALID_FAR); + } + return result; +} + +void PdecHandler::initializeReset() { + if (irqFd != 0) { + close(irqFd); + } + state = State::PDEC_RESET; +} + +std::string PdecHandler::getMonStatusString(uint32_t status) { + switch (status) { + case TC_CHANNEL_INACTIVE: + return std::string("inactive"); + case TC_CHANNEL_ACTIVE: + return std::string("active"); + case TC_CHANNEL_TIMEDOUT: + return std::string("timed out"); + default: + sif::warning << "PdecHandler::getMonStatusString: Invalid status" << std::endl; + return std::string(); + break; + } +} diff --git a/linux/ipcore/PdecHandler.h b/linux/ipcore/PdecHandler.h new file mode 100644 index 0000000..8832b5d --- /dev/null +++ b/linux/ipcore/PdecHandler.h @@ -0,0 +1,355 @@ +#ifndef LINUX_OBC_PDECHANDLER_H_ +#define LINUX_OBC_PDECHANDLER_H_ + +#include + +#include + +#include "OBSWConfig.h" +#include "PdecConfig.h" +#include "eive/definitions.h" +#include "fsfw/action/ActionHelper.h" +#include "fsfw/action/HasActionsIF.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/parameters/ParameterHelper.h" +#include "fsfw/parameters/ReceivesParameterMessagesIF.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/storagemanager/StorageManagerIF.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw/tmtcservices/AcceptsTelecommandsIF.h" +#include "fsfw_hal/common/gpio/gpioDefinitions.h" +#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h" + +struct UioNames { + const char* configMemory; + const char* ramMemory; + const char* registers; + const char* irq; +}; + +/** + * @brief This class controls the PDEC IP Core implemented in the programmable logic of the + * Zynq-7020. All registers and memories of the PDEC IP Core are accessed via UIO + * drivers. + * + * @details The PDEC IP Core is responsible for processing data received in form of CLTUs from the + * S-Band transceiver. This comprises the BCH decoding of the CLTUs and reconstruction of + * telecommand transfer frames. Finally the PDEC stores the TC segment transported with + * the TC transfer frame in a register. As soon as a new TC has been received a new + * frame acceptance report (FAR) will be generated. If the FAR confirms the validity of + * a received TC segment, the data can be read out from the associated register. + * Currently, the ground software only supports transmissions of CLTUs containing one + * space packet. + * Link to datasheet of PDEC IP Core: https://eive-cloud.irs.uni-stuttgart.de/index.php/ + * apps/files/?dir=/EIVE_IRS/Arbeitsdaten/08_Used%20Components/CCSDS_IP_Cores&fileid=1108967 + * + * @author J. Meier + */ +class PdecHandler : public SystemObject, + public ExecutableObjectIF, + public HasActionsIF, + public ReceivesParameterMessagesIF { + public: + static constexpr dur_millis_t IRQ_TIMEOUT_MS = 500; + static constexpr uint32_t PDEC_CFG_MEM_SIZE = 0x1000; + static constexpr uint32_t PDEC_RAM_SIZE = 0x10000; + + enum class Modes { POLLED, IRQ }; + + /** + * @brief Constructor + * @param objectId Object ID of PDEC handler system object + * @param tcDestinationId Object ID of object responsible for processing TCs. + * @param gpioComIF Pointer to GPIO interace responsible for driving GPIOs. + * @param pdecReset GPIO ID of GPIO connected to the reset signal of the PDEC. + * @param uioConfigMemory String of uio device file same mapped to the PDEC memory space + * @param uioregsiters String of uio device file same mapped to the PDEC register space + */ + PdecHandler(object_id_t objectId, object_id_t tcDestinationId, LinuxLibgpioIF* gpioComIF, + gpioId_t pdecReset, UioNames names, uint32_t cfgMemPhyAddr, uint32_t pdecRamPhyAddr); + + virtual ~PdecHandler(); + + ReturnValue_t performOperation(uint8_t operationCode = 0); + + ReturnValue_t initialize() override; + + MessageQueueId_t getCommandQueue() const; + + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueIdentifier, + ParameterWrapper* parameterWrapper, const ParameterWrapper* newValues, + uint16_t startAtIndex) override; + + private: + static constexpr Modes OP_MODE = Modes::IRQ; + + static const uint32_t QUEUE_SIZE = config::CCSDS_HANDLER_QUEUE_SIZE; + +#ifdef TE0720_1CFA + static const int CONFIG_MEMORY_MAP_SIZE = 0x400; + static const int RAM_MAP_SIZE = 0x4000; + static const int REGISTER_MAP_SIZE = 0x10000; +#else + static const int CONFIG_MEMORY_MAP_SIZE = 0x400; + static const int RAM_MAP_SIZE = 0x4000; + static const int REGISTER_MAP_SIZE = 0x4000; +#endif /* BOARD_TE0720 == 1 */ + + static const size_t MAX_TC_SEGMENT_SIZE = 1017; + static const uint8_t MAP_ID_MASK = 0x3F; + + // Expected value stored in FAR register after reset + static const uint32_t FAR_RESET = 0x7FE0; + + static const uint32_t TC_SEGMENT_LEN = 1017; + + static const uint32_t NO_RF_MASK = 0x8000; + static const uint32_t NO_BITLOCK_MASK = 0x4000; + + static const uint32_t MAX_INIT_TRIES = 20; + + class ParameterId { + public: + // ID of the parameter to update the positive window of AD frames + static const uint8_t POSITIVE_WINDOW = 0; + // ID of the parameter to update the negative window of AD frames + static const uint8_t NEGATIVE_WINDOW = 1; + }; + + static constexpr uint32_t MAX_ALLOWED_IRQS_PER_WINDOW = 800; + + enum class IReason_t : uint8_t { + NO_REPORT, + ERROR_VERSION_NUMBER, + ILLEGAL_COMBINATION, + INVALID_SC_ID, + INVALID_VC_ID_LSB, + INVALID_VC_ID_MSB, + NS_NOT_ZERO, + INCORRECT_BC_CC + }; + + enum class State : uint8_t { INIT, PDEC_RESET, RUNNING, WAIT_FOR_RECOVERY }; + + static uint32_t CURRENT_FAR; + + Countdown genericCheckCd = Countdown(IRQ_TIMEOUT_MS); + object_id_t tcDestinationId; + int irqFd = 0; + + AcceptsTelecommandsIF* tcDestination = nullptr; + + LinuxLibgpioIF* gpioComIF = nullptr; + + uint32_t interruptCounter = 0; + Countdown interruptWindowCd = Countdown(1000); + + /** + * Reset signal is required to hold PDEC in reset state until the configuration has been + * written to the appropriate memory space. + * Can also be used to reboot PDEC in case of erros. + */ + gpioId_t pdecReset = gpio::NO_GPIO; + + uint32_t tcAbortCounter = 0; + + ActionHelper actionHelper; + + StorageManagerIF* tcStore = nullptr; + + MessageQueueIF* commandQueue = nullptr; + + State state = State::INIT; + + /** + * Pointer pointing to base address of the PDEC memory space. + * This address is equivalent with the base address of the section named configuration area in + * the PDEC datasheet. + */ + uint32_t* memoryBaseAddress = nullptr; + + uint32_t* ramBaseAddress = nullptr; + + // Pointer pointing to base address of register space + uint32_t* registerBaseAddress = nullptr; + + uint8_t tcSegment[TC_SEGMENT_LEN]; + + // Used to check carrier and bit lock changes (default set to no rf and no bitlock) + uint32_t lastClcw = 0xC000; + + bool carrierLock = false; + bool bitLock = false; + + MessageQueueId_t commandedBy = MessageQueueIF::NO_QUEUE; + bool ptmeResetWithReinitializationPending = false; + + uint32_t cfgMemBaseAddr; + uint32_t pdecRamBaseAddr; + + UioNames uioNames; + + ParameterHelper paramHelper; + + PdecConfig pdecConfig; + + uint32_t initTries = 0; + + // scuffed test counter. + uint8_t testCntr = 0; + + /** + * @brief Performs initialization stuff which must be performed in first + * loop of running task + * + * @return OK if successful, otherwise FAILED + */ + ReturnValue_t firstLoop(); + + /** + * @brief Reads and handles messages stored in the commandQueue + */ + void readCommandQueue(void); + + ReturnValue_t polledOperation(); + ReturnValue_t irqOperation(); + ReturnValue_t handleInitState(); + void openIrqFile(); + ReturnValue_t checkAndHandleIrqs(uint32_t& info); + + uint32_t readFar(); + + /** + * @brief This functions writes the configuration parameters to the configuration + * section of the PDEC. + */ + void writePdecConfigDuringReset(PdecConfig& config); + + /** + * @brief Reading the FAR resets the set stat flag which signals a new TC. Without clearing + * this flag no new TC will be excepted. After start up the flag is set and needs + * to be reset. + * Stat flag 0 - new TC received + * Stat flag 1 - old TC (ready to receive next TC) + */ + ReturnValue_t resetFarStatFlag(); + + /** + * @brief Releases the PDEC from reset state. PDEC will start with loading the written + * configuration parameters. + */ + ReturnValue_t releasePdec(); + + /** + * @brief Will set PDEC in reset state. Use releasePdec() to release PDEC + * from reset state + * + * @return OK if successful, otherwise error return value + */ + ReturnValue_t pdecToReset(); + + /** + * @brief Reads the FAR register and checks if a new TC has been received. + */ + bool newTcReceived(); + + /** + * @brief Checks if carrier lock or bit lock has been detected and triggers appropriate + * event. + */ + void doPeriodicWork(); + + void checkLocks(); + + void resetIrqLimiters(); + + /** + * @brief Analyzes the FramAna field (frame analysis data) of a FAR report. + * + * @return True if frame valid, otherwise false. + */ + bool checkFrameAna(uint32_t pdecFar); + + /** + * @brief This function handles the IReason field of the frame analysis report. + * + * @details In case frame as been declared illegal for multiple reasons, the reason with the + * lowest value will be shown. + */ + void handleIReason(uint32_t pdecFar, ReturnValue_t parameter1); + + /** + * @brief Checks if PDEC configuration is still correct + */ + void checkConfig(); + + /** + * @brief Handles the reception of new TCs. Reads the pointer to the storage location of the + * new TC segment, extracts the PUS packet and forwards the data to the object + * responsible for processing the TC. + */ + void handleNewTc(); + + /** + * @brief Function reads the last received TC segment from the PDEC memory and copies + * the data to the tcSegement array. + * + * @param tcLength The length of the received TC. + * + */ + ReturnValue_t readTc(uint32_t& tcLength); + + /** + * @brief Prints the tc segment data + */ + void printTC(uint32_t tcLength); + + /** + * @brief This function calculates the entry for the configuration of the MAP ID routing. + * + * @param mapAddr The MAP ID to configure + * @param moduleId The destination module where all TCs with the map id mapAddr will be routed + * to. + * + * @details The PDEC has different modules where the TCs can be routed to. A lookup table is + * used which links the MAP ID field to the destination module. The entry for this + * lookup table is created by this function and must be stored in the configuration + * memory region of the PDEC. The entry has a specific format + */ + uint8_t calcMapAddrEntry(uint8_t moduleId); + + /** + * brief Returns the 32-bit wide communication link control word (CLCW) + */ + uint32_t getClcw(); + + /** + * @brief Returns the PDEC monitor register content + * + */ + uint32_t getPdecMon(); + + /** + * @brief Reads and prints the CLCW. Can be useful for debugging. + */ + void printClcw(); + + /** + * @brief Prints monitor register information to debug console. + */ + void printPdecMon(); + + void pdecResetNoInit(); + + ReturnValue_t postResetOperation(); + void initializeReset(); + + void initFailedHandler(ReturnValue_t reason); + + std::string getMonStatusString(uint32_t status); +}; + +#endif /* LINUX_OBC_PDECHANDLER_H_ */ diff --git a/linux/ipcore/Ptme.cpp b/linux/ipcore/Ptme.cpp new file mode 100644 index 0000000..9b688d1 --- /dev/null +++ b/linux/ipcore/Ptme.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "PtmeConfig.h" +#include "eive/definitions.h" +#include "fsfw/serviceinterface/ServiceInterface.h" + +Ptme::Ptme(object_id_t objectId) : SystemObject(objectId) {} + +Ptme::~Ptme() {} + +ReturnValue_t Ptme::initialize() { + VcInterfaceMapIter iter; + for (iter = vcInterfaceMap.begin(); iter != vcInterfaceMap.end(); iter++) { + iter->second->initialize(); + } + return returnvalue::OK; +} + +bool Ptme::containsVc(uint8_t vcId) const { + auto channelIter = vcInterfaceMap.find(vcId); + if (channelIter == vcInterfaceMap.end()) { + return false; + } + return true; +} + +void Ptme::addVcInterface(VcId_t vcId, VirtualChannelIF* vc) { + if (vcId > config::NUMBER_OF_VIRTUAL_CHANNELS) { + sif::warning << "Ptme::addVcInterface: Invalid virtual channel ID" << std::endl; + return; + } + + if (vc == nullptr) { + sif::warning << "Ptme::addVcInterface: Invalid virtual channel interface" << std::endl; + return; + } + + auto status = vcInterfaceMap.emplace(vcId, vc); + if (status.second == false) { + sif::warning << "Ptme::addVcInterface: Failed to add virtual channel interface to " + "virtual channel map" + << std::endl; + return; + } +} + +VirtualChannelIF* Ptme::getVirtChannel(uint8_t vcId) { + auto channelIter = vcInterfaceMap.find(vcId); + if (channelIter == vcInterfaceMap.end()) { + return nullptr; + } + return channelIter->second; +} diff --git a/linux/ipcore/Ptme.h b/linux/ipcore/Ptme.h new file mode 100644 index 0000000..d5e7702 --- /dev/null +++ b/linux/ipcore/Ptme.h @@ -0,0 +1,83 @@ +#ifndef LINUX_OBC_PTME_H_ +#define LINUX_OBC_PTME_H_ + +#include +#include +#include + +#include +#include + +#include "OBSWConfig.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "linux/ipcore/PtmeIF.h" + +/** + * @brief This class handles the interfacing to the telemetry (PTME) IP core. + * + * @details + * This module is responsible for the encoding of telemetry packets according to the CCSDS + * standards CCSDS 131.0-B-3 (TM Synchronization and channel coding) and CCSDS 132.0-B-2 + * (TM Space Data Link Protocoll). The IP cores are implemented on the programmable logic and are + * accessible through the linux UIO driver. + */ +class Ptme : public PtmeIF, public SystemObject { + public: + using VcId_t = uint8_t; + + /** + * @brief Constructor + * + * @param objectId + */ + Ptme(object_id_t objectId); + virtual ~Ptme(); + + ReturnValue_t initialize() override; + bool containsVc(uint8_t vcId) const override; + VirtualChannelIF* getVirtChannel(uint8_t vcId) override; + + /** + * @brief This function adds the reference to a virtual channel interface to the vcInterface + * map. + */ + void addVcInterface(VcId_t vcId, VirtualChannelIF* vc); + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::PTME; + + static const ReturnValue_t UNKNOWN_VC_ID = MAKE_RETURN_CODE(0xA0); + + /** + * Configuration bits: + * bit[1:0]: Size of data (1,2,3 or 4 bytes). 1 Byte <=> b00 + * bit[2]: Set this bit to 1 to abort a transfered packet + * bit[3]: Signals to PTME the start of a new telemetry packet + */ + static const uint32_t PTME_CONFIG_START = 0x8; + + /** + * Writing this word to the ptme base address signals to the PTME that a complete tm packet has + * been transferred. + */ + static const uint32_t PTME_CONFIG_END = 0x0; + + /** + * Writing to this offset within the PTME memory space will insert data for encoding to the + * PTME IP core. + * The address offset is 0x400 (= 4 * 256) + */ + static const int PTME_DATA_REG_OFFSET = 256; + + /** The file descriptor of the UIO driver */ + int fd = 0; + + uint32_t* ptmeBaseAddress = nullptr; + + using VcInterfaceMap = std::unordered_map; + using VcInterfaceMapIter = VcInterfaceMap::iterator; + + VcInterfaceMap vcInterfaceMap; +}; + +#endif /* LINUX_OBC_PTME_H_ */ diff --git a/linux/ipcore/PtmeConfig.cpp b/linux/ipcore/PtmeConfig.cpp new file mode 100644 index 0000000..5b6b934 --- /dev/null +++ b/linux/ipcore/PtmeConfig.cpp @@ -0,0 +1,60 @@ +#include "PtmeConfig.h" + +#include "fsfw/serviceinterface/ServiceInterface.h" + +PtmeConfig::PtmeConfig(object_id_t objectId, AxiPtmeConfig* axiPtmeConfig) + : SystemObject(objectId), axiPtmeConfig(axiPtmeConfig) {} + +PtmeConfig::~PtmeConfig() {} + +ReturnValue_t PtmeConfig::initialize() { + if (axiPtmeConfig == nullptr) { + sif::warning << "PtmeConfig::initialize: Invalid AxiPtmeConfig object" << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t PtmeConfig::setRate(uint32_t bitRate) { + if (bitRate == 0) { + return BAD_BIT_RATE; + } + uint32_t rateVal = BIT_CLK_FREQ / bitRate - 1; + if (rateVal > 0xFF) { + return RATE_NOT_SUPPORTED; + } + return axiPtmeConfig->writeCaduRateReg(static_cast(rateVal)); +} + +uint32_t PtmeConfig::getRate() { + uint8_t rateReg = axiPtmeConfig->readCaduRateReg(); + return (BIT_CLK_FREQ / (rateReg + 1)); +} + +void PtmeConfig::invertTxClock(bool invert) { + if (invert) { + axiPtmeConfig->enableTxclockInversion(); + } else { + axiPtmeConfig->disableTxclockInversion(); + } +} + +void PtmeConfig::configTxManipulator(bool enable) { + if (enable) { + axiPtmeConfig->enableTxclockManipulator(); + } else { + axiPtmeConfig->disableTxclockManipulator(); + } +} + +void PtmeConfig::enableBatPriorityBit(bool enable) { + if (enable) { + axiPtmeConfig->enableBatPriorityBit(); + } else { + axiPtmeConfig->disableBatPriorityBit(); + } +} + +void PtmeConfig::setPollThreshold(AxiPtmeConfig::IdlePollThreshold pollThreshold) { + axiPtmeConfig->writePollThreshold(pollThreshold); +} diff --git a/linux/ipcore/PtmeConfig.h b/linux/ipcore/PtmeConfig.h new file mode 100644 index 0000000..11eeff7 --- /dev/null +++ b/linux/ipcore/PtmeConfig.h @@ -0,0 +1,91 @@ +#ifndef LINUX_OBC_PTMECONFIG_H_ +#define LINUX_OBC_PTMECONFIG_H_ + +#include + +#include "AxiPtmeConfig.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "linux/ipcore/PtmeConfig.h" +#include "returnvalues/classIds.h" + +/** + * @brief Class to configure donwlink specific parameters in the PTME IP core. + * + * @author J. Meier + */ +class PtmeConfig : public SystemObject { + public: + /** + * @brief Constructor + * + * ptmeAxiConfig Pointer to object providing access to PTME configuration registers. + */ + PtmeConfig(object_id_t opbjectId, AxiPtmeConfig* axiPtmeConfig); + virtual ~PtmeConfig(); + + virtual ReturnValue_t initialize() override; + /** + * @brief Changes the input frequency to the S-Band transceiver and thus the downlink rate + * + * @details This is the bitrate of the CADU clock and not the downlink which has twice the bitrate + * of the CADU clock due to the convolutional code added by the s-Band transceiver. + */ + ReturnValue_t setRate(uint32_t bitRate); + uint32_t getRate(); + + /** + * @brief Will change the time the tx data signal is updated with respect to the tx clock + * + * @param invert True -> Data signal will be updated on the falling edge (not desired by the + * Syrlinks) + * False -> Data signal updated on rising edge (default configuration and desired + * by the syrlinks) + * + * @return REUTRN_OK if successful, otherwise error return value + */ + void invertTxClock(bool invert); + + /** + * @brief Controls the tx clock manipulator of the PTME wrapper component + * + * @param enable Manipulator will be enabled (this is also the default configuration) + * @param disable Manipulator will be disabled + * + * @return REUTRN_OK if successful, otherwise error return value + */ + void configTxManipulator(bool enable); + + /** + * Enable the bat priority bit in the PTME wrapper component. + * Please note that a reset of the PTME is still required as specified in the documentation. + * This is done by a higher level component. + * @param enable + * @return + */ + void enableBatPriorityBit(bool enable); + + void setPollThreshold(AxiPtmeConfig::IdlePollThreshold pollThreshold); + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::RATE_SETTER; + + //! [EXPORT] : [COMMENT] The commanded rate is not supported by the current FPGA design + static const ReturnValue_t RATE_NOT_SUPPORTED = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] Bad bitrate has been commanded (e.g. 0) + static const ReturnValue_t BAD_BIT_RATE = MAKE_RETURN_CODE(0xA1); + //! [EXPORT] : [COMMENT] Failed to invert clock and thus change the time the data is updated with + //! respect to the tx clock + static const ReturnValue_t CLK_INVERSION_FAILED = MAKE_RETURN_CODE(0xA2); + //! [EXPORT] : [COMMENT] Failed to change configuration bit of tx clock manipulator + static const ReturnValue_t TX_MANIPULATOR_CONFIG_FAILED = MAKE_RETURN_CODE(0xA3); + + // Bitrate register field is only 8 bit wide + static const uint32_t MAX_BITRATE = 0xFF; + // Bit clock frequency of PMTE IP core in Hz + static const uint32_t BIT_CLK_FREQ = 20000000; + + AxiPtmeConfig* axiPtmeConfig = nullptr; +}; + +#endif /* LINUX_OBC_PTMECONFIG_H_ */ diff --git a/linux/ipcore/PtmeIF.h b/linux/ipcore/PtmeIF.h new file mode 100644 index 0000000..ab7259d --- /dev/null +++ b/linux/ipcore/PtmeIF.h @@ -0,0 +1,23 @@ +#ifndef LINUX_OBC_PTMEIF_H_ +#define LINUX_OBC_PTMEIF_H_ + +#include + +#include "fsfw/returnvalues/returnvalue.h" + +/** + * @brief Interface class for managing the PTME IP Core implemented in the programmable logic. + * + * @details PTME IP Core: https://www.esa.int/Enabling_Support/Space_Engineering_Technology/ + * Microelectronics/PTME + * @author J. Meier + */ +class PtmeIF { + public: + virtual ~PtmeIF() {}; + + virtual bool containsVc(uint8_t vcId) const = 0; + virtual VirtualChannelIF* getVirtChannel(uint8_t vcId) = 0; +}; + +#endif /* LINUX_OBC_PTMEIF_H_ */ diff --git a/linux/ipcore/VirtualChannelIF.h b/linux/ipcore/VirtualChannelIF.h new file mode 100644 index 0000000..2b421e1 --- /dev/null +++ b/linux/ipcore/VirtualChannelIF.h @@ -0,0 +1,24 @@ +#ifndef LINUX_OBC_VCINTERFACEIF_H_ +#define LINUX_OBC_VCINTERFACEIF_H_ + +#include +#include + +#include "fsfw/returnvalues/returnvalue.h" + +/** + * @brief Interface class for managing different virtual channels of the PTME IP core implemented + * in the programmable logic. + * @details + * Also implements @DirectTmSinkIF to allow wiriting to the VC directly. + * @author J. Meier + */ +class VirtualChannelIF : public DirectTmSinkIF { + public: + virtual ~VirtualChannelIF() {}; + + virtual ReturnValue_t initialize() = 0; + virtual void cancelTransfer() = 0; +}; + +#endif /* LINUX_OBC_VCINTERFACEIF_H_ */ diff --git a/linux/ipcore/pdec.h b/linux/ipcore/pdec.h new file mode 100644 index 0000000..de703c5 --- /dev/null +++ b/linux/ipcore/pdec.h @@ -0,0 +1,150 @@ +#ifndef LINUX_OBC_PDEC_H_ +#define LINUX_OBC_PDEC_H_ + +#include +#include + +#include + +namespace pdec { + +static const uint8_t INTERFACE_ID = CLASS_ID::PDEC_HANDLER; +static const ReturnValue_t ABANDONED_CLTU_RETVAL = MAKE_RETURN_CODE(0xA0); +static const ReturnValue_t FRAME_DIRTY_RETVAL = MAKE_RETURN_CODE(0xA1); +static const ReturnValue_t FRAME_ILLEGAL_ONE_REASON = MAKE_RETURN_CODE(0xA2); +static const ReturnValue_t FRAME_ILLEGAL_MULTIPLE_REASONS = MAKE_RETURN_CODE(0xA2); +static const ReturnValue_t AD_DISCARDED_LOCKOUT_RETVAL = MAKE_RETURN_CODE(0xA3); +static const ReturnValue_t AD_DISCARDED_WAIT_RETVAL = MAKE_RETURN_CODE(0xA4); +static const ReturnValue_t AD_DISCARDED_NS_VS = MAKE_RETURN_CODE(0xA5); + +//! [EXPORT] : [COMMENT] Received action message with unknown action id +static const ReturnValue_t COMMAND_NOT_IMPLEMENTED = MAKE_RETURN_CODE(0xB0); + +static const ReturnValue_t NO_REPORT_RETVAL = MAKE_RETURN_CODE(0xA6); +//! Error in version number and reserved A and B fields +static const ReturnValue_t ERROR_VERSION_NUMBER_RETVAL = MAKE_RETURN_CODE(0xA7); +//! Illegal combination of bypass and control command flag +static const ReturnValue_t ILLEGAL_COMBINATION_RETVAL = MAKE_RETURN_CODE(0xA8); +//! Spacecraft identifier did not match +static const ReturnValue_t INVALID_SC_ID_RETVAL = MAKE_RETURN_CODE(0xA9); +//! VC identifier bits 0 to 4 did not match +static const ReturnValue_t INVALID_VC_ID_MSB_RETVAL = MAKE_RETURN_CODE(0xAA); +//! VC identifier bit 5 did not match +static const ReturnValue_t INVALID_VC_ID_LSB_RETVAL = MAKE_RETURN_CODE(0xAB); +//! N(S) of BC or BD frame not set to all zeros +static const ReturnValue_t NS_NOT_ZERO_RETVAL = MAKE_RETURN_CODE(0xAC); +//! Invalid BC control command +static const ReturnValue_t INVALID_BC_CC = MAKE_RETURN_CODE(0xAE); + +static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PDEC_HANDLER; + +//! [EXPORT] : [COMMENT] Frame acceptance report signals an invalid frame +//! P1: The frame analysis information (FrameAna field of PDEC_FAR register) +//! P2: When frame declared illegal this parameter this parameter gives information about the +//! reason (IReason field of the PDEC_FAR register) +static const Event INVALID_TC_FRAME = MAKE_EVENT(1, severity::HIGH); +//! [EXPORT] : [COMMENT] Read invalid FAR from PDEC after startup +static const Event INVALID_FAR = MAKE_EVENT(2, severity::HIGH); +//! [EXPORT] : [COMMENT] Carrier lock detected +static const Event CARRIER_LOCK = MAKE_EVENT(3, severity::INFO); +//! [EXPORT] : [COMMENT] Bit lock detected (data valid) +static const Event BIT_LOCK_PDEC = MAKE_EVENT(4, severity::INFO); +//! [EXPORT] : [COMMENT] Lost carrier lock +static const Event LOST_CARRIER_LOCK_PDEC = MAKE_EVENT(5, severity::INFO); +//! [EXPORT] : [COMMENT] Lost bit lock +static const Event LOST_BIT_LOCK_PDEC = MAKE_EVENT(6, severity::INFO); +//! [EXPORT] : [COMMENT] Too many IRQs over the time window of one second. P1: Allowed TCs +static constexpr Event TOO_MANY_IRQS = MAKE_EVENT(7, severity::MEDIUM); +static constexpr Event POLL_SYSCALL_ERROR_PDEC = + event::makeEvent(SUBSYSTEM_ID, 8, severity::MEDIUM); +static constexpr Event WRITE_SYSCALL_ERROR_PDEC = event::makeEvent(SUBSYSTEM_ID, 9, severity::HIGH); +//! [EXPORT] : [COMMENT] Trying a PDEC reset with complete re-initialization +static constexpr Event PDEC_TRYING_RESET_WITH_INIT = + event::makeEvent(SUBSYSTEM_ID, 10, severity::LOW); +//! [EXPORT] : [COMMENT] Trying a PDEC reset without re-initialization. +static constexpr Event PDEC_TRYING_RESET_NO_INIT = + event::makeEvent(SUBSYSTEM_ID, 11, severity::LOW); +//! [EXPORT] : [COMMENT] Failed to pull PDEC reset to low +static constexpr Event PDEC_RESET_FAILED = event::makeEvent(SUBSYSTEM_ID, 12, severity::HIGH); +//! [EXPORT] : [COMMENT] Failed to open the IRQ uio file +static constexpr Event OPEN_IRQ_FILE_FAILED = event::makeEvent(SUBSYSTEM_ID, 13, severity::HIGH); +//! [EXPORT] : [COMMENT] PDEC initialization failed. This might also be due to the persistent +//! confiuration never becoming available, for example due to SD card issues. +static constexpr Event PDEC_INIT_FAILED = event::makeEvent(SUBSYSTEM_ID, 14, severity::HIGH); +//! [EXPORT] : [COMMENT] The PDEC configuration area has been corrupted +//! P1: The first configuration word +//! P2: The second configuration word +static constexpr Event PDEC_CONFIG_CORRUPTED = event::makeEvent(SUBSYSTEM_ID, 15, severity::HIGH); + +// Action IDs +static constexpr ActionId_t PRINT_CLCW = 0; +// Print PDEC monitor register +static constexpr ActionId_t PRINT_PDEC_MON = 1; +static constexpr ActionId_t RESET_PDEC_NO_REINIITALIZATION = 2; +static constexpr ActionId_t RESET_PDEC_WITH_REINIITALIZATION = 3; + +enum class FrameAna_t : uint8_t { + ABANDONED_CLTU, + FRAME_DIRTY, + FRAME_ILLEGAL, + FRAME_ILLEGAL_MULTI_REASON, + AD_DISCARDED_LOCKOUT, + AD_DISCARDED_WAIT, + AD_DISCARDED_NS_VR, + FRAME_ACCEPTED +}; + +static const uint8_t STAT_POSITION = 31; +static const uint8_t FRAME_ANA_POSITION = 28; +static const uint8_t IREASON_POSITION = 25; + +static const uint8_t NEW_FAR_RECEIVED = 0; + +static constexpr uint32_t NEW_FAR_MASK = 1 << 2; +static constexpr uint32_t TC_ABORT_MASK = 1 << 1; +static constexpr uint32_t TC_NEW_MASK = 1 << 0; + +static constexpr uint32_t FAR_STAT_MASK = 1UL << 31; + +static const uint32_t FRAME_ANA_MASK = 0x70000000; +static const uint32_t IREASON_MASK = 0x0E000000; + +static const uint32_t TC_CHANNEL_INACTIVE = 0x0; +static const uint32_t TC_CHANNEL_ACTIVE = 0x1; +static const uint32_t TC_CHANNEL_TIMEDOUT = 0x2; + +static const uint32_t TC0_STATUS_MASK = 0x3; +static const uint32_t TC1_STATUS_MASK = 0xC; +static const uint32_t TC2_STATUS_MASK = 0x300; +static const uint32_t TC3_STATUS_MASK = 0xC00; +static const uint32_t TC4_STATUS_MASK = 0x30000; +static const uint32_t TC5_STATUS_MASK = 0xc00000; +// Lock register set to 1 when start sequence has been found (CLTU is beeing processed) +static const uint32_t LOCK_MASK = 0xc00000; + +static const uint32_t TC0_STATUS_POS = 0; +static const uint32_t TC1_STATUS_POS = 2; +static const uint32_t TC2_STATUS_POS = 4; +static const uint32_t TC3_STATUS_POS = 6; +static const uint32_t TC4_STATUS_POS = 8; +static const uint32_t TC5_STATUS_POS = 10; +// Lock register set to 1 when start sequence has been found (CLTU is beeing processed) +static const uint32_t LOCK_POS = 12; + +/** + * UIO is 4 byte aligned. Thus offset is calculated with "true offset" / 4 + * Example: PDEC_FAR = 0x2840 => Offset in virtual address space is 0xA10 + */ +static constexpr uint32_t PDEC_PISR_OFFSET = 0xA02; +static constexpr uint32_t PDEC_PIR_OFFSET = 0xA03; +static constexpr uint32_t PDEC_IMR_OFFSET = 0xA04; +static const uint32_t PDEC_FAR_OFFSET = 0xA10; +static const uint32_t PDEC_CLCW_OFFSET = 0xA12; +static const uint32_t PDEC_BFREE_OFFSET = 0xA24; +static const uint32_t PDEC_BPTR_OFFSET = 0xA25; +static const uint32_t PDEC_SLEN_OFFSET = 0xA26; +static const uint32_t PDEC_MON_OFFSET = 0xA27; + +} // namespace pdec + +#endif /* LINUX_OBC_PDEC_H_ */ diff --git a/linux/ipcore/pdecconfigdefs.h b/linux/ipcore/pdecconfigdefs.h new file mode 100644 index 0000000..0b7f392 --- /dev/null +++ b/linux/ipcore/pdecconfigdefs.h @@ -0,0 +1,20 @@ +#ifndef LINUX_IPCORE_PDECCONFIGDEFS_H_ +#define LINUX_IPCORE_PDECCONFIGDEFS_H_ + +#include + +namespace pdecconfigdefs { + +namespace paramkeys { +static const std::string POSITIVE_WINDOW = "positive_window"; +static const std::string NEGATIVE_WINDOW = "negattive_window"; +} // namespace paramkeys + +namespace defaultvalue { +static const uint8_t positiveWindow = 10; +static const uint8_t negativeWindow = 151; +} // namespace defaultvalue + +} // namespace pdecconfigdefs + +#endif /* LINUX_IPCORE_PDECCONFIGDEFS_H_ */ diff --git a/linux/payload/CMakeLists.txt b/linux/payload/CMakeLists.txt new file mode 100644 index 0000000..f0212c2 --- /dev/null +++ b/linux/payload/CMakeLists.txt @@ -0,0 +1,13 @@ +target_sources( + ${OBSW_NAME} + PUBLIC PlocMemoryDumper.cpp + MpsocCommunication.cpp + SerialCommunicationHelper.cpp + FreshMpsocHandler.cpp + FreshSupvHandler.cpp + PlocMpsocSpecialComHelper.cpp + plocMpsocHelpers.cpp + PlocSupvUartMan.cpp + ScexDleParser.cpp + ScexHelper.cpp + ScexUartReader.cpp) diff --git a/linux/payload/FreshMpsocHandler.cpp b/linux/payload/FreshMpsocHandler.cpp new file mode 100644 index 0000000..177d7e3 --- /dev/null +++ b/linux/payload/FreshMpsocHandler.cpp @@ -0,0 +1,1288 @@ +#include "FreshMpsocHandler.h" + +#include "OBSWConfig.h" +#include "eive/objects.h" +#include "fsfw/action/CommandActionHelper.h" +#include "fsfw/datapool/PoolReadGuard.h" +#include "fsfw/devicehandlers/DeviceHandlerIF.h" +#include "fsfw/devicehandlers/FreshDeviceHandlerBase.h" +#include "fsfw/ipc/MessageQueueIF.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/ipc/messageQueueDefinitions.h" +#include "fsfw/power/PowerSwitchIF.h" +#include "fsfw/power/definitions.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/serialize/SerializeAdapter.h" +#include "linux/payload/MpsocCommunication.h" +#include "linux/payload/plocMpsocHelpers.h" +#include "linux/payload/plocSupvDefs.h" +#include "mission/power/gsDefs.h" + +FreshMpsocHandler::FreshMpsocHandler(DhbConfig cfg, MpsocCommunication& comInterface, + PlocMpsocSpecialComHelper& specialComHelper, + Gpio uartIsolatorSwitch, object_id_t supervisorHandler, + PowerSwitchIF& powerSwitcher, power::Switch_t camSwitchId) + : FreshDeviceHandlerBase(cfg), + comInterface(comInterface), + specialComHelper(specialComHelper), + commandActionHelper(this), + uartIsolatorSwitch(uartIsolatorSwitch), + hkReport(this), + supervisorHandler(supervisorHandler), + powerSwitcher(powerSwitcher), + camSwitchId(camSwitchId) { + commandActionHelperQueue = QueueFactory::instance()->createMessageQueue(10); + eventQueue = QueueFactory::instance()->createMessageQueue(10); + spParams.maxSize = sizeof(commandBuffer); + spParams.buf = commandBuffer; +} + +void FreshMpsocHandler::performDeviceOperation(uint8_t opCode) { + if (transitionState == TransitionState::NONE and (mode == MODE_OFF or mode == MODE_UNDEFINED)) { + // Nothing to do for now. + return; + } + + if (opCode == OpCode::DEFAULT_OPERATION) { + performDefaultDeviceOperation(); + } else if (opCode == OpCode::PARSE_TM and not specialComHelperExecuting) { + // Just need to call this once, this should take care of processing the whole received + // Linux UART RX buffer. + comInterface.readSerialInterface(); + // Handle all received packets. + while (true) { + ReturnValue_t result = comInterface.parseAndRetrieveNextPacket(); + if (result == MpsocCommunication::PACKET_RECEIVED) { + handleDeviceReply(); + continue; + } + break; + } + } +} + +void FreshMpsocHandler::performDefaultDeviceOperation() { + if (transitionState != TransitionState::NONE) { + if (transitionState == TransitionState::TO_ON) { + handleTransitionToOn(); + } else if (transitionState == TransitionState::TO_OFF) { + handleTransitionToOff(); + } else if (transitionState == TransitionState::SUBMODE) { + if (!activeCmdInfo.pending) { + commandSubmodeTransition(); + } + } else { + // This should never happen. + sif::error << "FreshMpsocHandler: Invalid transition mode: " << targetMode << std::endl; + targetMode = MODE_OFF; + targetSubmode = 0; + handleTransitionToOff(); + } + if (modeHelper.isTimedOut()) { + // Set old mode and submode. + setMode(mode, submode); + } + } + + // We checked the action queue beforehand, so action commands should always be performed + // before normal commands. + if (mode == MODE_NORMAL and not activeCmdInfo.pending and not specialComHelperExecuting) { + ReturnValue_t result = commandTcGetHkReport(); + if (result == returnvalue::OK) { + commandInitHandling(mpsoc::TC_GET_HK_REPORT, MessageQueueIF::NO_QUEUE); + } + } + + if (activeCmdInfo.pending and activeCmdInfo.cmdCountdown.hasTimedOut()) { + sif::warning << "PlocMpsocHandler: Command " << activeCmdInfo.pendingCmd << " has timed out" + << std::endl; + cmdDoneHandler(false, mpsoc::COMMAND_TIMEOUT); + } + EventMessage event; + for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::OK; + result = eventQueue->receiveMessage(&event)) { + switch (event.getMessageId()) { + case EventMessage::EVENT_MESSAGE: + handleEvent(&event); + break; + default: + sif::debug << "PlocMPSoCHandler::performOperationHook: Did not subscribe to this event" + << " message" << std::endl; + break; + } + } + CommandMessage message; + for (ReturnValue_t result = commandActionHelperQueue->receiveMessage(&message); + result == returnvalue::OK; result = commandActionHelperQueue->receiveMessage(&message)) { + result = commandActionHelper.handleReply(&message); + if (result == returnvalue::OK) { + continue; + } + } +} + +ReturnValue_t FreshMpsocHandler::handleCommandMessage(CommandMessage* message) { + return returnvalue::FAILED; +} + +ReturnValue_t FreshMpsocHandler::initialize() { + ReturnValue_t result = FreshDeviceHandlerBase::initialize(); + if (result != returnvalue::OK) { + return result; + } + EventManagerIF* manager = ObjectManager::instance()->get(objects::EVENT_MANAGER); + if (manager == nullptr) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "PlocMPSoCHandler::initialize: Invalid event manager" << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + ; + } + result = manager->registerListener(eventQueue->getId()); + if (result != returnvalue::OK) { + return result; + } + result = manager->subscribeToEvent( + eventQueue->getId(), event::getEventId(PlocMpsocSpecialComHelper::MPSOC_FLASH_WRITE_FAILED)); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + result = manager->subscribeToEvent( + eventQueue->getId(), + event::getEventId(PlocMpsocSpecialComHelper::MPSOC_FLASH_WRITE_SUCCESSFUL)); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + result = manager->subscribeToEvent( + eventQueue->getId(), + event::getEventId(PlocMpsocSpecialComHelper::MPSOC_FLASH_READ_SUCCESSFUL)); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + result = manager->subscribeToEvent( + eventQueue->getId(), event::getEventId(PlocMpsocSpecialComHelper::MPSOC_FLASH_READ_FAILED)); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + result = commandActionHelper.initialize(); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + return result; +} + +// HK manager abstract functions. +LocalPoolDataSetBase* FreshMpsocHandler::getDataSetHandle(sid_t sid) { + if (sid == hkReport.getSid()) { + return &hkReport; + } + return nullptr; +} + +ReturnValue_t FreshMpsocHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(mpsoc::poolid::STATUS, &peStatus); + localDataPoolMap.emplace(mpsoc::poolid::MODE, &peMode); + localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_PWR_ON, &peDownlinkPwrOn); + localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_REPLY_ACTIIVE, &peDownlinkReplyActive); + localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_JESD_SYNC_STATUS, &peDownlinkJesdSyncStatus); + localDataPoolMap.emplace(mpsoc::poolid::DOWNLINK_DAC_STATUS, &peDownlinkDacStatus); + localDataPoolMap.emplace(mpsoc::poolid::CAM_STATUS, &peCameraStatus); + localDataPoolMap.emplace(mpsoc::poolid::CAM_SDI_STATUS, &peCameraSdiStatus); + localDataPoolMap.emplace(mpsoc::poolid::CAM_FPGA_TEMP, &peCameraFpgaTemp); + localDataPoolMap.emplace(mpsoc::poolid::CAM_SOC_TEMP, &peCameraSocTemp); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_TEMP, &peSysmonTemp); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCINT, &peSysmonVccInt); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCAUX, &peSysmonVccAux); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCBRAM, &peSysmonVccBram); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPAUX, &peSysmonVccPaux); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPINT, &peSysmonVccPint); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCCPDRO, &peSysmonVccPdro); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB12V, &peSysmonMb12V); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB3V3, &peSysmonMb3V3); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_MB1V8, &peSysmonMb1V8); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC12V, &peSysmonVcc12V); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC5V, &peSysmonVcc5V); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC3V3, &peSysmonVcc3V3); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC3V3VA, &peSysmonVcc3V3VA); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC2V5DDR, &peSysmonVcc2V5DDR); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC1V2DDR, &peSysmonVcc1V2DDR); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC0V9, &peSysmonVcc0V9); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_VCC0V6VTT, &peSysmonVcc0V6VTT); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_SAFE_COTS_CUR, &peSysmonSafeCotsCur); + localDataPoolMap.emplace(mpsoc::poolid::SYSMON_NVM4_XO_CUR, &peSysmonNvm4XoCur); + localDataPoolMap.emplace(mpsoc::poolid::SEM_UNCORRECTABLE_ERRS, &peSemUncorrectableErrs); + localDataPoolMap.emplace(mpsoc::poolid::SEM_CORRECTABLE_ERRS, &peSemCorrectableErrs); + localDataPoolMap.emplace(mpsoc::poolid::SEM_STATUS, &peSemStatus); + localDataPoolMap.emplace(mpsoc::poolid::REBOOT_MPSOC_REQUIRED, &peRebootMpsocRequired); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(hkReport.getSid(), false, 10.0)); + return returnvalue::OK; +} + +// Mode abstract functions +ReturnValue_t FreshMpsocHandler::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) { + if (this->mode == MODE_OFF or this->mode == MODE_UNDEFINED) { + // Device needs to be commanded to ON or NORMAL first before commanding submode. + if (submode != mpsoc::Submode::IDLE_OR_NONE) { + return HasModesIF::INVALID_SUBMODE; + } + } + if (mode == MODE_ON or mode == MODE_NORMAL) { + if (submode != mpsoc::Submode::IDLE_OR_NONE && submode != mpsoc::Submode::REPLAY && + submode != mpsoc::Submode::SNAPSHOT) { + return HasModesIF::INVALID_SUBMODE; + } + } + if (submode == mpsoc::Submode::SNAPSHOT and + powerSwitcher.getSwitchState(camSwitchId) != PowerSwitchIF::SWITCH_ON) { + triggerEvent(mpsoc::CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE); + return HasModesIF::TRANS_NOT_ALLOWED; + } + *msToReachTheMode = MPSOC_MODE_CMD_TIMEOUT_MS; + return returnvalue::OK; +} + +// Action override. Forward to user. +ReturnValue_t FreshMpsocHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + ReturnValue_t result = returnvalue::OK; + switch (actionId) { + case mpsoc::SET_UART_TX_TRISTATE: { + uartIsolatorSwitch.pullLow(); + return EXECUTION_FINISHED; + break; + } + case mpsoc::RELEASE_UART_TX: { + uartIsolatorSwitch.pullHigh(); + return EXECUTION_FINISHED; + break; + default: + break; + } + } + + if (specialComHelperExecuting) { + return mpsoc::MPSOC_HELPER_EXECUTING; + } + + // We do not accept the rest of the commands if we are not on. + if (mode != MODE_ON && mode != MODE_NORMAL) { + return HasModesIF::INVALID_MODE; + } + + switch (actionId) { + case mpsoc::TC_FLASH_WRITE_FULL_FILE: { + mpsoc::FlashBasePusCmd flashWritePusCmd; + result = flashWritePusCmd.extractFields(data, size); + if (result != returnvalue::OK) { + return result; + } + result = specialComHelper.startFlashWrite(flashWritePusCmd.getObcFile(), + flashWritePusCmd.getMpsocFile()); + if (result != returnvalue::OK) { + return result; + } + commonSpecialComInit(); + return EXECUTION_FINISHED; + } + case mpsoc::TC_FLASH_READ_FULL_FILE: { + mpsoc::FlashReadPusCmd flashReadPusCmd; + result = flashReadPusCmd.extractFields(data, size); + if (result != returnvalue::OK) { + return result; + } + result = specialComHelper.startFlashRead(flashReadPusCmd.getObcFile(), + flashReadPusCmd.getMpsocFile(), + flashReadPusCmd.getReadSize()); + if (result != returnvalue::OK) { + return result; + } + sif::info << "PLOC MPSoC: Reading " << flashReadPusCmd.getMpsocFile() << " with size " + << flashReadPusCmd.getReadSize() << " to " << flashReadPusCmd.getObcFile() + << std::endl; + commonSpecialComInit(); + return EXECUTION_FINISHED; + } + case (mpsoc::OBSW_RESET_SEQ_COUNT_LEGACY): { + commandSequenceCount = 0; + return EXECUTION_FINISHED; + } + default: + break; + } + executeRegularCmd(actionId, commandedBy, data, size); + return returnvalue::OK; +} + +void FreshMpsocHandler::commonSpecialComInit() { + specialComHelperExecuting = true; + specialComHelper.setCommandSequenceCount(commandSequenceCount.get()); +} + +/** + * @overload + * @param submode + */ +void FreshMpsocHandler::startTransition(Mode_t newMode, Submode_t submode) { + // OFF commands are always accepted. Otherwise, ignore transition requests. + if (transitionState != TransitionState::NONE && newMode != HasModesIF::MODE_OFF) { + return; + } + // We are already on and only a submode change is commanded. + if ((mode == MODE_ON or mode == MODE_NORMAL) && (newMode == MODE_ON or newMode == MODE_NORMAL)) { + transitionState = TransitionState::SUBMODE; + } else if ((newMode == MODE_ON or newMode == MODE_NORMAL) && + ((mode == MODE_OFF) or (mode == MODE_UNDEFINED))) { + transitionState = TransitionState::TO_ON; + } else if (mode == MODE_ON && newMode == MODE_NORMAL) { + hkReport.setReportingEnabled(true); + } else if (mode == MODE_NORMAL && newMode == MODE_ON) { + hkReport.setReportingEnabled(false); + } else if (newMode == MODE_OFF) { + transitionState = TransitionState::TO_OFF; + } + targetMode = newMode; + targetSubmode = submode; +} + +ReturnValue_t FreshMpsocHandler::performDeviceOperationPreQueueHandling(uint8_t opCode) { + return returnvalue::OK; +} + +void FreshMpsocHandler::commandSubmodeTransition() { + if (targetSubmode == mpsoc::Submode::IDLE_OR_NONE) { + commandTcModeIdle(); + } else if (targetSubmode == mpsoc::Submode::SNAPSHOT) { + commandTcModeSnapshot(); + } else if (targetSubmode == mpsoc::Submode::REPLAY) { + commandTcModeReplay(); + } else { + sif::error << "FreshMpsocHandler::handleTransitionToOn: Invalid submode" << std::endl; + } +} + +void FreshMpsocHandler::handleTransitionToOn() { + if (startupState == StartupState::IDLE) { + startupState = StartupState::HW_INIT; + } + if (startupState == StartupState::HW_INIT) { + if (handleHwStartup()) { + startupState = StartupState::DONE; + } + } + if (startupState == StartupState::DONE) { + setMode(targetMode, targetSubmode); + transitionState = TransitionState::NONE; + if (targetMode == MODE_NORMAL) { + hkReport.setReportingEnabled(true); + } + powerState = PowerState::IDLE; + startupState = StartupState::IDLE; + } +} + +void FreshMpsocHandler::handleTransitionToOff() { + if (handleHwShutdown()) { + hkReport.setReportingEnabled(false); + setMode(MODE_OFF, 0); + transitionState = TransitionState::NONE; + activeCmdInfo.reset(); + powerState = PowerState::IDLE; + startupState = StartupState::IDLE; + } +} + +MessageQueueIF* FreshMpsocHandler::getCommandQueuePtr() { return commandActionHelperQueue; } + +void FreshMpsocHandler::stepSuccessfulReceived(ActionId_t actionId, uint8_t step) { return; } + +void FreshMpsocHandler::stepFailedReceived(ActionId_t actionId, uint8_t step, + ReturnValue_t returnCode) { + switch (actionId) { + case supv::START_MPSOC: { + sif::warning << "PlocMPSoCHandler::stepFailedReceived: Failed to start MPSoC" << std::endl; + break; + } + case supv::SHUTDOWN_MPSOC: { + triggerEvent(mpsoc::MPSOC_SHUTDOWN_FAILED); + sif::warning << "PlocMPSoCHandler::stepFailedReceived: Failed to shutdown MPSoC" << std::endl; + break; + } + default: + sif::debug << "PlocMPSoCHandler::stepFailedReceived: Received unexpected action reply" + << std::endl; + break; + } + powerState = PowerState::SUPV_FAILED; +} + +void FreshMpsocHandler::dataReceived(ActionId_t actionId, const uint8_t* data, uint32_t size) { + return; +} + +void FreshMpsocHandler::completionSuccessfulReceived(ActionId_t actionId) { + switch (powerState) { + case PowerState::PENDING_STARTUP: { + if (actionId != supv::START_MPSOC) { + return; + } + mpsocBootTransitionCd.resetTimer(); + powerState = PowerState::DONE; + break; + } + case PowerState::PENDING_SHUTDOWN: { + if (actionId != supv::SHUTDOWN_MPSOC) { + return; + } + powerState = PowerState::DONE; + break; + } + default: { + break; + } + } +} + +void FreshMpsocHandler::completionFailedReceived(ActionId_t actionId, ReturnValue_t returnCode) { + handleActionCommandFailure(actionId, returnCode); +} + +void FreshMpsocHandler::handleActionCommandFailure(ActionId_t actionId, ReturnValue_t returnCode) { + switch (powerState) { + case PowerState::PENDING_STARTUP: { + if (actionId != supv::START_MPSOC) { + return; + } + sif::info << "FreshMpsocHandler::handleActionCommandFailure: MPSoC boot command failed" + << std::endl; + // This is commonly the case when the MPSoC is already operational. Thus the power state is + // set to on here + break; + } + case PowerState::PENDING_SHUTDOWN: { + // FDIR will intercept event and switch PLOC power off + triggerEvent(mpsoc::MPSOC_SHUTDOWN_FAILED); + if (actionId != supv::SHUTDOWN_MPSOC) { + return; + } + sif::warning << "PlocMPSoCHandler::handleActionCommandFailure: Failed to shutdown MPSoC" + << std::endl; + break; + } + default: + break; + } + powerState = PowerState::SUPV_FAILED; + return; +} + +ReturnValue_t FreshMpsocHandler::executeRegularCmd(ActionId_t actionId, + MessageQueueId_t commandedBy, + const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result; + switch (actionId) { + case (mpsoc::TC_MEM_WRITE): { + result = commandTcMemWrite(commandData, commandDataLen); + break; + } + case (mpsoc::TC_VERIFY_BOOT): { + uint8_t cmdDataForDeadbeefCheck[6]{}; + size_t serLen = 0; + uint16_t wordLen = 1; + SerializeAdapter::serialize(&mpsoc::DEADBEEF_ADDR, cmdDataForDeadbeefCheck, &serLen, 4, + SerializeIF::Endianness::NETWORK); + SerializeAdapter::serialize(&wordLen, cmdDataForDeadbeefCheck + 4, &serLen, 2, + SerializeIF::Endianness::NETWORK); + result = commandTcMemRead(cmdDataForDeadbeefCheck, 6); + break; + } + case (mpsoc::TC_MEM_READ): { + result = commandTcMemRead(commandData, commandDataLen); + break; + } + case (mpsoc::TC_FLASHFOPEN): { + mpsoc::TcFlashFopen cmd(spParams, commandSequenceCount); + // C string constructor. + std::string filename = std::string(reinterpret_cast(commandData)); + if (filename.size() > mpsoc::MAX_FILENAME_SIZE) { + return mpsoc::NAME_TOO_LONG; + } + uint8_t mode = commandData[filename.size() + 2]; + cmd.setPayload(filename, mode); + result = finishAndSendTc(actionId, cmd); + break; + } + case (mpsoc::TC_FLASHFCLOSE): { + mpsoc::TcFlashFclose cmd(spParams, commandSequenceCount); + result = finishAndSendTc(actionId, cmd); + break; + } + case (mpsoc::TC_FLASHDELETE): { + result = commandTcFlashDelete(commandData, commandDataLen); + break; + } + case (mpsoc::TC_REPLAY_START): { + result = commandTcReplayStart(commandData, commandDataLen); + break; + } + case (mpsoc::TC_REPLAY_STOP): { + result = commandTcReplayStop(); + break; + } + case (mpsoc::TC_DOWNLINK_PWR_ON): { + result = commandTcDownlinkPwrOn(commandData, commandDataLen); + break; + } + case (mpsoc::TC_DOWNLINK_PWR_OFF): { + result = commandTcDownlinkPwrOff(); + break; + } + case (mpsoc::TC_REPLAY_WRITE_SEQUENCE): { + result = commandTcReplayWriteSequence(commandData, commandDataLen); + break; + } + case (mpsoc::TC_ENABLE_TC_EXECTION): { + mpsoc::TcEnableTcExec cmd(spParams, commandSequenceCount); + result = cmd.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + result = finishAndSendTc(actionId, cmd); + break; + } + case (mpsoc::TC_FLASH_MKFS): { + if (commandDataLen != 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + if (commandData[0] != mpsoc::FlashId::FLASH_0 && commandData[1] != mpsoc::FlashId::FLASH_1) { + return HasActionsIF::INVALID_PARAMETERS; + } + mpsoc::TcFlashMkfs cmd(spParams, commandSequenceCount, + static_cast(commandData[0])); + sif::info << "PLOC MPSoC: Formatting Flash " << (int)commandData[0] << std::endl; + result = finishAndSendTc(actionId, cmd, mpsoc::CMD_TIMEOUT_MKFS); + break; + } + case (mpsoc::TC_GET_HK_REPORT): { + result = commandTcGetHkReport(); + break; + } + case (mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT): { + result = commandTcGetDirContent(commandData, commandDataLen); + break; + } + case (mpsoc::TC_CAM_CMD_SEND): { + result = commandTcCamCmdSend(commandData, commandDataLen); + break; + } + case (mpsoc::TC_CAM_TAKE_PIC): { + result = commandTcCamTakePic(commandData, commandDataLen); + break; + } + case (mpsoc::TC_SIMPLEX_STREAM_FILE): { + if (submode != mpsoc::Submode::SNAPSHOT) { + return HasModesIF::INVALID_SUBMODE; + } + result = commandTcSimplexStreamFile(commandData, commandDataLen); + break; + } + case (mpsoc::TC_SPLIT_FILE): { + result = commandTcSplitFile(commandData, commandDataLen); + break; + } + case (mpsoc::TC_DOWNLINK_DATA_MODULATE): { + result = commandTcDownlinkDataModulate(commandData, commandDataLen); + break; + } + default: + sif::debug << "PlocMPSoCHandler::buildCommandFromCommand: Command not implemented" + << std::endl; + result = DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + break; + } + + if (result == returnvalue::OK) { + commandInitHandling(actionId, commandedBy); + } + + return result; +} + +void FreshMpsocHandler::commandInitHandling(ActionId_t actionId, MessageQueueId_t commandedBy) { + activeCmdInfo.start(actionId, commandedBy); + /** + * Flushing the receive buffer to make sure there are no data left from a faulty reply. + */ + comInterface.getComHelper().flushUartRxBuffer(); +} + +ReturnValue_t FreshMpsocHandler::commandTcMemWrite(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + mpsoc::TcMemWrite tcMemWrite(spParams, commandSequenceCount); + result = tcMemWrite.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_MEM_WRITE, tcMemWrite); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcMemRead(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + mpsoc::TcMemRead tcMemRead(spParams, commandSequenceCount); + result = tcMemRead.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_MEM_READ, tcMemRead); + tmMemReadReport.rememberRequestedSize = tcMemRead.getMemLen() * 4 + TmMemReadReport::FIX_SIZE; + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcFlashDelete(const uint8_t* commandData, + size_t commandDataLen) { + if (commandDataLen > mpsoc::FILENAME_FIELD_SIZE) { + return mpsoc::NAME_TOO_LONG; + } + ReturnValue_t result = returnvalue::OK; + mpsoc::TcFlashDelete tcFlashDelete(spParams, commandSequenceCount); + std::string filename = std::string(reinterpret_cast(commandData), commandDataLen); + result = tcFlashDelete.setPayload(filename); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_FLASHDELETE, tcFlashDelete); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcReplayStart(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + mpsoc::TcReplayStart tcReplayStart(spParams, commandSequenceCount); + result = tcReplayStart.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_REPLAY_START, tcReplayStart); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcReplayStop() { + mpsoc::TcReplayStop tcReplayStop(spParams, commandSequenceCount); + finishAndSendTc(mpsoc::TC_REPLAY_STOP, tcReplayStop); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcDownlinkPwrOn(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + mpsoc::TcDownlinkPwrOn tcDownlinkPwrOn(spParams, commandSequenceCount); + result = tcDownlinkPwrOn.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_DOWNLINK_PWR_ON, tcDownlinkPwrOn); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcDownlinkPwrOff() { + mpsoc::TcDownlinkPwrOff tcDownlinkPwrOff(spParams, commandSequenceCount); + finishAndSendTc(mpsoc::TC_DOWNLINK_PWR_OFF, tcDownlinkPwrOff); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcGetHkReport() { + mpsoc::TcGetHkReport tcGetHkReport(spParams, commandSequenceCount); + finishAndSendTc(mpsoc::TC_GET_HK_REPORT, tcGetHkReport); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcReplayWriteSequence(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcReplayWriteSeq tcReplayWriteSeq(spParams, commandSequenceCount); + ReturnValue_t result = tcReplayWriteSeq.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_REPLAY_WRITE_SEQUENCE, tcReplayWriteSeq); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcModeReplay() { + mpsoc::TcModeReplay tcModeReplay(spParams, commandSequenceCount); + finishAndSendTc(mpsoc::TC_MODE_REPLAY, tcModeReplay); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcModeIdle() { + mpsoc::TcModeIdle tcModeIdle(spParams, commandSequenceCount); + finishAndSendTc(mpsoc::TC_MODE_IDLE, tcModeIdle); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcCamCmdSend(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcCamcmdSend tcCamCmdSend(spParams, commandSequenceCount); + ReturnValue_t result = tcCamCmdSend.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_CAM_CMD_SEND, tcCamCmdSend); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcCamTakePic(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcCamTakePic tcCamTakePic(spParams, commandSequenceCount); + ReturnValue_t result = tcCamTakePic.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + sif::info << "PLOC MPSoC Take Picture Command" << std::endl; + sif::info << "filename: " << tcCamTakePic.fileName << std::endl; + sif::info << "encoder [Y, Cb, Cr]: [" << (int)tcCamTakePic.encoderSettingY << ", " + << (int)tcCamTakePic.encoderSettingsCb << ", " << (int)tcCamTakePic.encoderSettingsCr + << "]" << std::endl; + sif::info << "quantization [Y, Cb, Cr]: [" << tcCamTakePic.quantizationY << ", " + << tcCamTakePic.quantizationCb << ", " << tcCamTakePic.quantizationCr << "]" + << std::endl; + sif::info << "bypass compressor: " << (int)tcCamTakePic.bypassCompressor << std::endl; + finishAndSendTc(mpsoc::TC_CAM_TAKE_PIC, tcCamTakePic); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcSimplexStreamFile(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcSimplexStreamFile tcSimplexStreamFile(spParams, commandSequenceCount); + ReturnValue_t result = tcSimplexStreamFile.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_SIMPLEX_STREAM_FILE, tcSimplexStreamFile); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcSplitFile(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcSplitFile tcSplitFile(spParams, commandSequenceCount); + ReturnValue_t result = tcSplitFile.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_SPLIT_FILE, tcSplitFile); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcGetDirContent(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcGetDirContent tcGetDirContent(spParams, commandSequenceCount); + ReturnValue_t result = tcGetDirContent.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_FLASH_GET_DIRECTORY_CONTENT, tcGetDirContent); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcDownlinkDataModulate(const uint8_t* commandData, + size_t commandDataLen) { + mpsoc::TcDownlinkDataModulate tcDownlinkDataModulate(spParams, commandSequenceCount); + ReturnValue_t result = tcDownlinkDataModulate.setPayload(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + finishAndSendTc(mpsoc::TC_DOWNLINK_DATA_MODULATE, tcDownlinkDataModulate); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::commandTcModeSnapshot() { + mpsoc::TcModeSnapshot tcModeSnapshot(spParams, commandSequenceCount); + finishAndSendTc(mpsoc::TC_MODE_SNAPSHOT, tcModeSnapshot); + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::finishAndSendTc(DeviceCommandId_t cmdId, mpsoc::TcBase& tcBase, + uint32_t cmdCountdownMs) { + // Emit warning but still send command. + if (specialComHelperExecuting) { + sif::warning << "PLOC MPSoC: Sending command even though special COM helper is executing" + << std::endl; + } + ReturnValue_t result = tcBase.finishPacket(); + if (result != returnvalue::OK) { + return result; + } + commandSequenceCount++; + + mpsoc::printTxPacket(tcBase); + activeCmdInfo.cmdCountdown.setTimeout(cmdCountdownMs); + activeCmdInfo.cmdCountdown.resetTimer(); + activeCmdInfo.pending = true; + activeCmdInfo.pendingCmd = cmdId; + activeCmdInfo.pendingCmdMpsocApid = tcBase.getApid(); + return comInterface.send(tcBase.getFullPacket(), tcBase.getFullPacketLen()); +} + +void FreshMpsocHandler::handleEvent(EventMessage* eventMessage) { + object_id_t objectId = eventMessage->getReporter(); + switch (objectId) { + case objects::PLOC_MPSOC_HELPER: { + commonSpecialComStop(); + break; + } + default: + sif::debug << "PlocMPSoCHandler::handleEvent: Did not subscribe to this event" << std::endl; + break; + } +} +void FreshMpsocHandler::commonSpecialComStop() { + specialComHelperExecuting = false; + commandSequenceCount.set(specialComHelper.getCommandSequenceCount()); +} + +void FreshMpsocHandler::cmdDoneHandler(bool success, ReturnValue_t result) { + if (transitionState == TransitionState::SUBMODE) { + if (success) { + setMode(targetMode, targetSubmode); + } else { + // Keep the old submode. + setMode(targetMode); + } + transitionState = TransitionState::NONE; + } + if (activeCmdInfo.pending && (activeCmdInfo.commandedBy != MessageQueueIF::NO_QUEUE)) { + actionHelper.finish(success, activeCmdInfo.commandedBy, activeCmdInfo.pendingCmd, result); + } + activeCmdInfo.reset(); +} + +ReturnValue_t FreshMpsocHandler::handleDeviceReply() { + ReturnValue_t result = returnvalue::OK; + + const auto& replyReader = comInterface.getSpReader(); + if (replyReader.isNull()) { + return returnvalue::FAILED; + } + mpsoc::printRxPacket(replyReader); + uint16_t apid = replyReader.getApid(); + + switch (apid) { + case (mpsoc::apid::ACK_SUCCESS): + result = handleAckReport(); + break; + case (mpsoc::apid::ACK_FAILURE): + break; + case (mpsoc::apid::TM_MEMORY_READ_REPORT): + if (activeCmdInfo.pendingCmd == mpsoc::TC_VERIFY_BOOT) { + // 6 byte header, 4 byte address, 2 byte read width, 4 byte read back value + if (replyReader.getFullPacketLen() >= 6 + 4 + 2 + 4) { + uint32_t readBack = 0; + size_t deserLen = 0; + result = SerializeAdapter::deSerialize(&readBack, replyReader.getFullData() + 6 + 4 + 2, + &deserLen, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK or readBack != mpsoc::DEADBEEF_VALUE) { + cmdDoneHandler(false, result); + } else { + cmdDoneHandler(true, returnvalue::OK); + } + } else { + cmdDoneHandler(false, result); + } + } + result = reportReplyData(mpsoc::TM_MEMORY_READ_REPORT); + break; + case (mpsoc::apid::TM_CAM_CMD_RPT): + result = reportReplyData(mpsoc::TM_CAM_CMD_RPT); + break; + case (mpsoc::apid::TM_HK_GET_REPORT): { + result = handleGetHkReport(); + break; + } + case (mpsoc::apid::TM_FLASH_DIRECTORY_CONTENT): { + result = reportReplyData(mpsoc::TM_FLASH_DIRECTORY_CONTENT); + break; + } + case (mpsoc::apid::EXE_SUCCESS): + case (mpsoc::apid::EXE_FAILURE): { + result = handleExecutionReport(); + break; + } + default: { + sif::debug << "FreshMpsocHandler:: Reply has invalid APID 0x" << std::hex << std::setfill('0') + << std::setw(2) << apid << std::dec << std::endl; + return mpsoc::INVALID_APID; + } + } + + uint16_t sequenceCount = replyReader.getSequenceCount(); + if (sequenceCount != lastReplySequenceCount + 1) { + // We could trigger event for possible missing reply packet to inform operator, but I don't + // think this is properly implemented and used on the MPSoC side anymore. + } + lastReplySequenceCount = sequenceCount; + + return result; +} + +ReturnValue_t FreshMpsocHandler::handleExecutionReport() { + ReturnValue_t result = returnvalue::OK; + auto& replyReader = comInterface.getSpReader(); + + switch (replyReader.getApid()) { + case (mpsoc::apid::EXE_SUCCESS): { + cmdDoneHandler(true, result); + break; + } + case (mpsoc::apid::EXE_FAILURE): { + DeviceCommandId_t commandId = activeCmdInfo.pendingCmd; + // Ensure idempotency: If the device is already in the mode, the command was successful. + if (mpsoc::getStatusFromRawData(replyReader.getFullData()) == + mpsoc::statusCode::TC_NOT_ALLOWED_IN_MODE) { + if ((activeCmdInfo.pendingCmdMpsocApid == mpsoc::apid::TC_MODE_SNAPSHOT && + submode == mpsoc::Submode::SNAPSHOT) or + (activeCmdInfo.pendingCmdMpsocApid == mpsoc::apid::TC_MODE_IDLE && + submode == mpsoc::Submode::IDLE_OR_NONE) or + (activeCmdInfo.pendingCmdMpsocApid == mpsoc::apid::TC_MODE_REPLAY && + submode == mpsoc::Submode::REPLAY)) { + cmdDoneHandler(true, returnvalue::OK); + return result; + } + } + if (commandId == DeviceHandlerIF::NO_COMMAND_ID) { + sif::warning << "FreshMpsocHandler::handleExecutionReport: Unknown Command ID" << std::endl; + } + uint16_t status = mpsoc::getStatusFromRawData(replyReader.getFullData()); + sif::warning << "MPSoC EXE Failure: " << mpsoc::getStatusString(status) << std::endl; + triggerEvent(mpsoc::EXE_FAILURE, commandId, status); + cmdDoneHandler(false, mpsoc::RECEIVED_EXE_FAILURE); + break; + } + default: { + sif::warning << "PlocMPSoCHandler::handleExecutionReport: Unknown APID" << std::endl; + result = returnvalue::FAILED; + break; + } + } + return result; +} + +ReturnValue_t FreshMpsocHandler::handleAckReport() { + ReturnValue_t result = returnvalue::OK; + auto& replyReader = comInterface.getSpReader(); + + switch (replyReader.getApid()) { + case mpsoc::apid::ACK_FAILURE: { + uint16_t status = mpsoc::getStatusFromRawData(replyReader.getFullData()); + sif::warning << "MPSoC ACK Failure: " << mpsoc::getStatusString(status) << std::endl; + triggerEvent(mpsoc::ACK_FAILURE, activeCmdInfo.pendingCmd, status); + cmdDoneHandler(false, status); + break; + } + case mpsoc::apid::ACK_SUCCESS: { + break; + } + default: { + sif::error << "FreshMpsocHandler::handleAckReport: Invalid APID in ACK report" << std::endl; + result = returnvalue::FAILED; + break; + } + } + + return result; +} + +ReturnValue_t FreshMpsocHandler::getParameter(uint8_t domainId, uint8_t uniqueId, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, + uint16_t startAtIndex) { + if (uniqueId == mpsoc::ParamId::SKIP_SUPV_ON_COMMANDING) { + uint8_t value = 0; + newValues->getElement(&value); + if (value > 1) { + return HasParametersIF::INVALID_VALUE; + } + parameterWrapper->set(skipSupvCommandingToOn); + return returnvalue::OK; + } + return FreshDeviceHandlerBase::getParameter(domainId, uniqueId, parameterWrapper, newValues, + startAtIndex); +} + +ReturnValue_t FreshMpsocHandler::reportReplyData(DeviceCommandId_t tmId) { + auto& replyReader = comInterface.getSpReader(); + if (activeCmdInfo.commandedBy != MessageQueueIF::NO_QUEUE) { + return actionHelper.reportData( + activeCmdInfo.commandedBy, tmId, replyReader.getFullData() + mpsoc::DATA_FIELD_OFFSET, + replyReader.getFullPacketLen() - mpsoc::DATA_FIELD_OFFSET - mpsoc::CRC_SIZE); + } + return returnvalue::OK; +} + +ReturnValue_t FreshMpsocHandler::handleGetHkReport() { + auto& spReader = comInterface.getSpReader(); + const uint8_t* dataStart = spReader.getFullData() + 6; + PoolReadGuard pg(&hkReport); + size_t deserLen = mpsoc::SIZE_TM_HK_REPORT; + SerializeIF::Endianness endianness = SerializeIF::Endianness::NETWORK; + ReturnValue_t result = + SerializeAdapter::deSerialize(&hkReport.status.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.mode.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.downlinkPwrOn.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.downlinkReplyActive.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.downlinkJesdSyncStatus.value, &dataStart, + &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.downlinkDacStatus.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.camStatus.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.camSdiStatus.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.camFpgaTemp.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonTemp.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccInt.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccAux.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccBram.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPaux.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPint.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVccPdro.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonMb12V.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonMb3V3.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonMb1V8.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc12V.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.sysmonVcc5V.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc3V3.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc3V3VA.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc2V5DDR.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc1V2DDR.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc0V9.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonVcc0V6VTT.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonSafeCotsCur.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.sysmonNvm4XoCur.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.semUncorrectableErrs.value, &dataStart, + &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&hkReport.semCorrectableErrs.value, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + result = + SerializeAdapter::deSerialize(&hkReport.semStatus.value, &dataStart, &deserLen, endianness); + if (result != returnvalue::OK) { + return result; + } + // Skip the weird filename + dataStart += 256; + result = SerializeAdapter::deSerialize(&hkReport.rebootMpsocRequired, &dataStart, &deserLen, + endianness); + if (result != returnvalue::OK) { + return result; + } + hkReport.setValidity(true, true); + return returnvalue::OK; +} + +bool FreshMpsocHandler::handleHwStartup() { +#if OBSW_MPSOC_JTAG_BOOT == 1 + uartIsolatorSwitch.pullHigh(); + startupState = StartupState::WAIT_CYCLES; + return true; +#endif + if (powerState == PowerState::IDLE) { + if (skipSupvCommandingToOn) { + powerState = PowerState::DONE; + } else { + if (supv::SUPV_ON) { + commandActionHelper.commandAction(supervisorHandler, supv::START_MPSOC); + supvTransitionCd.resetTimer(); + powerState = PowerState::PENDING_STARTUP; + } else { + triggerEvent(mpsoc::SUPV_NOT_ON, 1); + // Set back to OFF for now, failing the transition. + setMode(MODE_OFF); + } + } + } + if (powerState == PowerState::SUPV_FAILED) { + setMode(MODE_OFF); + powerState = PowerState::IDLE; + transitionState = TransitionState::NONE; + return false; + } + if (powerState == PowerState::PENDING_STARTUP) { + if (supvTransitionCd.hasTimedOut()) { + // Process with transition nonetheless.. + triggerEvent(mpsoc::SUPV_REPLY_TIMEOUT); + powerState = PowerState::DONE; + } else { + return false; + } + } + if (powerState == PowerState::DONE) { + // Wait a bit for the MPSoC to fully boot. + if (!mpsocBootTransitionCd.hasTimedOut()) { + return false; + } + uartIsolatorSwitch.pullHigh(); + comInterface.getComHelper().flushUartTxAndRxBuf(); + powerState = PowerState::IDLE; + } + return true; +} + +bool FreshMpsocHandler::handleHwShutdown() { + stopSpecialComHelper(); + uartIsolatorSwitch.pullLow(); +#if OBSW_MPSOC_JTAG_BOOT == 1 + powerState = PowerState::DONE; + return true; +#endif + + if (powerState == PowerState::IDLE) { + if (supv::SUPV_ON) { + commandActionHelper.commandAction(supervisorHandler, supv::SHUTDOWN_MPSOC); + supvTransitionCd.resetTimer(); + powerState = PowerState::PENDING_SHUTDOWN; + } else { + if ((this->mode != MODE_OFF) and (this->mode != MODE_UNDEFINED)) { + triggerEvent(mpsoc::SUPV_NOT_ON, 0); + } + powerState = PowerState::DONE; + } + } + if (powerState == PowerState::PENDING_SHUTDOWN) { + if (supvTransitionCd.hasTimedOut()) { + powerState = PowerState::DONE; + // Process with transition nonetheless.. + triggerEvent(mpsoc::SUPV_REPLY_TIMEOUT); + return true; + } else { + // Wait till power state is OFF. + return false; + } + } + return true; +} + +void FreshMpsocHandler::stopSpecialComHelper() { + specialComHelper.stopProcess(); + specialComHelperExecuting = false; +} diff --git a/linux/payload/FreshMpsocHandler.h b/linux/payload/FreshMpsocHandler.h new file mode 100644 index 0000000..fce42de --- /dev/null +++ b/linux/payload/FreshMpsocHandler.h @@ -0,0 +1,212 @@ +#include "fsfw/action/ActionMessage.h" +#include "fsfw/action/CommandsActionsIF.h" +#include "fsfw/devicehandlers/DeviceHandlerIF.h" +#include "fsfw/devicehandlers/FreshDeviceHandlerBase.h" +#include "fsfw/ipc/MessageQueueIF.h" +#include "fsfw/ipc/messageQueueDefinitions.h" +#include "fsfw/modes/ModeMessage.h" +#include "fsfw/objectmanager/SystemObjectIF.h" +#include "fsfw/power/PowerSwitchIF.h" +#include "fsfw/power/definitions.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw_hal/linux/gpio/Gpio.h" +#include "linux/payload/MpsocCommunication.h" +#include "linux/payload/PlocMpsocSpecialComHelper.h" +#include "linux/payload/plocMpsocHelpers.h" + +class FreshMpsocHandler : public FreshDeviceHandlerBase, public CommandsActionsIF { + public: + enum OpCode { DEFAULT_OPERATION = 0, PARSE_TM = 1 }; + static constexpr uint32_t MPSOC_MODE_CMD_TIMEOUT_MS = 120000; + + FreshMpsocHandler(DhbConfig cfg, MpsocCommunication& comInterface, + PlocMpsocSpecialComHelper& specialComHelper, Gpio uartIsolatorSwitch, + object_id_t supervisorHandler, PowerSwitchIF& powerSwitcher, + power::Switch_t camSwitchId); + + /** + * Periodic helper executed function, implemented by child class. + */ + void performDeviceOperation(uint8_t opCode) override; + + void performDefaultDeviceOperation(); + + /** + * Implemented by child class. Handle all command messages which are + * not health, mode, action or housekeeping messages. + * @param message + * @return + */ + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + + ReturnValue_t initialize() override; + + private: + enum class StartupState { IDLE, HW_INIT, DONE } startupState = StartupState::IDLE; + enum class PowerState { IDLE, PENDING_STARTUP, PENDING_SHUTDOWN, SUPV_FAILED, DONE }; + + enum TransitionState { NONE, TO_ON, TO_OFF, SUBMODE } transitionState = TransitionState::NONE; + MpsocCommunication& comInterface; + PlocMpsocSpecialComHelper& specialComHelper; + MessageQueueIF* eventQueue = nullptr; + SourceSequenceCounter commandSequenceCount = SourceSequenceCounter(0); + MessageQueueIF* commandActionHelperQueue = nullptr; + CommandActionHelper commandActionHelper; + Gpio uartIsolatorSwitch; + mpsoc::HkReport hkReport; + object_id_t supervisorHandler; + + Countdown mpsocBootTransitionCd = Countdown(6500); + Countdown supvTransitionCd = Countdown(3000); + + PoolEntry peStatus = PoolEntry(); + PoolEntry peMode = PoolEntry(); + PoolEntry peDownlinkPwrOn = PoolEntry(); + PoolEntry peDownlinkReplyActive = PoolEntry(); + PoolEntry peDownlinkJesdSyncStatus = PoolEntry(); + PoolEntry peDownlinkDacStatus = PoolEntry(); + PoolEntry peCameraStatus = PoolEntry(); + PoolEntry peCameraSdiStatus = PoolEntry(); + PoolEntry peCameraFpgaTemp = PoolEntry(); + PoolEntry peCameraSocTemp = PoolEntry(); + PoolEntry peSysmonTemp = PoolEntry(); + PoolEntry peSysmonVccInt = PoolEntry(); + PoolEntry peSysmonVccAux = PoolEntry(); + PoolEntry peSysmonVccBram = PoolEntry(); + PoolEntry peSysmonVccPaux = PoolEntry(); + PoolEntry peSysmonVccPint = PoolEntry(); + PoolEntry peSysmonVccPdro = PoolEntry(); + PoolEntry peSysmonMb12V = PoolEntry(); + PoolEntry peSysmonMb3V3 = PoolEntry(); + PoolEntry peSysmonMb1V8 = PoolEntry(); + PoolEntry peSysmonVcc12V = PoolEntry(); + PoolEntry peSysmonVcc5V = PoolEntry(); + PoolEntry peSysmonVcc3V3 = PoolEntry(); + PoolEntry peSysmonVcc3V3VA = PoolEntry(); + PoolEntry peSysmonVcc2V5DDR = PoolEntry(); + PoolEntry peSysmonVcc1V2DDR = PoolEntry(); + PoolEntry peSysmonVcc0V9 = PoolEntry(); + PoolEntry peSysmonVcc0V6VTT = PoolEntry(); + PoolEntry peSysmonSafeCotsCur = PoolEntry(); + PoolEntry peSysmonNvm4XoCur = PoolEntry(); + PoolEntry peSemUncorrectableErrs = PoolEntry(); + PoolEntry peSemCorrectableErrs = PoolEntry(); + PoolEntry peSemStatus = PoolEntry(); + PoolEntry peRebootMpsocRequired = PoolEntry(); + + PowerState powerState; + bool specialComHelperExecuting = false; + + struct ActionCommandInfo { + Countdown cmdCountdown = Countdown(mpsoc::DEFAULT_CMD_TIMEOUT_MS); + bool pending = false; + MessageQueueId_t commandedBy = MessageQueueIF::NO_QUEUE; + DeviceCommandId_t pendingCmd = DeviceHandlerIF::NO_COMMAND_ID; + uint16_t pendingCmdMpsocApid = 0; + + void reset() { + pending = false; + commandedBy = MessageQueueIF::NO_QUEUE; + pendingCmd = DeviceHandlerIF::NO_COMMAND_ID; + } + + void start(DeviceCommandId_t commandId, MessageQueueId_t commandedBy) { + pending = true; + cmdCountdown.resetTimer(); + pendingCmd = commandId; + this->commandedBy = commandedBy; + } + } activeCmdInfo; + + uint8_t commandBuffer[mpsoc::MAX_COMMAND_SIZE]; + SpacePacketCreator creator; + ploc::SpTcParams spParams = ploc::SpTcParams(creator); + Mode_t targetMode = HasModesIF::MODE_UNDEFINED; + Submode_t targetSubmode = 0; + + struct TmMemReadReport { + static const uint8_t FIX_SIZE = 14; + size_t rememberRequestedSize = 0; + }; + + TmMemReadReport tmMemReadReport; + uint32_t lastReplySequenceCount = 0; + uint8_t skipSupvCommandingToOn = false; + PowerSwitchIF& powerSwitcher; + power::Switch_t camSwitchId; + + // HK manager abstract functions. + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + // Mode abstract functions + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + // Action override. Forward to user. + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + + /** + * @overload + * @param submode + */ + void startTransition(Mode_t newMode, Submode_t submode) override; + + ReturnValue_t performDeviceOperationPreQueueHandling(uint8_t opCode) override; + + // CommandsActionsIF overrides. + MessageQueueIF* getCommandQueuePtr() override; + + void stepSuccessfulReceived(ActionId_t actionId, uint8_t step) override; + void stepFailedReceived(ActionId_t actionId, uint8_t step, ReturnValue_t returnCode) override; + void dataReceived(ActionId_t actionId, const uint8_t* data, uint32_t size) override; + void completionSuccessfulReceived(ActionId_t actionId) override; + void completionFailedReceived(ActionId_t actionId, ReturnValue_t returnCode) override; + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueId, ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, uint16_t startAtIndex) override; + + void handleActionCommandFailure(ActionId_t actionId, ReturnValue_t returnCode); + ReturnValue_t executeRegularCmd(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t dataLen); + void handleTransitionToOn(); + void handleTransitionToOff(); + + ReturnValue_t commandTcModeReplay(); + ReturnValue_t commandTcMemWrite(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcMemRead(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcFlashDelete(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcReplayStart(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcReplayStop(); + ReturnValue_t commandTcDownlinkPwrOn(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcDownlinkPwrOff(); + ReturnValue_t commandTcGetHkReport(); + ReturnValue_t commandTcGetDirContent(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcReplayWriteSequence(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcCamCmdSend(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcModeIdle(); + ReturnValue_t commandTcCamTakePic(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcSimplexStreamFile(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcSplitFile(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcDownlinkDataModulate(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t commandTcModeSnapshot(); + + ReturnValue_t finishAndSendTc(DeviceCommandId_t cmdId, mpsoc::TcBase& tcBase, + uint32_t cmdCountdown = mpsoc::DEFAULT_CMD_TIMEOUT_MS); + void handleEvent(EventMessage* eventMessage); + void cmdDoneHandler(bool success, ReturnValue_t result); + ReturnValue_t handleDeviceReply(); + ReturnValue_t handleAckReport(); + ReturnValue_t handleExecutionReport(); + void sendFailureReport(DeviceCommandId_t replyId, ReturnValue_t status); + ReturnValue_t reportReplyData(DeviceCommandId_t tmId); + ReturnValue_t handleGetHkReport(); + bool handleHwStartup(); + bool handleHwShutdown(); + + void stopSpecialComHelper(); + void commandSubmodeTransition(); + void commonSpecialComInit(); + void commonSpecialComStop(); + void commandInitHandling(ActionId_t actionId, MessageQueueId_t commandedBy); +}; diff --git a/linux/payload/FreshSupvHandler.cpp b/linux/payload/FreshSupvHandler.cpp new file mode 100644 index 0000000..ef980bb --- /dev/null +++ b/linux/payload/FreshSupvHandler.cpp @@ -0,0 +1,1607 @@ +#include "FreshSupvHandler.h" + +#include +#include + +#include +#include + +#include "eive/definitions.h" +#include "eive/objects.h" +#include "fsfw/action.h" +#include "fsfw/globalfunctions/arrayprinter.h" +#include "fsfw/ipc/CommandMessage.h" +#include "fsfw/ipc/MessageQueueIF.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/objectmanager/ObjectManagerIF.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tasks/TaskFactory.h" +#include "linux/payload/plocSupvDefs.h" + +using namespace supv; +using namespace returnvalue; + +std::atomic_bool supv::SUPV_ON = false; + +FreshSupvHandler::FreshSupvHandler(DhbConfig cfg, CookieIF* comCookie, Gpio uartIsolatorSwitch, + PowerSwitchIF& switchIF, power::Switch_t powerSwitch) + : FreshDeviceHandlerBase(cfg), + comCookie(comCookie), + switchIF(switchIF), + switchId(powerSwitch), + uartIsolatorSwitch(uartIsolatorSwitch), + hkSet(this), + bootStatusReport(this), + latchupStatusReport(this), + countersReport(this), + adcReport(this) { + spParams.buf = commandBuffer.data(); + spParams.maxSize = commandBuffer.size(); + eventQueue = QueueFactory::instance()->createMessageQueue(10); +} + +void FreshSupvHandler::performDeviceOperation(uint8_t opCode) { + if (not transitionActive and mode == MODE_OFF) { + // Nothing to do for now. + return; + } + if (opCode == OpCode::DEFAULT_OPERATION) { + EventMessage event; + for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::OK; + result = eventQueue->receiveMessage(&event)) { + switch (event.getMessageId()) { + case EventMessage::EVENT_MESSAGE: + handleEvent(&event); + break; + default: + sif::debug + << "PlocSupervisorHandler::performOperationHook: Did not subscribe to this event" + << " message" << std::endl; + break; + } + } + if (transitionActive) { + if (targetMode == MODE_ON or targetMode == MODE_NORMAL) { + handleTransitionToOn(); + } else if (targetMode == MODE_OFF) { + handleTransitionToOff(); + } else { + // This should never happen. + sif::error << "FreshSupvHandler: Invalid transition mode: " << targetMode << std::endl; + targetMode = MODE_OFF; + targetSubmode = 0; + handleTransitionToOff(); + } + } else { + // I think the SUPV is not able to process multiple commands consecutively, so only send + // normal command if no other command is pending. We handle the action queue first, which + // should ensure that these commands take precendence. + if (mode == MODE_NORMAL and not isCommandPending()) { + auto cmdIter = activeActionCmds.find( + buildActiveCmdKey(Apid::HK, static_cast(tc::HkId::GET_REPORT))); + if (cmdIter == activeActionCmds.end() or not cmdIter->second.isPending) { + spParams.buf = commandBuffer.data(); + commandedByCached = MessageQueueIF::NO_QUEUE; + sendEmptyCmd(supv::GET_HK_REPORT, Apid::HK, static_cast(tc::HkId::GET_REPORT), + true); + } + } + } + } else if (opCode == OpCode::PARSE_TM) { + for (auto& activeCmd : activeActionCmds) { + if (activeCmd.second.isPending and activeCmd.second.cmdCountdown.hasTimedOut()) { + if (activeCmd.second.commandedBy != MessageQueueIF::NO_QUEUE) { + actionHelper.finish(false, activeCmd.second.commandedBy, activeCmd.first, + DeviceHandlerIF::TIMEOUT); + } + activeCmd.second.isPending = false; + } + } + + parseTmPackets(); + } +} + +ReturnValue_t FreshSupvHandler::performDeviceOperationPreQueueHandling(uint8_t opCode) { + if (opCode != OpCode::DEFAULT_OPERATION) { + return returnvalue::OK; + } + // We parse for TM packets shortly before handling the queue, this might complete some packets, + // which then allows the handling of new action commands. + return parseTmPackets(); +} + +ReturnValue_t FreshSupvHandler::handleCommandMessage(CommandMessage* message) { + // No custom messages. + return returnvalue::FAILED; +} + +LocalPoolDataSetBase* FreshSupvHandler::getDataSetHandle(sid_t sid) { + if (sid == hkSet.getSid()) { + return &hkSet; + } else if (sid == bootStatusReport.getSid()) { + return &bootStatusReport; + } else if (sid == latchupStatusReport.getSid()) { + return &latchupStatusReport; + } else if (sid == countersReport.getSid()) { + return &countersReport; + } else if (sid == adcReport.getSid()) { + return &adcReport; + } + return nullptr; +} + +ReturnValue_t FreshSupvHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + // TODO: Copy code from god handler here. + localDataPoolMap.emplace(supv::NUM_TMS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TEMP_PS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TEMP_PL, new PoolEntry({0})); + localDataPoolMap.emplace(supv::HK_SOC_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::NVM0_1_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::NVM3_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MISSION_IO_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::FMC_STATE, &fmcStateEntry); + localDataPoolMap.emplace(supv::NUM_TCS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TEMP_SUP, &tempSupEntry); + localDataPoolMap.emplace(supv::UPTIME, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CPULOAD, new PoolEntry({0})); + localDataPoolMap.emplace(supv::AVAILABLEHEAP, new PoolEntry({0})); + + localDataPoolMap.emplace(supv::BR_SOC_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::POWER_CYCLES, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BOOT_AFTER_MS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BOOT_TIMEOUT_POOL_VAR_MS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::ACTIVE_NVM, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BP0_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BP1_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BP2_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BOOT_STATE, &bootStateEntry); + localDataPoolMap.emplace(supv::BOOT_CYCLES, &bootCyclesEntry); + + localDataPoolMap.emplace(supv::LATCHUP_ID, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT0, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT1, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT2, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT3, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT4, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT5, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CNT6, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_MSEC, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_SEC, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_MIN, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_HOUR, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_DAY, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_MON, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_TIME_YEAR, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LATCHUP_RPT_IS_SET, new PoolEntry({0})); + + localDataPoolMap.emplace(supv::SIGNATURE, new PoolEntry()); + localDataPoolMap.emplace(supv::LATCHUP_HAPPENED_CNTS, &latchupCounters); + localDataPoolMap.emplace(supv::ADC_DEVIATION_TRIGGERS_CNT, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TC_RECEIVED_CNT, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TM_AVAILABLE_CNT, new PoolEntry({0})); + localDataPoolMap.emplace(supv::SUPERVISOR_BOOTS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_BOOTS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_BOOT_FAILED_ATTEMPTS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_POWER_UP, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_UPDATES, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MPSOC_HEARTBEAT_RESETS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::CPU_WDT_RESETS, new PoolEntry({0})); + localDataPoolMap.emplace(supv::PS_HEARTBEATS_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::PL_HEARTBEATS_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::EB_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::BM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::LM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::AM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::TCTMM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::MM_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::HK_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::DL_TASK_LOST, new PoolEntry({0})); + localDataPoolMap.emplace(supv::RWS_TASKS_LOST, new PoolEntry(3)); + + localDataPoolMap.emplace(supv::ADC_RAW, &adcRawEntry); + localDataPoolMap.emplace(supv::ADC_ENG, &adcEngEntry); + + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(hkSet.getSid(), false, 10.0)); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + using namespace supv; + // The SUPV does not have any action commands where there is no communication with the device + // involved. + if (mode != MODE_ON and mode != MODE_NORMAL) { + return HasModesIF::INVALID_MODE; + } + ReturnValue_t result; + if (uartManager->longerRequestActive()) { + return result::SUPV_HELPER_EXECUTING; + } + + switch (actionId) { + case PERFORM_UPDATE: { + if (size > config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE) { + return result::FILENAME_TOO_LONG; + } + UpdateParams params; + result = extractUpdateCommand(data, size, params); + if (result != returnvalue::OK) { + return result; + } + result = uartManager->performUpdate(params); + if (result != returnvalue::OK) { + return result; + } + return EXECUTION_FINISHED; + } + case CONTINUE_UPDATE: { + uartManager->initiateUpdateContinuation(); + return EXECUTION_FINISHED; + } + case ABORT_LONGER_REQUEST: { + uartManager->stop(); + return EXECUTION_FINISHED; + } + case MEMORY_CHECK_WITH_FILE: { + UpdateParams params; + result = extractBaseParams(&data, size, params); + if (result != returnvalue::OK) { + return result; + } + if (not std::filesystem::exists(params.file)) { + return HasFileSystemIF::FILE_DOES_NOT_EXIST; + } + uartManager->performMemCheck(params.file, params.memId, params.startAddr); + return EXECUTION_FINISHED; + } + default: + break; + } + // The PLOC SUPV is not able to process multiple commands consecutively. + if (isCommandPending()) { + return HasActionsIF::IS_BUSY; + } + if (isCommandAlreadyActive(actionId)) { + return HasActionsIF::IS_BUSY; + } + spParams.buf = commandBuffer.data(); + this->commandedByCached = commandedBy; + switch (actionId) { + case GET_HK_REPORT: { + sendEmptyCmd(supv::GET_HK_REPORT, Apid::HK, static_cast(tc::HkId::GET_REPORT), true); + result = returnvalue::OK; + break; + } + case START_MPSOC: { + sif::info << "PLOC SUPV: Starting MPSoC" << std::endl; + sendEmptyCmd(supv::START_MPSOC, Apid::BOOT_MAN, + static_cast(tc::BootManId::START_MPSOC), false); + result = returnvalue::OK; + break; + } + case SHUTDOWN_MPSOC: { + sif::info << "PLOC SUPV: Shutting down MPSoC" << std::endl; + sendEmptyCmd(supv::SHUTDOWN_MPSOC, Apid::BOOT_MAN, + static_cast(tc::BootManId::SHUTDOWN_MPSOC), false); + result = returnvalue::OK; + break; + } + case SEL_MPSOC_BOOT_IMAGE: { + prepareSelBootImageCmd(data); + result = returnvalue::OK; + break; + } + case RESET_MPSOC: { + sif::info << "PLOC SUPV: Resetting MPSoC" << std::endl; + sendEmptyCmd(supv::RESET_MPSOC, Apid::BOOT_MAN, + static_cast(tc::BootManId::RESET_MPSOC), false); + result = returnvalue::OK; + break; + } + case SET_TIME_REF: { + result = prepareSetTimeRefCmd(); + break; + } + case SET_BOOT_TIMEOUT: { + prepareSetBootTimeoutCmd(data, size); + result = returnvalue::OK; + break; + } + case SET_MAX_RESTART_TRIES: { + prepareRestartTriesCmd(data, size); + result = returnvalue::OK; + break; + } + case DISABLE_PERIOIC_HK_TRANSMISSION: { + prepareDisableHk(); + result = returnvalue::OK; + break; + } + case GET_BOOT_STATUS_REPORT: { + sendEmptyCmd(supv::GET_BOOT_STATUS_REPORT, Apid::BOOT_MAN, + static_cast(tc::BootManId::GET_BOOT_STATUS_REPORT), true); + result = returnvalue::OK; + break; + } + case ENABLE_LATCHUP_ALERT: { + result = prepareLatchupConfigCmd(data, actionId, size); + break; + } + case DISABLE_LATCHUP_ALERT: { + result = prepareLatchupConfigCmd(data, actionId, size); + break; + } + case SET_ALERT_LIMIT: { + result = prepareSetAlertLimitCmd(data, size); + break; + } + case GET_LATCHUP_STATUS_REPORT: { + sendEmptyCmd(supv::GET_LATCHUP_STATUS_REPORT, Apid::LATCHUP_MON, + static_cast(tc::LatchupMonId::GET_STATUS_REPORT), true); + result = returnvalue::OK; + break; + } + case SET_GPIO: { + result = prepareSetGpioCmd(data, size); + break; + } + case FACTORY_RESET: { + result = prepareFactoryResetCmd(data, size); + break; + } + case READ_GPIO: { + result = prepareReadGpioCmd(data, size); + break; + } + case SET_SHUTDOWN_TIMEOUT: { + prepareSetShutdownTimeoutCmd(data, size); + result = returnvalue::OK; + break; + } + case FACTORY_FLASH: { + sendEmptyCmd(supv::FACTORY_FLASH, Apid::BOOT_MAN, + static_cast(tc::BootManId::FACTORY_FLASH), false); + result = returnvalue::OK; + break; + } + case RESET_PL: { + sendEmptyCmd(supv::RESET_PL, Apid::BOOT_MAN, static_cast(tc::BootManId::RESET_PL), + false); + result = returnvalue::OK; + break; + } + case SET_ADC_ENABLED_CHANNELS: { + prepareSetAdcEnabledChannelsCmd(data); + result = returnvalue::OK; + break; + } + case SET_ADC_WINDOW_AND_STRIDE: { + prepareSetAdcWindowAndStrideCmd(data); + result = returnvalue::OK; + break; + } + case SET_ADC_THRESHOLD: { + prepareSetAdcThresholdCmd(data); + result = returnvalue::OK; + break; + } + case WIPE_MRAM: { + result = prepareWipeMramCmd(data, size); + break; + } + case REQUEST_ADC_REPORT: { + sendEmptyCmd(supv::REQUEST_ADC_REPORT, Apid::ADC_MON, + static_cast(tc::AdcMonId::REQUEST_ADC_SAMPLE), true); + result = returnvalue::OK; + break; + } + case REQUEST_LOGGING_COUNTERS: { + sendEmptyCmd(supv::REQUEST_LOGGING_COUNTERS, Apid::DATA_LOGGER, + static_cast(tc::DataLoggerServiceId::REQUEST_COUNTERS), true); + result = returnvalue::OK; + break; + } + default: + sif::debug << "PlocSupervisorHandler::buildCommandFromCommand: Command not implemented" + << std::endl; + result = DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + break; + } + return result; +} + +ReturnValue_t FreshSupvHandler::prepareSetTimeRefCmd() { + Clock::TimeOfDay_t time; + ReturnValue_t result = Clock::getDateAndTime(&time); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::prepareSetTimeRefCmd: Failed to get current time" + << std::endl; + return result::GET_TIME_FAILURE; + } + supv::SetTimeRef packet(spParams); + result = packet.buildPacket(&time); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SET_TIME_REF, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t* msToReachTheMode) { + if (commandedMode != MODE_OFF) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) { + return NON_OP_STATE_OF_CHARGE; + } + } + } + if (commandedMode != HasModesIF::MODE_OFF and commandedMode != HasModesIF::MODE_ON and + commandedMode != MODE_NORMAL) { + return returnvalue::FAILED; + } + if (commandedMode != mode and msToReachTheMode != nullptr) { + if (commandedMode == MODE_OFF) { + *msToReachTheMode = supv::MAX_TRANSITION_TIME_TO_OFF_MS; + } else { + *msToReachTheMode = supv::MAX_TRANSITION_TIME_TO_ON_MS; + } + } + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareSelBootImageCmd(const uint8_t* commandData) { + supv::MPSoCBootSelect packet(spParams); + ReturnValue_t result = + packet.buildPacket(commandData[0], commandData[1], commandData[2], commandData[3]); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SEL_MPSOC_BOOT_IMAGE, packet, false); + return returnvalue::OK; +} + +void FreshSupvHandler::startTransition(Mode_t newMode, Submode_t newSubmode) { + if (newMode == mode or (mode == MODE_ON and newMode == MODE_NORMAL) or + (newMode == MODE_ON and mode == MODE_NORMAL)) { + // Can finish immediately. + setMode(newMode, newSubmode); + return; + } + // Transition for both OFF to ON/NORMAL and ON/NORMAL to OFF require small state machine. + transitionActive = true; + targetMode = newMode; + targetSubmode = newSubmode; + if (targetMode == MODE_ON or targetMode == MODE_NORMAL) { + startupState = StartupState::IDLE; + } else if (targetMode == MODE_OFF) { + shutdownState = ShutdownState::IDLE; + } +} + +void FreshSupvHandler::handleTransitionToOn() { + if (startupState == StartupState::IDLE) { + startupState = StartupState::POWER_SWITCHING; + switchIF.sendSwitchCommand(switchId, PowerSwitchIF::SWITCH_ON); + switchTimeout.resetTimer(); + } else if (modeHelper.isTimedOut()) { + targetMode = MODE_OFF; + shutdownState = ShutdownState::IDLE; + handleTransitionToOff(); + return; + } + if (startupState == StartupState::POWER_SWITCHING) { + if (switchIF.getSwitchState(switchId) == PowerSwitchIF::SWITCH_ON) { + startupState = StartupState::BOOTING; + bootTimeout.resetTimer(); + } else if (switchTimeout.hasTimedOut()) { + targetMode = MODE_OFF; + shutdownState = ShutdownState::IDLE; + handleTransitionToOff(); + return; + } + } + if (startupState == StartupState::BOOTING and bootTimeout.hasTimedOut()) { + uartIsolatorSwitch.pullHigh(); + uartManager->start(); + if (SET_TIME_DURING_BOOT) { + startupState = StartupState::SET_TIME; + } else { + startupState = StartupState::ON; + } + } + if (startupState == StartupState::SET_TIME) { + spParams.buf = commandBuffer.data(); + ReturnValue_t result = prepareSetTimeRefCmd(); + if (result != returnvalue::OK) { + sif::error << "FreshSupvHandler: Setting time command prepration failed" << std::endl; + startupState = StartupState::ON; + } else { + startupState = StartupState::WAIT_FOR_TIME_REPLY; + } + } + if (startupState == StartupState::TIME_WAS_SET) { + startupState = StartupState::ON; + } + if (startupState == StartupState::ON) { + hkSet.setReportingEnabled(true); + supv::SUPV_ON = true; + transitionActive = false; + setMode(targetMode); + } +} + +void FreshSupvHandler::handleTransitionToOff() { + if (shutdownState == ShutdownState::IDLE) { + hkSet.setReportingEnabled(false); + hkSet.setValidity(false, true); + uartManager->stop(); + activeActionCmds.clear(); + uartIsolatorSwitch.pullLow(); + switchIF.sendSwitchCommand(switchId, PowerSwitchIF::SWITCH_OFF); + shutdownState = ShutdownState::POWER_SWITCHING; + } + if (shutdownState == ShutdownState::POWER_SWITCHING) { + if (switchIF.getSwitchState(switchId) == PowerSwitchIF::SWITCH_OFF or modeHelper.isTimedOut()) { + supv::SUPV_ON = false; + transitionActive = false; + setMode(MODE_OFF); + } + } +} + +ReturnValue_t FreshSupvHandler::sendCommand(DeviceCommandId_t commandId, TcBase& tc, + bool replyExpected, uint32_t cmdCountdownMs) { + if (supv::DEBUG_PLOC_SUPV) { + if (not(supv::REDUCE_NORMAL_MODE_PRINTOUT and mode == MODE_NORMAL and + commandId == supv::GET_HK_REPORT)) { + sif::debug << "PLOC SUPV: SEND PACKET Size " << tc.getFullPacketLen() << " Module APID " + << (int)tc.getModuleApid() << " Service ID " << (int)tc.getServiceId() + << std::endl; + } + } + ActiveCmdInfo info(commandId, cmdCountdownMs); + auto activeCmdIter = + activeActionCmds.find(buildActiveCmdKey(tc.getModuleApid(), tc.getServiceId())); + if (activeCmdIter == activeActionCmds.end()) { + info.isPending = true; + info.replyPacketExpected = replyExpected; + info.commandedBy = commandedByCached; + info.cmdCountdown.resetTimer(); + activeActionCmds.emplace(buildActiveCmdKey(tc.getModuleApid(), tc.getServiceId()), info); + } else { + if (activeCmdIter->second.isPending) { + return HasActionsIF::IS_BUSY; + } + activeCmdIter->second.isPending = true; + activeCmdIter->second.commandId = commandId; + activeCmdIter->second.commandedBy = commandedByCached; + activeCmdIter->second.ackRecv = false; + activeCmdIter->second.ackExeRecv = false; + activeCmdIter->second.replyPacketExpected = replyExpected; + activeCmdIter->second.replyPacketReceived = false; + activeCmdIter->second.cmdCountdown.setTimeout(cmdCountdownMs); + activeCmdIter->second.cmdCountdown.resetTimer(); + } + ReturnValue_t result = + uartManager->sendMessage(comCookie, tc.getFullPacket(), tc.getFullPacketLen()); + return result; +} + +ReturnValue_t FreshSupvHandler::initialize() { + uartManager = + ObjectManager::instance()->get(objects::PLOC_SUPERVISOR_HELPER); + if (uartManager == nullptr) { + return returnvalue::FAILED; + } + uartManager->initializeInterface(comCookie); + + ReturnValue_t result = eventSubscription(); + if (result != returnvalue::OK) { + return result; + } + return FreshDeviceHandlerBase::initialize(); +} + +ReturnValue_t FreshSupvHandler::sendEmptyCmd(DeviceCommandId_t commandId, uint16_t apid, + uint8_t serviceId, bool replyExpected) { + supv::NoPayloadPacket packet(spParams, apid, serviceId); + ReturnValue_t result = packet.buildPacket(); + if (result != returnvalue::OK) { + return result; + } + sendCommand(commandId, packet, replyExpected); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareSetBootTimeoutCmd(const uint8_t* commandData, + size_t cmdDataLen) { + if (cmdDataLen < 4) { + return HasActionsIF::INVALID_PARAMETERS; + } + supv::SetBootTimeout packet(spParams); + uint32_t timeout = *(commandData) << 24 | *(commandData + 1) << 16 | *(commandData + 2) << 8 | + *(commandData + 3); + ReturnValue_t result = packet.buildPacket(timeout); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SET_BOOT_TIMEOUT, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareRestartTriesCmd(const uint8_t* commandData, + size_t cmdDataLen) { + if (cmdDataLen < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint8_t restartTries = *(commandData); + supv::SetRestartTries packet(spParams); + ReturnValue_t result = packet.buildPacket(restartTries); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::ENABLE_AUTO_TM, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareDisableHk() { + supv::DisablePeriodicHkTransmission packet(spParams); + ReturnValue_t result = packet.buildPacket(); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::DISABLE_AUTO_TM, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareLatchupConfigCmd(const uint8_t* commandData, + DeviceCommandId_t deviceCommand, + size_t cmdDataLen) { + ReturnValue_t result = returnvalue::OK; + if (cmdDataLen < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint8_t latchupId = *commandData; + if (latchupId > 6) { + return result::INVALID_LATCHUP_ID; + } + switch (deviceCommand) { + case (supv::ENABLE_LATCHUP_ALERT): { + supv::LatchupAlert packet(spParams); + result = packet.buildPacket(true, latchupId); + if (result != returnvalue::OK) { + return result; + } + sendCommand(deviceCommand, packet, false); + break; + } + case (supv::DISABLE_LATCHUP_ALERT): { + supv::LatchupAlert packet(spParams); + result = packet.buildPacket(false, latchupId); + if (result != returnvalue::OK) { + return result; + } + sendCommand(deviceCommand, packet, false); + break; + } + default: { + sif::debug << "PlocSupervisorHandler::prepareLatchupConfigCmd: Invalid command id" + << std::endl; + result = returnvalue::FAILED; + break; + } + } + return result; +} + +ReturnValue_t FreshSupvHandler::prepareSetAlertLimitCmd(const uint8_t* commandData, + size_t cmdDataLen) { + if (cmdDataLen < 5) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint8_t offset = 0; + uint8_t latchupId = *commandData; + offset += 1; + uint32_t dutycycle = *(commandData + offset) << 24 | *(commandData + offset + 1) << 16 | + *(commandData + offset + 2) << 8 | *(commandData + offset + 3); + if (latchupId > 6) { + return result::INVALID_LATCHUP_ID; + } + supv::SetAlertlimit packet(spParams); + ReturnValue_t result = packet.buildPacket(latchupId, dutycycle); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SET_ALERT_LIMIT, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareSetShutdownTimeoutCmd(const uint8_t* commandData, + size_t cmdDataLen) { + if (cmdDataLen < 4) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint32_t timeout = 0; + ReturnValue_t result = returnvalue::OK; + size_t size = sizeof(timeout); + result = + SerializeAdapter::deSerialize(&timeout, &commandData, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning + << "PlocSupervisorHandler::prepareSetShutdownTimeoutCmd: Failed to deserialize timeout" + << std::endl; + return result; + } + supv::SetShutdownTimeout packet(spParams); + result = packet.buildPacket(timeout); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SET_SHUTDOWN_TIMEOUT, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareSetGpioCmd(const uint8_t* commandData, + size_t commandDataLen) { + if (commandDataLen < 3) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint8_t port = *commandData; + uint8_t pin = *(commandData + 1); + uint8_t val = *(commandData + 2); + supv::SetGpio packet(spParams); + ReturnValue_t result = packet.buildPacket(port, pin, val); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SET_GPIO, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareReadGpioCmd(const uint8_t* commandData, + size_t commandDataLen) { + if (commandDataLen < 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint8_t port = *commandData; + uint8_t pin = *(commandData + 1); + supv::ReadGpio packet(spParams); + ReturnValue_t result = packet.buildPacket(port, pin); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::READ_GPIO, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareFactoryResetCmd(const uint8_t* commandData, size_t len) { + FactoryReset resetCmd(spParams); + if (len < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + ReturnValue_t result = resetCmd.buildPacket(commandData[0]); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::FACTORY_RESET, resetCmd, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareSetAdcEnabledChannelsCmd(const uint8_t* commandData) { + uint16_t ch = *(commandData) << 8 | *(commandData + 1); + supv::SetAdcEnabledChannels packet(spParams); + ReturnValue_t result = packet.buildPacket(ch); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SET_ADC_ENABLED_CHANNELS, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareSetAdcWindowAndStrideCmd(const uint8_t* commandData) { + uint8_t offset = 0; + uint16_t windowSize = *(commandData + offset) << 8 | *(commandData + offset + 1); + offset += 2; + uint16_t stridingStepSize = *(commandData + offset) << 8 | *(commandData + offset + 1); + supv::SetAdcWindowAndStride packet(spParams); + ReturnValue_t result = packet.buildPacket(windowSize, stridingStepSize); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SET_ADC_WINDOW_AND_STRIDE, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareSetAdcThresholdCmd(const uint8_t* commandData) { + uint32_t threshold = *(commandData) << 24 | *(commandData + 1) << 16 | *(commandData + 2) << 8 | + *(commandData + 3); + supv::SetAdcThreshold packet(spParams); + ReturnValue_t result = packet.buildPacket(threshold); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::SET_ADC_THRESHOLD, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::prepareWipeMramCmd(const uint8_t* commandData, size_t cmdDataLen) { + if (cmdDataLen < 8) { + return HasActionsIF::INVALID_PARAMETERS; + } + uint32_t start = 0; + uint32_t stop = 0; + size_t size = sizeof(start) + sizeof(stop); + SerializeAdapter::deSerialize(&start, &commandData, &size, SerializeIF::Endianness::BIG); + SerializeAdapter::deSerialize(&stop, &commandData, &size, SerializeIF::Endianness::BIG); + if ((stop - start) <= 0) { + return result::INVALID_MRAM_ADDRESSES; + } + supv::MramCmd packet(spParams); + ReturnValue_t result = packet.buildPacket(start, stop, supv::MramCmd::MramAction::WIPE); + if (result != returnvalue::OK) { + return result; + } + sendCommand(supv::WIPE_MRAM, packet, false); + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::parseTmPackets() { + uint8_t* receivedData = nullptr; + size_t receivedSize = 0; + // We do not want to steal packets from the long request handler. + if (uartManager->longerRequestActive()) { + return returnvalue::OK; + } + while (true) { + ReturnValue_t result = + uartManager->readReceivedMessage(comCookie, &receivedData, &receivedSize); + if (result != returnvalue::OK or receivedSize == 0) { + break; + } + tmReader.setReadOnlyData(receivedData, receivedSize); + if (tmReader.checkCrc() != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::parseTmPackets: CRC failure for packet with size " + << receivedSize << std::endl; + arrayprinter::print(receivedData, receivedSize); + continue; + } + if (supv::DEBUG_PLOC_SUPV) { + handlePacketPrint(); + } + uint16_t apid = tmReader.getModuleApid(); + switch (apid) { + case (Apid::TMTC_MAN): { + switch (tmReader.getServiceId()) { + case (static_cast(supv::tm::TmtcId::ACK)): + case (static_cast(supv::tm::TmtcId::NAK)): { + handleAckReport(receivedData); + continue; + } + case (static_cast(supv::tm::TmtcId::EXEC_ACK)): + case (static_cast(supv::tm::TmtcId::EXEC_NAK)): { + handleExecutionReport(receivedData); + continue; + } + } + break; + } + case (Apid::HK): { + if (tmReader.getServiceId() == static_cast(supv::tm::HkId::REPORT)) { + handleHkReport(receivedData); + continue; + } else if (tmReader.getServiceId() == static_cast(supv::tm::HkId::HARDFAULTS)) { + handleBadApidServiceCombination(SUPV_UNINIMPLEMENTED_TM, apid, tmReader.getServiceId()); + return INVALID_DATA; + } + break; + } + case (Apid::BOOT_MAN): { + if (tmReader.getServiceId() == + static_cast(supv::tm::BootManId::BOOT_STATUS_REPORT)) { + handleBootStatusReport(receivedData); + continue; + } + break; + } + case (Apid::LATCHUP_MON): { + if (tmReader.getServiceId() == + static_cast(supv::tm::LatchupMonId::LATCHUP_STATUS_REPORT)) { + handleLatchupStatusReport(receivedData); + continue; + } + break; + } + case (Apid::ADC_MON): { + if (tmReader.getServiceId() == static_cast(supv::tm::AdcMonId::ADC_REPORT)) { + genericHandleTm("ADC", receivedData, adcReport, supv::Apid::ADC_MON, + static_cast(supv::tc::AdcMonId::REQUEST_ADC_SAMPLE)); + continue; + } + break; + } + case (Apid::MEM_MAN): { + if (tmReader.getServiceId() == + static_cast(supv::tm::MemManId::UPDATE_STATUS_REPORT)) { + sif::warning << "FreshSupvHandler: Update status report parsing not implemented" + << std::endl; + // confirmReplyPacketReceived(supv::Apid::MEM_MAN, + // supv::tc::MemManId::UPDATE_STATUS_REPORT); + continue; + } + break; + } + case (Apid::DATA_LOGGER): { + if (tmReader.getServiceId() == + static_cast(supv::tm::DataLoggerId::COUNTERS_REPORT)) { + genericHandleTm("COUNTERS", receivedData, countersReport, supv::Apid::DATA_LOGGER, + static_cast(supv::tc::DataLoggerServiceId::REQUEST_COUNTERS)); + continue; + } + } + } + handleBadApidServiceCombination(SUPV_UNKNOWN_TM, apid, tmReader.getServiceId()); + } + return returnvalue::OK; +} + +void FreshSupvHandler::handleBadApidServiceCombination(Event event, unsigned int apid, + unsigned int serviceId) { + const char* printString = ""; + if (event == SUPV_UNKNOWN_TM) { + printString = "PlocSupervisorHandler: Unknown"; + } else if (event == SUPV_UNINIMPLEMENTED_TM) { + printString = "PlocSupervisorHandler: Unimplemented"; + } + triggerEvent(event, apid, tmReader.getServiceId()); + sif::warning << printString << " APID service combination 0x" << std::setw(2) << std::setfill('0') + << std::hex << apid << ", 0x" << std::setw(2) << serviceId << std::endl; +} + +void FreshSupvHandler::handlePacketPrint() { + if (tmReader.getModuleApid() == Apid::TMTC_MAN) { + if ((tmReader.getServiceId() == static_cast(supv::tm::TmtcId::ACK)) or + (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::NAK))) { + AcknowledgmentReport ack(tmReader); + ReturnValue_t result = ack.parse(false); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler: Parsing ACK failed. "; + if (result == result::INVALID_SERVICE_ID) { + sif::warning << "Invalid service ID" << std::endl; + } else if (result == result::CRC_FAILURE) { + sif::warning << "CRC check failed" << std::endl; + } else { + sif::warning << "Returncode 0x" << std::hex << std::setw(4) << result << std::dec + << std::endl; + } + } + if (mode == MODE_NORMAL and supv::REDUCE_NORMAL_MODE_PRINTOUT and + ack.getRefModuleApid() == (uint8_t)supv::Apid::HK and + ack.getRefServiceId() == (uint8_t)supv::tc::HkId::GET_REPORT) { + return; + } + const char* printStr = "???"; + if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::ACK)) { + printStr = "ACK"; + + } else if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::NAK)) { + printStr = "NAK"; + } + sif::debug << "PlocSupervisorHandler: RECV " << printStr << " for APID Module ID " + << (int)ack.getRefModuleApid() << " Service ID " << (int)ack.getRefServiceId() + << " Seq Count " << ack.getRefSequenceCount() << std::endl; + return; + } else if ((tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_ACK)) or + (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_NAK))) { + ExecutionReport exe(tmReader); + ReturnValue_t result = exe.parse(false); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler: Parsing EXE failed" << std::endl; + } + const char* printStr = "???"; + if (mode == MODE_NORMAL and supv::REDUCE_NORMAL_MODE_PRINTOUT and + exe.getRefModuleApid() == (uint8_t)supv::Apid::HK and + exe.getRefServiceId() == (uint8_t)supv::tc::HkId::GET_REPORT) { + return; + } + if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_ACK)) { + printStr = "ACK EXE"; + + } else if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_NAK)) { + printStr = "NAK EXE"; + } + sif::debug << "PlocSupervisorHandler: RECV " << printStr << " for APID Module ID " + << (int)exe.getRefModuleApid() << " Service ID " << (int)exe.getRefServiceId() + << " Seq Count " << exe.getRefSequenceCount() << std::endl; + return; + } + } + if (mode == MODE_NORMAL and supv::REDUCE_NORMAL_MODE_PRINTOUT and + tmReader.getModuleApid() == supv::Apid::HK and + tmReader.getServiceId() == static_cast(supv::tm::HkId::REPORT)) { + return; + } + sif::debug << "PlocSupervisorHandler: RECV PACKET Size " << tmReader.getFullPacketLen() + << " Module APID " << (int)tmReader.getModuleApid() << " Service ID " + << (int)tmReader.getServiceId() << std::endl; +} + +bool FreshSupvHandler::isCommandAlreadyActive(ActionId_t actionId) const { + // Not the most efficient implementation but who cares. It's not like this map is going + // to be huge in the nominal case.. + for (const auto& info : activeActionCmds) { + if (info.second.commandId == actionId and info.second.isPending) { + return true; + } + } + return false; +} + +ReturnValue_t FreshSupvHandler::extractUpdateCommand(const uint8_t* commandData, size_t size, + supv::UpdateParams& params) { + size_t remSize = size; + if (size > (config::MAX_FILENAME_SIZE + config::MAX_PATH_SIZE + sizeof(params.memId)) + + sizeof(params.startAddr) + sizeof(params.bytesWritten) + sizeof(params.seqCount) + + sizeof(uint8_t)) { + sif::warning << "PlocSupervisorHandler::extractUpdateCommand: Data size too big" << std::endl; + return result::INVALID_LENGTH; + } + ReturnValue_t result = returnvalue::OK; + result = extractBaseParams(&commandData, size, params); + result = SerializeAdapter::deSerialize(¶ms.bytesWritten, &commandData, &remSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::extractUpdateCommand: Failed to deserialize bytes " + "already written" + << std::endl; + return result; + } + result = SerializeAdapter::deSerialize(¶ms.seqCount, &commandData, &remSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning + << "PlocSupervisorHandler::extractUpdateCommand: Failed to deserialize start sequence count" + << std::endl; + return result; + } + uint8_t delMemRaw = 0; + result = SerializeAdapter::deSerialize(&delMemRaw, &commandData, &remSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning + << "PlocSupervisorHandler::extractUpdateCommand: Failed to deserialize whether to delete " + "memory" + << std::endl; + return result; + } + params.deleteMemory = delMemRaw; + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::extractBaseParams(const uint8_t** commandData, size_t& remSize, + supv::UpdateParams& params) { + bool nullTermFound = false; + for (size_t idx = 0; idx < remSize; idx++) { + if ((*commandData)[idx] == '\0') { + nullTermFound = true; + break; + } + } + if (not nullTermFound) { + return returnvalue::FAILED; + } + params.file = std::string(reinterpret_cast(*commandData)); + if (params.file.size() > (config::MAX_FILENAME_SIZE + config::MAX_PATH_SIZE)) { + sif::warning << "PlocSupervisorHandler::extractUpdateCommand: Filename too long" << std::endl; + return result::FILENAME_TOO_LONG; + } + *commandData += params.file.size() + SIZE_NULL_TERMINATOR; + remSize -= (params.file.size() + SIZE_NULL_TERMINATOR); + params.memId = **commandData; + *commandData += 1; + remSize -= 1; + ReturnValue_t result = SerializeAdapter::deSerialize(¶ms.startAddr, commandData, &remSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler::extractBaseParams: Failed to deserialize start address" + << std::endl; + return result; + } + return result; +} + +void FreshSupvHandler::handleEvent(EventMessage* eventMessage) { + ReturnValue_t result = returnvalue::OK; + object_id_t objectId = eventMessage->getReporter(); + Event event = eventMessage->getEvent(); + switch (objectId) { + case objects::PLOC_SUPERVISOR_HELPER: { + // After execution of update procedure, PLOC is in a state where it draws approx. 700 mA of + // current. To leave this state the shutdown MPSoC command must be sent here. + if (event == PlocSupvUartManager::SUPV_UPDATE_FAILED || + event == PlocSupvUartManager::SUPV_UPDATE_SUCCESSFUL || + event == PlocSupvUartManager::SUPV_CONTINUE_UPDATE_FAILED || + event == PlocSupvUartManager::SUPV_CONTINUE_UPDATE_SUCCESSFUL || + event == PlocSupvUartManager::SUPV_MEM_CHECK_FAIL || + event == PlocSupvUartManager::SUPV_MEM_CHECK_OK) { + if (not isCommandAlreadyActive(supv::SHUTDOWN_MPSOC)) { + CommandMessage actionMsg; + ActionMessage::setCommand(&actionMsg, supv::SHUTDOWN_MPSOC, store_address_t::invalid()); + result = messageQueue->sendMessageFrom(getCommandQueue(), &actionMsg, + MessageQueueIF::NO_QUEUE); + if (result != returnvalue::OK) { + triggerEvent(supv::SUPV_MPSOC_SHUTDOWN_BUILD_FAILED); + sif::warning << "PlocSupervisorHandler::handleEvent: Failed to build MPSoC shutdown " + "command" + << std::endl; + return; + } + } + } + break; + } + default: + sif::debug << "PlocMPSoCHandler::handleEvent: Did not subscribe to this event" << std::endl; + break; + } +} + +ReturnValue_t FreshSupvHandler::eventSubscription() { + ReturnValue_t result = returnvalue::OK; + EventManagerIF* manager = ObjectManager::instance()->get(objects::EVENT_MANAGER); + if (manager == nullptr) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "PlocSupervisorHandler::eventSubscritpion: Invalid event manager" << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + ; + } + result = manager->registerListener(eventQueue->getId()); + if (result != returnvalue::OK) { + return result; + } + result = manager->subscribeToEventRange( + eventQueue->getId(), event::getEventId(PlocSupvUartManager::SUPV_UPDATE_FAILED), + event::getEventId(PlocSupvUartManager::SUPV_MEM_CHECK_FAIL)); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "PlocSupervisorHandler::eventSubscritpion: Failed to subscribe to events from " + " ploc supervisor helper" + << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + } + return result; +} + +ReturnValue_t FreshSupvHandler::handleAckReport(const uint8_t* data) { + using namespace supv; + ReturnValue_t result = returnvalue::OK; + AcknowledgmentReport ack(tmReader); + result = ack.parse(false); + if (result != returnvalue::OK) { + return result; + } + auto infoIter = + activeActionCmds.find(buildActiveCmdKey(ack.getRefModuleApid(), ack.getRefServiceId())); + if (infoIter == activeActionCmds.end()) { + triggerEvent(SUPV_ACK_UNKNOWN_COMMAND, ack.getRefModuleApid(), ack.getRefServiceId()); + return result; + } + ActiveCmdInfo& info = infoIter->second; + if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::NAK)) { + triggerEvent(SUPV_ACK_FAILURE, info.commandId, static_cast(ack.getStatusCode())); + ack.printStatusInformationAck(); + printAckFailureInfo(ack.getStatusCode(), info.commandId); + if (info.commandedBy != MessageQueueIF::NO_QUEUE) { + actionHelper.finish(false, info.commandedBy, info.commandId, result::RECEIVED_ACK_FAILURE); + } + info.isPending = false; + return returnvalue::OK; + } + info.ackRecv = true; + performCommandCompletionHandling(static_cast((infoIter->first >> 16) & 0xffff), + infoIter->first & 0xff, info); + return result; +} + +void FreshSupvHandler::performCommandCompletionHandling(supv::Apid apid, uint8_t serviceId, + ActiveCmdInfo& info) { + if (info.ackRecv and info.ackExeRecv and + (not info.replyPacketExpected or info.replyPacketReceived)) { + if (info.commandedBy != MessageQueueIF::NO_QUEUE) { + actionHelper.finish(true, info.commandedBy, info.commandId, returnvalue::OK); + } + info.isPending = false; + } +} + +void FreshSupvHandler::printAckFailureInfo(uint16_t statusCode, DeviceCommandId_t commandId) { + switch (commandId) { + case (supv::SET_TIME_REF): { + sif::warning + << "PlocSupervisoHandler: Setting time failed. Make sure the OBC has a valid time" + << std::endl; + break; + } + default: + break; + } +} + +uint32_t FreshSupvHandler::buildActiveCmdKey(uint16_t moduleApid, uint8_t serviceId) { + return (moduleApid << 16) | serviceId; +} + +ReturnValue_t FreshSupvHandler::handleExecutionReport(const uint8_t* data) { + using namespace supv; + ReturnValue_t result = returnvalue::OK; + ExecutionReport exe(tmReader); + result = exe.parse(false); + if (result != OK) { + sif::warning << "FreshSupvHandler::handleExecutionReport: Parsing ACK EXE failed" << std::endl; + return result; + } + auto infoIter = + activeActionCmds.find(buildActiveCmdKey(exe.getRefModuleApid(), exe.getRefServiceId())); + if (infoIter == activeActionCmds.end()) { + triggerEvent(SUPV_EXE_ACK_UNKNOWN_COMMAND, exe.getRefModuleApid(), exe.getRefServiceId()); + return result; + } + ActiveCmdInfo& info = infoIter->second; + if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_ACK)) { + result = handleExecutionSuccessReport(info, exe); + } else if (tmReader.getServiceId() == static_cast(supv::tm::TmtcId::EXEC_NAK)) { + handleExecutionFailureReport(info, exe); + return returnvalue::OK; + } + info.ackExeRecv = true; + performCommandCompletionHandling(static_cast((infoIter->first >> 16) & 0xffff), + infoIter->first & 0xff, info); + + return result; +} + +ReturnValue_t FreshSupvHandler::handleExecutionSuccessReport(ActiveCmdInfo& info, + ExecutionReport& report) { + switch (info.commandId) { + case supv::READ_GPIO: { + // TODO: Fix + uint16_t gpioState = report.getStatusCode(); +#if OBSW_DEBUG_PLOC_SUPERVISOR == 1 + sif::info << "PlocSupervisorHandler: Read GPIO TM, State: " << gpioState << std::endl; +#endif /* OBSW_DEBUG_PLOC_SUPERVISOR == 1 */ + uint8_t data[sizeof(gpioState)]; + size_t size = 0; + ReturnValue_t result = SerializeAdapter::serialize(&gpioState, data, &size, sizeof(gpioState), + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::debug << "PlocSupervisorHandler: Failed to deserialize GPIO state" << std::endl; + } + result = actionHelper.reportData(info.commandedBy, info.commandId, data, sizeof(data)); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler: Read GPIO, failed to report data" << std::endl; + } + break; + } + case supv::SET_TIME_REF: { + if (startupState == StartupState::WAIT_FOR_TIME_REPLY) { + startupState = StartupState::TIME_WAS_SET; + } + break; + } + default: + break; + } + return returnvalue::OK; +} + +void FreshSupvHandler::handleExecutionFailureReport(ActiveCmdInfo& info, ExecutionReport& report) { + using namespace supv; + report.printStatusInformationExe(); + if (info.commandId != DeviceHandlerIF::NO_COMMAND_ID) { + triggerEvent(SUPV_EXE_FAILURE, info.commandId, static_cast(report.getStatusCode())); + } + if (info.commandedBy) { + actionHelper.finish(false, info.commandedBy, info.commandId, result::RECEIVED_EXE_FAILURE); + } + info.isPending = false; +} + +void FreshSupvHandler::confirmReplyPacketReceived(supv::Apid apid, uint8_t serviceId) { + auto infoIter = activeActionCmds.find(buildActiveCmdKey(apid, serviceId)); + if (infoIter != activeActionCmds.end()) { + ActiveCmdInfo& info = infoIter->second; + info.replyPacketReceived = true; + performCommandCompletionHandling(static_cast((infoIter->first >> 16) & 0xffff), + infoIter->first & 0xff, info); + } +} + +ReturnValue_t FreshSupvHandler::handleHkReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, tmReader.getFullPacketLen()); + + if (result == result::CRC_FAILURE) { + sif::error << "PlocSupervisorHandler::handleHkReport: Hk report has invalid crc" << std::endl; + return result; + } + + uint16_t offset = supv::PAYLOAD_OFFSET; + hkSet.tempPs = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkSet.tempPl = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkSet.tempSup = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + size_t size = sizeof(hkSet.uptime.value); + result = SerializeAdapter::deSerialize(&hkSet.uptime, data + offset, &size, + SerializeIF::Endianness::BIG); + offset += 8; + hkSet.cpuLoad = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkSet.availableHeap = *(data + offset) << 24 | *(data + offset + 1) << 16 | + *(data + offset + 2) << 8 | *(data + offset + 3); + offset += 4; + hkSet.numTcs = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkSet.numTms = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkSet.socState = *(data + offset) << 24 | *(data + offset + 1) << 16 | *(data + offset + 2) << 8 | + *(data + offset + 3); + offset += 4; + hkSet.nvm0_1_state = *(data + offset); + offset += 1; + hkSet.nvm3_state = *(data + offset); + offset += 1; + hkSet.missionIoState = *(data + offset); + offset += 1; + hkSet.fmcState = *(data + offset); + offset += 1; + + hkSet.setValidity(true, true); + confirmReplyPacketReceived(supv::Apid::HK, static_cast(supv::tc::HkId::GET_REPORT)); + +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_PLOC_SUPERVISOR == 1 + sif::info << "PlocSupervisorHandler::handleHkReport: temp_ps: " << hkSet.tempPs << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: temp_pl: " << hkSet.tempPl << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: temp_sup: " << hkSet.tempSup << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: uptime: " << hkSet.uptime << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: cpu_load: " << hkSet.cpuLoad << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: available_heap: " << hkSet.availableHeap + << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: num_tcs: " << hkSet.numTcs << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: num_tms: " << hkSet.numTms << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: soc_state: " << hkSet.socState << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: nvm0_1_state: " + << static_cast(hkSet.nvm0_1_state.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: nvm3_state: " + << static_cast(hkSet.nvm3_state.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: mission_io_state: " + << static_cast(hkSet.missionIoState.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleHkReport: fmc_state: " + << static_cast(hkSet.fmcState.value) << std::endl; + +#endif + + return result; +} + +ReturnValue_t FreshSupvHandler::verifyPacket(const uint8_t* start, size_t foundLen) { + if (CRC::crc16ccitt(start, foundLen) != 0) { + return result::CRC_FAILURE; + } + return returnvalue::OK; +} + +ReturnValue_t FreshSupvHandler::handleBootStatusReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + result = verifyPacket(data, tmReader.getFullPacketLen()); + if (result == result::CRC_FAILURE) { + sif::error << "PlocSupervisorHandler::handleBootStatusReport: Boot status report has invalid" + " crc" + << std::endl; + return result; + } + PoolReadGuard pg(&bootStatusReport); + if (pg.getReadResult() != returnvalue::OK) { + return pg.getReadResult(); + } + + const uint8_t* payloadStart = tmReader.getPayloadStart(); + uint16_t offset = 0; + bootStatusReport.socState = payloadStart[0]; + offset += 1; + bootStatusReport.powerCycles = payloadStart[1]; + offset += 1; + bootStatusReport.bootAfterMs = *(payloadStart + offset) << 24 | + *(payloadStart + offset + 1) << 16 | + *(payloadStart + offset + 2) << 8 | *(payloadStart + offset + 3); + offset += 4; + bootStatusReport.bootTimeoutMs = *(payloadStart + offset) << 24 | + *(payloadStart + offset + 1) << 16 | + *(payloadStart + offset + 2) << 8 | *(payloadStart + offset + 3); + offset += 4; + bootStatusReport.activeNvm = *(payloadStart + offset); + offset += 1; + bootStatusReport.bp0State = *(payloadStart + offset); + offset += 1; + bootStatusReport.bp1State = *(payloadStart + offset); + offset += 1; + bootStatusReport.bp2State = *(payloadStart + offset); + offset += 1; + bootStatusReport.bootState = *(payloadStart + offset); + offset += 1; + bootStatusReport.bootCycles = *(payloadStart + offset); + + bootStatusReport.setValidity(true, true); + confirmReplyPacketReceived(supv::Apid::BOOT_MAN, + static_cast(supv::tc::BootManId::GET_BOOT_STATUS_REPORT)); + +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_PLOC_SUPERVISOR == 1 + sif::info << "PlocSupervisorHandler::handleBootStatusReport: SoC State (0 - off, 1 - booting, 2 " + "- Update, 3 " + "- operating, 4 - Shutdown, 5 - Reset): " + << static_cast(bootStatusReport.socState.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: Power Cycles: " + << static_cast(bootStatusReport.powerCycles.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BootAfterMs: " + << bootStatusReport.bootAfterMs << " ms" << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BootTimeoutMs: " << std::dec + << bootStatusReport.bootTimeoutMs << " ms" << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: Active NVM: " + << static_cast(bootStatusReport.activeNvm.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BP0: " + << static_cast(bootStatusReport.bp0State.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BP1: " + << static_cast(bootStatusReport.bp1State.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: BP2: " + << static_cast(bootStatusReport.bp2State.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: Boot state: " + << static_cast(bootStatusReport.bootState.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleBootStatusReport: Boot cycles: " + << static_cast(bootStatusReport.bootCycles.value) << std::endl; +#endif + + return result; +} + +ReturnValue_t FreshSupvHandler::handleLatchupStatusReport(const uint8_t* data) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, tmReader.getFullPacketLen()); + if (result == result::CRC_FAILURE) { + sif::error << "PlocSupervisorHandler::handleLatchupStatusReport: Latchup status report has " + << "invalid crc" << std::endl; + return result; + } + + PoolReadGuard pg(&latchupStatusReport); + if (pg.getReadResult() != returnvalue::OK) { + return pg.getReadResult(); + } + + const uint8_t* payloadData = tmReader.getPayloadStart(); + uint16_t offset = 0; + latchupStatusReport.id = *(payloadData + offset); + offset += 1; + latchupStatusReport.cnt0 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt1 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt2 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt3 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt4 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt5 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + latchupStatusReport.cnt6 = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + offset += 2; + uint16_t msec = *(payloadData + offset) << 8 | *(payloadData + offset + 1); + latchupStatusReport.isSynced = msec >> supv::LatchupStatusReport::IS_SET_BIT_POS; + latchupStatusReport.timeMsec = msec & (~(1 << latchupStatusReport.IS_SET_BIT_POS)); + offset += 2; + latchupStatusReport.timeSec = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeMin = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeHour = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeDay = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeMon = *(payloadData + offset); + offset += 1; + latchupStatusReport.timeYear = *(payloadData + offset); + + latchupStatusReport.setValidity(true, true); + confirmReplyPacketReceived(supv::Apid::LATCHUP_MON, + static_cast(supv::tc::LatchupMonId::GET_STATUS_REPORT)); +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_PLOC_SUPERVISOR == 1 + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Latchup ID: " + << static_cast(latchupStatusReport.id.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT0: " + << latchupStatusReport.cnt0 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT1: " + << latchupStatusReport.cnt1 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT2: " + << latchupStatusReport.cnt2 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT3: " + << latchupStatusReport.cnt3 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT4: " + << latchupStatusReport.cnt4 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT5: " + << latchupStatusReport.cnt5 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: CNT6: " + << latchupStatusReport.cnt6 << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Sec: " + << static_cast(latchupStatusReport.timeSec.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Min: " + << static_cast(latchupStatusReport.timeMin.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Hour: " + << static_cast(latchupStatusReport.timeHour.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Day: " + << static_cast(latchupStatusReport.timeDay.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Mon: " + << static_cast(latchupStatusReport.timeMon.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Year: " + << static_cast(latchupStatusReport.timeYear.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: Msec: " + << static_cast(latchupStatusReport.timeMsec.value) << std::endl; + sif::info << "PlocSupervisorHandler::handleLatchupStatusReport: isSet: " + << static_cast(latchupStatusReport.isSet.value) << std::endl; +#endif + + return result; +} + +ReturnValue_t FreshSupvHandler::genericHandleTm(const char* contextString, const uint8_t* data, + LocalPoolDataSetBase& set, supv::Apid apid, + uint8_t serviceId) { + ReturnValue_t result = returnvalue::OK; + + result = verifyPacket(data, tmReader.getFullPacketLen()); + + if (result == result::CRC_FAILURE) { + sif::warning << "PlocSupervisorHandler: " << contextString << " report has " + << "invalid CRC" << std::endl; + return result; + } + + const uint8_t* dataField = data + supv::PAYLOAD_OFFSET; + PoolReadGuard pg(&set); + if (pg.getReadResult() != returnvalue::OK) { + return result; + } + set.setValidityBufferGeneration(false); + size_t size = set.getSerializedSize(); + result = set.deSerialize(&dataField, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "PlocSupervisorHandler: Deserialization failed" << std::endl; + } + set.setValidityBufferGeneration(true); + set.setValidity(true, true); + confirmReplyPacketReceived(apid, serviceId); + return result; +} + +bool FreshSupvHandler::isCommandPending() const { + for (const auto& info : activeActionCmds) { + if (info.second.isPending) { + return true; + } + } + return false; +} diff --git a/linux/payload/FreshSupvHandler.h b/linux/payload/FreshSupvHandler.h new file mode 100644 index 0000000..2c700de --- /dev/null +++ b/linux/payload/FreshSupvHandler.h @@ -0,0 +1,188 @@ +#ifndef LINUX_PAYLOAD_FRESHSUPVHANDLER_H_ +#define LINUX_PAYLOAD_FRESHSUPVHANDLER_H_ + +#include +#include + +#include + +#include "PlocSupvUartMan.h" +#include "fsfw/devicehandlers/FreshDeviceHandlerBase.h" +#include "fsfw/power/definitions.h" +#include "fsfw_hal/linux/gpio/Gpio.h" +#include "plocSupvDefs.h" + +using supv::TcBase; + +class FreshSupvHandler : public FreshDeviceHandlerBase { + public: + enum OpCode { DEFAULT_OPERATION = 0, PARSE_TM = 1 }; + + FreshSupvHandler(DhbConfig cfg, CookieIF* comCookie, Gpio uartIsolatorSwitch, + PowerSwitchIF& switchIF, power::Switch_t powerSwitch); + /** + * Periodic helper executed function, implemented by child class. + */ + void performDeviceOperation(uint8_t opCode) override; + + /** + * Implemented by child class. Handle all command messages which are + * not health, mode, action or housekeeping messages. + * @param message + * @return + */ + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + + ReturnValue_t initialize() override; + + private: + // HK manager abstract functions. + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + // Mode abstract functions + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + // Action override. Forward to user. + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + + /** + * @overload + * @param submode + */ + void startTransition(Mode_t newMode, Submode_t submode) override; + + ReturnValue_t performDeviceOperationPreQueueHandling(uint8_t opCode) override; + void handleTransitionToOn(); + void handleTransitionToOff(); + + private: + static constexpr bool SET_TIME_DURING_BOOT = true; + static const uint8_t SIZE_NULL_TERMINATOR = 1; + + enum class StartupState : uint8_t { + IDLE, + POWER_SWITCHING, + BOOTING, + SET_TIME, + WAIT_FOR_TIME_REPLY, + TIME_WAS_SET, + ON + }; + + StartupState startupState = StartupState::IDLE; + MessageQueueIF* eventQueue = nullptr; + supv::TmBase tmReader; + + enum class ShutdownState : uint8_t { IDLE, POWER_SWITCHING }; + ShutdownState shutdownState = ShutdownState::IDLE; + + PlocSupvUartManager* uartManager; + CookieIF* comCookie; + PowerSwitchIF& switchIF; + power::Switch_t switchId; + Gpio uartIsolatorSwitch; + + supv::HkSet hkSet; + supv::BootStatusReport bootStatusReport; + supv::LatchupStatusReport latchupStatusReport; + supv::CountersReport countersReport; + supv::AdcReport adcReport; + + bool transitionActive = false; + + Mode_t targetMode = HasModesIF::MODE_INVALID; + Submode_t targetSubmode = 0; + + Countdown switchTimeout = Countdown(2000); + // Vorago nees some time to boot properly + Countdown bootTimeout = Countdown(supv::BOOT_TIMEOUT_MS); + // Countdown interCmdCd = Countdown(supv::INTER_COMMAND_DELAY); + + PoolEntry adcRawEntry = PoolEntry(16); + PoolEntry adcEngEntry = PoolEntry(16); + PoolEntry latchupCounters = PoolEntry(7); + PoolEntry fmcStateEntry = PoolEntry(1); + PoolEntry bootStateEntry = PoolEntry(1); + PoolEntry bootCyclesEntry = PoolEntry(1); + PoolEntry tempSupEntry = PoolEntry(1); + + pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER); + + struct ActiveCmdInfo { + ActiveCmdInfo(DeviceCommandId_t commandId, uint32_t cmdCountdownMs) + : commandId(commandId), cmdCountdown(cmdCountdownMs) {} + + DeviceCommandId_t commandId = DeviceHandlerIF::NO_COMMAND_ID; + bool isPending = false; + bool ackRecv = false; + bool ackExeRecv = false; + bool replyPacketExpected = false; + bool replyPacketReceived = false; + MessageQueueId_t commandedBy = MessageQueueIF::NO_QUEUE; + bool requiresActionReply = false; + Countdown cmdCountdown; + }; + + uint32_t buildActiveCmdKey(uint16_t moduleApid, uint8_t serviceId); + + // Map for Action commands. For normal commands, a separate static structure will be used. + std::map activeActionCmds; + + std::array commandBuffer{}; + SpacePacketCreator creator; + supv::TcParams spParams = supv::TcParams(creator); + DeviceCommandId_t commandedByCached = MessageQueueIF::NO_QUEUE; + + ReturnValue_t parseTmPackets(); + + ReturnValue_t sendCommand(DeviceCommandId_t commandId, TcBase& tc, bool replyPacketExpected, + uint32_t cmdCountdownMs = 1000); + ReturnValue_t sendEmptyCmd(DeviceCommandId_t commandId, uint16_t apid, uint8_t serviceId, + bool replyPacketExpected); + ReturnValue_t prepareSelBootImageCmd(const uint8_t* commandData); + ReturnValue_t prepareSetTimeRefCmd(); + ReturnValue_t prepareSetBootTimeoutCmd(const uint8_t* commandData, size_t cmdDataLen); + ReturnValue_t prepareRestartTriesCmd(const uint8_t* commandData, size_t cmdDataLen); + ReturnValue_t prepareDisableHk(); + ReturnValue_t prepareLatchupConfigCmd(const uint8_t* commandData, DeviceCommandId_t deviceCommand, + size_t cmdDataLen); + ReturnValue_t prepareSetAlertLimitCmd(const uint8_t* commandData, size_t cmdDataLen); + ReturnValue_t prepareFactoryResetCmd(const uint8_t* commandData, size_t len); + ReturnValue_t prepareSetShutdownTimeoutCmd(const uint8_t* commandData, size_t cmdDataLen); + ReturnValue_t prepareSetGpioCmd(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareReadGpioCmd(const uint8_t* commandData, size_t commandDataLen); + ReturnValue_t prepareSetAdcEnabledChannelsCmd(const uint8_t* commandData); + ReturnValue_t prepareSetAdcWindowAndStrideCmd(const uint8_t* commandData); + ReturnValue_t prepareSetAdcThresholdCmd(const uint8_t* commandData); + ReturnValue_t prepareWipeMramCmd(const uint8_t* commandData, size_t cmdDataLen); + ReturnValue_t extractUpdateCommand(const uint8_t* commandData, size_t size, + supv::UpdateParams& params); + ReturnValue_t extractBaseParams(const uint8_t** commandData, size_t& remSize, + supv::UpdateParams& params); + void handleEvent(EventMessage* eventMessage); + + void handleBadApidServiceCombination(Event event, unsigned int apid, unsigned int serviceId); + ReturnValue_t eventSubscription(); + void handlePacketPrint(); + bool isCommandAlreadyActive(ActionId_t actionId) const; + ReturnValue_t handleAckReport(const uint8_t* data); + void printAckFailureInfo(uint16_t statusCode, DeviceCommandId_t commandId); + ReturnValue_t handleExecutionReport(const uint8_t* data); + ReturnValue_t handleExecutionSuccessReport(ActiveCmdInfo& info, supv::ExecutionReport& report); + void handleExecutionFailureReport(ActiveCmdInfo& info, supv::ExecutionReport& report); + ReturnValue_t handleHkReport(const uint8_t* data); + ReturnValue_t verifyPacket(const uint8_t* start, size_t foundLen); + void confirmReplyPacketReceived(supv::Apid apid, uint8_t serviceId); + void performCommandCompletionHandling(supv::Apid apid, uint8_t serviceId, ActiveCmdInfo& info); + ReturnValue_t handleBootStatusReport(const uint8_t* data); + ReturnValue_t genericHandleTm(const char* contextString, const uint8_t* data, + LocalPoolDataSetBase& set, supv::Apid apid, uint8_t serviceId); + ReturnValue_t handleLatchupStatusReport(const uint8_t* data); + + bool isCommandPending() const; +}; + +#endif /* LINUX_PAYLOAD_FRESHSUPVHANDLER_H_ */ diff --git a/linux/payload/MpsocCommunication.cpp b/linux/payload/MpsocCommunication.cpp new file mode 100644 index 0000000..edb8822 --- /dev/null +++ b/linux/payload/MpsocCommunication.cpp @@ -0,0 +1,75 @@ +#include "MpsocCommunication.h" + +#include "fsfw/globalfunctions/CRC.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tmtcpacket/ccsds/SpacePacketReader.h" +#include "fsfw/tmtcpacket/ccsds/header.h" +#include "linux/payload/plocMpsocHelpers.h" +#include "unistd.h" + +MpsocCommunication::MpsocCommunication(object_id_t objectId, SerialConfig cfg) + : SystemObject(objectId), readRingBuf(4096, true), helper(cfg) {} + +ReturnValue_t MpsocCommunication::initialize() { return helper.initialize(); } + +ReturnValue_t MpsocCommunication::send(const uint8_t* data, size_t dataLen) { + if (MPSOC_LOW_LEVEL_TX_WIRETAPPING) { + sif::debug << "SEND MPSOC packet with size " << dataLen << std::endl; + } + return helper.send(data, dataLen); +} + +ReturnValue_t MpsocCommunication::parseAndRetrieveNextPacket() { + // We do not have a data link layer, so this whole thing is a mess in any case.. + // But basically, we try to parse space packets from the internal ring buffer and trasnfer + // them to the higher level device handler. The CRC check is performed here as well, with + // few other ways to detect if we even have a valid packet. + size_t availableReadData = readRingBuf.getAvailableReadData(); + // Minimum valid size for a space packet header. + if (availableReadData < ccsds::HEADER_LEN + 1) { + return returnvalue::OK; + } + readRingBuf.readData(readBuf, availableReadData); + spReader.setReadOnlyData(readBuf, sizeof(readBuf)); + auto res = spReader.checkSize(); + if (res != returnvalue::OK) { + return res; + } + // The packet might be garbage, with no way to recover without a data link layer. + if (spReader.getFullPacketLen() > 4096) { + readRingBuf.clear(); + // TODO: Maybe we should also clear the serial input buffer in Linux? + return FAULTY_PACKET_SIZE; + } + if (availableReadData < spReader.getFullPacketLen()) { + // Might be split packet where the rest still has to be read. + return returnvalue::OK; + } + if (CRC::crc16ccitt(readBuf, spReader.getFullPacketLen()) != 0) { + // Possibly invalid packet. We can not even trust the detected packet length. + // Just clear the whole read buffer as well. + readRingBuf.clear(); + triggerEvent(mpsoc::CRC_FAILURE); + return CRC_CHECK_FAILED; + } + readRingBuf.deleteData(spReader.getFullPacketLen()); + return PACKET_RECEIVED; +} + +ReturnValue_t MpsocCommunication::readSerialInterface() { + int bytesRead = read(helper.rawFd(), readBuf, sizeof(readBuf)); + if (bytesRead < 0) { + return returnvalue::FAILED; + } + if (bytesRead > 0) { + if (MPSOC_LOW_LEVEL_RX_WIRETAPPING) { + sif::debug << "Read " << bytesRead << " bytes on the MPSoC interface" << std::endl; + } + return readRingBuf.writeData(readBuf, bytesRead); + } + return returnvalue::OK; +} + +const SpacePacketReader& MpsocCommunication::getSpReader() const { return spReader; } + +SerialCommunicationHelper& MpsocCommunication::getComHelper() { return helper; } diff --git a/linux/payload/MpsocCommunication.h b/linux/payload/MpsocCommunication.h new file mode 100644 index 0000000..b46c361 --- /dev/null +++ b/linux/payload/MpsocCommunication.h @@ -0,0 +1,44 @@ + +#pragma once + +#include + +#include "eive/resultClassIds.h" +#include "fsfw/container/SimpleRingBuffer.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tmtcpacket/ccsds/SpacePacketReader.h" +#include "linux/payload/SerialCommunicationHelper.h" + +static constexpr bool MPSOC_LOW_LEVEL_TX_WIRETAPPING = false; +static constexpr bool MPSOC_LOW_LEVEL_RX_WIRETAPPING = false; + +class MpsocCommunication : public SystemObject { + public: + static const uint8_t CLASS_ID = CLASS_ID::PLOC_MPSOC_COM; + static constexpr ReturnValue_t PACKET_RECEIVED = returnvalue::makeCode(CLASS_ID, 0); + static constexpr ReturnValue_t FAULTY_PACKET_SIZE = returnvalue::makeCode(CLASS_ID, 1); + static constexpr ReturnValue_t CRC_CHECK_FAILED = returnvalue::makeCode(CLASS_ID, 2); + + MpsocCommunication(object_id_t objectId, SerialConfig cfg); + ReturnValue_t initialize() override; + + ReturnValue_t send(const uint8_t* data, size_t dataLen); + + // Should be called periodically to transfer the received data from the MPSoC from the Linux + // buffer to the internal ring buffer for further processing. + ReturnValue_t readSerialInterface(); + + // Parses the internal ring buffer for packets and checks whether a packet was received. + ReturnValue_t parseAndRetrieveNextPacket(); + + // Can be used to read the parse packet, if one was received. + const SpacePacketReader& getSpReader() const; + + SerialCommunicationHelper& getComHelper(); + + private: + SpacePacketReader spReader; + uint8_t readBuf[4096]; + SimpleRingBuffer readRingBuf; + SerialCommunicationHelper helper; +}; diff --git a/linux/payload/PlocMemoryDumper.cpp b/linux/payload/PlocMemoryDumper.cpp new file mode 100644 index 0000000..2d61af6 --- /dev/null +++ b/linux/payload/PlocMemoryDumper.cpp @@ -0,0 +1,195 @@ +#include +#include + +#include +#include +#include + +#include "fsfw/ipc/QueueFactory.h" + +PlocMemoryDumper::PlocMemoryDumper(object_id_t objectId) + : SystemObject(objectId), commandActionHelper(this), actionHelper(this, nullptr) { + auto mqArgs = MqArgs(this->getObjectId()); + commandQueue = QueueFactory::instance()->createMessageQueue( + QUEUE_SIZE, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); +} + +PlocMemoryDumper::~PlocMemoryDumper() {} + +ReturnValue_t PlocMemoryDumper::initialize() { + ReturnValue_t result = SystemObject::initialize(); + if (result != returnvalue::OK) { + return result; + } + result = commandActionHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + result = actionHelper.initialize(commandQueue); + if (result != returnvalue::OK) { + return result; + } + + return returnvalue::OK; +} + +ReturnValue_t PlocMemoryDumper::performOperation(uint8_t operationCode) { + readCommandQueue(); + doStateMachine(); + return returnvalue::OK; +} + +ReturnValue_t PlocMemoryDumper::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + if (state != State::IDLE) { + return IS_BUSY; + } + + switch (actionId) { + case DUMP_MRAM: { + size_t deserializeSize = sizeof(mram.startAddress) + sizeof(mram.endAddress); + SerializeAdapter::deSerialize(&mram.startAddress, &data, &deserializeSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::deSerialize(&mram.endAddress, &data, &deserializeSize, + SerializeIF::Endianness::BIG); + if (mram.endAddress > MAX_MRAM_ADDRESS) { + return MRAM_ADDRESS_TOO_HIGH; + } + if (mram.endAddress <= mram.startAddress) { + return MRAM_INVALID_ADDRESS_COMBINATION; + } + state = State::COMMAND_FIRST_MRAM_DUMP; + break; + } + default: { + sif::warning << "PlocMemoryDumper::executeAction: Received command with invalid action id" + << std::endl; + return INVALID_ACTION_ID; + } + } + + return EXECUTION_FINISHED; +} + +MessageQueueId_t PlocMemoryDumper::getCommandQueue() const { return commandQueue->getId(); } + +MessageQueueIF* PlocMemoryDumper::getCommandQueuePtr() { return commandQueue; } + +void PlocMemoryDumper::readCommandQueue() { + CommandMessage message; + ReturnValue_t result = returnvalue::OK; + + for (result = commandQueue->receiveMessage(&message); result == returnvalue::OK; + result = commandQueue->receiveMessage(&message)) { + if (result != returnvalue::OK) { + continue; + } + result = actionHelper.handleActionMessage(&message); + if (result == returnvalue::OK) { + continue; + } + + result = commandActionHelper.handleReply(&message); + if (result == returnvalue::OK) { + continue; + } + + sif::debug << "PlocMemoryDumper::readCommandQueue: Received message with invalid format" + << std::endl; + } +} + +void PlocMemoryDumper::doStateMachine() { + switch (state) { + case State::IDLE: + break; + case State::COMMAND_FIRST_MRAM_DUMP: + commandNextMramDump(supv::FIRST_MRAM_DUMP); + break; + case State::COMMAND_CONSECUTIVE_MRAM_DUMP: + commandNextMramDump(supv::CONSECUTIVE_MRAM_DUMP); + break; + case State::EXECUTING_MRAM_DUMP: + break; + default: + sif::debug << "PlocMemoryDumper::doStateMachine: Invalid state" << std::endl; + break; + } +} + +void PlocMemoryDumper::stepSuccessfulReceived(ActionId_t actionId, uint8_t step) {} + +void PlocMemoryDumper::stepFailedReceived(ActionId_t actionId, uint8_t step, + ReturnValue_t returnCode) { + triggerEvent(MRAM_DUMP_FAILED); + state = State::IDLE; +} + +void PlocMemoryDumper::dataReceived(ActionId_t actionId, const uint8_t* data, uint32_t size) {} + +void PlocMemoryDumper::completionSuccessfulReceived(ActionId_t actionId) { + switch (pendingCommand) { + case (supv::FIRST_MRAM_DUMP): + case (supv::CONSECUTIVE_MRAM_DUMP): + if (mram.endAddress == mram.startAddress) { + triggerEvent(MRAM_DUMP_FINISHED); + state = State::IDLE; + } else { + state = State::COMMAND_CONSECUTIVE_MRAM_DUMP; + } + break; + default: + sif::debug << "PlocMemoryDumper::completionSuccessfulReceived: Invalid pending command" + << std::endl; + state = State::IDLE; + break; + } +} + +void PlocMemoryDumper::completionFailedReceived(ActionId_t actionId, ReturnValue_t returnCode) { + switch (pendingCommand) { + case (supv::FIRST_MRAM_DUMP): + case (supv::CONSECUTIVE_MRAM_DUMP): + triggerEvent(MRAM_DUMP_FAILED, mram.lastStartAddress); + pendingCommand = NONE; + break; + default: + break; + } + state = State::IDLE; +} + +void PlocMemoryDumper::commandNextMramDump(ActionId_t dumpCommand) { + ReturnValue_t result = returnvalue::OK; + + uint32_t tempStartAddress = 0; + uint32_t tempEndAddress = 0; + + if (mram.endAddress - mram.startAddress > MAX_MRAM_DUMP_SIZE) { + tempStartAddress = mram.startAddress; + tempEndAddress = mram.startAddress + MAX_MRAM_DUMP_SIZE; + mram.startAddress += MAX_MRAM_DUMP_SIZE; + } else { + tempStartAddress = mram.startAddress; + tempEndAddress = mram.endAddress; + mram.startAddress = mram.endAddress; + } + mram.lastStartAddress = tempStartAddress; + + MemoryParams params(tempStartAddress, tempEndAddress); + + result = + commandActionHelper.commandAction(objects::PLOC_SUPERVISOR_HANDLER, dumpCommand, ¶ms); + if (result != returnvalue::OK) { + sif::warning << "PlocMemoryDumper::commandNextMramDump: Failed to send mram dump command " + << "with start address " << tempStartAddress << " and end address " + << tempEndAddress << std::endl; + triggerEvent(SEND_MRAM_DUMP_FAILED, result, tempStartAddress); + state = State::IDLE; + pendingCommand = NONE; + return; + } + state = State::EXECUTING_MRAM_DUMP; + pendingCommand = dumpCommand; + return; +} diff --git a/linux/payload/PlocMemoryDumper.h b/linux/payload/PlocMemoryDumper.h new file mode 100644 index 0000000..63346d6 --- /dev/null +++ b/linux/payload/PlocMemoryDumper.h @@ -0,0 +1,117 @@ +#ifndef MISSION_DEVICES_PLOCMEMORYDUMPER_H_ +#define MISSION_DEVICES_PLOCMEMORYDUMPER_H_ + +#include +#include + +#include "OBSWConfig.h" + +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/SdCardManager.h" +#endif + +#include "eive/eventSubsystemIds.h" +#include "fsfw/action/ActionHelper.h" +#include "fsfw/action/CommandActionHelper.h" +#include "fsfw/action/CommandsActionsIF.h" +#include "fsfw/action/HasActionsIF.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "objects/systemObjectList.h" + +/** + * @brief Because the buffer of the linux tty driver is limited to 2 x 65535 bytes, this class is + * created to perform large dumps of PLOC memories. + * + * @details Currently the PLOC supervisor only implements the functionality to dump the MRAM. + * + * @author J. Meier + */ +class PlocMemoryDumper : public SystemObject, + public HasActionsIF, + public ExecutableObjectIF, + public CommandsActionsIF { + public: + static const ActionId_t NONE = 0; + static const ActionId_t DUMP_MRAM = 1; + + PlocMemoryDumper(object_id_t objectId); + virtual ~PlocMemoryDumper(); + + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size); + MessageQueueId_t getCommandQueue() const; + ReturnValue_t initialize() override; + MessageQueueIF* getCommandQueuePtr() override; + void stepSuccessfulReceived(ActionId_t actionId, uint8_t step) override; + void stepFailedReceived(ActionId_t actionId, uint8_t step, ReturnValue_t returnCode) override; + void dataReceived(ActionId_t actionId, const uint8_t* data, uint32_t size) override; + void completionSuccessfulReceived(ActionId_t actionId) override; + void completionFailedReceived(ActionId_t actionId, ReturnValue_t returnCode) override; + + private: + static const uint32_t QUEUE_SIZE = 10; + + static const uint8_t INTERFACE_ID = CLASS_ID::PLOC_MEMORY_DUMPER; + + //! [EXPORT] : [COMMENT] The capacity of the MRAM amounts to 512 kB. Thus the maximum address must + //! not be higher than 0x7d000. + static const ReturnValue_t MRAM_ADDRESS_TOO_HIGH = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] The specified end address is lower than the start address + static const ReturnValue_t MRAM_INVALID_ADDRESS_COMBINATION = MAKE_RETURN_CODE(0xA1); + + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_MEMORY_DUMPER; + + //! [EXPORT] : [COMMENT] Failed to send mram dump command to supervisor handler + //! P1: Return value of commandAction function + //! P2: Start address of MRAM to dump with this command + static const Event SEND_MRAM_DUMP_FAILED = MAKE_EVENT(0, severity::LOW); + //! [EXPORT] : [COMMENT] Received completion failure report form PLOC supervisor handler + //! P1: MRAM start address of failing dump command + static const Event MRAM_DUMP_FAILED = MAKE_EVENT(1, severity::LOW); + //! [EXPORT] : [COMMENT] MRAM dump finished successfully + static const Event MRAM_DUMP_FINISHED = MAKE_EVENT(2, severity::LOW); + + // Maximum size of mram dump which can be retrieved with one command + static const uint32_t MAX_MRAM_DUMP_SIZE = 100000; + static const uint32_t MAX_MRAM_ADDRESS = 0x7d000; + + MessageQueueIF* commandQueue = nullptr; + + CommandActionHelper commandActionHelper; + + ActionHelper actionHelper; + + enum class State : uint8_t { + IDLE, + COMMAND_FIRST_MRAM_DUMP, + COMMAND_CONSECUTIVE_MRAM_DUMP, + EXECUTING_MRAM_DUMP + }; + + State state = State::IDLE; + + ActionId_t pendingCommand = NONE; + + typedef struct MemoryInfo { + // Stores the start address of the next memory range to dump + uint32_t startAddress; + uint32_t endAddress; + // Stores the start address of the last sent dump command + uint32_t lastStartAddress; + } MemoryInfo_t; + + MemoryInfo_t mram = {0, 0, 0}; + + void readCommandQueue(); + void doStateMachine(); + + /** + * @brief Sends the next mram dump command to the PLOC supervisor handler. + */ + void commandNextMramDump(ActionId_t dumpCommand); +}; + +#endif /* MISSION_DEVICES_PLOCMEMORYDUMPER_H_ */ diff --git a/linux/payload/PlocMpsocSpecialComHelper.cpp b/linux/payload/PlocMpsocSpecialComHelper.cpp new file mode 100644 index 0000000..0af2c3d --- /dev/null +++ b/linux/payload/PlocMpsocSpecialComHelper.cpp @@ -0,0 +1,517 @@ +#include +#include +#include +#include + +#include +#include + +#include "fsfw/serviceinterface/ServiceInterfacePrinter.h" +#include "fsfw/serviceinterface/ServiceInterfaceStream.h" +#include "fsfw/tmtcpacket/ccsds/SpacePacketReader.h" +#include "linux/payload/MpsocCommunication.h" +#include "linux/payload/plocMpsocHelpers.h" + +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/FilesystemHelper.h" +#endif + +using namespace ploc; + +PlocMpsocSpecialComHelper::PlocMpsocSpecialComHelper(object_id_t objectId, + MpsocCommunication& comInterface) + : SystemObject(objectId), comInterface(comInterface) { + spParams.buf = commandBuffer; + spParams.maxSize = sizeof(commandBuffer); +} + +PlocMpsocSpecialComHelper::~PlocMpsocSpecialComHelper() {} + +ReturnValue_t PlocMpsocSpecialComHelper::initialize() { +#ifdef XIPHOS_Q7S + sdcMan = SdCardManager::instance(); + if (sdcMan == nullptr) { + sif::warning << "PlocMPSoCHelper::initialize: Invalid SD Card Manager" << std::endl; + return returnvalue::FAILED; + } +#endif + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelper::performOperation(uint8_t operationCode) { + ReturnValue_t result = returnvalue::OK; + semaphore.acquire(); + while (true) { +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "PLOC MPSOC Helper"); +#endif + switch (internalState) { + case InternalState::IDLE: { + semaphore.acquire(); + break; + } + case InternalState::FLASH_WRITE: { + result = performFlashWrite(); + if (result == returnvalue::OK) { + triggerEvent(MPSOC_FLASH_WRITE_SUCCESSFUL, txSequenceCount.get()); + } else { + triggerEvent(MPSOC_FLASH_WRITE_FAILED, txSequenceCount.get()); + } + internalState = InternalState::IDLE; + break; + } + case InternalState::FLASH_READ: { + result = performFlashRead(); + if (result == returnvalue::OK) { + triggerEvent(MPSOC_FLASH_READ_SUCCESSFUL, txSequenceCount.get()); + } else { + sif::printWarning("PLOC MPSoC Helper: Flash read failed with code %04x\n", result); + triggerEvent(MPSOC_FLASH_READ_FAILED, txSequenceCount.get(), result); + } + internalState = InternalState::IDLE; + break; + } + default: + sif::debug << "PlocMPSoCHelper::performOperation: Invalid state" << std::endl; + break; + } + } +} + +void PlocMpsocSpecialComHelper::setCommandSequenceCount(uint16_t sequenceCount_) { + txSequenceCount.set(sequenceCount_); +} + +uint16_t PlocMpsocSpecialComHelper::getCommandSequenceCount() const { + return txSequenceCount.get(); +} + +ReturnValue_t PlocMpsocSpecialComHelper::startFlashWrite(std::string obcFile, + std::string mpsocFile) { + if (internalState != InternalState::IDLE) { + return returnvalue::FAILED; + } + ReturnValue_t result = startFlashReadOrWriteBase(std::move(obcFile), std::move(mpsocFile)); + if (result != returnvalue::OK) { + return result; + } + internalState = InternalState::FLASH_WRITE; + return semaphore.release(); +} + +ReturnValue_t PlocMpsocSpecialComHelper::startFlashRead(std::string obcFile, std::string mpsocFile, + size_t readFileSize) { + if (internalState != InternalState::IDLE) { + return returnvalue::FAILED; + } + ReturnValue_t result = startFlashReadOrWriteBase(std::move(obcFile), std::move(mpsocFile)); + if (result != returnvalue::OK) { + return result; + } + flashReadAndWrite.totalReadSize = readFileSize; + internalState = InternalState::FLASH_READ; + return semaphore.release(); +} + +void PlocMpsocSpecialComHelper::resetHelper() { + spParams.buf = commandBuffer; + terminate = false; + auto& helper = comInterface.getComHelper(); + helper.flushUartRxBuffer(); +} + +void PlocMpsocSpecialComHelper::stopProcess() { terminate = true; } + +ReturnValue_t PlocMpsocSpecialComHelper::performFlashWrite() { + ReturnValue_t result = returnvalue::OK; + std::ifstream file(flashReadAndWrite.obcFile, std::ifstream::binary); + if (file.bad()) { + return returnvalue::FAILED; + } + result = flashfopen(mpsoc::FileAccessModes::WRITE | mpsoc::FileAccessModes::OPEN_ALWAYS); + if (result != returnvalue::OK) { + return result; + } + // Set position of next character to end of file input stream + file.seekg(0, file.end); + // tellg returns position of character in input stream + size_t remainingSize = file.tellg(); + size_t dataLength = 0; + size_t bytesRead = 0; + while (remainingSize > 0) { + if (terminate) { + return returnvalue::OK; + } + // The minus 4 is necessary for unknown reasons. Maybe some bug in the ILH software? + if (remainingSize > mpsoc::MAX_FLASH_WRITE_DATA_SIZE - 4) { + dataLength = mpsoc::MAX_FLASH_WRITE_DATA_SIZE - 4; + } else { + dataLength = remainingSize; + } + if (file.bad() or not file.is_open()) { + return FILE_WRITE_ERROR; + } + file.seekg(bytesRead, file.beg); + file.read(reinterpret_cast(fileBuf.data()), dataLength); + bytesRead += dataLength; + remainingSize -= dataLength; + mpsoc::TcFlashWrite tc(spParams, txSequenceCount); + result = tc.setPayload(fileBuf.data(), dataLength); + if (result != returnvalue::OK) { + return result; + } + result = tc.finishPacket(); + if (result != returnvalue::OK) { + return result; + } + txSequenceCount.increment(); + result = handlePacketTransmissionNoReply(tc); + if (result != returnvalue::OK) { + return result; + } + } + result = flashfclose(); + if (result != returnvalue::OK) { + return result; + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelper::performFlashRead() { + std::error_code e; + if (std::filesystem::exists(flashReadAndWrite.obcFile)) { + // Truncate the file first. + std::ofstream ofile(flashReadAndWrite.obcFile, std::ios::binary | std::ios::trunc); + } + std::ofstream ofile(flashReadAndWrite.obcFile, std::ios::binary | std::ios::app); + if (ofile.bad() or not ofile.is_open()) { + return returnvalue::FAILED; + } + ReturnValue_t result = flashfopen(mpsoc::FileAccessModes::READ); + if (result != returnvalue::OK) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return result; + } + size_t readSoFar = 0; + size_t nextReadSize = mpsoc::MAX_FLASH_READ_DATA_SIZE; + while (readSoFar < flashReadAndWrite.totalReadSize) { + if (terminate) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return returnvalue::OK; + } + nextReadSize = mpsoc::MAX_FLASH_READ_DATA_SIZE; + if (flashReadAndWrite.totalReadSize - readSoFar < mpsoc::MAX_FLASH_READ_DATA_SIZE) { + nextReadSize = flashReadAndWrite.totalReadSize - readSoFar; + } + if (ofile.bad() or not ofile.is_open()) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return FILE_READ_ERROR; + } + mpsoc::TcFlashRead flashReadRequest(spParams, txSequenceCount); + result = flashReadRequest.setPayload(nextReadSize); + if (result != returnvalue::OK) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return result; + } + result = flashReadRequest.finishPacket(); + if (result != returnvalue::OK) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return result; + } + txSequenceCount.increment(); + result = handlePacketTransmissionFlashRead(flashReadRequest, ofile, nextReadSize); + if (result != returnvalue::OK) { + std::filesystem::remove(flashReadAndWrite.obcFile, e); + return result; + } + readSoFar += nextReadSize; + } + result = flashfclose(); + if (result != returnvalue::OK) { + return result; + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelper::flashfopen(uint8_t mode) { + spParams.buf = commandBuffer; + mpsoc::TcFlashFopen flashFopen(spParams, txSequenceCount); + ReturnValue_t result = flashFopen.setPayload(flashReadAndWrite.mpsocFile, mode); + if (result != returnvalue::OK) { + return result; + } + result = flashFopen.finishPacket(); + if (result != returnvalue::OK) { + return result; + } + txSequenceCount.increment(); + result = handlePacketTransmissionNoReply(flashFopen); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelper::flashfclose() { + spParams.buf = commandBuffer; + mpsoc::TcFlashFclose flashFclose(spParams, txSequenceCount); + ReturnValue_t result = flashFclose.finishPacket(); + if (result != returnvalue::OK) { + return result; + } + txSequenceCount.increment(); + result = handlePacketTransmissionNoReply(flashFclose); + if (result != returnvalue::OK) { + return result; + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelper::handlePacketTransmissionFlashRead(mpsoc::TcFlashRead& tc, + std::ofstream& ofile, + size_t expectedReadLen) { + ReturnValue_t result = sendCommand(tc); + if (result != returnvalue::OK) { + return result; + } + result = handleAck(); + if (result != returnvalue::OK) { + return result; + } + result = handleTmReception(); + if (result != returnvalue::OK) { + return result; + } + auto& spReader = comInterface.getSpReader(); + + // We have the nominal case where the flash read report appears first, or the case where we + // get an EXE failure immediately. + if (spReader.getApid() == mpsoc::apid::TM_FLASH_READ_REPORT) { + result = handleFlashReadReply(ofile, expectedReadLen); + if (result != returnvalue::OK) { + return result; + } + return handleExe(); + } else if (spReader.getApid() == mpsoc::apid::EXE_FAILURE) { + handleExeFailure(spReader); + } else { + triggerEvent(MPSOC_EXE_INVALID_APID, spReader.getApid(), static_cast(internalState)); + sif::warning << "PLOC MPSoC: Expected execution report " + << "but received space packet with apid " << std::hex << spReader.getApid() + << std::endl; + } + return returnvalue::FAILED; +} + +ReturnValue_t PlocMpsocSpecialComHelper::handlePacketTransmissionNoReply(ploc::SpTcBase& tc) { + ReturnValue_t result = sendCommand(tc); + if (result != returnvalue::OK) { + return result; + } + result = handleAck(); + if (result != returnvalue::OK) { + return result; + } + return handleExe(); +} + +ReturnValue_t PlocMpsocSpecialComHelper::sendCommand(ploc::SpTcBase& tc) { + ReturnValue_t result = comInterface.send(tc.getFullPacket(), tc.getFullPacketLen()); + mpsoc::printTxPacket(tc); + if (result != returnvalue::OK) { + sif::warning << "PlocMPSoCHelper::sendCommand: Failed to send command" << std::endl; + triggerEvent(MPSOC_SENDING_COMMAND_FAILED, result, static_cast(internalState)); + return result; + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelper::handleAck() { + ReturnValue_t result = returnvalue::OK; + result = handleTmReception(); + if (result != returnvalue::OK) { + return result; + } + result = checkReceivedTm(); + if (result != returnvalue::OK) { + return result; + } + const auto& spReader = comInterface.getSpReader(); + + uint16_t apid = spReader.getApid(); + if (apid != mpsoc::apid::ACK_SUCCESS) { + handleAckApidFailure(spReader); + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void PlocMpsocSpecialComHelper::handleAckApidFailure(const SpacePacketReader& reader) { + uint16_t apid = reader.getApid(); + if (apid == mpsoc::apid::ACK_FAILURE) { + uint16_t status = mpsoc::getStatusFromRawData(reader.getFullData()); + sif::warning << "PLOC MPSoC ACK Failure: " << mpsoc::getStatusString(status) << std::endl; + triggerEvent(MPSOC_ACK_FAILURE_REPORT, static_cast(internalState), status); + } else { + triggerEvent(MPSOC_ACK_INVALID_APID, apid, static_cast(internalState)); + sif::warning << "PlocMPSoCHelper::handleAckApidFailure: Expected acknowledgement report " + << "but received space packet with apid " << std::hex << apid << std::endl; + } +} + +ReturnValue_t PlocMpsocSpecialComHelper::handleExe() { + ReturnValue_t result = returnvalue::OK; + + result = handleTmReception(); + if (result != returnvalue::OK) { + return result; + } + result = checkReceivedTm(); + if (result != returnvalue::OK) { + return result; + } + const auto& spReader = comInterface.getSpReader(); + uint16_t apid = spReader.getApid(); + if (apid == mpsoc::apid::EXE_FAILURE) { + handleExeFailure(spReader); + return returnvalue::FAILED; + } else if (apid != mpsoc::apid::EXE_SUCCESS) { + triggerEvent(MPSOC_EXE_INVALID_APID, apid, static_cast(internalState)); + sif::warning << "PLOC MPSoC: Expected execution report " + << "but received space packet with apid " << std::hex << apid << std::endl; + } + return returnvalue::OK; +} + +void PlocMpsocSpecialComHelper::handleExeFailure(const SpacePacketReader& spReader) { + uint16_t status = mpsoc::getStatusFromRawData(spReader.getFullData()); + sif::warning << "PLOC MPSoC EXE Failure: " << mpsoc::getStatusString(status) << std::endl; + triggerEvent(MPSOC_EXE_FAILURE_REPORT, static_cast(internalState)); +} + +ReturnValue_t PlocMpsocSpecialComHelper::handleTmReception() { + ReturnValue_t result = returnvalue::OK; + tmCountdown.resetTimer(); + uint32_t usleepDelay = 5; + while (true) { + if (tmCountdown.hasTimedOut()) { + triggerEvent(MPSOC_READ_TIMEOUT, tmCountdown.getTimeoutMs()); + return returnvalue::FAILED; + } + result = tryReceiveNextReply(); + if (result == MpsocCommunication::PACKET_RECEIVED) { + // Need to convert this, we are faking a synchronous API here. + result = returnvalue::OK; + break; + } + if (result != returnvalue::OK) { + if (result == MpsocCommunication::FAULTY_PACKET_SIZE) { + sif::printWarning("PLOC MPSoC Helper: retrieving next reply failed: faulty packet size\n"); + } else if (result == MpsocCommunication::CRC_CHECK_FAILED) { + sif::printWarning("PLOC MPSoC Helper: retrieving next reply failed: CRC check failed\n"); + } + sif::printWarning("PLOC MPSoC Helper: retrieving next reply failed with code %d\n", result); + return result; + } + usleep(usleepDelay); + if (usleepDelay < 200000) { + usleepDelay *= 4; + } + } + return result; +} + +ReturnValue_t PlocMpsocSpecialComHelper::handleFlashReadReply(std::ofstream& ofile, + size_t expectedReadLen) { + ReturnValue_t result = checkReceivedTm(); + if (result != returnvalue::OK) { + return result; + } + auto& spReader = comInterface.getSpReader(); + uint16_t apid = spReader.getApid(); + if (apid != mpsoc::apid::TM_FLASH_READ_REPORT) { + triggerEvent(MPSOC_FLASH_READ_PACKET_ERROR, FlashReadErrorType::FLASH_READ_APID_ERROR); + sif::warning << "PLOC MPSoC Flash Read: Unexpected APID" << std::endl; + return result; + } + const uint8_t* packetData = spReader.getPacketData(); + size_t deserDummy = spReader.getPacketDataLen() - mpsoc::CRC_SIZE; + uint32_t receivedReadLen = 0; + // I think this is buggy, weird stuff in the short name field. + // std::string receivedShortName = std::string(reinterpret_cast(packetData), 12); + // if (receivedShortName != flashReadAndWrite.mpsocFile.substr(0, 11)) { + // sif::warning << "PLOC MPSoC Flash Read: Missmatch between request file name and " + // "received file name" + // << std::endl; + // triggerEvent(MPSOC_FLASH_READ_PACKET_ERROR, FlashReadErrorType::FLASH_READ_FILENAME_ERROR); + // return returnvalue::FAILED; + // } + packetData += 12; + result = SerializeAdapter::deSerialize(&receivedReadLen, &packetData, &deserDummy, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + if (receivedReadLen != expectedReadLen) { + sif::warning << "PLOC MPSoC Flash Read: Missmatch between request read length and " + "received read length" + << std::endl; + triggerEvent(MPSOC_FLASH_READ_PACKET_ERROR, FlashReadErrorType::FLASH_READ_READLEN_ERROR); + return returnvalue::FAILED; + } + ofile.write(reinterpret_cast(packetData), receivedReadLen); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelper::fileCheck(std::string obcFile) { +#ifdef XIPHOS_Q7S + ReturnValue_t result = FilesystemHelper::checkPath(obcFile); + if (result != returnvalue::OK) { + return result; + } +#elif defined(TE0720_1CFA) + if (not std::filesystem::exists(obcFile)) { + sif::warning << "PlocMPSoCHelper::startFlashWrite: File " << obcFile << "does not exist" + << std::endl; + return returnvalue::FAILED; + } +#endif + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelper::startFlashReadOrWriteBase(std::string obcFile, + std::string mpsocFile) { + ReturnValue_t result = fileCheck(obcFile); + if (result != returnvalue::OK) { + return result; + } + + flashReadAndWrite.obcFile = std::move(obcFile); + flashReadAndWrite.mpsocFile = std::move(mpsocFile); + resetHelper(); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelper::checkReceivedTm() { + const auto& spReader = comInterface.getSpReader(); + ReturnValue_t result = spReader.checkSize(); + if (result != returnvalue::OK) { + sif::error << "PLOC MPSoC: Size check on received TM failed" << std::endl; + triggerEvent(MPSOC_TM_SIZE_ERROR); + return result; + } + rxSequenceCount = spReader.getSequenceCount(); + mpsoc::printRxPacket(spReader); + return returnvalue::OK; +} + +ReturnValue_t PlocMpsocSpecialComHelper::tryReceiveNextReply() { + ReturnValue_t result = returnvalue::OK; + result = comInterface.readSerialInterface(); + if (result != returnvalue::OK) { + triggerEvent(MPSOC_HELPER_REQUESTING_REPLY_FAILED, result, + static_cast(static_cast(internalState))); + return returnvalue::FAILED; + } + return comInterface.parseAndRetrieveNextPacket(); +} diff --git a/linux/payload/PlocMpsocSpecialComHelper.h b/linux/payload/PlocMpsocSpecialComHelper.h new file mode 100644 index 0000000..dce6892 --- /dev/null +++ b/linux/payload/PlocMpsocSpecialComHelper.h @@ -0,0 +1,199 @@ +#ifndef BSP_Q7S_DEVICES_PLOCMPSOCHELPER_H_ +#define BSP_Q7S_DEVICES_PLOCMPSOCHELPER_H_ + +#include +#include + +#include + +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/osal/linux/BinarySemaphore.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw/tmtcpacket/ccsds/SpacePacketReader.h" +#include "fsfw/tmtcservices/SourceSequenceCounter.h" +#include "linux/payload/MpsocCommunication.h" +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/SdCardManager.h" +#endif + +/** + * @brief Helper class for MPSoC of PLOC intended to accelerate large data transfers between + * MPSoC and OBC. + * @author J. Meier + */ +class PlocMpsocSpecialComHelper : public SystemObject, public ExecutableObjectIF { + public: + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_MPSOC_HELPER; + + //! [EXPORT] : [COMMENT] Flash write fails + static const Event MPSOC_FLASH_WRITE_FAILED = MAKE_EVENT(0, severity::LOW); + //! [EXPORT] : [COMMENT] Flash write successful + static const Event MPSOC_FLASH_WRITE_SUCCESSFUL = MAKE_EVENT(1, severity::INFO); + //! [EXPORT] : [COMMENT] Communication interface returned failure when trying to send the command + //! to the MPSoC + //! P1: Return value returned by the communication interface sendMessage function + //! P2: Internal state of MPSoC helper + static const Event MPSOC_SENDING_COMMAND_FAILED = MAKE_EVENT(2, severity::LOW); + //! [EXPORT] : [COMMENT] Request receive message of communication interface failed + //! P1: Return value returned by the communication interface requestReceiveMessage function + //! P2: Internal state of MPSoC helper + static const Event MPSOC_HELPER_REQUESTING_REPLY_FAILED = MAKE_EVENT(3, severity::LOW); + //! [EXPORT] : [COMMENT] Reading receive message of communication interface failed + //! P1: Return value returned by the communication interface readingReceivedMessage function + //! P2: Internal state of MPSoC helper + static const Event MPSOC_HELPER_READING_REPLY_FAILED = MAKE_EVENT(4, severity::LOW); + //! [EXPORT] : [COMMENT] Did not receive acknowledgment report + //! P1: Number of bytes missing + //! P2: Internal state of MPSoC helper + static const Event MPSOC_MISSING_ACK = MAKE_EVENT(5, severity::LOW); + //! [EXPORT] : [COMMENT] Did not receive execution report + //! P1: Number of bytes missing + //! P2: Internal state of MPSoC helper + static const Event MPSOC_MISSING_EXE = MAKE_EVENT(6, severity::LOW); + //! [EXPORT] : [COMMENT] Received acknowledgment failure report + //! P1: Internal state of MPSoC + static const Event MPSOC_ACK_FAILURE_REPORT = MAKE_EVENT(7, severity::LOW); + //! [EXPORT] : [COMMENT] Received execution failure report + //! P1: Internal state of MPSoC + static const Event MPSOC_EXE_FAILURE_REPORT = MAKE_EVENT(8, severity::LOW); + //! [EXPORT] : [COMMENT] Expected acknowledgment report but received space packet with other apid + //! P1: Apid of received space packet + //! P2: Internal state of MPSoC + static const Event MPSOC_ACK_INVALID_APID = MAKE_EVENT(9, severity::LOW); + //! [EXPORT] : [COMMENT] Expected execution report but received space packet with other apid + //! P1: Apid of received space packet + //! P2: Internal state of MPSoC + static const Event MPSOC_EXE_INVALID_APID = MAKE_EVENT(10, severity::LOW); + //! [EXPORT] : [COMMENT] Received sequence count does not match expected sequence count + //! P1: Expected sequence count + //! P2: Received sequence count + static const Event MPSOC_HELPER_SEQ_CNT_MISMATCH = MAKE_EVENT(11, severity::LOW); + static const Event MPSOC_TM_SIZE_ERROR = MAKE_EVENT(12, severity::LOW); + static const Event MPSOC_TM_CRC_MISSMATCH = MAKE_EVENT(13, severity::LOW); + static const Event MPSOC_FLASH_READ_PACKET_ERROR = MAKE_EVENT(14, severity::LOW); + static const Event MPSOC_FLASH_READ_FAILED = MAKE_EVENT(15, severity::LOW); + static const Event MPSOC_FLASH_READ_SUCCESSFUL = MAKE_EVENT(16, severity::INFO); + static const Event MPSOC_READ_TIMEOUT = MAKE_EVENT(17, severity::LOW); + + enum FlashReadErrorType : uint32_t { + FLASH_READ_APID_ERROR = 0, + FLASH_READ_FILENAME_ERROR = 1, + FLASH_READ_READLEN_ERROR = 2 + }; + + PlocMpsocSpecialComHelper(object_id_t objectId, MpsocCommunication& comInterface); + virtual ~PlocMpsocSpecialComHelper(); + + ReturnValue_t initialize() override; + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + + /** + * @brief Starts flash write sequence + * + * @param obcFile File where to read from the data + * @param mpsocFile The file of the MPSoC where should be written to + * + * @return returnvalue::OK if successful, otherwise error return value + */ + ReturnValue_t startFlashWrite(std::string obcFile, std::string mpsocFile); + /** + * + * @param obcFile Full target file name on OBC + * @param mpsocFile The file on the MPSoC which should be copied ot the OBC + * @param readFileSize The size of the file on the MPSoC. + * @return + */ + ReturnValue_t startFlashRead(std::string obcFile, std::string mpsocFile, size_t readFileSize); + + /** + * @brief Can be used to interrupt a running data transfer. + */ + void stopProcess(); + + /** + * @brief Sets the sequence count object responsible for the sequence count handling + */ + void setCommandSequenceCount(uint16_t sequenceCount_); + uint16_t getCommandSequenceCount() const; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::PLOC_MPSOC_HELPER; + + //! [EXPORT] : [COMMENT] File error occured for file transfers from OBC to the MPSoC. + static const ReturnValue_t FILE_WRITE_ERROR = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] File error occured for file transfers from MPSoC to OBC. + static const ReturnValue_t FILE_READ_ERROR = MAKE_RETURN_CODE(0xA1); + + // Maximum number of times the communication interface retries polling data from the reply + // buffer + static const int RETRIES = 10000; + + struct FlashInfo { + std::string obcFile; + std::string mpsocFile; + }; + + struct FlashRead : public FlashInfo { + size_t totalReadSize = 0; + }; + + struct FlashRead flashReadAndWrite; +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter = 0; +#endif + + enum class InternalState { IDLE, FLASH_WRITE, FLASH_READ }; + + InternalState internalState = InternalState::IDLE; + + BinarySemaphore semaphore; +#ifdef XIPHOS_Q7S + SdCardManager* sdcMan = nullptr; +#endif + uint8_t commandBuffer[mpsoc::MAX_COMMAND_SIZE]; + SpacePacketCreator creator; + ploc::SpTcParams spParams = ploc::SpTcParams(creator); + + Countdown tmCountdown = Countdown(5000); + + std::array fileBuf{}; + std::array tmBuf{}; + + bool terminate = false; + + /** + * Communication interface of MPSoC responsible for low level access. Must be set by the + * MPSoC Handler. + */ + // SerialComIF* uartComIF = nullptr; + // Communication cookie. Must be set by the MPSoC Handler + // CookieIF* comCookie = nullptr; + MpsocCommunication& comInterface; + // Sequence count, must be set by Ploc MPSoC Handler + // ploc::SpTmReader spReader; + uint16_t rxSequenceCount = 0; + SourceSequenceCounter txSequenceCount = 0; + + void resetHelper(); + ReturnValue_t performFlashWrite(); + ReturnValue_t performFlashRead(); + ReturnValue_t flashfopen(uint8_t accessMode); + ReturnValue_t flashfclose(); + ReturnValue_t handlePacketTransmissionNoReply(ploc::SpTcBase& tc); + ReturnValue_t handlePacketTransmissionFlashRead(mpsoc::TcFlashRead& tc, std::ofstream& ofile, + size_t expectedReadLen); + ReturnValue_t handleFlashReadReply(std::ofstream& ofile, size_t expectedReadLen); + ReturnValue_t sendCommand(ploc::SpTcBase& tc); + ReturnValue_t tryReceiveNextReply(); + ReturnValue_t handleAck(); + ReturnValue_t handleExe(); + ReturnValue_t startFlashReadOrWriteBase(std::string obcFile, std::string mpsocFile); + ReturnValue_t fileCheck(std::string obcFile); + void handleAckApidFailure(const SpacePacketReader& reader); + void handleExeFailure(const SpacePacketReader& reader); + ReturnValue_t handleTmReception(); + ReturnValue_t checkReceivedTm(); +}; + +#endif /* BSP_Q7S_DEVICES_PLOCMPSOCHELPER_H_ */ diff --git a/linux/payload/PlocSupvUartMan.cpp b/linux/payload/PlocSupvUartMan.cpp new file mode 100644 index 0000000..cb93b4e --- /dev/null +++ b/linux/payload/PlocSupvUartMan.cpp @@ -0,0 +1,1183 @@ +#include +#include // Contains file controls like O_RDWR +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "OBSWConfig.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "linux/payload/plocSupvDefs.h" +#include "tas/hdlc.h" +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/FilesystemHelper.h" +#include "bsp_q7s/fs/SdCardManager.h" +#endif + +#include + +#include "fsfw/tasks/TaskFactory.h" +#include "fsfw/timemanager/Countdown.h" + +#if OBSW_DEBUG_PLOC_SUPERVISOR == 1 +#include "mission/utility/Filenaming.h" +#include "mission/utility/ProgressPrinter.h" +#include "mission/utility/Timestamp.h" +#endif + +#include "tas/crc.h" + +using namespace returnvalue; +using namespace supv; + +PlocSupvUartManager::PlocSupvUartManager(object_id_t objectId) + : SystemObject(objectId), + recRingBuf(4096, true), + decodedRingBuf(1200 * MAX_STORED_DECODED_PACKETS, true), + ipcRingBuf(1200 * MAX_STORED_DECODED_PACKETS, true) { + resetSpParams(); + spParams.maxSize = cmdBuf.size(); + semaphore = SemaphoreFactory::instance()->createBinarySemaphore(); + semaphore->acquire(); + lock = MutexFactory::instance()->createMutex(); + ipcLock = MutexFactory::instance()->createMutex(); +} + +PlocSupvUartManager::~PlocSupvUartManager() = default; + +ReturnValue_t PlocSupvUartManager::initializeInterface(CookieIF* cookie) { + auto* uartCookie = dynamic_cast(cookie); + if (uartCookie == nullptr) { + return FAILED; + } + std::string devname = uartCookie->getDeviceFile(); + /* Get file descriptor */ + serialPort = open(devname.c_str(), O_RDWR); + if (serialPort < 0) { + sif::warning << "PlocSupvUartManager::initializeInterface: open call failed with error [" + << errno << ", " << strerror(errno) << std::endl; + return FAILED; + } + // Setting up UART parameters + tty.c_cflag &= ~PARENB; // Clear parity bit + serial::setParity(tty, uartCookie->getParity()); + serial::setStopbits(tty, uartCookie->getStopBits()); + serial::setBitsPerWord(tty, BitsPerWord::BITS_8); + tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control + serial::enableRead(tty); + serial::ignoreCtrlLines(tty); + + // Use non-canonical mode and clear echo flag + tty.c_lflag &= ~(ICANON | ECHO); + + // Blocking mode, 0.2 seconds timeout + tty.c_cc[VTIME] = 2; + tty.c_cc[VMIN] = 0; + + serial::setBaudrate(tty, uartCookie->getBaudrate()); + if (tcsetattr(serialPort, TCSANOW, &tty) != 0) { + sif::warning << "PlocSupvUartManager::initializeInterface: tcsetattr call failed with error [" + << errno << ", " << strerror(errno) << std::endl; + } + // Flush received and unread data + tcflush(serialPort, TCIOFLUSH); + return OK; +} + +ReturnValue_t PlocSupvUartManager::initialize() { +#ifdef XIPHOS_Q7S + sdcMan = SdCardManager::instance(); + if (sdcMan == nullptr) { + sif::warning << "PlocSupvHelper::initialize: Invalid SD Card Manager" << std::endl; + return returnvalue::FAILED; + } +#endif + return returnvalue::OK; +} + +ReturnValue_t PlocSupvUartManager::performOperation(uint8_t operationCode) { + bool putTaskToSleep = false; + while (true) { + { + MutexGuard mg(lock); + state = InternalState::SLEEPING; + } + semaphore->acquire(); + putTaskToSleep = false; +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "PLOC SUPV Helper PST"); +#endif + while (true) { + if (putTaskToSleep) { + performUartShutdown(); + break; + } + handleUartReception(); + InternalState currentState; + { + MutexGuard mg(lock); + currentState = state; + } + switch (currentState) { + case InternalState::SLEEPING: + case InternalState::GO_TO_SLEEP: { + putTaskToSleep = true; + break; + } + case InternalState::DEDICATED_REQUEST: { + updateVtime(1); + handleRunningLongerRequest(); + updateVtime(2); + MutexGuard mg(lock); + state = InternalState::DEFAULT; + break; + } + case InternalState::DEFAULT: { + break; + } + } + } + } +} + +ReturnValue_t PlocSupvUartManager::handleUartReception() { + ReturnValue_t result = OK; + ReturnValue_t status = OK; + ssize_t bytesRead = read(serialPort, reinterpret_cast(recBuf.data()), + static_cast(recBuf.size())); + if (bytesRead == 0) { + while (result != NO_PACKET_FOUND) { + result = tryHdlcParsing(); + if (result != NO_PACKET_FOUND and result != OK) { + status = result; + } + } + } else if (bytesRead < 0) { + sif::warning << "PlocSupvHelper::performOperation: read call failed with error [" << errno + << ", " << strerror(errno) << "]" << std::endl; + return FAILED; + } else if (bytesRead >= static_cast(recBuf.size())) { + sif::error << "PlocSupvHelper::performOperation: Receive buffer too small for " << bytesRead + << " bytes" << std::endl; + return FAILED; + } else if (bytesRead > 0) { + if (DEBUG_MODE) { + sif::info << "Received " << bytesRead << " bytes from the PLOC Supervisor:" << std::endl; + arrayprinter::print(recBuf.data(), bytesRead); + } + recRingBuf.writeData(recBuf.data(), bytesRead); + status = tryHdlcParsing(); + } + return status; +} + +ReturnValue_t PlocSupvUartManager::startUpdate(std::string file, uint8_t memoryId, + uint32_t startAddress) { + supv::UpdateParams params; + params.file = file; + params.memId = memoryId; + params.startAddr = startAddress; + params.bytesWritten = 0; + params.seqCount = 1; + params.deleteMemory = true; + return performUpdate(params); +} + +ReturnValue_t PlocSupvUartManager::performUpdate(const supv::UpdateParams& params) { + lock->lockMutex(); + InternalState current = state; + lock->unlockMutex(); + if (current != InternalState::DEFAULT) { + return HasActionsIF::IS_BUSY; + } + ReturnValue_t result = returnvalue::OK; +#ifdef XIPHOS_Q7S + result = FilesystemHelper::checkPath(params.file); + if (result != returnvalue::OK) { + sif::warning << "PlocSupvHelper::startUpdate: File " << params.file << " does not exist" + << std::endl; + return result; + } + result = FilesystemHelper::fileExists(params.file); + if (result != returnvalue::OK) { + sif::warning << "PlocSupvHelper::startUpdate: The file " << params.file << " does not exist" + << std::endl; + return result; + } +#endif +#ifdef TE0720_1CFA + if (not std::filesystem::exists(file)) { + sif::warning << "PlocSupvHelper::startUpdate: The file " << file << " does not exist" + << std::endl; + return returnvalue::FAILED; + } +#endif + { + MutexGuard mg(lock); + + update.file = params.file; + update.fullFileSize = getFileSize(update.file); + if (params.bytesWritten > update.fullFileSize) { + sif::warning << "Invalid start bytes counter " << params.bytesWritten + << ", smaller than full file length" << update.fullFileSize << std::endl; + return returnvalue::FAILED; + } + update.length = update.fullFileSize - params.bytesWritten; + update.memoryId = params.memId; + update.startAddress = params.startAddr; + update.progressPercent = 0; + update.bytesWritten = params.bytesWritten; + update.crcShouldBeChecked = true; + update.packetNum = 1; + update.deleteMemory = params.deleteMemory; + update.sequenceCount = params.seqCount; + state = InternalState::DEDICATED_REQUEST; + request = Request::UPDATE; + } + return result; +} + +ReturnValue_t PlocSupvUartManager::performMemCheck(std::string file, uint8_t memoryId, + uint32_t startAddress) { + lock->lockMutex(); + InternalState current = state; + lock->unlockMutex(); + if (current != InternalState::DEFAULT) { + return HasActionsIF::IS_BUSY; + } + return performMemCheck(file, memoryId, startAddress, getFileSize(update.file), true); +} + +ReturnValue_t PlocSupvUartManager::performMemCheck(std::string file, uint8_t memoryId, + uint32_t startAddress, size_t sizeToCheck, + bool checkCrc) { + { + MutexGuard mg(lock); + update.file = file; + update.fullFileSize = getFileSize(file); + state = InternalState::DEDICATED_REQUEST; + request = Request::CHECK_MEMORY; + update.memoryId = memoryId; + update.startAddress = startAddress; + update.length = sizeToCheck; + update.crcShouldBeChecked = checkCrc; + } + return returnvalue::OK; +} + +ReturnValue_t PlocSupvUartManager::initiateUpdateContinuation() { + lock->lockMutex(); + InternalState current = state; + lock->unlockMutex(); + if (current != InternalState::DEFAULT) { + return HasActionsIF::IS_BUSY; + } + MutexGuard mg(lock); + state = InternalState::DEDICATED_REQUEST; + request = Request::CONTINUE_UPDATE; + return returnvalue::OK; +} + +void PlocSupvUartManager::stop() { + MutexGuard mg(lock); + if (state == InternalState::SLEEPING or state == InternalState::GO_TO_SLEEP) { + return; + } + state = InternalState::GO_TO_SLEEP; +} + +void PlocSupvUartManager::start() { + { + MutexGuard mg(lock); + if (state != InternalState::SLEEPING and state != InternalState::GO_TO_SLEEP) { + return; + } + state = InternalState::DEFAULT; + } + + semaphore->release(); +} + +void PlocSupvUartManager::executeFullCheckMemoryCommand() { + ReturnValue_t result; + if (update.crcShouldBeChecked) { + sif::info << "PLOC SUPV Mem Check: Calculating Image CRC" << std::endl; + result = calcImageCrc(); + if (result != returnvalue::OK) { + triggerEvent(SUPV_MEM_CHECK_FAIL, result); + return; + } + } + sif::info << "PLOC SUPV Mem Check: Selecting Memory" << std::endl; + result = selectMemory(); + if (result != returnvalue::OK) { + triggerEvent(SUPV_MEM_CHECK_FAIL, result, 1); + return; + } + sif::info << "PLOC SUPV Mem Check: Preparing Update" << std::endl; + result = prepareUpdate(); + if (result != returnvalue::OK) { + triggerEvent(SUPV_MEM_CHECK_FAIL, result, 2); + return; + } + sif::info << "PLOC SUPV Mem Check: Memory Check" << std::endl; + handleCheckMemoryCommand(3); +} + +ReturnValue_t PlocSupvUartManager::executeUpdate() { + ReturnValue_t result = returnvalue::OK; + sif::info << "PLOC SUPV Update MPSoC: Calculating Image CRC" << std::endl; + result = calcImageCrc(); + if (result != returnvalue::OK) { + return result; + } + sif::info << "PLOC SUPV Update MPSoC: Selecting Memory" << std::endl; + result = selectMemory(); + if (result != returnvalue::OK) { + return result; + } + sif::info << "PLOC SUPV Update MPSoC: Preparing Update" << std::endl; + result = prepareUpdate(); + if (result != returnvalue::OK) { + return result; + } + if (update.deleteMemory) { + sif::info << "PLOC SUPV Update MPSoC: Erasing Memory" << std::endl; + result = eraseMemory(); + if (result != returnvalue::OK) { + return result; + } + } + return updateOperation(); +} + +ReturnValue_t PlocSupvUartManager::continueUpdate() { + ReturnValue_t result = prepareUpdate(); + if (result != returnvalue::OK) { + return result; + } + return updateOperation(); +} + +ReturnValue_t PlocSupvUartManager::updateOperation() { + sif::info << "PlocSupvHelper::performUpdate: Writing Update Packets" << std::endl; + auto result = writeUpdatePackets(); + if (result != returnvalue::OK) { + return result; + } + sif::info << "PlocSupvHelper::performUpdate: Memory Check" << std::endl; + return handleCheckMemoryCommand(0); +} + +ReturnValue_t PlocSupvUartManager::writeUpdatePackets() { + ReturnValue_t result = returnvalue::OK; +#if OBSW_DEBUG_PLOC_SUPERVISOR == 1 + ProgressPrinter progressPrinter("Supervisor update", update.fullFileSize, + ProgressPrinter::HALF_PERCENT); +#endif /* OBSW_DEBUG_PLOC_SUPERVISOR == 1 */ + uint8_t tempData[supv::WriteMemory::CHUNK_MAX + 1]{}; + if (not std::filesystem::exists(update.file)) { + sif::error << "PlocSupvUartManager::writeUpdatePackets: File " << update.file + << " does not exist" << std::endl; + return HasFileSystemIF::FILE_DOES_NOT_EXIST; + } + std::ifstream file(update.file, std::ifstream::binary); + uint16_t dataLength = 0; + ccsds::SequenceFlags seqFlags; + while (update.bytesWritten < update.fullFileSize) { + size_t remainingSize = update.fullFileSize - update.bytesWritten; + bool lastSegment = false; + if (remainingSize > supv::WriteMemory::CHUNK_MAX) { + dataLength = supv::WriteMemory::CHUNK_MAX; + } else { + lastSegment = true; + dataLength = static_cast(remainingSize); + } + if (file.is_open()) { + file.seekg(update.bytesWritten, std::ios::beg); + file.read(reinterpret_cast(tempData), dataLength); + if (!file) { + sif::warning << "PlocSupvHelper::performUpdate: Read only " << file.gcount() << " of " + << dataLength << " bytes" << std::endl; + sif::info << "PlocSupvHelper::performUpdate: Failed when trying to read byte " + << update.bytesWritten << std::endl; + } + } else { + return FILE_CLOSED_ACCIDENTALLY; + } + if (update.bytesWritten == 0) { + seqFlags = ccsds::SequenceFlags::FIRST_SEGMENT; + } else if (lastSegment) { + seqFlags = ccsds::SequenceFlags::LAST_SEGMENT; + } else { + seqFlags = ccsds::SequenceFlags::CONTINUATION; + } + resetSpParams(); + float progress = static_cast(update.bytesWritten) / update.fullFileSize; + uint8_t progPercent = std::floor(progress * 100); + if (progPercent > update.progressPercent) { + update.progressPercent = progPercent; + if (progPercent % 5 == 0) { + // Useful to allow restarting the update + triggerEvent(SUPV_UPDATE_PROGRESS, buildProgParams1(progPercent, update.sequenceCount), + update.bytesWritten); + sif::info << "PLOC SUPV update progress " << (int)progPercent << " % at " + << update.bytesWritten << " bytes" << std::endl; + } + } + supv::WriteMemory packet(spParams); + result = packet.buildPacket(seqFlags, update.sequenceCount, update.memoryId, + update.startAddress + update.bytesWritten, dataLength, tempData); + if (result != returnvalue::OK) { + triggerEvent(WRITE_MEMORY_FAILED, buildProgParams1(progPercent, update.sequenceCount), + update.bytesWritten); + return result; + } + result = writeMemoryHandlingWithRetryLogic(packet, progPercent); + if (result != returnvalue::OK) { + return result; + } + + update.sequenceCount++; + update.packetNum += 1; + update.bytesWritten += dataLength; + +#if OBSW_DEBUG_PLOC_SUPERVISOR == 1 + progressPrinter.print(update.bytesWritten); +#endif /* OBSW_DEBUG_PLOC_SUPERVISOR == 1 */ + } + return result; +} + +ReturnValue_t PlocSupvUartManager::writeMemoryHandlingWithRetryLogic(supv::WriteMemory& packet, + unsigned progPercent) { + ReturnValue_t result = returnvalue::OK; + // Simple re-try logic in place to deal with communication unreliability in orbit. + for (uint8_t retryCount = 0; retryCount < MAX_RETRY_COUNT; retryCount++) { + result = handlePacketTransmissionNoReply(packet, COM_TIMEOUT_MS); + if (result == returnvalue::OK) { + return result; + } + triggerEvent(WRITE_MEMORY_FAILED, buildProgParams1(progPercent, update.sequenceCount), + update.bytesWritten); + // Clear data structures related to reply handling. + serial::flushTxRxBuf(serialPort); + recRingBuf.clear(); + decodedRingBuf.clear(); + } + return result; +} + +uint32_t PlocSupvUartManager::buildProgParams1(uint8_t percent, uint16_t seqCount) { + return (static_cast(percent) << 24) | static_cast(seqCount); +} + +// ReturnValue_t PlocSupvHelper::performEventBufferRequest() { +// using namespace supv; +// ReturnValue_t result = returnvalue::OK; +// resetSpParams(); +// RequestLoggingData packet(spParams); +// result = packet.buildPacket(RequestLoggingData::Sa::REQUEST_EVENT_BUFFERS); +// if (result != returnvalue::OK) { +// return result; +// } +// result = sendCommand(packet); +// if (result != returnvalue::OK) { +// return result; +// } +// result = handleAck(); +// if (result != returnvalue::OK) { +// return result; +// } +// result = +// handleTmReception(ccsds::HEADER_LEN, tmBuf.data(), +// supv::recv_timeout::UPDATE_STATUS_REPORT); +// if (result != returnvalue::OK) { +// return result; +// } +// ploc::SpTmReader spReader(tmBuf.data(), tmBuf.size()); +// bool exeAlreadyReceived = false; +// if (spReader.getApid() == supv::APID_EXE_FAILURE) { +// exeAlreadyReceived = true; +// result = handleRemainingExeReport(spReader); +// } else if (spReader.getApid() == supv::APID_MRAM_DUMP_TM) { +// result = handleEventBufferReception(spReader); +// } +// +// if (not exeAlreadyReceived) { +// result = handleExe(); +// if (result != returnvalue::OK) { +// return result; +// } +// } +// return result; +// } + +ReturnValue_t PlocSupvUartManager::selectMemory() { + ReturnValue_t result = returnvalue::OK; + resetSpParams(); + supv::MPSoCBootSelect packet(spParams); + result = packet.buildPacket(update.memoryId); + if (result != returnvalue::OK) { + return result; + } + result = handlePacketTransmissionNoReply(packet, 2000); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PlocSupvUartManager::prepareUpdate() { + ReturnValue_t result = returnvalue::OK; + resetSpParams(); + supv::NoPayloadPacket packet(spParams, Apid::BOOT_MAN, + static_cast(tc::BootManId::PREPARE_UPDATE)); + result = packet.buildPacket(); + if (result != returnvalue::OK) { + return result; + } + result = handlePacketTransmissionNoReply(packet, PREPARE_UPDATE_EXECUTION_REPORT); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PlocSupvUartManager::eraseMemory() { + ReturnValue_t result = returnvalue::OK; + resetSpParams(); + supv::EraseMemory eraseMemory(spParams); + result = eraseMemory.buildPacket(update.memoryId, update.startAddress + update.bytesWritten, + update.length); + if (result != returnvalue::OK) { + return result; + } + result = handlePacketTransmissionNoReply(eraseMemory, supv::timeout::ERASE_MEMORY); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PlocSupvUartManager::handlePacketTransmissionNoReply( + supv::TcBase& packet, uint32_t timeoutExecutionReport) { + ReturnValue_t result = returnvalue::OK; + result = encodeAndSendPacket(packet.getFullPacket(), packet.getFullPacketLen()); + if (result != returnvalue::OK) { + return result; + } + Countdown countdown(timeoutExecutionReport); + dur_millis_t currentDelay = 5; + bool ackReceived = false; + bool packetWasHandled = false; + while (true) { + ReturnValue_t status = handleUartReception(); + if (status != returnvalue::OK) { + result = status; + if (result == HDLC_ERROR) { + // We could bail here immediately.. but I prefer to wait for the timeout, because we should + // ensure that all packets which might be related to the transfer are still received and + // cleared from all data structures related to reply handling. + // return result; + } + } + if (not decodedQueue.empty()) { + size_t packetLen = 0; + decodedQueue.retrieve(&packetLen); + decodedRingBuf.readData(decodedBuf.data(), packetLen, true); + tmReader.setReadOnlyData(decodedBuf.data(), packetLen); + result = checkReceivedTm(); + if (result != returnvalue::OK) { + continue; + } + if (tmReader.getModuleApid() == Apid::TMTC_MAN) { + int retval = 0; + if (not ackReceived) { + retval = handleAckReception(packet, packetLen); + if (retval == 1) { + ackReceived = true; + packetWasHandled = true; + } else if (retval == -1) { + return returnvalue::FAILED; + } + } else { + retval = handleExeAckReception(packet, packetLen); + if (retval == 1) { + break; + } else if (retval == -1) { + return returnvalue::FAILED; + } + } + } + if (not packetWasHandled) { + pushIpcData(decodedBuf.data(), packetLen); + decodedRingBuf.deleteData(packetLen); + } + } else { + TaskFactory::delayTask(currentDelay); + if (currentDelay < 80) { + currentDelay *= 2; + } + } + if (countdown.hasTimedOut()) { + return result::NO_REPLY_TIMEOUT; + } + } + return result; +} + +int PlocSupvUartManager::handleAckReception(supv::TcBase& tc, size_t packetLen) { + uint8_t serviceId = tmReader.getServiceId(); + if (serviceId == static_cast(supv::tm::TmtcId::ACK) or + serviceId == static_cast(supv::tm::TmtcId::NAK)) { + AcknowledgmentReport ackReport(tmReader); + ReturnValue_t result = ackReport.parse(false); + if (result != returnvalue::OK) { + triggerEvent(ACK_RECEPTION_FAILURE); + return -1; + } + if (ackReport.getRefModuleApid() == tc.getModuleApid() and + ackReport.getRefServiceId() == tc.getServiceId()) { + if (serviceId == static_cast(supv::tm::TmtcId::ACK)) { + return 1; + } else if (serviceId == static_cast(supv::tm::TmtcId::NAK)) { + ackReport.printStatusInformationAck(); + triggerEvent( + SUPV_ACK_FAILURE_REPORT, + buildApidServiceParam1(ackReport.getRefModuleApid(), ackReport.getRefServiceId()), + ackReport.getStatusCode()); + return -1; + } + // Should never happen + return -1; + } else { + pushIpcData(decodedBuf.data(), packetLen); + decodedRingBuf.deleteData(packetLen); + } + } + return 0; +} + +int PlocSupvUartManager::handleExeAckReception(supv::TcBase& tc, size_t packetLen) { + uint8_t serviceId = tmReader.getServiceId(); + if (serviceId == static_cast(supv::tm::TmtcId::EXEC_ACK) or + serviceId == static_cast(supv::tm::TmtcId::EXEC_NAK)) { + ExecutionReport exeReport(tmReader); + ReturnValue_t result = exeReport.parse(false); + if (result != returnvalue::OK) { + triggerEvent(EXE_RECEPTION_FAILURE); + return -1; + } + if (exeReport.getRefModuleApid() == tc.getModuleApid() and + exeReport.getRefServiceId() == tc.getServiceId()) { + if (serviceId == static_cast(supv::tm::TmtcId::EXEC_ACK)) { + return 1; + } else if (serviceId == static_cast(supv::tm::TmtcId::EXEC_NAK)) { + exeReport.printStatusInformationExe(); + triggerEvent( + SUPV_EXE_FAILURE_REPORT, + buildApidServiceParam1(exeReport.getRefModuleApid(), exeReport.getRefServiceId()), + exeReport.getStatusCode()); + return -1; + } + // Should never happen + return -1; + } else { + pushIpcData(decodedBuf.data(), packetLen); + decodedRingBuf.deleteData(packetLen); + } + } + return 0; +} + +ReturnValue_t PlocSupvUartManager::checkReceivedTm() { + ReturnValue_t result = tmReader.checkSize(); + if (result != returnvalue::OK) { + triggerEvent(SUPV_REPLY_SIZE_MISSMATCH, rememberApid); + return result; + } + if (tmReader.checkCrc() != returnvalue::OK) { + triggerEvent(SUPV_REPLY_CRC_MISSMATCH, rememberApid); + return result; + } + return result; +} + +ReturnValue_t PlocSupvUartManager::calcImageCrc() { + ReturnValue_t result = returnvalue::OK; + if (update.fullFileSize == 0) { + return returnvalue::FAILED; + } +#ifdef XIPHOS_Q7S + result = FilesystemHelper::checkPath(update.file); + if (result != returnvalue::OK) { + sif::warning << "PlocSupvHelper::calcImageCrc: File " << update.file << " does not exist" + << std::endl; + return result; + } +#endif + + auto crc16Calcer = etl::crc16_ccitt(); + std::ifstream file(update.file, std::ifstream::binary); + std::array crcBuf{}; +#if OBSW_DEBUG_PLOC_SUPERVISOR == 1 + ProgressPrinter progress("Supervisor update crc calculation", update.fullFileSize, + ProgressPrinter::ONE_PERCENT); +#endif /* OBSW_DEBUG_PLOC_SUPERVISOR == 1 */ + uint32_t byteCount = 0; + size_t bytesToRead = 1024; + while (byteCount < update.fullFileSize) { + size_t remLen = update.fullFileSize - byteCount; + if (remLen < 1024) { + bytesToRead = remLen; + } else { + bytesToRead = 1024; + } + file.seekg(byteCount, file.beg); + file.read(reinterpret_cast(crcBuf.data()), bytesToRead); + crc16Calcer.add(crcBuf.begin(), crcBuf.begin() + bytesToRead); + +#if OBSW_DEBUG_PLOC_SUPERVISOR == 1 + progress.print(byteCount); +#endif /* OBSW_DEBUG_PLOC_SUPERVISOR == 1 */ + byteCount += bytesToRead; + } +#if OBSW_DEBUG_PLOC_SUPERVISOR == 1 + progress.print(byteCount); +#endif /* OBSW_DEBUG_PLOC_SUPERVISOR == 1 */ + update.crc = crc16Calcer.value(); + return result; +} + +ReturnValue_t PlocSupvUartManager::handleCheckMemoryCommand(uint8_t failStep) { + ReturnValue_t result = returnvalue::OK; + resetSpParams(); + supv::CheckMemory tcPacket(spParams); + result = tcPacket.buildPacket(update.memoryId, update.startAddress, update.fullFileSize); + if (result != returnvalue::OK) { + return result; + } + result = encodeAndSendPacket(tcPacket.getFullPacket(), tcPacket.getFullPacketLen()); + if (result != returnvalue::OK) { + return result; + } + Countdown countdown(timeout::CRC_EXECUTION_TIMEOUT); + bool ackReceived = false; + bool checkReplyReceived = false; + bool packetWasHandled = false; + bool exeReceived = false; + while (true) { + handleUartReception(); + if (not decodedQueue.empty()) { + size_t packetLen = 0; + decodedQueue.retrieve(&packetLen); + decodedRingBuf.readData(decodedBuf.data(), packetLen, true); + tmReader.setReadOnlyData(decodedBuf.data(), packetLen); + result = checkReceivedTm(); + if (result != returnvalue::OK) { + continue; + } + packetWasHandled = false; + if (tmReader.getModuleApid() == Apid::TMTC_MAN) { + int retval = 0; + if (not ackReceived) { + retval = handleAckReception(tcPacket, packetLen); + if (retval == 1) { + packetWasHandled = true; + ackReceived = true; + } else if (retval == -1) { + return returnvalue::FAILED; + } + } else { + retval = handleExeAckReception(tcPacket, packetLen); + if (retval == 1) { + packetWasHandled = true; + exeReceived = true; + } else if (retval == -1) { + return returnvalue::FAILED; + } + } + } else if (tmReader.getModuleApid() == Apid::MEM_MAN) { + if (ackReceived) { + supv::UpdateStatusReport report(tmReader); + result = report.parse(false); + if (result != returnvalue::OK) { + return result; + } + packetWasHandled = true; + checkReplyReceived = true; + if (update.crcShouldBeChecked) { + result = report.verifyCrc(update.crc); + if (result == returnvalue::OK) { + triggerEvent(SUPV_MEM_CHECK_OK, result); + } else { + sif::warning + + << "PlocSupvHelper::handleCheckMemoryCommand: CRC failure. Expected CRC 0x" + << std::setfill('0') << std::hex << std::setw(4) + << static_cast(update.crc) << " but received CRC 0x" << std::setw(4) + << report.getCrc() << std::dec << std::endl; + triggerEvent(SUPV_MEM_CHECK_FAIL, result, failStep); + } + } + } + } + if (not packetWasHandled) { + pushIpcData(decodedBuf.data(), packetLen); + decodedRingBuf.deleteData(packetLen); + } + } else { + TaskFactory::delayTask(20); + } + if (ackReceived and exeReceived and checkReplyReceived) { + break; + } + if (countdown.hasTimedOut()) { + return result::NO_REPLY_TIMEOUT; + } + } + return returnvalue::OK; +} + +uint32_t PlocSupvUartManager::getFileSize(std::string filename) { + std::ifstream file(filename, std::ifstream::binary); + file.seekg(0, file.end); + uint32_t size = file.tellg(); + file.close(); + return size; +} + +ReturnValue_t PlocSupvUartManager::handleEventBufferReception(ploc::SpTmReader& reader) { + ReturnValue_t result = returnvalue::OK; + // TODO: Fix + // #ifdef XIPHOS_Q7S + // if (not sdcMan->getActiveSdCard()) { + // return HasFileSystemIF::FILESYSTEM_INACTIVE; + // } + // #endif + // std::string filename = Filenaming::generateAbsoluteFilename( + // eventBufferReq.path, eventBufferReq.filename, timestamping); + // std::ofstream file(filename, std::ios_base::app | std::ios_base::out); + // uint32_t packetsRead = 0; + // size_t requestLen = 0; + // bool firstPacket = true; + // for (packetsRead = 0; packetsRead < NUM_EVENT_BUFFER_PACKETS; packetsRead++) { + // if (terminate) { + // triggerEvent(SUPV_EVENT_BUFFER_REQUEST_TERMINATED, packetsRead - 1); + // file.close(); + // return PROCESS_TERMINATED; + // } + // if (packetsRead == NUM_EVENT_BUFFER_PACKETS - 1) { + // requestLen = SIZE_EVENT_BUFFER_LAST_PACKET; + // } else { + // requestLen = SIZE_EVENT_BUFFER_FULL_PACKET; + // } + // if (firstPacket) { + // firstPacket = false; + // requestLen -= 6; + // } + // result = handleTmReception(requestLen); + // if (result != returnvalue::OK) { + // sif::debug << "PlocSupvHelper::handleEventBufferReception: Failed while trying to read + // packet" + // << " " << packetsRead + 1 << std::endl; + // file.close(); + // return result; + // } + // ReturnValue_t result = reader.checkCrc(); + // if (result != returnvalue::OK) { + // triggerEvent(SUPV_REPLY_CRC_MISSMATCH, rememberApid); + // return result; + // } + // uint16_t apid = reader.getApid(); + // if (apid != supv::APID_MRAM_DUMP_TM) { + // sif::warning << "PlocSupvHelper::handleEventBufferReception: Did not expect space packet " + // << "with APID 0x" << std::hex << apid << std::endl; + // file.close(); + // return EVENT_BUFFER_REPLY_INVALID_APID; + // } + // // TODO: Fix + // // file.write(reinterpret_cast(reader.getPacketData()), + // // reader.getPayloadDataLength()); + // } + return result; +} + +void PlocSupvUartManager::resetSpParams() { spParams.buf = cmdBuf.data(); } + +ReturnValue_t PlocSupvUartManager::sendMessage(CookieIF* cookie, const uint8_t* sendData, + size_t sendLen) { + if (sendData == nullptr or sendLen == 0) { + return FAILED; + } + { + MutexGuard mg(lock); + if (state == InternalState::SLEEPING or state == InternalState::DEDICATED_REQUEST) { + return FAILED; + } + } + return encodeAndSendPacket(sendData, sendLen); +} + +ReturnValue_t PlocSupvUartManager::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } + +ReturnValue_t PlocSupvUartManager::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t PlocSupvUartManager::handleRunningLongerRequest() { + ReturnValue_t result = OK; + switch (request) { + case Request::UPDATE: { + result = executeUpdate(); + if (result == returnvalue::OK) { + triggerEvent(SUPV_UPDATE_SUCCESSFUL, result); + } else if (result == PROCESS_TERMINATED) { + // Event already triggered + } else { + triggerEvent(SUPV_UPDATE_FAILED, result); + } + break; + } + case Request::CHECK_MEMORY: { + executeFullCheckMemoryCommand(); + break; + } + case Request::CONTINUE_UPDATE: { + result = continueUpdate(); + if (result == returnvalue::OK) { + triggerEvent(SUPV_CONTINUE_UPDATE_SUCCESSFUL, result); + } else if (result == PROCESS_TERMINATED) { + // Event already triggered + } else { + triggerEvent(SUPV_CONTINUE_UPDATE_FAILED, result); + } + break; + } + case Request::REQUEST_EVENT_BUFFER: { + sif::error << "Requesting event buffer is not implemented" << std::endl; + break; + } + case Request::DEFAULT: { + break; + } + } + return false; +} + +ReturnValue_t PlocSupvUartManager::encodeAndSendPacket(const uint8_t* sendData, size_t sendLen) { + size_t encodedLen = 0; + addHdlcFraming(sendData, sendLen, encodedSendBuf.data(), &encodedLen, encodedSendBuf.size()); + if (PRINT_TC) { + sif::debug << "Sending TC" << std::endl; + arrayprinter::print(encodedSendBuf.data(), encodedLen); + } + size_t bytesWritten = write(serialPort, encodedSendBuf.data(), encodedLen); + if (bytesWritten != encodedLen) { + sif::warning + << "PlocSupvUartManager::sendMessage: Sending ping command to solar experiment failed" + << std::endl; + return FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t PlocSupvUartManager::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, + size_t* size) { + MutexGuard mg(ipcLock); + if (ipcQueue.empty()) { + *size = 0; + return OK; + } + ipcQueue.retrieve(size); + if (*size > ipcBuffer.size()) { + return FAILED; + } + *buffer = ipcBuffer.data(); + ReturnValue_t result = ipcRingBuf.readData(ipcBuffer.data(), *size, true); + if (result != OK) { + sif::warning << "PlocSupvHelper::readReceivedMessage: Reading RingBuffer failed" << std::endl; + } + return OK; +} + +ReturnValue_t PlocSupvUartManager::tryHdlcParsing() { + size_t bytesRead = 0; + size_t decodedLen = 0; + ReturnValue_t result = parseRecRingBufForHdlc(bytesRead, decodedLen); + if (result == returnvalue::OK) { + // Packet found, advance read pointer. + if (state == InternalState::DEDICATED_REQUEST) { + decodedRingBuf.writeData(decodedBuf.data(), decodedLen); + decodedQueue.insert(decodedLen); + } else { + MutexGuard mg(ipcLock); + ipcRingBuf.writeData(decodedBuf.data(), decodedLen); + ipcQueue.insert(decodedLen); + } + recRingBuf.deleteData(bytesRead); + } else if (result != NO_PACKET_FOUND) { + sif::warning << "PlocSupvUartMan::performOperation: Possible packet loss" << std::endl; + // Markers found at wrong place + // which might be a hint for a possibly lost packet. + // Maybe trigger event? + recRingBuf.deleteData(bytesRead); + } + return result; +} + +ReturnValue_t PlocSupvUartManager::parseRecRingBufForHdlc(size_t& readSize, size_t& decodedLen) { + size_t availableData = recRingBuf.getAvailableReadData(); + if (availableData == 0) { + return NO_PACKET_FOUND; + } + if (availableData > encodedBuf.size()) { + return DECODE_BUF_TOO_SMALL; + } + ReturnValue_t result = recRingBuf.readData(encodedBuf.data(), availableData); + if (result != returnvalue::OK) { + return result; + } + bool startMarkerFound = false; + size_t startIdx = 0; + for (size_t idx = 0; idx < availableData; idx++) { + // handle start marker + if (encodedBuf[idx] == HDLC_START_MARKER) { + if (not startMarkerFound) { + startMarkerFound = true; + startIdx = idx; + } else { + readSize = idx; + return POSSIBLE_PACKET_LOSS_CONSECUTIVE_START; + } + } + if (encodedBuf[idx] == HDLC_END_MARKER) { + if (startMarkerFound) { + // Probably a packet, so decode it + int retval = removeHdlcFramingWithCrcCheck(encodedBuf.data() + startIdx, idx + 1 - startIdx, + decodedBuf.data(), &decodedLen); + readSize = idx + 1; + if (retval == -1 or retval == -2) { + triggerEvent(HDLC_FRAME_REMOVAL_ERROR, retval); + } else if (retval == 1) { + triggerEvent(HDLC_CRC_ERROR); + } + if (retval != 0) { + readSize = ++idx; + return HDLC_ERROR; + } + return returnvalue::OK; + } else { + readSize = ++idx; + return POSSIBLE_PACKET_LOSS_CONSECUTIVE_END; + } + } + } + return NO_PACKET_FOUND; +} + +void PlocSupvUartManager::pushIpcData(const uint8_t* data, size_t len) { + MutexGuard mg(ipcLock); + ipcRingBuf.writeData(data, len); + ipcQueue.insert(len); +} + +uint32_t PlocSupvUartManager::buildApidServiceParam1(uint8_t apid, uint8_t serviceId) { + return (apid << 8) | serviceId; +} + +void PlocSupvUartManager::performUartShutdown() { + tcflush(serialPort, TCIOFLUSH); + // Clear ring buffers + recRingBuf.clear(); + decodedRingBuf.clear(); + while (not decodedQueue.empty()) { + decodedQueue.pop(); + } + { + MutexGuard mg0(ipcLock); + ipcRingBuf.clear(); + while (not ipcQueue.empty()) { + ipcQueue.pop(); + } + } + MutexGuard mg1(lock); + state = InternalState::GO_TO_SLEEP; +} + +void PlocSupvUartManager::addHdlcFraming(const uint8_t* src, size_t slen, uint8_t* dst, + size_t* dlen, size_t maxDest) { + size_t tlen = 0; + uint16_t ii; + uint8_t bt; + + // calc crc16 + uint16_t crc16 = calc_crc16_buff_reflected(src, slen); + + dst[tlen++] = 0x7E; + for (ii = 0; ii < slen; ii++) { + bt = *src++; + hdlc_add_byte(bt, dst, &tlen); + } + + size_t dummy = 0; + uint8_t crcRaw[2]; + // hdlc crc16 is in little endian format + SerializeAdapter::serialize(&crc16, crcRaw, &dummy, maxDest, SerializeIF::Endianness::LITTLE); + hdlc_add_byte(crcRaw[0], dst, &tlen); + hdlc_add_byte(crcRaw[1], dst, &tlen); + + dst[tlen++] = 0x7C; + *dlen = tlen; +} + +int PlocSupvUartManager::removeHdlcFramingWithCrcCheck(const uint8_t* src, size_t slen, + uint8_t* dst, size_t* dlen) { + uint16_t tlen = 0; + uint16_t ii; + uint8_t bt; + + *dlen = 0; + if (slen < 4) return -1; + if ((src[tlen] != 0x7E) && (src[slen - 1] != 0x7C)) return -2; + src++; + for (ii = 1; ii < slen - 1; ii++) { + bt = *src++; + + if (bt == 0x7D) { + bt = *src++ ^ 0x20; + ii++; + } + dst[tlen++] = bt; + } + // calc crc16 + uint16_t calcCrc = calc_crc16_buff_reflected(dst, tlen - 2); + uint16_t crc = 0; + size_t dummy; + SerializeAdapter::deSerialize(&crc, dst + tlen - 2, &dummy, SerializeIF::Endianness::LITTLE); + if (calcCrc != crc) { + return 1; + } + // if(calc_crc16_buff_reflected(dst, tlen) != 0) { + // return 1; + // } + *dlen = tlen - 2; + return 0; +} + +bool PlocSupvUartManager::longerRequestActive() const { + MutexGuard mg(lock); + return state == InternalState::DEDICATED_REQUEST; +} + +void PlocSupvUartManager::updateVtime(uint8_t vtime) { + tcgetattr(serialPort, &tty); + tty.c_cc[VTIME] = vtime; + tcsetattr(serialPort, TCSANOW, &tty); +} diff --git a/linux/payload/PlocSupvUartMan.h b/linux/payload/PlocSupvUartMan.h new file mode 100644 index 0000000..1cea200 --- /dev/null +++ b/linux/payload/PlocSupvUartMan.h @@ -0,0 +1,381 @@ +#ifndef BSP_Q7S_DEVICES_PLOCSUPVHELPER_H_ +#define BSP_Q7S_DEVICES_PLOCSUPVHELPER_H_ + +#include +#include +#include +#include + +#include + +#include "OBSWConfig.h" +#include "fsfw/container/FIFO.h" +#include "fsfw/devicehandlers/CookieIF.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/osal/linux/BinarySemaphore.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw_hal/linux/serial/SerialComIF.h" + +#ifdef XIPHOS_Q7S +#include "bsp_q7s/fs/SdCardManager.h" +#endif + +/** + * @brief Helper class for supervisor of PLOC intended to accelerate large data transfers between + * the supervisor and the OBC. + * @author J. Meier + */ +class PlocSupvUartManager : public DeviceCommunicationIF, + public SystemObject, + public ExecutableObjectIF { + public: + static const uint8_t INTERFACE_ID = CLASS_ID::PLOC_SUPV_HELPER; + + //! [EXPORT] : [COMMENT] File accidentally close + static const ReturnValue_t FILE_CLOSED_ACCIDENTALLY = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] Process has been terminated by command + static const ReturnValue_t PROCESS_TERMINATED = MAKE_RETURN_CODE(0xA1); + //! [EXPORT] : [COMMENT] Received command with invalid pathname + static const ReturnValue_t PATH_NOT_EXISTS = MAKE_RETURN_CODE(0xA2); + //! [EXPORT] : [COMMENT] Expected event buffer TM but received space packet with other APID + static const ReturnValue_t EVENT_BUFFER_REPLY_INVALID_APID = MAKE_RETURN_CODE(0xA3); + + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_SUPV_HELPER; + + //! [EXPORT] : [COMMENT] update failed + static const Event SUPV_UPDATE_FAILED = MAKE_EVENT(0, severity::LOW); + //! [EXPORT] : [COMMENT] update successful + static const Event SUPV_UPDATE_SUCCESSFUL = MAKE_EVENT(1, severity::LOW); + //! [EXPORT] : [COMMENT] Continue update command failed + static const Event SUPV_CONTINUE_UPDATE_FAILED = MAKE_EVENT(2, severity::LOW); + //! [EXPORT] : [COMMENT] Continue update command successful + static const Event SUPV_CONTINUE_UPDATE_SUCCESSFUL = MAKE_EVENT(3, severity::LOW); + //! [EXPORT] : [COMMENT] Terminated update procedure by command + static const Event TERMINATED_UPDATE_PROCEDURE = MAKE_EVENT(4, severity::LOW); + //! [EXPORT] : [COMMENT] Requesting event buffer was successful + static const Event SUPV_EVENT_BUFFER_REQUEST_SUCCESSFUL = MAKE_EVENT(5, severity::LOW); + //! [EXPORT] : [COMMENT] Requesting event buffer failed + static const Event SUPV_EVENT_BUFFER_REQUEST_FAILED = MAKE_EVENT(6, severity::LOW); + //! [EXPORT] : [COMMENT] Terminated event buffer request by command + //! P1: Number of packets read before process was terminated + static const Event SUPV_EVENT_BUFFER_REQUEST_TERMINATED = MAKE_EVENT(7, severity::LOW); + //! Status of memory check command + //! P1: Returncode, 0 for success, other value with returncode for failure + static constexpr Event SUPV_MEM_CHECK_OK = MAKE_EVENT(8, severity::INFO); + static constexpr Event SUPV_MEM_CHECK_FAIL = MAKE_EVENT(9, severity::INFO); + + //! [EXPORT] : [COMMENT] Communication interface returned failure when trying to send the command + //! to the supervisor + //! P1: Return value returned by the communication interface sendMessage function + //! P2: Internal state of supervisor helper + static const Event SUPV_SENDING_COMMAND_FAILED = MAKE_EVENT(16, severity::LOW); + //! [EXPORT] : [COMMENT] Request receive message of communication interface failed + //! P1: Return value returned by the communication interface requestReceiveMessage function + //! P2: Internal state of supervisor helper + static const Event SUPV_HELPER_REQUESTING_REPLY_FAILED = MAKE_EVENT(17, severity::LOW); + //! [EXPORT] : [COMMENT] Reading receive message of communication interface failed + //! P1: Return value returned by the communication interface readingReceivedMessage function + //! P2: Internal state of supervisor helper + static const Event SUPV_HELPER_READING_REPLY_FAILED = MAKE_EVENT(18, severity::LOW); + //! [EXPORT] : [COMMENT] Did not receive acknowledgement report + //! P1: Number of bytes missing + //! P2: Internal state of MPSoC helper + static const Event SUPV_MISSING_ACK = MAKE_EVENT(19, severity::LOW); + //! [EXPORT] : [COMMENT] Supervisor did not receive execution report + //! P1: Number of bytes missing + //! P2: Internal state of supervisor helper + static const Event SUPV_MISSING_EXE = MAKE_EVENT(20, severity::LOW); + //! [EXPORT] : [COMMENT] Supervisor received acknowledgment failure report + //! P1: Internal state of supervisor helper + static const Event SUPV_ACK_FAILURE_REPORT = MAKE_EVENT(21, severity::LOW); + //! [EXPORT] : [COMMENT] Execution report failure + //! P1: + static const Event SUPV_EXE_FAILURE_REPORT = MAKE_EVENT(22, severity::LOW); + //! [EXPORT] : [COMMENT] Supervisor expected acknowledgment report but received space packet with + //! other apid P1: Apid of received space packet P2: Internal state of supervisor helper + static const Event SUPV_ACK_INVALID_APID = MAKE_EVENT(23, severity::LOW); + //! [EXPORT] : [COMMENT] Supervisor helper expected execution report but received space packet + //! with other apid P1: Apid of received space packet P2: Internal state of supervisor helper + static const Event SUPV_EXE_INVALID_APID = MAKE_EVENT(24, severity::LOW); + //! [EXPORT] : [COMMENT] Failed to receive acknowledgment report + //! P1: Return value + //! P2: Apid of command for which the reception of the acknowledgment report failed + static const Event ACK_RECEPTION_FAILURE = MAKE_EVENT(25, severity::LOW); + //! [EXPORT] : [COMMENT] Failed to receive execution report + //! P1: Return value + //! P2: Apid of command for which the reception of the execution report failed + static const Event EXE_RECEPTION_FAILURE = MAKE_EVENT(26, severity::LOW); + //! [EXPORT] : [COMMENT] Update procedure failed when sending packet. + //! P1: First byte percent, third and fourth byte Sequence Count, P2: Bytes written + static const Event WRITE_MEMORY_FAILED = MAKE_EVENT(27, severity::LOW); + static const Event SUPV_REPLY_SIZE_MISSMATCH = MAKE_EVENT(28, severity::LOW); + static const Event SUPV_REPLY_CRC_MISSMATCH = MAKE_EVENT(29, severity::LOW); + + //! [EXPORT] : [COMMENT] Will be triggered every 5 percent of the update progress. + //! P1: First byte percent, third and fourth byte Sequence Count, P2: Bytes written + static constexpr Event SUPV_UPDATE_PROGRESS = MAKE_EVENT(30, severity::INFO); + static constexpr Event HDLC_FRAME_REMOVAL_ERROR = MAKE_EVENT(31, severity::INFO); + static constexpr Event HDLC_CRC_ERROR = MAKE_EVENT(32, severity::INFO); + + static constexpr unsigned MAX_RETRY_COUNT = 3; + PlocSupvUartManager(object_id_t objectId); + virtual ~PlocSupvUartManager(); + /** + * @brief Device specific initialization, using the cookie. + * @details + * The cookie is already prepared in the factory. If the communication + * interface needs to be set up in some way and requires cookie information, + * this can be performed in this function, which is called on device handler + * initialization. + * @param cookie + * @return + * - @c returnvalue::OK if initialization was successfull + * - Everything else triggers failure event with returnvalue as parameter 1 + */ + ReturnValue_t initializeInterface(CookieIF* cookie) override; + /** + * Called by DHB in the SEND_WRITE doSendWrite(). + * This function is used to send data to the physical device + * by implementing and calling related drivers or wrapper functions. + * @param cookie + * @param data + * @param len If this is 0, nothing shall be sent. + * @return + * - @c returnvalue::OK for successfull send + * - Everything else triggers failure event with returnvalue as parameter 1 + */ + ReturnValue_t sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) override; + ReturnValue_t readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) override; + + ReturnValue_t initialize() override; + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + + /** + * @brief Starts update procedure + * + * @param file File containing the update data + * @param memoryId ID of the memory where to write to + * @param startAddress Address where to write data + * + * @return returnvalue::OK if successful, otherwise error return value + */ + ReturnValue_t performUpdate(const supv::UpdateParams& params); + ReturnValue_t startUpdate(std::string file, uint8_t memoryId, uint32_t startAddress); + + ReturnValue_t performMemCheck(std::string file, uint8_t memoryId, uint32_t startAddress, + size_t sizeToCheck, bool checkCrc); + ReturnValue_t performMemCheck(std::string file, uint8_t memoryId, uint32_t startAddress); + + /** + * @brief This initiate the continuation of a failed update. + */ + ReturnValue_t initiateUpdateContinuation(); + + /** + * @brief Calling this function will initiate the procedure to request the event buffer + */ + // ReturnValue_t startEventBufferRequest(std::string path); + + /** + * @brief Can be used to stop the UART reception and put the task to sleep + */ + void stop(); + + /** + * @brief Can be used to start the UART reception + */ + void start(); + bool longerRequestActive() const; + + static uint32_t buildProgParams1(uint8_t percent, uint16_t seqCount); + static uint32_t buildApidServiceParam1(uint8_t apid, uint8_t serviceId); + + private: + static constexpr ReturnValue_t REQUEST_DONE = returnvalue::makeCode(1, 0); + static constexpr ReturnValue_t NO_PACKET_FOUND = returnvalue::makeCode(1, 1); + static constexpr ReturnValue_t DECODE_BUF_TOO_SMALL = returnvalue::makeCode(1, 2); + static constexpr ReturnValue_t POSSIBLE_PACKET_LOSS_CONSECUTIVE_START = + returnvalue::makeCode(1, 3); + static constexpr ReturnValue_t POSSIBLE_PACKET_LOSS_CONSECUTIVE_END = returnvalue::makeCode(1, 4); + static constexpr ReturnValue_t HDLC_ERROR = returnvalue::makeCode(1, 5); + + static constexpr uint32_t COM_TIMEOUT_MS = 3000; + + static const uint16_t CRC16_INIT = 0xFFFF; + // Event buffer reply will carry 24 space packets with 1016 bytes and one space packet with + // 192 bytes + static const uint8_t NUM_EVENT_BUFFER_PACKETS = 25; + static const size_t SIZE_EVENT_BUFFER_FULL_PACKET = 1024; + static const size_t SIZE_EVENT_BUFFER_LAST_PACKET = 200; + static const uint32_t PREPARE_UPDATE_EXECUTION_REPORT = 2000; + + static constexpr uint8_t MAX_STORED_DECODED_PACKETS = 4; + static constexpr uint8_t HDLC_START_MARKER = 0x7E; + static constexpr uint8_t HDLC_END_MARKER = 0x7C; + + struct Update { + uint8_t memoryId; + uint32_t startAddress; + // Absolute name of file containing update data + std::string file; + // Length of full file + size_t fullFileSize = 0; + // Size of update + uint32_t length = 0; + uint32_t crc = 0; + bool crcShouldBeChecked = true; + size_t bytesWritten; + uint32_t packetNum; + uint16_t sequenceCount; + uint8_t progressPercent; + bool deleteMemory = false; + }; + + struct Update update; + + int serialPort = 0; + SemaphoreIF* semaphore; + MutexIF* lock; + MutexIF* ipcLock; + supv::TmBase tmReader; + struct termios tty = {}; +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter = 0; +#endif + + struct EventBufferRequest { + std::string path = ""; + // Default name of file where event buffer data will be written to. Timestamp will be added to + // name when new file is created + std::string filename = "event-buffer.bin"; + }; + + EventBufferRequest eventBufferReq; + + enum class InternalState { SLEEPING, DEFAULT, DEDICATED_REQUEST, GO_TO_SLEEP }; + + enum class Request { + DEFAULT, + UPDATE, + CONTINUE_UPDATE, + REQUEST_EVENT_BUFFER, + CHECK_MEMORY, + }; + InternalState state = InternalState::SLEEPING; + Request request = Request::DEFAULT; + +#ifdef XIPHOS_Q7S + SdCardManager* sdcMan = nullptr; +#endif + SimpleRingBuffer recRingBuf; + std::array cmdBuf = {}; + std::array encodedSendBuf = {}; + std::array recBuf = {}; + std::array encodedBuf = {}; + std::array decodedBuf = {}; + SimpleRingBuffer decodedRingBuf; + FIFO decodedQueue; + std::array ipcBuffer = {}; + SimpleRingBuffer ipcRingBuf; + FIFO ipcQueue; + + SpacePacketCreator creator; + supv::TcParams spParams = supv::TcParams(creator); + + std::array tmBuf{}; + + static constexpr bool PRINT_TC = false; + static constexpr bool DEBUG_MODE = false; + bool timestamping = true; + + // Remembers APID to know at which command a procedure failed + uint16_t rememberApid = 0; + + ReturnValue_t handleRunningLongerRequest(); + ReturnValue_t handleUartReception(); + void addHdlcFraming(const uint8_t* src, size_t slen, uint8_t* dst, size_t* dlen, size_t maxDest); + int removeHdlcFramingWithCrcCheck(const uint8_t* src, size_t slen, uint8_t* dst, size_t* dlen); + + ReturnValue_t encodeAndSendPacket(const uint8_t* sendData, size_t sendLen); + void executeFullCheckMemoryCommand(); + + ReturnValue_t tryHdlcParsing(); + ReturnValue_t parseRecRingBufForHdlc(size_t& readSize, size_t& decodedLen); + ReturnValue_t executeUpdate(); + ReturnValue_t continueUpdate(); + ReturnValue_t updateOperation(); + ReturnValue_t writeUpdatePackets(); + // ReturnValue_t performEventBufferRequest(); + ReturnValue_t handlePacketTransmissionNoReply(supv::TcBase& packet, + uint32_t timeoutExecutionReport); + int handleAckReception(supv::TcBase& tc, size_t packetLen); + int handleExeAckReception(supv::TcBase& tc, size_t packetLen); + + /** + * @brief Handles reading of TM packets from the communication interface + * + * @param tmPacket Pointer to space packet where received data will be written to + * @param reaminingBytes Number of bytes to read in the space packet + * @param timeout Receive timeout in milliseconds + * + * @note It can take up to 70 seconds until the supervisor replies with an acknowledgment + * failure report. + */ + ReturnValue_t handleTmReception(size_t remainingBytes, uint8_t* readBuf = nullptr, + uint32_t timeout = 70000); + ReturnValue_t checkReceivedTm(); + + ReturnValue_t selectMemory(); + ReturnValue_t prepareUpdate(); + ReturnValue_t eraseMemory(); + // Calculates CRC over image. Will be used for verification after update writing has + // finished. + ReturnValue_t calcImageCrc(); + ReturnValue_t handleCheckMemoryCommand(uint8_t failStep); + ReturnValue_t exeReportHandling(); + /** + * @brief Return size of file with name filename + * + * @param filename + * + * @return The size of the file + */ + uint32_t getFileSize(std::string filename); + ReturnValue_t handleEventBufferReception(ploc::SpTmReader& reader); + + void resetSpParams(); + void pushIpcData(const uint8_t* data, size_t len); + + /** + * Called by DHB in the GET_WRITE doGetWrite(). + * Get send confirmation that the data in sendMessage() was sent successfully. + * @param cookie + * @return + * - @c returnvalue::OK if data was sent successfully but a reply is expected + * - NO_REPLY_EXPECTED if data was sent successfully and no reply is expected + * - Everything else to indicate failure + */ + ReturnValue_t getSendSuccess(CookieIF* cookie) override; + /** + * Called by DHB in the SEND_WRITE doSendRead(). + * It is assumed that it is always possible to request a reply + * from a device. If a requestLen of 0 is supplied, no reply was enabled + * and communication specific action should be taken (e.g. read nothing + * or read everything). + * + * @param cookie + * @param requestLen Size of data to read + * @return - @c returnvalue::OK to confirm the request for data has been sent. + * - Everything else triggers failure event with + * returnvalue as parameter 1 + */ + ReturnValue_t requestReceiveMessage(CookieIF* cookie, size_t requestLen) override; + + ReturnValue_t writeMemoryHandlingWithRetryLogic(supv::WriteMemory& packet, unsigned progPercent); + + void performUartShutdown(); + void updateVtime(uint8_t vtime); +}; + +#endif /* BSP_Q7S_DEVICES_PLOCSUPVHELPER_H_ */ diff --git a/linux/payload/ScexDleParser.cpp b/linux/payload/ScexDleParser.cpp new file mode 100644 index 0000000..9c2facd --- /dev/null +++ b/linux/payload/ScexDleParser.cpp @@ -0,0 +1,7 @@ +#include + +ScexDleParser::ScexDleParser(SimpleRingBuffer &decodeRingBuf, DleEncoder &decoder, + BufPair encodedBuf, BufPair decodedBuf) + : DleParser(decodeRingBuf, decoder, encodedBuf, decodedBuf) {}; + +ScexDleParser::~ScexDleParser() {}; diff --git a/linux/payload/ScexDleParser.h b/linux/payload/ScexDleParser.h new file mode 100644 index 0000000..c91a44a --- /dev/null +++ b/linux/payload/ScexDleParser.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class ScexDleParser : public DleParser { + public: + ScexDleParser(SimpleRingBuffer &decodeRingBuf, DleEncoder &decoder, BufPair encodedBuf, + BufPair decodedBuf); + + virtual ~ScexDleParser(); + + private: +}; diff --git a/linux/payload/ScexHelper.cpp b/linux/payload/ScexHelper.cpp new file mode 100644 index 0000000..7393703 --- /dev/null +++ b/linux/payload/ScexHelper.cpp @@ -0,0 +1,85 @@ +#include +#include + +#include "fsfw/serviceinterface.h" + +using namespace returnvalue; + +ScexHelper::ScexHelper() {} + +ReturnValue_t ScexHelper::serialize(uint8_t** buffer, size_t* size, size_t maxSize, + Endianness streamEndianness) const { + return FAILED; +} + +size_t ScexHelper::getSerializedSize() const { return totalPacketLen; } + +ReturnValue_t ScexHelper::deSerialize(const uint8_t** buffer, size_t* size, + Endianness streamEndianness) { + if (buffer == nullptr or size == nullptr) { + return FAILED; + } + if (*size < 7) { + return STREAM_TOO_SHORT; + } + start = *buffer; + cmdByteRaw = **buffer; + cmd = static_cast((cmdByteRaw >> 1) & 0b11111); + + *buffer += 1; + packetCounter = **buffer; + + *buffer += 1; + totalPacketCounter = **buffer; + + *buffer += 1; + payloadLen = (**buffer << 8) | *(*buffer + 1); + + *buffer += 2; + payloadStart = *buffer; + totalPacketLen = payloadLen + scex::HEADER_LEN + scex::CRC_LEN; + if (totalPacketLen >= *size) { + return STREAM_TOO_SHORT; + } + *buffer += payloadLen; + crc = (**buffer << 8) | *(*buffer + 1); + if (CRC::crc16ccitt(start, totalPacketLen) != 0) { + return INVALID_CRC; + } + return OK; +} + +scex::Cmds ScexHelper::getCmd() const { return cmd; } + +uint8_t ScexHelper::getCmdByteRaw() const { return cmdByteRaw; } + +uint16_t ScexHelper::getCrc() const { return crc; } + +size_t ScexHelper::getExpectedPacketLen() const { return totalPacketLen; } + +uint8_t ScexHelper::getPacketCounter() const { return packetCounter; } + +uint16_t ScexHelper::getPayloadLen() const { return payloadLen; } + +const uint8_t* ScexHelper::getStart() const { return start; } + +uint8_t ScexHelper::getTotalPacketCounter() const { return totalPacketCounter; } + +std::ostream& operator<<(std::ostream& os, const ScexHelper& h) { + using namespace std; + sif::info << "Command Byte Raw: 0x" << std::setw(2) << std::setfill('0') << std::hex + << (int)h.cmdByteRaw << " | Command: 0x" << std::setw(2) << std::setfill('0') + << std::hex << static_cast(h.cmd) << std::dec << std::endl; + sif::info << "PacketCounter: " << h.packetCounter << endl; + sif::info << "TotalPacketCount: " << h.totalPacketCounter << endl; + sif::info << "PayloadLength: " << h.payloadLen << endl; + sif::info << "TotalPacketLength: " << h.totalPacketLen; + + return os; +} + +std::ofstream& operator<<(std::ofstream& of, const ScexHelper& h) { + of.write(reinterpret_cast(h.start), h.getSerializedSize()); + + return of; +} diff --git a/linux/payload/ScexHelper.h b/linux/payload/ScexHelper.h new file mode 100644 index 0000000..649ee7e --- /dev/null +++ b/linux/payload/ScexHelper.h @@ -0,0 +1,47 @@ +#ifndef LINUX_PAYLOAD_SCEXHELPER_H_ +#define LINUX_PAYLOAD_SCEXHELPER_H_ +#include +#include + +#include +#include +#include +#include + +class ScexHelper : public SerializeIF { + public: + //! [EXPORT] : [SKIP] + static const ReturnValue_t INVALID_CRC = returnvalue::makeCode(0, 2); + + ScexHelper(); + ReturnValue_t serialize(uint8_t **buffer, size_t *size, size_t maxSize, + Endianness streamEndianness) const override; + + size_t getSerializedSize() const override; + ReturnValue_t deSerialize(const uint8_t **buffer, size_t *size, + Endianness streamEndianness = Endianness::BIG) override; + friend std::ostream &operator<<(std::ostream &os, const ScexHelper &h); + friend std::ofstream &operator<<(std::ofstream &os, const ScexHelper &h); + + scex::Cmds getCmd() const; + uint8_t getCmdByteRaw() const; + uint16_t getCrc() const; + size_t getExpectedPacketLen() const; + uint8_t getPacketCounter() const; + uint16_t getPayloadLen() const; + const uint8_t *getStart() const; + uint8_t getTotalPacketCounter() const; + + private: + const uint8_t *start = nullptr; + uint16_t crc = 0; + uint8_t cmdByteRaw = 0; + scex::Cmds cmd = scex::Cmds::INVALID; + int packetCounter = 0; + int totalPacketCounter = 0; + uint16_t payloadLen = 0; + const uint8_t *payloadStart = 0; + size_t totalPacketLen = 0; +}; + +#endif /* LINUX_PAYLOAD_SCEXHELPER_H_ */ diff --git a/linux/payload/ScexUartReader.cpp b/linux/payload/ScexUartReader.cpp new file mode 100644 index 0000000..ccc629f --- /dev/null +++ b/linux/payload/ScexUartReader.cpp @@ -0,0 +1,228 @@ +#include // Contains file controls like O_RDWR +#include +#include +#include +#include +#include +#include +#include +#include // write(), read(), close() + +#include // Error integer and strerror() function +#include + +#include "OBSWConfig.h" + +using namespace returnvalue; + +ScexUartReader::ScexUartReader(object_id_t objectId) + : SystemObject(objectId), + decodeRingBuf(4096, true), + ipcRingBuf(200 * 2048, true), + ipcQueue(200), + dleParser(decodeRingBuf, dleEncoder, {encodedBuf.data(), encodedBuf.size()}, + {decodedBuf.data(), decodedBuf.size()}) { + semaphore = SemaphoreFactory::instance()->createBinarySemaphore(); + semaphore->acquire(); + lock = MutexFactory::instance()->createMutex(); +} + +ReturnValue_t ScexUartReader::performOperation(uint8_t operationCode) { + lock->lockMutex(); + state = States::IDLE; + lock->unlockMutex(); + while (true) { + semaphore->acquire(); + int bytesRead = 0; + // debugMode = true; + while (true) { + bytesRead = read(serialPort, reinterpret_cast(recBuf.data()), + static_cast(recBuf.size())); + if (bytesRead == 0) { + { + MutexGuard mg(lock); + if (state == States::FINISH) { + dleParser.reset(); + // Flush received and unread data + tcflush(serialPort, TCIOFLUSH); + state = States::IDLE; + break; + } + } + ReturnValue_t result = returnvalue::OK; + // Can be used to read frame, parity and overrun errors + // serial_icounter_struct icounter{}; + // uart::readCountersAndErrors(serialPort, icounter); + while (result != DleParser::NO_PACKET_FOUND) { + result = tryDleParsing(); + } + + TaskFactory::delayTask(150); + } else if (bytesRead < 0) { + sif::warning << "ScexUartReader::performOperation: read call failed with error [" << errno + << ", " << strerror(errno) << "]" << std::endl; + break; + } else if (bytesRead >= static_cast(recBuf.size())) { + sif::error << "ScexUartReader::performOperation: Receive buffer too small for " << bytesRead + << " bytes" << std::endl; + } else if (bytesRead > 0) { + if (debugMode) { + sif::info << "Received " << bytesRead + << " bytes from the Solar Cell Experiment:" << std::endl; + } + ReturnValue_t result = dleParser.passData(recBuf.data(), bytesRead); + if (result != OK) { + sif::warning << "ScexUartReader::performOperation: Passing data to DLE parser failed" + << std::endl; + } + result = tryDleParsing(); + } + }; + } + return OK; +} + +ReturnValue_t ScexUartReader::initializeInterface(CookieIF *cookie) { + SerialCookie *uartCookie = dynamic_cast(cookie); + if (uartCookie == nullptr) { + return FAILED; + } + std::string devname = uartCookie->getDeviceFile(); + /* Get file descriptor */ + serialPort = open(devname.c_str(), O_RDWR); + if (serialPort < 0) { + sif::warning << "ScexUartReader::initializeInterface: open call failed with error [" << errno + << ", " << strerror(errno) << std::endl; + return FAILED; + } + // Setting up UART parameters + tty.c_cflag &= ~PARENB; // Clear parity bit + serial::setStopbits(tty, uartCookie->getStopBits()); + serial::setBitsPerWord(tty, BitsPerWord::BITS_8); + tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control + serial::enableRead(tty); + serial::ignoreCtrlLines(tty); + + // Use non-canonical mode and clear echo flag + tty.c_lflag &= ~(ICANON | ECHO); + + // Non-blocking mode, use polling + tty.c_cc[VTIME] = 0; + tty.c_cc[VMIN] = 0; + + serial::setBaudrate(tty, uartCookie->getBaudrate()); + if (tcsetattr(serialPort, TCSANOW, &tty) != 0) { + sif::warning << "ScexUartReader::initializeInterface: tcsetattr call failed with error [" + << errno << ", " << strerror(errno) << std::endl; + } + // Flush received and unread data + tcflush(serialPort, TCIOFLUSH); + return OK; +} + +ReturnValue_t ScexUartReader::sendMessage(CookieIF *cookie, const uint8_t *sendData, + size_t sendLen) { + ReturnValue_t result; + if (sendData == nullptr or sendLen == 0) { + return FAILED; + } + lock->lockMutex(); + if (state == States::NOT_READY or state == States::RUNNING) { + lock->unlockMutex(); + return FAILED; + } + tcflush(serialPort, TCIFLUSH); + state = States::RUNNING; + lock->unlockMutex(); + + result = semaphore->release(); + if (result != OK) { + std::cout << "ScexUartReader::sendMessage: Releasing semaphore failed" << std::endl; + } + size_t encodedLen = 0; + result = dleEncoder.encode(sendData, sendLen, cmdbuf.data(), cmdbuf.size(), &encodedLen, true); + if (result != OK) { + sif::warning << "ScexUartReader::sendMessage: Encoding failed" << std::endl; + return FAILED; + } + size_t bytesWritten = write(serialPort, cmdbuf.data(), encodedLen); + if (bytesWritten != encodedLen) { + sif::warning << "ScexUartReader::sendMessage: Sending command failed" << std::endl; + return FAILED; + } + + return OK; +} + +ReturnValue_t ScexUartReader::getSendSuccess(CookieIF *cookie) { return OK; } + +ReturnValue_t ScexUartReader::requestReceiveMessage(CookieIF *cookie, size_t requestLen) { + return OK; +} + +void ScexUartReader::setDebugMode(bool enable) { this->debugMode = enable; } + +ReturnValue_t ScexUartReader::finish() { + MutexGuard mg(lock); + if (state == States::IDLE) { + return FAILED; + } + state = States::FINISH; + return OK; +} + +void ScexUartReader::handleFoundDlePacket(uint8_t *packet, size_t len) { + MutexGuard mg(lock); + ReturnValue_t result = ipcQueue.insert(len); + if (result != OK) { + sif::warning << "ScexUartReader::handleFoundDlePacket: IPCQueue error" << std::endl; + } + result = ipcRingBuf.writeData(packet, len); + if (result != OK) { + sif::warning << "ScexUartReader::handleFoundDlePacket: IPCRingBuf error" << std::endl; + } +} + +ReturnValue_t ScexUartReader::tryDleParsing() { + size_t bytesRead = 0; + ReturnValue_t result = dleParser.parseRingBuf(bytesRead); + if (result == returnvalue::OK) { + // Packet found, advance read pointer. + auto &decodedPacket = dleParser.getContext().decodedPacket; + handleFoundDlePacket(decodedPacket.first, decodedPacket.second); + dleParser.confirmBytesRead(bytesRead); + } else if (result != DleParser::NO_PACKET_FOUND) { + sif::warning << "ScexUartReader::performOperation: Possible packet loss" << std::endl; + // Markers found at wrong place + // which might be a hint for a possibly lost packet. + dleParser.defaultErrorHandler(); + dleParser.confirmBytesRead(bytesRead); + } + return result; +} + +void ScexUartReader::reset() { + lock->lockMutex(); + state = States::FINISH; + ipcRingBuf.clear(); + while (not ipcQueue.empty()) { + ipcQueue.pop(); + } + lock->unlockMutex(); +} + +ReturnValue_t ScexUartReader::readReceivedMessage(CookieIF *cookie, uint8_t **buffer, + size_t *size) { + MutexGuard mg(lock); + if (ipcQueue.empty()) { + *size = 0; + return OK; + } + ipcQueue.retrieve(size); + *buffer = ipcBuffer.data(); + ReturnValue_t result = ipcRingBuf.readData(ipcBuffer.data(), *size, true); + if (result != OK) { + sif::warning << "ScexUartReader::readReceivedMessage: Reading RingBuffer failed" << std::endl; + } + return OK; +} diff --git a/linux/payload/ScexUartReader.h b/linux/payload/ScexUartReader.h new file mode 100644 index 0000000..5b2bbb8 --- /dev/null +++ b/linux/payload/ScexUartReader.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include // Contains POSIX terminal control definitions + +class SemaphoreIF; +class MutexIF; + +class ScexUartReader : public SystemObject, + public ExecutableObjectIF, + public DeviceCommunicationIF { + friend class UartTestClass; + + public: + enum class States { NOT_READY, IDLE, RUNNING, FINISH }; + ScexUartReader(object_id_t objectId); + + void reset(); + ReturnValue_t finish(); + void setDebugMode(bool enable); + + private: + SemaphoreIF *semaphore; + bool debugMode = false; + MutexIF *lock; + int serialPort = 0; + States state = States::IDLE; + struct termios tty = {}; + bool doFinish = false; + DleEncoder dleEncoder = DleEncoder(); + SimpleRingBuffer decodeRingBuf; + + std::array cmdbuf = {}; + std::array recBuf = {}; + std::array encodedBuf = {}; + std::array decodedBuf = {}; + std::array ipcBuffer = {}; + SimpleRingBuffer ipcRingBuf; + DynamicFIFO ipcQueue; + ScexDleParser dleParser; + + static void foundDlePacketHandler(const DleParser::Context &ctx); + void handleFoundDlePacket(uint8_t *packet, size_t len); + ReturnValue_t tryDleParsing(); + + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + + // DeviceCommunicationIF implementation + ReturnValue_t initializeInterface(CookieIF *cookie) override; + ReturnValue_t sendMessage(CookieIF *cookie, const uint8_t *sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF *cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF *cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF *cookie, uint8_t **buffer, size_t *size) override; +}; diff --git a/linux/payload/SerialCommunicationHelper.cpp b/linux/payload/SerialCommunicationHelper.cpp new file mode 100644 index 0000000..cb35e03 --- /dev/null +++ b/linux/payload/SerialCommunicationHelper.cpp @@ -0,0 +1,126 @@ +#include "SerialCommunicationHelper.h" + +#include +#include +#include +#include + +#include + +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw_hal/linux/serial/helper.h" + +SerialCommunicationHelper::SerialCommunicationHelper(SerialConfig cfg) : cfg(cfg) {} + +ReturnValue_t SerialCommunicationHelper::initialize() { + fd = configureUartPort(); + if (fd < 0) { + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +int SerialCommunicationHelper::rawFd() const { return fd; } + +ReturnValue_t SerialCommunicationHelper::send(const uint8_t* data, size_t dataLen) { + if (write(fd, data, dataLen) != static_cast(dataLen)) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "UartComIF::sendMessage: Failed to send data with error code " << errno + << ": Error description: " << strerror(errno) << std::endl; +#endif + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +int SerialCommunicationHelper::configureUartPort() { + struct termios options = {}; + + int flags = O_RDWR; + if (cfg.getUartMode() == UartModes::CANONICAL) { + // In non-canonical mode, don't specify O_NONBLOCK because these properties will be + // controlled by the VTIME and VMIN parameters and O_NONBLOCK would override this + flags |= O_NONBLOCK; + } + int fd = open(cfg.getDeviceFile().c_str(), flags); + + if (fd < 0) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "UartComIF::configureUartPort: Failed to open uart " + << cfg.getDeviceFile().c_str() + + << "with error code " << errno << strerror(errno) << std::endl; +#endif + return fd; + } + + /* Read in existing settings */ + if (tcgetattr(fd, &options) != 0) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "UartComIF::configureUartPort: Error " << errno + << "from tcgetattr: " << strerror(errno) << std::endl; +#endif + return fd; + } + + serial::setParity(options, cfg.getParity()); + serial::setStopbits(options, cfg.getStopBits()); + serial::setBitsPerWord(options, cfg.getBitsPerWord()); + setFixedOptions(&options); + serial::setMode(options, cfg.getUartMode()); + tcflush(fd, TCIFLUSH); + + /* Sets uart to non-blocking mode. Read returns immediately when there are no data available */ + options.c_cc[VTIME] = 0; + options.c_cc[VMIN] = 0; + + serial::setBaudrate(options, cfg.getBaudrate()); + + /* Save option settings */ + if (tcsetattr(fd, TCSANOW, &options) != 0) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "UartComIF::configureUartPort: Failed to set options with error " << errno + << ": " << strerror(errno); +#endif + return fd; + } + return fd; +} + +void SerialCommunicationHelper::setFixedOptions(struct termios* options) { + /* Disable RTS/CTS hardware flow control */ + options->c_cflag &= ~CRTSCTS; + /* Turn on READ & ignore ctrl lines (CLOCAL = 1) */ + options->c_cflag |= CREAD | CLOCAL; + /* Disable echo */ + options->c_lflag &= ~ECHO; + /* Disable erasure */ + options->c_lflag &= ~ECHOE; + /* Disable new-line echo */ + options->c_lflag &= ~ECHONL; + /* Disable interpretation of INTR, QUIT and SUSP */ + options->c_lflag &= ~ISIG; + /* Turn off s/w flow ctrl */ + options->c_iflag &= ~(IXON | IXOFF | IXANY); + /* Disable any special handling of received bytes */ + options->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); + /* Prevent special interpretation of output bytes (e.g. newline chars) */ + options->c_oflag &= ~OPOST; + /* Prevent conversion of newline to carriage return/line feed */ + options->c_oflag &= ~ONLCR; +} + +ReturnValue_t SerialCommunicationHelper::flushUartRxBuffer() { + serial::flushRxBuf(fd); + return returnvalue::OK; +} + +ReturnValue_t SerialCommunicationHelper::flushUartTxBuffer() { + serial::flushTxBuf(fd); + return returnvalue::OK; +} + +ReturnValue_t SerialCommunicationHelper::flushUartTxAndRxBuf() { + serial::flushTxRxBuf(fd); + return returnvalue::OK; +} diff --git a/linux/payload/SerialCommunicationHelper.h b/linux/payload/SerialCommunicationHelper.h new file mode 100644 index 0000000..7943189 --- /dev/null +++ b/linux/payload/SerialCommunicationHelper.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include + +#include "SerialConfig.h" +#include "fsfw/returnvalues/returnvalue.h" + +/** + * @brief This is the communication interface to access serial ports on linux based operating + * systems. + * + * @details The implementation follows the instructions from https://blog.mbedded.ninja/programming/ + * operating-systems/linux/linux-serial-ports-using-c-cpp/#disabling-canonical-mode + * + * @author J. Meier + */ +class SerialCommunicationHelper { + public: + SerialCommunicationHelper(SerialConfig serialCfg); + + ReturnValue_t send(const uint8_t* data, size_t dataLen); + + int rawFd() const; + + ReturnValue_t initialize(); + + /** + * @brief This function discards all data received but not read in the UART buffer. + */ + ReturnValue_t flushUartRxBuffer(); + + /** + * @brief This function discards all data in the transmit buffer of the UART driver. + */ + ReturnValue_t flushUartTxBuffer(); + + /** + * @brief This function discards both data in the transmit and receive buffer of the UART. + */ + ReturnValue_t flushUartTxAndRxBuf(); + + private: + SerialConfig cfg; + int fd = 0; + + /** + * @brief This function opens and configures a uart device by using the information stored + * in the uart cookie. + * @param uartCookie Pointer to uart cookie with information about the uart. Contains the + * uart device file, baudrate, parity, stopbits etc. + * @return The file descriptor of the configured uart. + */ + int configureUartPort(); + + void setStopBitOptions(struct termios* options); + + /** + * @brief This function sets options which are not configurable by the uartCookie. + */ + void setFixedOptions(struct termios* options); + + /** + * @brief With this function the datasize settings are added to the termios options struct. + */ + void setDatasizeOptions(struct termios* options); +}; diff --git a/linux/payload/SerialConfig.h b/linux/payload/SerialConfig.h new file mode 100644 index 0000000..2ef8ccd --- /dev/null +++ b/linux/payload/SerialConfig.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include + +#include + +/** + * @brief Cookie for the UartComIF. There are many options available to configure the UART driver. + * The constructor only requests for common options like the baudrate. Other options can + * be set by member functions. + * + * @author J. Meier + */ +class SerialConfig : public CookieIF { + public: + /** + * @brief Constructor for the uart cookie. + * @param deviceFile The device file specifying the uart to use, e.g. "/dev/ttyPS1" + * @param uartMode Specify the UART mode. The canonical mode should be used if the + * messages are separated by a delimited character like '\n'. See the + * termios documentation for more information + * @param baudrate The baudrate to use for input and output. + * @param maxReplyLen The maximum size an object using this cookie expects + * @details + * Default configuration: No parity + * 8 databits (number of bits transfered with one uart frame) + * One stop bit + */ + SerialConfig(std::string deviceFile, UartBaudRate baudrate, size_t maxReplyLen, + UartModes uartMode = UartModes::NON_CANONICAL) + : deviceFile(deviceFile), baudrate(baudrate), maxReplyLen(maxReplyLen), uartMode(uartMode) {} + + virtual ~SerialConfig() = default; + + UartBaudRate getBaudrate() const { return baudrate; } + size_t getMaxReplyLen() const { return maxReplyLen; } + std::string getDeviceFile() const { return deviceFile; } + Parity getParity() const { return parity; } + BitsPerWord getBitsPerWord() const { return bitsPerWord; } + StopBits getStopBits() const { return stopBits; } + UartModes getUartMode() const { return uartMode; } + + /** + * Functions two enable parity checking. + */ + void setParityOdd() { parity = Parity::ODD; } + void setParityEven() { parity = Parity::EVEN; } + + /** + * Function two set number of bits per UART frame. + */ + void setBitsPerWord(BitsPerWord bitsPerWord_) { bitsPerWord = bitsPerWord_; } + + /** + * Function to specify the number of stopbits. + */ + void setTwoStopBits() { stopBits = StopBits::TWO_STOP_BITS; } + void setOneStopBit() { stopBits = StopBits::ONE_STOP_BIT; } + + private: + std::string deviceFile; + UartBaudRate baudrate; + size_t maxReplyLen = 0; + const UartModes uartMode; + Parity parity = Parity::NONE; + BitsPerWord bitsPerWord = BitsPerWord::BITS_8; + StopBits stopBits = StopBits::ONE_STOP_BIT; +}; diff --git a/linux/payload/plocMemDumpDefs.h b/linux/payload/plocMemDumpDefs.h new file mode 100644 index 0000000..00dc900 --- /dev/null +++ b/linux/payload/plocMemDumpDefs.h @@ -0,0 +1,28 @@ +#ifndef BSP_Q7S_DEVICES_DEVICEDEFINITIONS_PLOCMEMDUMPDEFINITIONS_H_ +#define BSP_Q7S_DEVICES_DEVICEDEFINITIONS_PLOCMEMDUMPDEFINITIONS_H_ + +#include + +class MemoryParams : public SerialLinkedListAdapter { + public: + /** + * @brief Constructor + * @param startAddress Start of address range to dump + * @param endAddress End of address range to dump + */ + MemoryParams(uint32_t startAddress, uint32_t endAddress) + : startAddress(startAddress), endAddress(endAddress) { + setLinks(); + } + + private: + void setLinks() { + setStart(&startAddress); + startAddress.setNext(&endAddress); + } + + SerializeElement startAddress; + SerializeElement endAddress; +}; + +#endif /* BSP_Q7S_DEVICES_DEVICEDEFINITIONS_PLOCMEMDUMPDEFINITIONS_H_ */ diff --git a/linux/payload/plocMpsocHelpers.cpp b/linux/payload/plocMpsocHelpers.cpp new file mode 100644 index 0000000..2109749 --- /dev/null +++ b/linux/payload/plocMpsocHelpers.cpp @@ -0,0 +1,118 @@ +#include "plocMpsocHelpers.h" + +#include "fsfw/tmtcpacket/ccsds/SpacePacketReader.h" +#include "mission/payload/plocSpBase.h" + +uint16_t mpsoc::getStatusFromRawData(const uint8_t* data) { + return (*(data + STATUS_OFFSET) << 8) | *(data + STATUS_OFFSET + 1); +} +std::string mpsoc::getStatusString(uint16_t status) { + switch (status) { + case (mpsoc::statusCode::UNKNOWN_APID): { + return "Unknown APID"; + break; + } + case (mpsoc::statusCode::INCORRECT_LENGTH): { + return "Incorrect length"; + break; + } + case (mpsoc::statusCode::FLASH_DRIVE_ERROR): { + return "flash drive error"; + break; + } + case (mpsoc::statusCode::INCORRECT_CRC): { + return "Incorrect crc"; + break; + } + case (mpsoc::statusCode::INCORRECT_PKT_SEQ_CNT): { + return "Incorrect packet sequence count"; + break; + } + case (mpsoc::statusCode::TC_NOT_ALLOWED_IN_MODE): { + return "TC not allowed in this mode"; + break; + } + case (mpsoc::statusCode::TC_EXEUTION_DISABLED): { + return "TC execution disabled"; + break; + } + case (mpsoc::statusCode::FLASH_MOUNT_FAILED): { + return "Flash mount failed"; + break; + } + case (mpsoc::statusCode::FLASH_FILE_ALREADY_OPEN): { + return "Flash file already open"; + break; + } + case (mpsoc::statusCode::FLASH_FILE_ALREADY_CLOSED): { + return "Flash file already closed"; + break; + } + case (mpsoc::statusCode::FLASH_FILE_OPEN_FAILED): { + return "Flash file open failed"; + break; + } + case (mpsoc::statusCode::FLASH_FILE_NOT_OPEN): { + return "Flash file not open"; + break; + } + case (mpsoc::statusCode::FLASH_UNMOUNT_FAILED): { + return "Flash unmount failed"; + break; + } + case (mpsoc::statusCode::HEAP_ALLOCATION_FAILED): { + return "Heap allocation failed"; + break; + } + case (mpsoc::statusCode::INVALID_PARAMETER): { + return "Invalid parameter"; + break; + } + case (mpsoc::statusCode::NOT_INITIALIZED): { + return "Not initialized"; + break; + } + case (mpsoc::statusCode::REBOOT_IMMINENT): { + return "Reboot imminent"; + break; + } + case (mpsoc::statusCode::CORRUPT_DATA): { + return "Corrupt data"; + break; + } + case (mpsoc::statusCode::FLASH_CORRECTABLE_MISMATCH): { + return "Flash correctable mismatch"; + break; + } + case (mpsoc::statusCode::FLASH_UNCORRECTABLE_MISMATCH): { + return "Flash uncorrectable mismatch"; + break; + } + case (mpsoc::statusCode::DEFAULT_ERROR_CODE): { + return "Default error code"; + break; + } + default: + std::stringstream ss; + ss << "0x" << std::hex << status; + return ss.str().c_str(); + break; + } + return ""; +} + +void mpsoc::printRxPacket(const SpacePacketReader& spReader) { + if (mpsoc::MPSOC_RX_WIRETAPPING) { + sif::debug << "RECV MPSOC packet. APID 0x" << std::hex << std::setw(3) << spReader.getApid() + << std::dec << " Size " << spReader.getFullPacketLen() << " SSC " + << spReader.getSequenceCount() << std::endl; + } +} + +void mpsoc::printTxPacket(const ploc::SpTcBase& tcBase) { + if (mpsoc::MPSOC_TX_WIRETAPPING) { + sif::debug << "SEND MPSOC packet. APID 0x" << std::hex << std::setw(3) << tcBase.getApid() + << " Size " << std::dec << tcBase.getFullPacketLen() << " SSC " + << tcBase.getSeqCount() << std::endl; + } +} diff --git a/linux/payload/plocMpsocHelpers.h b/linux/payload/plocMpsocHelpers.h new file mode 100644 index 0000000..6f59f4c --- /dev/null +++ b/linux/payload/plocMpsocHelpers.h @@ -0,0 +1,1220 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_PLOCMPSOCDEFINITIONS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_PLOCMPSOCDEFINITIONS_H_ + +#include +#include +#include + +#include "eive/eventSubsystemIds.h" +#include "eive/resultClassIds.h" +#include "fsfw/action/HasActionsIF.h" +#include "fsfw/events/Event.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/serialize/SerializeAdapter.h" +#include "fsfw/serialize/SerializeIF.h" + +namespace mpsoc { + +static constexpr bool MPSOC_TX_WIRETAPPING = false; +static constexpr bool MPSOC_RX_WIRETAPPING = false; + +static constexpr size_t CRC_SIZE = 2; + +/** + * @brief Abstract base class for TC space packet of MPSoC. + */ +class TcBase : public ploc::SpTcBase { + public: + virtual ~TcBase() = default; + + // Initial length field of space packet. Will always be updated when packet is created. + static const uint16_t INIT_LENGTH = CRC_SIZE; + + /** + * @brief Constructor + * + * @param sequenceCount Sequence count of space packet which will be incremented with each + * sent and received packets. + */ + TcBase(ploc::SpTcParams params, uint16_t apid, uint16_t sequenceCount) + : ploc::SpTcBase(params, apid, 0, sequenceCount) { + payloadStart = spParams.buf + ccsds::HEADER_LEN; + spParams.setFullPayloadLen(INIT_LENGTH); + } + + /** + * @brief Function to finsh and write the space packet. It is expected that the user has + * set the payload fields in the child class* + * @return returnvalue::OK if packet creation was successful, otherwise error return value + */ + ReturnValue_t finishPacket() { + updateSpFields(); + ReturnValue_t res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + return calcAndSetCrc(); + } +}; + +void printRxPacket(const SpacePacketReader& spReader); +void printTxPacket(const ploc::SpTcBase& tcBase); + +static constexpr uint32_t DEFAULT_CMD_TIMEOUT_MS = 5000; +static constexpr uint32_t CMD_TIMEOUT_MKFS = 15000; + +enum FlashId : uint8_t { FLASH_0 = 0, FLASH_1 = 1 }; + +static const uint8_t INTERFACE_ID = CLASS_ID::MPSOC_RETURN_VALUES_IF; + +//! [EXPORT] : [COMMENT] Space Packet received from PLOC has invalid CRC +static const ReturnValue_t CRC_FAILURE = MAKE_RETURN_CODE(0xA0); +//! [EXPORT] : [COMMENT] Received ACK failure reply from PLOC +static const ReturnValue_t RECEIVED_ACK_FAILURE = MAKE_RETURN_CODE(0xA1); +//! [EXPORT] : [COMMENT] Received execution failure reply from PLOC +static const ReturnValue_t RECEIVED_EXE_FAILURE = MAKE_RETURN_CODE(0xA2); +//! [EXPORT] : [COMMENT] Received space packet with invalid APID from PLOC +static const ReturnValue_t INVALID_APID = MAKE_RETURN_CODE(0xA3); +//! [EXPORT] : [COMMENT] Received command with invalid length +static const ReturnValue_t INVALID_LENGTH = MAKE_RETURN_CODE(0xA4); +//! [EXPORT] : [COMMENT] Filename of file in OBC filesystem is too long +static const ReturnValue_t FILENAME_TOO_LONG = MAKE_RETURN_CODE(0xA5); +//! [EXPORT] : [COMMENT] MPSoC helper is currently executing a command +static const ReturnValue_t MPSOC_HELPER_EXECUTING = MAKE_RETURN_CODE(0xA6); +//! [EXPORT] : [COMMENT] Filename of MPSoC file is to long (max. 256 bytes) +static const ReturnValue_t MPSOC_FILENAME_TOO_LONG = MAKE_RETURN_CODE(0xA7); +//! [EXPORT] : [COMMENT] Command has invalid parameter +static const ReturnValue_t INVALID_PARAMETER = MAKE_RETURN_CODE(0xA8); +//! [EXPORT] : [COMMENT] Received command has file string with invalid length +static const ReturnValue_t NAME_TOO_LONG = MAKE_RETURN_CODE(0xA9); +//! [EXPORT] : [COMMENT] Command has timed out. +static const ReturnValue_t COMMAND_TIMEOUT = MAKE_RETURN_CODE(0x10); + +static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_MPSOC_HANDLER; + +//! [EXPORT] : [COMMENT] PLOC crc failure in telemetry packet +static const Event MEMORY_READ_RPT_CRC_FAILURE = MAKE_EVENT(1, severity::LOW); +//! [EXPORT] : [COMMENT] PLOC receive acknowledgment failure report +//! P1: Command Id which leads the acknowledgment failure report +//! P2: The status field inserted by the MPSoC into the data field +static const Event ACK_FAILURE = MAKE_EVENT(2, severity::LOW); +//! [EXPORT] : [COMMENT] PLOC receive execution failure report +//! P1: Command Id which leads the execution failure report +//! P2: The status field inserted by the MPSoC into the data field +static const Event EXE_FAILURE = MAKE_EVENT(3, severity::LOW); +//! [EXPORT] : [COMMENT] PLOC reply has invalid crc +static const Event MPSOC_HANDLER_CRC_FAILURE = MAKE_EVENT(4, severity::LOW); +//! [EXPORT] : [COMMENT] Packet sequence count in received space packet does not match expected +//! count P1: Expected sequence count P2: Received sequence count +static const Event MPSOC_HANDLER_SEQUENCE_COUNT_MISMATCH = MAKE_EVENT(5, severity::LOW); +//! [EXPORT] : [COMMENT] Supervisor fails to shutdown MPSoC. Requires to power off the PLOC and +//! thus also to shutdown the supervisor. +static const Event MPSOC_SHUTDOWN_FAILED = MAKE_EVENT(6, severity::HIGH); +//! [EXPORT] : [COMMENT] SUPV not on for boot or shutdown process. P1: 0 for OFF transition, 1 for +//! ON transition. +static constexpr Event SUPV_NOT_ON = event::makeEvent(SUBSYSTEM_ID, 7, severity::LOW); +//! [EXPORT] : [COMMENT] SUPV reply timeout. +static constexpr Event SUPV_REPLY_TIMEOUT = event::makeEvent(SUBSYSTEM_ID, 8, severity::LOW); +//! [EXPORT] : [COMMENT] Camera must be commanded on first. +static constexpr Event CAM_MUST_BE_ON_FOR_SNAPSHOT_MODE = + event::makeEvent(SUBSYSTEM_ID, 9, severity::LOW); + +enum ParamId : uint8_t { SKIP_SUPV_ON_COMMANDING = 0x01 }; + +enum FileAccessModes : uint8_t { + // Opens a file, fails if the file does not exist. + OPEN_EXISTING = 0x00, + READ = 0x01, + WRITE = 0x02, + // Creates a new file, fails if it already exists. + CREATE_NEW = 0x04, + // Creates a new file, existing file is truncated and overwritten. + CREATE_ALWAYS = 0x08, + // Opens the file if it is existing. If not, a new file is created. + OPEN_ALWAYS = 0x10, + OPEN_APPEND = 0x30 +}; + +static constexpr uint32_t HK_SET_ID = 0; +static constexpr uint32_t DEADBEEF_ADDR = 0x40000004; +static constexpr uint32_t DEADBEEF_VALUE = 0xdeadbeef; + +namespace poolid { +enum { + STATUS = 0, + MODE = 1, + DOWNLINK_PWR_ON = 2, + DOWNLINK_REPLY_ACTIIVE = 3, + DOWNLINK_JESD_SYNC_STATUS = 4, + DOWNLINK_DAC_STATUS = 5, + CAM_STATUS = 6, + CAM_SDI_STATUS = 7, + CAM_FPGA_TEMP = 8, + CAM_SOC_TEMP = 9, + SYSMON_TEMP = 10, + SYSMON_VCCINT = 11, + SYSMON_VCCAUX = 12, + SYSMON_VCCBRAM = 13, + SYSMON_VCCPAUX = 14, + SYSMON_VCCPINT = 15, + SYSMON_VCCPDRO = 16, + SYSMON_MB12V = 17, + SYSMON_MB3V3 = 18, + SYSMON_MB1V8 = 19, + SYSMON_VCC12V = 20, + SYSMON_VCC5V = 21, + SYSMON_VCC3V3 = 22, + SYSMON_VCC3V3VA = 23, + SYSMON_VCC2V5DDR = 24, + SYSMON_VCC1V2DDR = 25, + SYSMON_VCC0V9 = 26, + SYSMON_VCC0V6VTT = 27, + SYSMON_SAFE_COTS_CUR = 28, + SYSMON_NVM4_XO_CUR = 29, + SEM_UNCORRECTABLE_ERRS = 30, + SEM_CORRECTABLE_ERRS = 31, + SEM_STATUS = 32, + REBOOT_MPSOC_REQUIRED = 33, +}; +} + +static const DeviceCommandId_t NONE = 0; +static const DeviceCommandId_t TC_MEM_WRITE = 1; +static const DeviceCommandId_t TC_MEM_READ = 2; +static const DeviceCommandId_t ACK_REPORT = 3; +static const DeviceCommandId_t EXE_REPORT = 5; +static const DeviceCommandId_t TM_MEMORY_READ_REPORT = 6; +static const DeviceCommandId_t TC_FLASHFOPEN = 7; +static const DeviceCommandId_t TC_FLASHFCLOSE = 8; +static const DeviceCommandId_t TC_FLASH_WRITE_FULL_FILE = 9; +static const DeviceCommandId_t TC_FLASHDELETE = 10; +static const DeviceCommandId_t TC_REPLAY_START = 11; +static const DeviceCommandId_t TC_REPLAY_STOP = 12; +static const DeviceCommandId_t TC_REPLAY_WRITE_SEQUENCE = 13; +static const DeviceCommandId_t TC_DOWNLINK_PWR_ON = 14; +static const DeviceCommandId_t TC_DOWNLINK_PWR_OFF = 15; +static const DeviceCommandId_t TC_MODE_REPLAY = 16; +static const DeviceCommandId_t TC_CAM_CMD_SEND = 17; +static const DeviceCommandId_t TC_MODE_IDLE = 18; +static const DeviceCommandId_t TM_CAM_CMD_RPT = 19; +static const DeviceCommandId_t SET_UART_TX_TRISTATE = 20; +static const DeviceCommandId_t RELEASE_UART_TX = 21; +static const DeviceCommandId_t TC_CAM_TAKE_PIC = 22; +// Stream file down using E-Band component directly. +static const DeviceCommandId_t TC_SIMPLEX_STREAM_FILE = 23; +static const DeviceCommandId_t TC_DOWNLINK_DATA_MODULATE = 24; +static const DeviceCommandId_t TC_MODE_SNAPSHOT = 25; +static const DeviceCommandId_t TC_GET_HK_REPORT = 26; +static const DeviceCommandId_t TM_GET_HK_REPORT = 27; +static const DeviceCommandId_t TC_FLASH_GET_DIRECTORY_CONTENT = 28; +static const DeviceCommandId_t TM_FLASH_DIRECTORY_CONTENT = 29; +static constexpr DeviceCommandId_t TC_FLASH_READ_FULL_FILE = 30; +// Store file on MPSoC. +static const DeviceCommandId_t TC_SPLIT_FILE = 31; +static const DeviceCommandId_t TC_VERIFY_BOOT = 32; +static const DeviceCommandId_t TC_ENABLE_TC_EXECTION = 33; +static const DeviceCommandId_t TC_FLASH_MKFS = 34; + +// Will reset the sequence count of the OBSW. Not required anymore after MPSoC update. +static const DeviceCommandId_t OBSW_RESET_SEQ_COUNT_LEGACY = 50; + +static const uint16_t SIZE_ACK_REPORT = 14; +static const uint16_t SIZE_EXE_REPORT = 14; +static constexpr size_t SIZE_TM_HK_REPORT = 369; + +enum Submode : uint8_t { IDLE_OR_NONE = 0, REPLAY = 1, SNAPSHOT = 2 }; + +// Setting the internal mode value to the actual telecommand ID +/* +enum InternalMode { + OFF = HasModesIF::MODE_OFF, + IDLE = , + REPLAY = TC_MODE_REPLAY, + SNAPSHOT = TC_MODE_SNAPSHOT +}; +*/ + +/** + * SpacePacket apids of PLOC telecommands and telemetry. + */ +namespace apid { +static const uint16_t TC_REPLAY_START = 0x110; +static const uint16_t TC_REPLAY_STOP = 0x111; +static const uint16_t TC_REPLAY_WRITE_SEQUENCE = 0x112; +static const uint16_t TC_DOWNLINK_PWR_ON = 0x113; +static const uint16_t TC_MEM_WRITE = 0x114; +static const uint16_t TC_MEM_READ = 0x115; +static const uint16_t TC_CAM_TAKE_PIC = 0x116; +static constexpr uint16_t TC_FLASHWRITE = 0x117; +static constexpr uint16_t TC_FLASHREAD = 0x118; +static const uint16_t TC_FLASHFOPEN = 0x119; +static const uint16_t TC_FLASHFCLOSE = 0x11A; +static constexpr uint16_t TC_FLASH_GET_DIRECTORY_CONTENT = 0x11B; +static const uint16_t TC_FLASHDELETE = 0x11C; +static constexpr uint16_t TC_FLASH_CREATE_DIR = 0x11D; +static const uint16_t TC_MODE_IDLE = 0x11E; +static const uint16_t TC_MODE_REPLAY = 0x11F; +static const uint16_t TC_MODE_SNAPSHOT = 0x120; +static const uint16_t TC_DOWNLINK_DATA_MODULATE = 0x121; +static constexpr uint16_t TC_HK_GET_REPORT = 0x123; +static const uint16_t TC_DOWNLINK_PWR_OFF = 0x124; +static constexpr uint16_t TC_ENABLE_TC_EXECUTION = 0x129; +static constexpr uint16_t TC_FLASH_MKFS = 0x12A; +static const uint16_t TC_CAM_CMD_SEND = 0x12C; +static constexpr uint16_t TC_FLASH_COPY_FILE = 0x12E; +static const uint16_t TC_SIMPLEX_SEND_FILE = 0x130; + +static const uint16_t ACK_SUCCESS = 0x400; +static const uint16_t ACK_FAILURE = 0x401; +static const uint16_t EXE_SUCCESS = 0x402; +static const uint16_t EXE_FAILURE = 0x403; +static const uint16_t TM_MEMORY_READ_REPORT = 0x404; +static const uint16_t TM_FLASH_READ_REPORT = 0x405; +static constexpr uint16_t TM_FLASH_DIRECTORY_CONTENT = 0x406; +static const uint16_t TM_CAM_CMD_RPT = 0x407; +static constexpr uint16_t TM_HK_GET_REPORT = 0x408; +} // namespace apid + +/** Offset from first byte in space packet to first byte of data field */ +static const uint8_t DATA_FIELD_OFFSET = 6; +static const size_t MEM_READ_RPT_LEN_OFFSET = 10; +static const char NULL_TERMINATOR = '\0'; +static const uint8_t MIN_SPACE_PACKET_LENGTH = 7; +static const uint8_t SPACE_PACKET_HEADER_SIZE = 6; + +static const uint8_t STATUS_OFFSET = 10; + +/** + * The size of payload data which will be forwarded to the requesting object. e.g. PUS Service + * 8. + */ +static const uint8_t SIZE_MEM_READ_RPT_FIX = 6; + +static const size_t FILENAME_FIELD_SIZE = 256; +// Subtract size of NULL terminator. +static const size_t MAX_FILENAME_SIZE = FILENAME_FIELD_SIZE - 1; + +/** + * PLOC space packet length for fixed size packets. This is the size of the whole packet data + * field. For the length field in the space packet this size will be substracted by one. + */ +static const uint16_t LENGTH_TC_MEM_WRITE = 12; +static const uint16_t LENGTH_TC_MEM_READ = 8; + +/** + * Maximum SP packet size as specified in the TAS Supversior ICD. + * https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_TAS-ILH-IRS/ICD-PLOC/TAS&fileid=942896 + * at sheet README + */ +static constexpr size_t SP_MAX_SIZE = 1024; +static constexpr size_t MAX_REPLY_SIZE = SP_MAX_SIZE * 3; +static constexpr size_t MAX_COMMAND_SIZE = SP_MAX_SIZE; +// 1016 bytes. +static constexpr size_t SP_MAX_DATA_SIZE = SP_MAX_SIZE - ccsds::HEADER_LEN - CRC_SIZE; +static constexpr size_t FLASH_READ_MIN_OVERHEAD = 16; +// 1000 bytes. +static const size_t MAX_FLASH_READ_DATA_SIZE = SP_MAX_DATA_SIZE - FLASH_READ_MIN_OVERHEAD; +// 1012 bytes, 4 bytes for the write length. +static constexpr size_t MAX_FLASH_WRITE_DATA_SIZE = SP_MAX_DATA_SIZE - sizeof(uint32_t); + +/** + * The replay write sequence command has a maximum delay for the execution report which amounts to + * 30 seconds. (80 cycles * 0.5 seconds = 40 seconds). + */ +static const uint16_t TC_WRITE_SEQ_EXECUTION_DELAY = 80; +// Requires approx. 2 seconds for execution. 8 => 4 seconds +static const uint16_t TC_DOWNLINK_PWR_ON_EXECUTION_DELAY = 8; +static const uint16_t TC_CAM_TAKE_PIC_EXECUTION_DELAY = 20; +static const uint16_t TC_SIMPLEX_SEND_FILE_DELAY = 80; + +namespace statusCode { +static const uint16_t DEFAULT_ERROR_CODE = 0x1; +static constexpr uint16_t FLASH_DRIVE_ERROR = 0xb; +static const uint16_t UNKNOWN_APID = 0x5DD; +static const uint16_t INCORRECT_LENGTH = 0x5DE; +static const uint16_t INCORRECT_CRC = 0x5DF; +static const uint16_t INCORRECT_PKT_SEQ_CNT = 0x5E0; +static const uint16_t TC_NOT_ALLOWED_IN_MODE = 0x5E1; +static const uint16_t TC_EXEUTION_DISABLED = 0x5E2; +static const uint16_t FLASH_MOUNT_FAILED = 0x5E3; +static const uint16_t FLASH_FILE_ALREADY_CLOSED = 0x5E4; +static const uint16_t FLASH_FILE_OPEN_FAILED = 0x5E5; +static const uint16_t FLASH_FILE_ALREADY_OPEN = 0x5E6; +static const uint16_t FLASH_FILE_NOT_OPEN = 0x5E7; +static const uint16_t FLASH_UNMOUNT_FAILED = 0x5E8; +static const uint16_t HEAP_ALLOCATION_FAILED = 0x5E9; +static const uint16_t INVALID_PARAMETER = 0x5EA; +static const uint16_t NOT_INITIALIZED = 0x5EB; +static const uint16_t REBOOT_IMMINENT = 0x5EC; +static const uint16_t CORRUPT_DATA = 0x5ED; +static const uint16_t FLASH_CORRECTABLE_MISMATCH = 0x5EE; +static const uint16_t FLASH_UNCORRECTABLE_MISMATCH = 0x5EF; +static const uint16_t RESERVED_0 = 0x5F0; +static const uint16_t RESERVED_1 = 0x5F1; +static const uint16_t RESERVED_2 = 0x5F2; +static const uint16_t RESERVED_3 = 0x5F3; +static const uint16_t RESERVED_4 = 0x5F4; +} // namespace statusCode + +/** + * @brief This class helps to build the memory read command for the PLOC. + */ +class TcMemRead : public mpsoc::TcBase { + public: + /** + * @brief Constructor + */ + TcMemRead(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_MEM_READ, sequenceCount) { + spParams.setFullPayloadLen(COMMAND_LENGTH + CRC_SIZE); + } + + uint16_t getMemLen() const { return memLen; } + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + result = lengthCheck(commandDataLen); + if (result != returnvalue::OK) { + return result; + } + std::memcpy(payloadStart, commandData, MEM_ADDRESS_SIZE); + std::memcpy(payloadStart + MEM_ADDRESS_SIZE, commandData + MEM_ADDRESS_SIZE, MEM_LEN_SIZE); + size_t size = sizeof(memLen); + const uint8_t* memLenPtr = commandData + MEM_ADDRESS_SIZE; + result = + SerializeAdapter::deSerialize(&memLen, &memLenPtr, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + return result; + } + + private: + static const size_t COMMAND_LENGTH = 6; + static const size_t MEM_ADDRESS_SIZE = 4; + static const size_t MEM_LEN_SIZE = 2; + static const uint16_t PACKET_LENGTH = 7; + + uint16_t memLen = 0; + + ReturnValue_t lengthCheck(size_t commandDataLen) { + if (commandDataLen != COMMAND_LENGTH or checkPayloadLen() != returnvalue::OK) { + return INVALID_LENGTH; + } + return returnvalue::OK; + } +}; + +/** + * @brief This class helps to generate the space packet to write data to a memory address within + * the PLOC. + */ +class TcMemWrite : public mpsoc::TcBase { + public: + /** + * @brief Constructor + */ + TcMemWrite(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_MEM_WRITE, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + result = lengthCheck(commandDataLen); + if (result != returnvalue::OK) { + return result; + } + uint16_t memLen = + *(commandData + MEM_ADDRESS_SIZE) << 8 | *(commandData + MEM_ADDRESS_SIZE + 1); + spParams.setFullPayloadLen(MIN_FIXED_PAYLOAD_LENGTH + memLen * 4 + CRC_SIZE); + result = checkPayloadLen(); + if (result != returnvalue::OK) { + return result; + } + std::memcpy(payloadStart, commandData, commandDataLen); + return result; + } + + private: + // 4 byte address, 2 byte mem length field + static const size_t MEM_ADDRESS_SIZE = 4; + static const size_t MIN_FIXED_PAYLOAD_LENGTH = MEM_ADDRESS_SIZE + 2; + // Min length consists of 4 byte address, 2 byte mem length field, 4 byte data (1 word) + static const size_t MIN_COMMAND_DATA_LENGTH = MIN_FIXED_PAYLOAD_LENGTH + 4; + + ReturnValue_t lengthCheck(size_t commandDataLen) { + if (commandDataLen < MIN_COMMAND_DATA_LENGTH) { + sif::warning << "TcMemWrite: Length " << commandDataLen << " smaller than minimum " + << MIN_COMMAND_DATA_LENGTH << std::endl; + return INVALID_LENGTH; + } + if (commandDataLen + CRC_SIZE > spParams.maxSize) { + sif::warning << "TcMemWrite: Length " << commandDataLen << " larger than allowed " + << spParams.maxSize - CRC_SIZE << std::endl; + return INVALID_LENGTH; + } + return returnvalue::OK; + } +}; + +/** + * @brief Class to help creation of flash fopen command. + */ +class TcFlashFopen : public mpsoc::TcBase { + public: + TcFlashFopen(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_FLASHFOPEN, sequenceCount) {} + + ReturnValue_t setPayload(std::string filename, uint8_t mode) { + accessMode = mode; + ReturnValue_t result = checkPayloadLen(); + if (result != returnvalue::OK) { + return result; + } + std::memset(payloadStart, 0, FILENAME_FIELD_SIZE); + std::memcpy(payloadStart, filename.c_str(), filename.size()); + payloadStart[FILENAME_FIELD_SIZE] = accessMode; + spParams.setFullPayloadLen(FILENAME_FIELD_SIZE + 1 + CRC_SIZE); + return returnvalue::OK; + } + + private: + uint8_t accessMode = FileAccessModes::OPEN_EXISTING; +}; + +/** + * @brief Class to help creation of flash fclose command. + */ +class TcFlashFclose : public TcBase { + public: + TcFlashFclose(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_FLASHFCLOSE, sequenceCount) { + spParams.setFullPayloadLen(CRC_SIZE); + } +}; + +class TcEnableTcExec : public TcBase { + public: + TcEnableTcExec(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_ENABLE_TC_EXECUTION, sequenceCount) { + spParams.setFullPayloadLen(CRC_SIZE); + } + + ReturnValue_t setPayload(const uint8_t* cmdData, size_t cmdDataLen) { + if (cmdDataLen != 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + std::memcpy(payloadStart, cmdData, 2); + spParams.setFullPayloadLen(2 + CRC_SIZE); + return returnvalue::OK; + } +}; + +class TcFlashMkfs : public TcBase { + public: + TcFlashMkfs(ploc::SpTcParams params, uint16_t sequenceCount, FlashId flashId) + : TcBase(params, apid::TC_FLASH_MKFS, sequenceCount) { + const char* flashIdStr = "0:\\"; + if (flashId == FlashId::FLASH_1) { + flashIdStr = "1:\\"; + } + std::memcpy(payloadStart, flashIdStr, 3); + // Null terminator + payloadStart[3] = 0; + spParams.setFullPayloadLen(4 + CRC_SIZE); + } +}; + +/** + * @brief Class to build flash write space packet. + */ +class TcFlashWrite : public TcBase { + public: + TcFlashWrite(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_FLASHWRITE, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* writeData, uint32_t writeLen_) { + writeLen = writeLen_; + if (writeLen > MAX_FLASH_WRITE_DATA_SIZE) { + sif::error << "TcFlashWrite: Command data too big" << std::endl; + return returnvalue::FAILED; + } + spParams.setFullPayloadLen(sizeof(uint32_t) + static_cast(writeLen) + CRC_SIZE); + ReturnValue_t result = checkPayloadLen(); + if (result != returnvalue::OK) { + return result; + } + size_t serializedSize = ccsds::HEADER_LEN; + result = SerializeAdapter::serialize(&writeLen, payloadStart, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + std::memcpy(payloadStart + sizeof(uint32_t), writeData, writeLen); + updateSpFields(); + result = checkSizeAndSerializeHeader(); + if (result != returnvalue::OK) { + return result; + } + return calcAndSetCrc(); + } + + private: + uint32_t writeLen = 0; +}; + +class TcFlashRead : public TcBase { + public: + TcFlashRead(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_FLASHREAD, sequenceCount) {} + + ReturnValue_t setPayload(uint32_t readLen) { + if (readLen > MAX_FLASH_READ_DATA_SIZE) { + sif::error << "TcFlashRead: Read length " << readLen << " too large" << std::endl; + return returnvalue::FAILED; + } + spParams.setFullPayloadLen(sizeof(uint32_t) + CRC_SIZE); + ReturnValue_t result = checkPayloadLen(); + if (result != returnvalue::OK) { + return result; + } + size_t serializedSize = ccsds::HEADER_LEN; + result = SerializeAdapter::serialize(&readLen, payloadStart, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + readSize = readLen; + return result; + } + + uint32_t readSize = 0; +}; + +/** + * @brief Class to help creation of flash delete command. + */ +class TcFlashDelete : public TcBase { + public: + TcFlashDelete(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_FLASHDELETE, sequenceCount) {} + + ReturnValue_t setPayload(std::string filename) { + size_t nameSize = filename.size(); + spParams.setFullPayloadLen(FILENAME_FIELD_SIZE + CRC_SIZE); + auto res = checkPayloadLen(); + if (res != returnvalue::OK) { + return res; + } + std::memcpy(payloadStart, filename.c_str(), nameSize); + *(payloadStart + nameSize) = NULL_TERMINATOR; + return returnvalue::OK; + } +}; + +/** + * @brief Class to build replay stop space packet. + */ +class TcReplayStop : public TcBase { + public: + TcReplayStop(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_REPLAY_STOP, sequenceCount) {} +}; + +/** + * @brief This class helps to build the replay start command. + */ +class TcReplayStart : public TcBase { + public: + /** + * @brief Constructor + */ + TcReplayStart(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_REPLAY_START, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + spParams.setFullPayloadLen(commandDataLen + CRC_SIZE); + result = lengthCheck(commandDataLen); + if (result != returnvalue::OK) { + return result; + } + result = checkData(*commandData); + if (result != returnvalue::OK) { + return result; + } + std::memcpy(payloadStart, commandData, commandDataLen); + return result; + } + + private: + static const size_t COMMAND_DATA_LENGTH = 1; + static const uint8_t REPEATING = 0; + static const uint8_t ONCE = 1; + + ReturnValue_t lengthCheck(size_t commandDataLen) { + if (commandDataLen != COMMAND_DATA_LENGTH or checkPayloadLen() != returnvalue::OK) { + sif::warning << "TcReplayStart: Command has invalid length " << commandDataLen << std::endl; + return INVALID_LENGTH; + } + return returnvalue::OK; + } + + ReturnValue_t checkData(uint8_t replay) { + if (replay != REPEATING && replay != ONCE) { + sif::warning << "TcReplayStart::checkData: Invalid replay value" << std::endl; + return INVALID_PARAMETER; + } + return returnvalue::OK; + } +}; + +/** + * @brief This class helps to build downlink power on command. + */ +class TcDownlinkPwrOn : public TcBase { + public: + /** + * @brief Constructor + */ + TcDownlinkPwrOn(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_DOWNLINK_PWR_ON, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + result = lengthCheck(commandDataLen); + if (result != returnvalue::OK) { + return result; + } + result = modeCheck(*commandData); + if (result != returnvalue::OK) { + return result; + } + result = laneRateCheck(*(commandData + 1)); + if (result != returnvalue::OK) { + return result; + } + spParams.setFullPayloadLen(commandDataLen + sizeof(MAX_AMPLITUDE) + CRC_SIZE); + result = checkPayloadLen(); + if (result != returnvalue::OK) { + return result; + } + std::memcpy(payloadStart, commandData, commandDataLen); + std::memcpy(payloadStart + commandDataLen, &MAX_AMPLITUDE, sizeof(MAX_AMPLITUDE)); + return result; + } + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::DWLPWRON_CMD; + + //! [EXPORT] : [COMMENT] Received command has invalid JESD mode (valid modes are 0 - 5) + static const ReturnValue_t INVALID_MODE = MAKE_RETURN_CODE(0xE0); + //! [EXPORT] : [COMMENT] Received command has invalid lane rate (valid lane rate are 0 - 9) + static const ReturnValue_t INVALID_LANE_RATE = MAKE_RETURN_CODE(0xE1); + + static const size_t COMMAND_DATA_LENGTH = 2; + static const uint8_t MAX_MODE = 5; + static const uint8_t MAX_LANE_RATE = 9; + static const uint16_t MAX_AMPLITUDE = 0; + + ReturnValue_t lengthCheck(size_t commandDataLen) { + if (commandDataLen != COMMAND_DATA_LENGTH) { + sif::warning << "TcDownlinkPwrOn: Command has invalid length " << commandDataLen << std::endl; + return INVALID_LENGTH; + } + return returnvalue::OK; + } + + ReturnValue_t modeCheck(uint8_t mode) { + if (mode > MAX_MODE) { + sif::warning << "TcDwonlinkPwrOn::modeCheck: Invalid JESD mode" << std::endl; + return INVALID_MODE; + } + return returnvalue::OK; + } + + ReturnValue_t laneRateCheck(uint8_t laneRate) { + if (laneRate > MAX_LANE_RATE) { + sif::warning << "TcReplayStart::laneRateCheck: Invalid lane rate" << std::endl; + return INVALID_LANE_RATE; + } + return returnvalue::OK; + } +}; + +class TcGetHkReport : public TcBase { + public: + TcGetHkReport(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_HK_GET_REPORT, sequenceCount) {} +}; + +class TcGetDirContent : public TcBase { + public: + TcGetDirContent(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_FLASH_GET_DIRECTORY_CONTENT, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + // Yeah it needs to be 256.. even if the path is shorter. + spParams.setFullPayloadLen(256 + CRC_SIZE); + if (result != returnvalue::OK) { + return result; + } + std::memset(payloadStart, 0, 256); + std::memcpy(payloadStart, commandData, commandDataLen); + payloadStart[255] = 0; + return result; + } +}; + +/** + * @brief Class to build replay stop space packet. + */ +class TcDownlinkPwrOff : public TcBase { + public: + TcDownlinkPwrOff(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_DOWNLINK_PWR_OFF, sequenceCount) {} +}; + +/** + * @brief This class helps to build the replay start command. + */ +class TcReplayWriteSeq : public TcBase { + public: + /** + * @brief Constructor + */ + TcReplayWriteSeq(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_REPLAY_WRITE_SEQUENCE, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + spParams.setFullPayloadLen(commandDataLen + sizeof(NULL_TERMINATOR) + CRC_SIZE); + result = lengthCheck(commandDataLen); + if (result != returnvalue::OK) { + return result; + } + std::memcpy(payloadStart, commandData, commandDataLen); + *(payloadStart + commandDataLen) = NULL_TERMINATOR; + return result; + } + + private: + static const size_t USE_DECODING_LENGTH = 1; + + ReturnValue_t lengthCheck(size_t commandDataLen) { + if (commandDataLen > USE_DECODING_LENGTH + FILENAME_FIELD_SIZE or + checkPayloadLen() != returnvalue::OK) { + sif::warning << "TcReplayWriteSeq: Command has invalid length " << commandDataLen + << std::endl; + return INVALID_LENGTH; + } + return returnvalue::OK; + } +}; + +/** + * @brief Helps to extract the fields of the flash write command from the PUS packet. + */ +class FlashBasePusCmd { + public: + FlashBasePusCmd() = default; + virtual ~FlashBasePusCmd() = default; + + virtual ReturnValue_t extractFields(const uint8_t* commandData, size_t commandDataLen) { + if (commandDataLen > FILENAME_FIELD_SIZE) { + return INVALID_LENGTH; + } + size_t fileLen = strnlen(reinterpret_cast(commandData), commandDataLen); + if (fileLen > MAX_FILENAME_SIZE) { + return FILENAME_TOO_LONG; + } + obcFile = std::string(reinterpret_cast(commandData), fileLen); + fileLen = + strnlen(reinterpret_cast(commandData + obcFile.size() + SIZE_NULL_TERMINATOR), + commandDataLen - obcFile.size() - 1); + if (fileLen > FILENAME_FIELD_SIZE) { + return MPSOC_FILENAME_TOO_LONG; + } + mpsocFile = std::string( + reinterpret_cast(commandData + obcFile.size() + SIZE_NULL_TERMINATOR), + fileLen); + return returnvalue::OK; + } + + const std::string& getObcFile() const { return obcFile; } + + const std::string& getMpsocFile() const { return mpsocFile; } + + protected: + size_t getParsedSize() const { + return getObcFile().size() + getMpsocFile().size() + 2 * SIZE_NULL_TERMINATOR; + } + static const size_t SIZE_NULL_TERMINATOR = 1; + + private: + std::string obcFile; + std::string mpsocFile; +}; + +class FlashReadPusCmd : public FlashBasePusCmd { + public: + FlashReadPusCmd() {}; + + ReturnValue_t extractFields(const uint8_t* commandData, size_t commandDataLen) override { + ReturnValue_t result = FlashBasePusCmd::extractFields(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + if (commandDataLen < (getParsedSize() + 4)) { + return returnvalue::FAILED; + } + size_t deserDummy = 4; + return SerializeAdapter::deSerialize(&readSize, commandData + getParsedSize(), &deserDummy, + SerializeIF::Endianness::NETWORK); + } + + size_t getReadSize() const { return readSize; } + + private: + size_t readSize = 0; +}; +/** + * @brief Class to build replay stop space packet. + */ +class TcModeReplay : public TcBase { + public: + TcModeReplay(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_MODE_REPLAY, sequenceCount) {} +}; + +/** + * @brief Class to build mode idle command + */ +class TcModeIdle : public TcBase { + public: + TcModeIdle(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_MODE_IDLE, sequenceCount) {} +}; + +/** + * @brief Class to build mode idle command + */ +class TcModeSnapshot : public TcBase { + public: + TcModeSnapshot(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_MODE_SNAPSHOT, sequenceCount) {} +}; + +/** + * @brief Class to build camera take picture command + */ +class TcCamTakePic : public TcBase { + public: + TcCamTakePic(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_CAM_TAKE_PIC, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + const uint8_t** dataPtr = &commandData; + if (commandDataLen > FULL_PAYLOAD_SIZE) { + return INVALID_LENGTH; + } + size_t deserLen = commandDataLen; + size_t serLen = 0; + fileName = std::string(reinterpret_cast(commandData)); + if (fileName.size() > MAX_FILENAME_SIZE) { + return FILENAME_TOO_LONG; + } + deserLen -= fileName.length() + 1; + *dataPtr += fileName.length() + 1; + uint8_t** payloadPtr = &payloadStart; + memset(payloadStart, 0, FILENAME_FIELD_SIZE); + memcpy(payloadStart, fileName.data(), fileName.size()); + *payloadPtr += FILENAME_FIELD_SIZE; + serLen += FILENAME_FIELD_SIZE; + ReturnValue_t result = SerializeAdapter::deSerialize(&encoderSettingY, dataPtr, &deserLen, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::serialize(&encoderSettingY, payloadPtr, &serLen, FULL_PAYLOAD_SIZE, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + + result = SerializeAdapter::deSerialize(&quantizationY, dataPtr, &deserLen, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::serialize(&quantizationY, payloadPtr, &serLen, FULL_PAYLOAD_SIZE, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + + result = SerializeAdapter::deSerialize(&encoderSettingsCb, dataPtr, &deserLen, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::serialize(&encoderSettingsCb, payloadPtr, &serLen, FULL_PAYLOAD_SIZE, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + + result = SerializeAdapter::deSerialize(&quantizationCb, dataPtr, &deserLen, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::serialize(&quantizationCb, payloadPtr, &serLen, FULL_PAYLOAD_SIZE, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + + result = SerializeAdapter::deSerialize(&encoderSettingsCr, dataPtr, &deserLen, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::serialize(&encoderSettingsCr, payloadPtr, &serLen, FULL_PAYLOAD_SIZE, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + + result = SerializeAdapter::deSerialize(&quantizationCr, dataPtr, &deserLen, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::serialize(&quantizationCr, payloadPtr, &serLen, FULL_PAYLOAD_SIZE, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + + result = SerializeAdapter::deSerialize(&bypassCompressor, dataPtr, &deserLen, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::serialize(&bypassCompressor, payloadPtr, &serLen, FULL_PAYLOAD_SIZE, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + + spParams.setFullPayloadLen(FULL_PAYLOAD_SIZE + CRC_SIZE); + return returnvalue::OK; + } + + std::string fileName; + uint8_t encoderSettingY = 7; + uint64_t quantizationY = 0; + uint8_t encoderSettingsCb = 7; + uint64_t quantizationCb = 0; + uint8_t encoderSettingsCr = 7; + uint64_t quantizationCr = 0; + uint8_t bypassCompressor = 0; + + private: + static const size_t PARAMETER_SIZE = 28; + static constexpr size_t FULL_PAYLOAD_SIZE = FILENAME_FIELD_SIZE + PARAMETER_SIZE; +}; + +/** + * @brief Class to build simplex send file command + */ +class TcSimplexStreamFile : public TcBase { + public: + TcSimplexStreamFile(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_SIMPLEX_SEND_FILE, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + if (commandDataLen > MAX_FILENAME_SIZE) { + return INVALID_LENGTH; + } + std::string fileName(reinterpret_cast(commandData)); + if (fileName.size() > MAX_FILENAME_SIZE) { + return FILENAME_TOO_LONG; + } + + std::memset(payloadStart, 0, FILENAME_FIELD_SIZE); + std::memcpy(payloadStart, fileName.data(), fileName.length()); + payloadStart[fileName.length()] = 0; + spParams.setFullPayloadLen(FILENAME_FIELD_SIZE + CRC_SIZE); + ; + return returnvalue::OK; + } + + private: +}; + +/** + * @brief Class to build simplex send file command + */ +class TcSplitFile : public TcBase { + public: + TcSplitFile(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_SIMPLEX_SEND_FILE, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + if (commandDataLen < MIN_DATA_LENGTH) { + return INVALID_LENGTH; + } + if (commandDataLen > MAX_DATA_LENGTH) { + return INVALID_LENGTH; + } + const uint8_t** dataPtr = &commandData; + ReturnValue_t result = SerializeAdapter::deSerialize(&chunkParameter, dataPtr, &commandDataLen, + SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + /// No chunks makes no sense, and DIV str can not be longer than whats representable with 3 + /// decimal digits. + if (chunkParameter == 0 or chunkParameter > 999) { + return INVALID_PARAMETER; + } + std::string fileName(reinterpret_cast(*dataPtr)); + if (fileName.size() > MAX_FILENAME_SIZE) { + return FILENAME_TOO_LONG; + } + char divStr[16]{}; + sprintf(divStr, "DIV%03u", chunkParameter); + std::memcpy(payloadStart, divStr, DIV_STR_LEN); + payloadStart[DIV_STR_LEN] = 0; + std::memset(payloadStart + DIV_STR_LEN + 1, 0, FILENAME_FIELD_SIZE - DIV_STR_LEN - 1); + std::memcpy(payloadStart + DIV_STR_LEN + 1, fileName.data(), fileName.length()); + spParams.setFullPayloadLen(FILENAME_FIELD_SIZE + CRC_SIZE); + return returnvalue::OK; + } + + private: + uint32_t chunkParameter = 0; + static constexpr size_t MIN_DATA_LENGTH = 4; + static constexpr size_t DIV_STR_LEN = 6; + static constexpr size_t MAX_DATA_LENGTH = 4 + MAX_FILENAME_SIZE; +}; + +/** + * @brief Class to build downlink data modulate command + */ +class TcDownlinkDataModulate : public TcBase { + public: + TcDownlinkDataModulate(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_DOWNLINK_DATA_MODULATE, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + if (commandDataLen > MAX_DATA_LENGTH) { + return INVALID_LENGTH; + } + spParams.setFullPayloadLen(commandDataLen + CRC_SIZE); + std::memcpy(payloadStart, commandData, commandDataLen); + return returnvalue::OK; + } + + private: + static const size_t MAX_DATA_LENGTH = 11; +}; + +class TcCamcmdSend : public TcBase { + public: + TcCamcmdSend(ploc::SpTcParams params, uint16_t sequenceCount) + : TcBase(params, apid::TC_CAM_CMD_SEND, sequenceCount) {} + + ReturnValue_t setPayload(const uint8_t* commandData, size_t commandDataLen) { + if (commandDataLen > MAX_DATA_LENGTH) { + return INVALID_LENGTH; + } + uint16_t dataLen = static_cast(commandDataLen + sizeof(CARRIAGE_RETURN)); + spParams.setFullPayloadLen(sizeof(dataLen) + commandDataLen + sizeof(CARRIAGE_RETURN) + + CRC_SIZE); + auto res = checkPayloadLen(); + if (res != returnvalue::OK) { + return res; + } + size_t size = ccsds::HEADER_LEN; + SerializeAdapter::serialize(&dataLen, payloadStart, &size, spParams.maxSize, + SerializeIF::Endianness::BIG); + std::memcpy(payloadStart + sizeof(dataLen), commandData, commandDataLen); + *(payloadStart + sizeof(dataLen) + commandDataLen) = CARRIAGE_RETURN; + + return returnvalue::OK; + } + + private: + static const uint8_t MAX_DATA_LENGTH = 10; + static const uint8_t CARRIAGE_RETURN = 0xD; +}; + +class HkReport : public StaticLocalDataSet<36> { + public: + HkReport(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, HK_SET_ID) {} + + HkReport(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, HK_SET_ID)) {} + + lp_var_t status = lp_var_t(sid.objectId, mpsoc::poolid::STATUS, this); + lp_var_t mode = lp_var_t(sid.objectId, mpsoc::poolid::MODE, this); + lp_var_t downlinkPwrOn = + lp_var_t(sid.objectId, mpsoc::poolid::DOWNLINK_PWR_ON, this); + lp_var_t downlinkReplyActive = + lp_var_t(sid.objectId, mpsoc::poolid::DOWNLINK_REPLY_ACTIIVE, this); + lp_var_t downlinkJesdSyncStatus = + lp_var_t(sid.objectId, mpsoc::poolid::DOWNLINK_JESD_SYNC_STATUS, this); + lp_var_t downlinkDacStatus = + lp_var_t(sid.objectId, mpsoc::poolid::DOWNLINK_DAC_STATUS, this); + lp_var_t camStatus = lp_var_t(sid.objectId, mpsoc::poolid::CAM_STATUS, this); + lp_var_t camSdiStatus = + lp_var_t(sid.objectId, mpsoc::poolid::CAM_SDI_STATUS, this); + lp_var_t camFpgaTemp = lp_var_t(sid.objectId, mpsoc::poolid::CAM_FPGA_TEMP, this); + lp_var_t camSocTemp = lp_var_t(sid.objectId, mpsoc::poolid::CAM_SOC_TEMP, this); + lp_var_t sysmonTemp = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_TEMP, this); + lp_var_t sysmonVccInt = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCCINT, this); + lp_var_t sysmonVccAux = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCCAUX, this); + lp_var_t sysmonVccBram = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCCBRAM, this); + lp_var_t sysmonVccPaux = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCCPAUX, this); + lp_var_t sysmonVccPint = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCCPINT, this); + lp_var_t sysmonVccPdro = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCCPDRO, this); + lp_var_t sysmonMb12V = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_MB12V, this); + lp_var_t sysmonMb3V3 = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_MB3V3, this); + lp_var_t sysmonMb1V8 = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_MB1V8, this); + lp_var_t sysmonVcc12V = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCC12V, this); + lp_var_t sysmonVcc5V = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCC5V, this); + lp_var_t sysmonVcc3V3 = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCC3V3, this); + lp_var_t sysmonVcc3V3VA = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCC3V3VA, this); + lp_var_t sysmonVcc2V5DDR = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCC2V5DDR, this); + lp_var_t sysmonVcc1V2DDR = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCC1V2DDR, this); + lp_var_t sysmonVcc0V9 = lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCC0V9, this); + lp_var_t sysmonVcc0V6VTT = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_VCC0V6VTT, this); + lp_var_t sysmonSafeCotsCur = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_SAFE_COTS_CUR, this); + lp_var_t sysmonNvm4XoCur = + lp_var_t(sid.objectId, mpsoc::poolid::SYSMON_NVM4_XO_CUR, this); + lp_var_t semUncorrectableErrs = + lp_var_t(sid.objectId, mpsoc::poolid::SEM_UNCORRECTABLE_ERRS, this); + lp_var_t semCorrectableErrs = + lp_var_t(sid.objectId, mpsoc::poolid::SEM_CORRECTABLE_ERRS, this); + lp_var_t semStatus = lp_var_t(sid.objectId, mpsoc::poolid::SEM_STATUS, this); + lp_var_t rebootMpsocRequired = + lp_var_t(sid.objectId, mpsoc::poolid::REBOOT_MPSOC_REQUIRED, this); +}; + +uint16_t getStatusFromRawData(const uint8_t* data); +std::string getStatusString(uint16_t status); + +} // namespace mpsoc + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_PLOCMPSOCDEFINITIONS_H_ */ diff --git a/linux/payload/plocSupvDefs.h b/linux/payload/plocSupvDefs.h new file mode 100644 index 0000000..340da81 --- /dev/null +++ b/linux/payload/plocSupvDefs.h @@ -0,0 +1,2089 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_PLOCSVPDEFINITIONS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_PLOCSVPDEFINITIONS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "eive/eventSubsystemIds.h" +#include "eive/resultClassIds.h" + +namespace supv { + +static constexpr bool DEBUG_PLOC_SUPV = false; +static constexpr bool REDUCE_NORMAL_MODE_PRINTOUT = true; + +static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_SUPERVISOR_HANDLER; + +//! [EXPORT] : [COMMENT] PLOC supervisor crc failure in telemetry packet +static const Event SUPV_MEMORY_READ_RPT_CRC_FAILURE = MAKE_EVENT(1, severity::LOW); +//! [EXPORT] : [COMMENT] Unhandled event. P1: APID, P2: Service ID +static constexpr Event SUPV_UNKNOWN_TM = MAKE_EVENT(2, severity::LOW); +static constexpr Event SUPV_UNINIMPLEMENTED_TM = MAKE_EVENT(3, severity::LOW); +//! [EXPORT] : [COMMENT] PLOC supervisor received acknowledgment failure report +static const Event SUPV_ACK_FAILURE = MAKE_EVENT(4, severity::LOW); +//! [EXPORT] : [COMMENT] PLOC received execution failure report +//! P1: ID of command for which the execution failed +//! P2: Status code sent by the supervisor handler +static const Event SUPV_EXE_FAILURE = MAKE_EVENT(5, severity::LOW); +//! [EXPORT] : [COMMENT] PLOC supervisor reply has invalid crc +static const Event SUPV_CRC_FAILURE_EVENT = MAKE_EVENT(6, severity::LOW); +//! [EXPORT] : [COMMENT] Supervisor helper currently executing a command +static const Event SUPV_HELPER_EXECUTING = MAKE_EVENT(7, severity::LOW); +//! [EXPORT] : [COMMENT] Failed to build the command to shutdown the MPSoC +static const Event SUPV_MPSOC_SHUTDOWN_BUILD_FAILED = MAKE_EVENT(8, severity::LOW); +//! [EXPORT] : [COMMENT] Received ACK, but no related command is unknown or has not been sent +// by this software instance. P1: Module APID. P2: Service ID. +static const Event SUPV_ACK_UNKNOWN_COMMAND = MAKE_EVENT(9, severity::LOW); +//! [EXPORT] : [COMMENT] Received ACK EXE, but no related command is unknown or has not been sent +// by this software instance. P1: Module APID. P2: Service ID. +static const Event SUPV_EXE_ACK_UNKNOWN_COMMAND = MAKE_EVENT(10, severity::LOW); + +extern std::atomic_bool SUPV_ON; +static constexpr uint32_t INTER_COMMAND_DELAY = 20; +static constexpr uint32_t BOOT_TIMEOUT_MS = 4000; +static constexpr uint32_t MAX_TRANSITION_TIME_TO_ON_MS = BOOT_TIMEOUT_MS + 3000; +static constexpr uint32_t MAX_TRANSITION_TIME_TO_OFF_MS = 1000; + +namespace result { +static const uint8_t INTERFACE_ID = CLASS_ID::SUPV_RETURN_VALUES_IF; + +//! [EXPORT] : [COMMENT] Space Packet received from PLOC supervisor has invalid CRC +static const ReturnValue_t CRC_FAILURE = MAKE_RETURN_CODE(0xA0); +static constexpr ReturnValue_t INVALID_SERVICE_ID = MAKE_RETURN_CODE(0xA1); +//! [EXPORT] : [COMMENT] Received ACK failure reply from PLOC supervisor +static const ReturnValue_t RECEIVED_ACK_FAILURE = MAKE_RETURN_CODE(0xA2); +//! [EXPORT] : [COMMENT] Received execution failure reply from PLOC supervisor +static const ReturnValue_t RECEIVED_EXE_FAILURE = MAKE_RETURN_CODE(0xA3); +//! [EXPORT] : [COMMENT] Received space packet with invalid APID from PLOC supervisor +static const ReturnValue_t INVALID_APID = MAKE_RETURN_CODE(0xA4); +//! [EXPORT] : [COMMENT] Failed to read current system time +static const ReturnValue_t GET_TIME_FAILURE = MAKE_RETURN_CODE(0xA5); +//! [EXPORT] : [COMMENT] Received command with invalid watchdog parameter. Valid watchdogs are 0 +//! for PS, 1 for PL and 2 for INT +static const ReturnValue_t INVALID_WATCHDOG = MAKE_RETURN_CODE(0xA6); +//! [EXPORT] : [COMMENT] Received watchdog timeout config command with invalid timeout. Valid +//! timeouts must be in the range between 1000 and 360000 ms. +static const ReturnValue_t INVALID_WATCHDOG_TIMEOUT = MAKE_RETURN_CODE(0xA7); +//! [EXPORT] : [COMMENT] Received latchup config command with invalid latchup ID +static const ReturnValue_t INVALID_LATCHUP_ID = MAKE_RETURN_CODE(0xA8); +//! [EXPORT] : [COMMENT] Received set adc sweep period command with invalid sweep period. Must be +//! larger than 21. +static const ReturnValue_t SWEEP_PERIOD_TOO_SMALL = MAKE_RETURN_CODE(0xA9); +//! [EXPORT] : [COMMENT] Receive auto EM test command with invalid test param. Valid params are 1 +//! and 2. +static const ReturnValue_t INVALID_TEST_PARAM = MAKE_RETURN_CODE(0xAA); +//! [EXPORT] : [COMMENT] Returned when scanning for MRAM dump packets failed. +static const ReturnValue_t MRAM_PACKET_PARSING_FAILURE = MAKE_RETURN_CODE(0xAB); +//! [EXPORT] : [COMMENT] Returned when the start and stop addresses of the MRAM dump or MRAM wipe +//! commands are invalid (e.g. start address bigger than stop address) +static const ReturnValue_t INVALID_MRAM_ADDRESSES = MAKE_RETURN_CODE(0xAC); +//! [EXPORT] : [COMMENT] Expect reception of an MRAM dump packet but received space packet with +//! other apid. +static const ReturnValue_t NO_MRAM_PACKET = MAKE_RETURN_CODE(0xAD); +//! [EXPORT] : [COMMENT] Path to PLOC directory on SD card does not exist +static const ReturnValue_t PATH_DOES_NOT_EXIST = MAKE_RETURN_CODE(0xAE); +//! [EXPORT] : [COMMENT] MRAM dump file does not exists. The file should actually already have +//! been created with the reception of the first dump packet. +static const ReturnValue_t MRAM_FILE_NOT_EXISTS = MAKE_RETURN_CODE(0xAF); +static constexpr ReturnValue_t INVALID_REPLY_LENGTH = MAKE_RETURN_CODE(0xB0); +//! [EXPORT] : [COMMENT] Received action command has invalid length +static const ReturnValue_t INVALID_LENGTH = MAKE_RETURN_CODE(0xB1); +//! [EXPORT] : [COMMENT] Filename too long +static const ReturnValue_t FILENAME_TOO_LONG = MAKE_RETURN_CODE(0xB2); +//! [EXPORT] : [COMMENT] Received update status report with invalid packet length field +static const ReturnValue_t UPDATE_STATUS_REPORT_INVALID_LENGTH = MAKE_RETURN_CODE(0xB3); +//! [EXPORT] : [COMMENT] Update status report does not contain expected CRC. There might be a bit +//! flip in the update memory region. +static const ReturnValue_t UPDATE_CRC_FAILURE = MAKE_RETURN_CODE(0xB4); +//! [EXPORT] : [COMMENT] Supervisor helper task ist currently executing a command (wait until +//! helper tas has finished or interrupt by sending the terminate command) +static const ReturnValue_t SUPV_HELPER_EXECUTING = MAKE_RETURN_CODE(0xB5); + +static constexpr ReturnValue_t BUF_TOO_SMALL = MAKE_RETURN_CODE(0xC0); +static constexpr ReturnValue_t NO_REPLY_TIMEOUT = MAKE_RETURN_CODE(0xC1); + +}; // namespace result + +static constexpr uint16_t DEFAULT_SEQ_COUNT = 0; + +/** Command IDs */ +static const DeviceCommandId_t NONE = 0; +static const DeviceCommandId_t GET_HK_REPORT = 1; +static const DeviceCommandId_t START_MPSOC = 3; +static const DeviceCommandId_t SHUTDOWN_MPSOC = 4; +static const DeviceCommandId_t SEL_MPSOC_BOOT_IMAGE = 5; +static const DeviceCommandId_t SET_BOOT_TIMEOUT = 6; +static const DeviceCommandId_t SET_MAX_RESTART_TRIES = 7; +static const DeviceCommandId_t RESET_MPSOC = 8; +static const DeviceCommandId_t SET_TIME_REF = 9; +static const DeviceCommandId_t DISABLE_PERIOIC_HK_TRANSMISSION = 10; +static const DeviceCommandId_t GET_BOOT_STATUS_REPORT = 11; +static const DeviceCommandId_t ENABLE_LATCHUP_ALERT = 15; +static const DeviceCommandId_t DISABLE_LATCHUP_ALERT = 16; +static const DeviceCommandId_t SET_ALERT_LIMIT = 18; +static const DeviceCommandId_t SET_ADC_ENABLED_CHANNELS = 21; +static const DeviceCommandId_t SET_ADC_WINDOW_AND_STRIDE = 22; +static const DeviceCommandId_t SET_ADC_THRESHOLD = 23; +static const DeviceCommandId_t GET_LATCHUP_STATUS_REPORT = 24; +static const DeviceCommandId_t COPY_ADC_DATA_TO_MRAM = 25; +static const DeviceCommandId_t RUN_AUTO_EM_TESTS = 28; +static const DeviceCommandId_t WIPE_MRAM = 29; +static const DeviceCommandId_t FIRST_MRAM_DUMP = 30; +static const DeviceCommandId_t SET_GPIO = 34; +static const DeviceCommandId_t READ_GPIO = 35; +static const DeviceCommandId_t RESTART_SUPERVISOR = 36; +static const DeviceCommandId_t REQUEST_LOGGING_COUNTERS = 38; +static constexpr DeviceCommandId_t FACTORY_RESET = 39; +static const DeviceCommandId_t CONSECUTIVE_MRAM_DUMP = 43; +static const DeviceCommandId_t START_MPSOC_QUIET = 45; +static const DeviceCommandId_t SET_SHUTDOWN_TIMEOUT = 46; +static const DeviceCommandId_t FACTORY_FLASH = 47; +static const DeviceCommandId_t PERFORM_UPDATE = 48; +static const DeviceCommandId_t TERMINATE_SUPV_HELPER = 49; +static const DeviceCommandId_t ENABLE_AUTO_TM = 50; +static const DeviceCommandId_t DISABLE_AUTO_TM = 51; +static const DeviceCommandId_t LOGGING_REQUEST_EVENT_BUFFERS = 54; +static const DeviceCommandId_t LOGGING_CLEAR_COUNTERS = 55; +static const DeviceCommandId_t LOGGING_SET_TOPIC = 56; +static constexpr DeviceCommandId_t REQUEST_ADC_REPORT = 57; +static const DeviceCommandId_t RESET_PL = 58; +static const DeviceCommandId_t ENABLE_NVMS = 59; +static const DeviceCommandId_t CONTINUE_UPDATE = 60; +static const DeviceCommandId_t MEMORY_CHECK_WITH_FILE = 61; +static constexpr DeviceCommandId_t MEMORY_CHECK = 62; +static constexpr DeviceCommandId_t ABORT_LONGER_REQUEST = 63; + +/** Reply IDs */ +enum ReplyId : DeviceCommandId_t { + ACK_REPORT = 100, + EXE_REPORT = 101, + HK_REPORT = 102, + BOOT_STATUS_REPORT = 103, + LATCHUP_REPORT = 104, + COUNTERS_REPORT = 105, + ADC_REPORT = 106, + UPDATE_STATUS_REPORT = 107, +}; + +// Size of complete space packet (6 byte header + size of data + 2 byte CRC) +static const uint16_t SIZE_ACK_REPORT = 14; +static const uint16_t SIZE_EXE_REPORT = 14; +static const uint16_t SIZE_BOOT_STATUS_REPORT = 24; +static const uint16_t SIZE_LATCHUP_STATUS_REPORT = 31; +static const uint16_t SIZE_COUNTERS_REPORT = 120; +static const uint16_t SIZE_ADC_REPORT = 72; + +// 2 bits APID SRC, 00 for OBC, 2 bits APID DEST, 01 for SUPV, 7 bits CMD ID -> Mask 0x080 +static constexpr uint16_t APID_TC_SUPV_MASK = 0x080; + +enum Apid { + TMTC_MAN = 0x00, + HK = 0x01, + BOOT_MAN = 0x02, + LATCHUP_MON = 0x03, + ADC_MON = 0x04, + MEM_MAN = 0x05, + DATA_LOGGER = 0x06, + WDOG_MAN = 0x07 +}; + +namespace tc { + +enum class HkId : uint8_t { + ENABLE = 0x01, + SET_PERIOD = 0x02, + GET_REPORT = 0x03, + GET_HARDFAULTS_REPORT = 0x04, +}; + +enum class TmtcId : uint8_t { + TIME_REF = 0x03, + GET_SUPV_VERSION = 0x05, + RUN_AUTO_EM_TEST = 0x08, + SET_GPIO = 0x0E, + READ_GPIO = 0x0F, + GET_MPSOC_POWER_INFO = 0x10 +}; + +enum class BootManId : uint8_t { + START_MPSOC = 0x01, + SHUTDOWN_MPSOC = 0x02, + SELECT_IMAGE = 0x03, + SET_BOOT_TIMEOUT = 0x04, + SET_MAX_REBOOT_TRIES = 0x05, + RESET_MPSOC = 0x06, + RESET_PL = 0x07, + GET_BOOT_STATUS_REPORT = 0x08, + PREPARE_UPDATE = 0x09, + SHUTDOWN_TIMEOUT = 0x0B, + FACTORY_FLASH = 0x0C +}; + +enum class LatchupMonId : uint8_t { + ENABLE = 0x01, + DISABLE = 0x02, + SET_ALERT_LIMIT = 0x04, + GET_STATUS_REPORT = 0x06 +}; + +// Right now, none of the commands seem to be implemented, but still +// keep the enum here in case some are added +enum class AdcMonId : uint8_t { + SET_SWEEP_PERIOD = 0x01, + SET_ENABLED_CHANNELS = 0x02, + SET_WINDOW_STRIDE = 0x03, + SET_ADC_THRESHOLD = 0x04, + COPY_ADC_DATA_TO_MRAM = 0x05, + REQUEST_ADC_SAMPLE = 0x06 +}; + +enum class MemManId : uint8_t { ERASE = 0x01, WRITE = 0x02, CHECK = 0x03 }; + +enum class DataLoggerServiceId : uint8_t { + // Not implemented. + READ_MRAM_CFG_DATA_LOGGER = 0x00, + REQUEST_COUNTERS = 0x01, + // Not implemented. + EVENT_BUFFER_DOWNLOAD = 0x02, + WIPE_MRAM = 0x05, + DUMP_MRAM = 0x06, + FACTORY_RESET = 0x07 +}; + +// Right now, none of the commands seem to be implemented, but still +// keep the enum here in case some are added +enum class WdogManServiceId : uint8_t {}; + +} // namespace tc + +namespace tm { + +enum class TmtcId : uint8_t { ACK = 0x01, NAK = 0x02, EXEC_ACK = 0x03, EXEC_NAK = 0x04 }; + +enum class HkId : uint8_t { REPORT = 0x01, HARDFAULTS = 0x02 }; + +enum class BootManId : uint8_t { BOOT_STATUS_REPORT = 0x01 }; +enum class AdcMonId : uint8_t { ADC_REPORT = 0x01 }; + +enum class MemManId : uint8_t { UPDATE_STATUS_REPORT = 0x01 }; + +enum class LatchupMonId : uint8_t { LATCHUP_STATUS_REPORT = 0x01 }; +enum class DataLoggerId : uint8_t { COUNTERS_REPORT = 0x01 }; + +} // namespace tm + +enum class GeneralStatusCode : uint32_t { + OK = 0x000, + NAK = 0x001, + INIT_ERROR = 0x002, + BAD_PARAM = 0x003, + NOT_INITIALIZED = 0x004, + BAD_PERIPH_ID = 0x005, + TIMEOUT = 0x006, + RX_ERROR = 0x007, + TX_ERROR = 0x008, + ARB_LOST = 0x009, + BUSY = 0x00A, + NOT_IMPL = 0x00B, + ALIGNMENT_ERROR = 0x00C, + PERIPH_ERROR = 0x00D, + FAILED_LATCH = 0x00E, + GPIO_HIGH = 0x00F, + GPIO_LOW = 0x010, + TEST_PASSED = 0x011, + TEST_FAILED = 0x012, + BAD_NOF_PARAMS = 0x013, + NULL_POINTER = 0x014, + TASK_CREATION_ERROR = 0x015, + CORRUPTED_MRAM_VAL = 0x016, + BUF_EMPTY = 0x017 +}; + +enum class BootManStatusCode : uint32_t { + NOTHING_TODO = 0x100, + POWER_FAULT = 0x101, + INVALID_LENGTH = 0x102, + OUT_OF_RANGE = 0x103, + OUT_OF_HEAP_MEMORY = 0x104, + INVALID_STATE_TRANSITION = 0x105, + MPSOC_ALREADY_BOOTING = 0x106, + MPSOC_ALREADY_OPERATIONAL = 0x107, + MPSOC_BOOT_FAILED = 0x108, +}; + +enum class MemManStatusCode : uint32_t { + SP_NOT_AVAILABLE = 0x200, + SP_DATA_INSUFFICIENT = 0x201, + SP_MEMORY_ID_INVALID = 0x202, + MPSOC_NOT_IN_RESET = 0x203, + FLASH_INIT_FAILED = 0x204, + FLASH_ERASE_FAILED = 0x205, + FLASH_WRITE_FAILED = 0x206, + FLASH_VERIFY_FAILED = 0x207, + CANNOT_ACCESS_TM = 0x208, + CANNOT_SEND_TM = 0x209, +}; + +enum class PowerManStatusCode : uint32_t { + PG_LOW = 0x300, + PG_5V_LOW = 0x301, + PG_0V85_LOW = 0x302, + PG_1V8_LOW = 0x303, + PG_MISC_LOW = 0x304, + PG_3V3_LOW = 0x305, + PG_MB_VAIO_LOW = 0x306, + PG_MB_MPSOCIO_LOW = 0x307 +}; + +enum class TmtcManStatusCode : uint32_t { + BUF_FULL = 0x600, + WRONG_APID = 0x601, + WRONG_SERVICE_ID = 0x602, + TC_DELIVERY_ACCEPTED = 0x603, + TC_DELIVERY_REJECTED = 0x0604, + TC_PACKET_LEN_INCORRECT = 0x605, + BAD_CRC = 0x606, + BAD_DEST = 0x607, + BAD_SP_HEADER = 0x608 +}; + +static constexpr uint16_t APID_MASK_TC = 0x80; +static constexpr uint16_t APID_MASK_TM = 0x200; +static constexpr uint16_t APID_MODULE_MASK = 0x7F; +static const uint16_t SEQUENCE_COUNT_MASK = 0xFFF; + +static const uint8_t HK_SET_ENTRIES = 13; +static const uint8_t BOOT_REPORT_SET_ENTRIES = 10; +static const uint8_t LATCHUP_RPT_SET_ENTRIES = 16; +static const uint8_t LOGGING_RPT_SET_ENTRIES = 30; +static const uint8_t ADC_RPT_SET_ENTRIES = 32; + +static const uint32_t HK_SET_ID = HK_REPORT; +static const uint32_t BOOT_REPORT_SET_ID = BOOT_STATUS_REPORT; +static const uint32_t LATCHUP_RPT_ID = LATCHUP_REPORT; +static const uint32_t LOGGING_RPT_ID = COUNTERS_REPORT; +static const uint32_t ADC_REPORT_SET_ID = ADC_REPORT; + +namespace timeout { +// Erase memory can require up to 60 seconds for execution +static const uint32_t ERASE_MEMORY = 60000; +static const uint32_t UPDATE_STATUS_REPORT = 70000; +static const uint32_t CRC_EXECUTION_TIMEOUT = 60000; +} // namespace timeout + +static constexpr size_t TIMESTAMP_LEN = 7; +static constexpr size_t SECONDARY_HEADER_LEN = TIMESTAMP_LEN + 1; +static constexpr size_t CRC_LEN = 2; + +/** This is the maximum length of a space packet as defined by the TAS ICD */ +static const size_t MAX_COMMAND_SIZE = 1024; +static const size_t MAX_DATA_CAPACITY = 1016; +/** This is the maximum size of a space packet for the supervisor */ +static const size_t MAX_PACKET_SIZE = 1024; + +static constexpr size_t MIN_PAYLOAD_LEN = SECONDARY_HEADER_LEN + CRC_LEN; +static constexpr size_t MIN_TMTC_LEN = ccsds::HEADER_LEN + MIN_PAYLOAD_LEN; +static constexpr size_t PAYLOAD_OFFSET = ccsds::HEADER_LEN + SECONDARY_HEADER_LEN; + +enum class FactoryResetSelect : uint8_t { + EVENT_BUF = 0x00, + ADC_BUF = 0x01, + SYS_CFG = 0x02, + DEBUG_CFG = 0x03, + BOOT_MAN_CFG = 0x04, + DATA_LOGGER_CFG = 0x05, + DATA_LOGGER_OP_DATA = 0x06, + LATCHUP_MON_CFG = 0x07, + ADC_MON_CFG = 0x08, + WDOG_MAN_CFG = 0x09, + HK_CFG = 0x0A, + MEM_MAN_CFG = 0xB9 +}; + +struct UpdateParams { + std::string file; + uint8_t memId; + uint32_t startAddr; + uint32_t bytesWritten; + uint16_t seqCount; + bool deleteMemory; +}; + +enum PoolIds : lp_id_t { + NUM_TMS, + TEMP_PS, + TEMP_PL, + HK_SOC_STATE, + NVM0_1_STATE, + NVM3_STATE, + MISSION_IO_STATE, + FMC_STATE, + NUM_TCS, + TEMP_SUP, + UPTIME, + CPULOAD, + AVAILABLEHEAP, + BR_SOC_STATE, + POWER_CYCLES, + BOOT_AFTER_MS, + BOOT_TIMEOUT_POOL_VAR_MS, + ACTIVE_NVM, + BP0_STATE, + BP1_STATE, + BP2_STATE, + BOOT_STATE, + BOOT_CYCLES, + + LATCHUP_ID, + CNT0, + CNT1, + CNT2, + CNT3, + CNT4, + CNT5, + CNT6, + LATCHUP_RPT_TIME_SEC, + LATCHUP_RPT_TIME_MIN, + LATCHUP_RPT_TIME_HOUR, + LATCHUP_RPT_TIME_DAY, + LATCHUP_RPT_TIME_MON, + LATCHUP_RPT_TIME_YEAR, + LATCHUP_RPT_TIME_MSEC, + LATCHUP_RPT_IS_SET, + + SIGNATURE, + LATCHUP_HAPPENED_CNTS, + ADC_DEVIATION_TRIGGERS_CNT, + TC_RECEIVED_CNT, + TM_AVAILABLE_CNT, + SUPERVISOR_BOOTS, + MPSOC_BOOTS, + MPSOC_BOOT_FAILED_ATTEMPTS, + MPSOC_POWER_UP, + MPSOC_UPDATES, + MPSOC_HEARTBEAT_RESETS, + CPU_WDT_RESETS, + PS_HEARTBEATS_LOST, + PL_HEARTBEATS_LOST, + EB_TASK_LOST, + BM_TASK_LOST, + LM_TASK_LOST, + AM_TASK_LOST, + TCTMM_TASK_LOST, + MM_TASK_LOST, + HK_TASK_LOST, + DL_TASK_LOST, + RWS_TASKS_LOST, + + ADC_RAW, + ADC_ENG, +}; + +struct TcParams : public ploc::SpTcParams { + public: + TcParams(SpacePacketCreator& creator) : ploc::SpTcParams(creator) {} + + TcParams(SpacePacketCreator& creator, uint8_t* buf, size_t maxSize) + : ploc::SpTcParams(creator, buf, maxSize) {} + + void setLenFromPayloadLen(size_t payloadLen) { + setFullPayloadLen(ccsds::HEADER_LEN + SECONDARY_HEADER_LEN + payloadLen + CRC_LEN); + } +}; + +class TcBase : public ploc::SpTcBase { + public: + TcBase(TcParams params) : TcBase(params, 0x00, 0x00, MIN_PAYLOAD_LEN) {} + + TcBase(TcParams params, uint16_t apid) : TcBase(params, apid, 0x00, MIN_PAYLOAD_LEN) {} + + TcBase(TcParams params, uint16_t apid, uint8_t service, size_t payloadLen) + : TcBase(params, apid, service, payloadLen, DEFAULT_SEQ_COUNT) {} + + TcBase(TcParams params, uint16_t apid, uint8_t serviceId, size_t payloadLen, uint16_t seqCount) + : ploc::SpTcBase(params, apid | APID_MASK_TC, fullSpDataLenFromPayloadLen(payloadLen), + seqCount) { + setup(serviceId); + } + + void setServiceId(uint8_t id) { + if (spParams.maxSize < MIN_PAYLOAD_LEN) { + return; + } + payloadStart[supv::PAYLOAD_OFFSET] = id; + } + + uint16_t getModuleApid() const { return getApid() & APID_MODULE_MASK; } + + uint8_t getServiceId() const { return getPacketData()[TIMESTAMP_LEN]; } + + static size_t fullSpDataLenFromPayloadLen(size_t payloadLen) { + return SECONDARY_HEADER_LEN + payloadLen + CRC_LEN; + } + + void setLenFromPayloadLen(size_t payloadLen) { + spParams.setFullPayloadLen(fullSpDataLenFromPayloadLen(payloadLen)); + updateLenFromParams(); + } + + private: + ReturnValue_t setup(uint8_t serviceId) { + if (spParams.maxSize < MIN_PAYLOAD_LEN) { + sif::error << "SupvTcBase::SupvTcBase: Passed buffer is too small" << std::endl; + return returnvalue::FAILED; + } + std::memset(spParams.buf + ccsds::HEADER_LEN, 0, TIMESTAMP_LEN); + payloadStart = spParams.buf + ccsds::HEADER_LEN + SECONDARY_HEADER_LEN; + spParams.buf[ccsds::HEADER_LEN + SECONDARY_HEADER_LEN - 1] = serviceId; + spParams.creator.setSecHeaderFlag(); + return returnvalue::OK; + } +}; + +class TmBase : public ploc::SpTmReader { + public: + TmBase() = default; + + TmBase(const uint8_t* data, size_t maxSize) : ploc::SpTmReader(data, maxSize) { + if (maxSize < MIN_TMTC_LEN) { + sif::error << "SupvTcBase::SupvTcBase: Passed buffer is too small" << std::endl; + } + } + + uint8_t getServiceId() const { return getPacketData()[TIMESTAMP_LEN]; } + + uint16_t getModuleApid() const { return getApid() & APID_MODULE_MASK; } + + const uint8_t* getPayloadStart() const { return getPacketData() + SECONDARY_HEADER_LEN; } + size_t getPayloadLen() const { + if (getFullPacketLen() > SECONDARY_HEADER_LEN + ccsds::HEADER_LEN) { + return getFullPacketLen() - SECONDARY_HEADER_LEN - ccsds::HEADER_LEN; + } + return 0; + } +}; + +class NoPayloadPacket : public TcBase { + public: + NoPayloadPacket(TcParams params, uint16_t apid, uint8_t serviceId) + : NoPayloadPacket(params, apid, serviceId, 0) {} + + NoPayloadPacket(TcParams params, uint16_t apid, uint8_t serviceId, uint16_t seqId) + : TcBase(params, apid, serviceId, MIN_PAYLOAD_LEN, seqId) {} + + ReturnValue_t buildPacket() { + ReturnValue_t result = checkSizeAndSerializeHeader(); + if (result != returnvalue::OK) { + return result; + } + return calcAndSetCrc(); + } + + private: +}; + +/** + * @brief This class can be used to generate the space packet selecting the boot image of + * of the MPSoC. + */ +class MPSoCBootSelect : public TcBase { + public: + static const uint8_t NVM0 = 0; + static const uint8_t NVM1 = 1; + + /** + * @brief Constructor + * + * @param mem The memory to boot from: NVM0 (0), NVM1 (1) + * @param bp0 Partition pin 0 + * @param bp1 Partition pin 1 + * @param bp2 Partition pin 2 + * + * @note Selection of partitions is currently not supported. + */ + MPSoCBootSelect(TcParams params) + : TcBase(params, Apid::BOOT_MAN, static_cast(tc::BootManId::SELECT_IMAGE), 4) {} + + ReturnValue_t buildPacket(uint8_t mem = 0, uint8_t bp0 = 0, uint8_t bp1 = 0, uint8_t bp2 = 0) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(mem, bp0, bp1, bp2); + return calcAndSetCrc(); + } + + private: + void initPacket(uint8_t mem = 0, uint8_t bp0 = 0, uint8_t bp1 = 0, uint8_t bp2 = 0) { + payloadStart[0] = mem; + payloadStart[1] = bp0; + payloadStart[2] = bp1; + payloadStart[3] = bp2; + } +}; + +/** + * @brief This class generates the space packet to update the time of the PLOC supervisor. + */ +class SetTimeRef : public TcBase { + public: + static constexpr size_t PAYLOAD_LEN = 8; + SetTimeRef(TcParams params) + : TcBase(params, Apid::TMTC_MAN, static_cast(tc::TmtcId::TIME_REF), 8) {} + + ReturnValue_t buildPacket(Clock::TimeOfDay_t* time) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + res = initPacket(time); + if (res != returnvalue::OK) { + return res; + } + return calcAndSetCrc(); + } + + private: + static const uint16_t SYNC = 0x8000; + + ReturnValue_t initPacket(Clock::TimeOfDay_t* time) { + size_t serializedSize = 6; + uint8_t* dataFieldPtr = payloadStart; + uint16_t milliseconds = static_cast(time->usecond / 1000) | SYNC; + ReturnValue_t result = + SerializeAdapter::serialize(&milliseconds, &dataFieldPtr, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + uint8_t second = static_cast(time->second); + result = SerializeAdapter::serialize(&second, &dataFieldPtr, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + uint8_t minute = static_cast(time->minute); + result = SerializeAdapter::serialize(&minute, &dataFieldPtr, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + uint8_t hour = static_cast(time->hour); + result = SerializeAdapter::serialize(&hour, &dataFieldPtr, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + uint8_t day = static_cast(time->day); + result = SerializeAdapter::serialize(&day, &dataFieldPtr, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + uint8_t month = static_cast(time->month); + result = SerializeAdapter::serialize(&month, &dataFieldPtr, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + uint8_t year = static_cast(time->year - 1900); + return SerializeAdapter::serialize(&year, &dataFieldPtr, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + } +}; + +/** + * @brief This class can be used to generate the set boot timout command. + */ +class SetBootTimeout : public TcBase { + public: + static constexpr size_t PAYLOAD_LEN = 4; + /** + * @brief Constructor + * + * @param timeout The boot timeout in milliseconds. + */ + SetBootTimeout(TcParams params) + : TcBase(params, Apid::BOOT_MAN, static_cast(tc::BootManId::SET_BOOT_TIMEOUT), + PAYLOAD_LEN) {} + + ReturnValue_t buildPacket(uint32_t timeout) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(timeout); + return calcAndSetCrc(); + } + + private: + void initPacket(uint32_t timeout) { + size_t serializedSize = 0; + uint8_t* dataFieldPtr = payloadStart; + SerializeAdapter::serialize(&timeout, &dataFieldPtr, &serializedSize, sizeof(timeout), + SerializeIF::Endianness::BIG); + } +}; + +class FactoryReset : public TcBase { + public: + FactoryReset(TcParams params) + : TcBase(params, Apid::DATA_LOGGER, + static_cast(tc::DataLoggerServiceId::FACTORY_RESET), 0) {} + + ReturnValue_t buildPacket(uint8_t op) { + setLenFromPayloadLen(1); + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + payloadStart[0] = op; + return calcAndSetCrc(); + } + + private: +}; + +/** + * @brief This class can be used to generate the space packet to set the maximum boot tries. + */ +class SetRestartTries : public TcBase { + public: + /** + * @brief Constructor + * + * @param restartTries Maximum restart tries to set. + */ + SetRestartTries(TcParams params) + : TcBase(params, Apid::BOOT_MAN, static_cast(tc::BootManId::SET_MAX_REBOOT_TRIES), + 1) {} + + ReturnValue_t buildPacket(uint8_t restartTries) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(restartTries); + return calcAndSetCrc(); + } + + private: + void initPacket(uint8_t restartTries) { payloadStart[0] = restartTries; } +}; + +/** + * @brief With this class the space packet can be generated to disable to periodic transmission + * of housekeeping data. Normally, this will be disabled by default. However, adding this + * command can be useful for debugging. + */ +class DisablePeriodicHkTransmission : public TcBase { + public: + /** + * @brief Constructor + */ + DisablePeriodicHkTransmission(TcParams params) + : TcBase(params, Apid::HK, static_cast(tc::HkId::ENABLE), 1) {} + + ReturnValue_t buildPacket() { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(); + return calcAndSetCrc(); + } + + private: + void initPacket() { payloadStart[0] = false; } +}; + +/** + * @brief This class packages the command to enable of disable the latchup alert. + * + * @details There are 7 different latchup alerts. + */ +class LatchupAlert : public TcBase { + public: + /** + * @brief Constructor + * + * @param state true - enable, false - disable + * @param latchupId Identifies the latchup to enable/disable (0 - 0.85V, 1 - 1.8V, 2 - MISC, + * 3 - 3.3V, 4 - NVM_4XO, 5 - MISSION, 6 - SAFECOTS) + */ + LatchupAlert(TcParams params) : TcBase(params, Apid::LATCHUP_MON) { setLenFromPayloadLen(1); } + + ReturnValue_t buildPacket(bool state, uint8_t latchupId) { + if (state) { + setServiceId(static_cast(tc::LatchupMonId::ENABLE)); + } else { + setServiceId(static_cast(tc::LatchupMonId::DISABLE)); + } + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(latchupId); + return calcAndSetCrc(); + } + + private: + void initPacket(uint8_t latchupId) { payloadStart[0] = latchupId; } +}; + +class SetAlertlimit : public TcBase { + public: + /** + * @brief Constructor + * + * @param latchupId Identifies the latchup alert to calibrate (0 - 0.85V, 1 - 1.8V, 2 - MISC, + * 3 - 3.3V, 4 - NVM_4XO, 5 - MISSION, 6 - SAFECOTS) + * @param dutycycle + */ + SetAlertlimit(TcParams params) + : TcBase(params, Apid::LATCHUP_MON, static_cast(tc::LatchupMonId::SET_ALERT_LIMIT), + 5) {} + + ReturnValue_t buildPacket(uint8_t latchupId, uint32_t dutycycle) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + res = initPacket(latchupId, dutycycle); + if (res != returnvalue::OK) { + return res; + } + return calcAndSetCrc(); + } + + private: + ReturnValue_t initPacket(uint8_t latchupId, uint32_t dutycycle) { + payloadStart[0] = latchupId; + size_t serLen = 0; + return SerializeAdapter::serialize(&dutycycle, payloadStart + 1, &serLen, + sizeof(dutycycle), SerializeIF::Endianness::BIG); + } +}; + +/** + * @brief This class packages the space packet to configures the window size and striding step of + * the moving average filter applied to the ADC readings. + */ +class SetAdcWindowAndStride : public TcBase { + public: + /** + * @brief Constructor + * + * @param windowSize + * @param stridingStepSize + */ + SetAdcWindowAndStride(TcParams params) + : TcBase(params, Apid::ADC_MON, static_cast(tc::AdcMonId::SET_WINDOW_STRIDE), 4) {} + + ReturnValue_t buildPacket(uint16_t windowSize, uint16_t stridingStepSize) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(windowSize, stridingStepSize); + return calcAndSetCrc(); + } + + private: + void initPacket(uint16_t windowSize, uint16_t stridingStepSize) { + size_t serializedSize = 6; + uint8_t* data = payloadStart; + SerializeAdapter::serialize(&windowSize, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&stridingStepSize, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + } +}; + +/** + * @brief This class packages the space packet to set the ADC trigger threshold. + */ +class SetAdcThreshold : public TcBase { + public: + /** + * @brief Constructor + * + * @param threshold + */ + SetAdcThreshold(TcParams params) + : TcBase(params, Apid::ADC_MON, static_cast(tc::AdcMonId::SET_ADC_THRESHOLD), 4) {} + + ReturnValue_t buildPacket(uint32_t threshold) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(threshold); + return calcAndSetCrc(); + } + + private: + void initPacket(uint32_t threshold) { + size_t serializedSize = 0; + SerializeAdapter::serialize(&threshold, payloadStart, &serializedSize, sizeof(threshold), + SerializeIF::Endianness::BIG); + } +}; + +/** + * @brief This class packages the space packet to run auto EM tests. + */ +class RunAutoEmTests : public TcBase { + public: + /** + * @brief Constructor + * + * @param test 1 - complete EM test, 2 - Short test (only memory readback NVM0,1,3) + */ + RunAutoEmTests(TcParams params) + : TcBase(params, Apid::TMTC_MAN, static_cast(tc::TmtcId::RUN_AUTO_EM_TEST), 1) {} + + ReturnValue_t buildPacket(uint8_t test) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(test); + return calcAndSetCrc(); + } + + private: + uint8_t test = 0; + + void initPacket(uint8_t test) { payloadStart[0] = test; } +}; + +/** + * @brief This class packages the space packet to enable or disable ADC channels. + */ +class SetAdcEnabledChannels : public TcBase { + public: + /** + * @brief Constructor + * + * @param ch Defines channels to be enabled or disabled. + */ + SetAdcEnabledChannels(TcParams params) + : TcBase(params, Apid::ADC_MON, static_cast(tc::AdcMonId::SET_ENABLED_CHANNELS), 2) { + } + + ReturnValue_t buildPacket(uint16_t ch) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(ch); + return calcAndSetCrc(); + } + + private: + void initPacket(uint16_t ch) { + size_t serializedSize = 0; + SerializeAdapter::serialize(&ch, &payloadStart, &serializedSize, sizeof(ch), + SerializeIF::Endianness::BIG); + } +}; + +/** + * @brief This class packages the space packet change the state of a GPIO. This command is only + * required for ground testing. + */ +class SetGpio : public TcBase { + public: + /** + * @brief Constructor + * + * @param port + * @param pin + * @param val + */ + SetGpio(TcParams params) + : TcBase(params, Apid::TMTC_MAN, static_cast(tc::TmtcId::SET_GPIO), 3) {} + + ReturnValue_t buildPacket(uint8_t port, uint8_t pin, uint8_t val) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(port, pin, val); + return calcAndSetCrc(); + } + + private: + uint8_t port = 0; + uint8_t pin = 0; + uint8_t val = 0; + + void initPacket(uint8_t port, uint8_t pin, uint8_t val) { + payloadStart[0] = port; + payloadStart[1] = pin; + payloadStart[2] = val; + } +}; + +/** + * @brief This class packages the space packet causing the supervisor print the state of a GPIO + * to the debug output. + */ +class ReadGpio : public TcBase { + public: + /** + * @brief Constructor + * + * @param port + * @param pin + */ + ReadGpio(TcParams params) + : TcBase(params, Apid::TMTC_MAN, static_cast(tc::TmtcId::READ_GPIO), 2) {} + + ReturnValue_t buildPacket(uint8_t port, uint8_t pin) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(port, pin); + return calcAndSetCrc(); + } + + private: + uint8_t port = 0; + uint8_t pin = 0; + + void initPacket(uint8_t port, uint8_t pin) { + payloadStart[0] = port; + payloadStart[1] = pin; + } +}; + +class SetShutdownTimeout : public TcBase { + public: + SetShutdownTimeout(TcParams params) + : TcBase(params, Apid::BOOT_MAN, static_cast(tc::BootManId::SHUTDOWN_TIMEOUT), 4) {} + + ReturnValue_t buildPacket(uint32_t timeout) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(timeout); + return calcAndSetCrc(); + } + + private: + void initPacket(uint32_t timeout) { + size_t serLen = 0; + SerializeAdapter::serialize(&timeout, payloadStart, &serLen, sizeof(timeout), + SerializeIF::Endianness::BIG); + } +}; + +/** + * @brief Command to request CRC over memory region of the supervisor. + */ +class CheckMemory : public TcBase { + public: + /** + * @brief Constructor + * + * @param memoryId + * @param startAddress Start address of CRC calculation + * @param length Length in bytes of memory region + */ + CheckMemory(TcParams params) + : TcBase(params, Apid::MEM_MAN, static_cast(tc::MemManId::CHECK), 10) {} + + ReturnValue_t buildPacket(uint8_t memoryId, uint32_t startAddress, uint32_t length) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(memoryId, startAddress, length); + return calcAndSetCrc(); + } + + private: + uint8_t n = 1; + + void initPacket(uint8_t memoryId, uint32_t startAddress, uint32_t length) { + uint8_t* data = payloadStart; + size_t serLen = 6; + SerializeAdapter::serialize(&memoryId, &data, &serLen, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&n, &data, &serLen, spParams.maxSize, SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&startAddress, &data, &serLen, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&length, &data, &serLen, spParams.maxSize, + SerializeIF::Endianness::BIG); + } +}; + +/** + * @brief This class packages the space packet transporting a part of an MPSoC update. + */ +class WriteMemory : public TcBase { + public: + /** + * @brief Constructor + * + * @param seqFlags Sequence flags + * @param sequenceCount Sequence count (first update packet expects 1 as sequence count) + * @param updateData Pointer to buffer containing update data + */ + WriteMemory(TcParams params) + : TcBase(params, Apid::MEM_MAN, static_cast(tc::MemManId::WRITE), 1) {} + + ReturnValue_t buildPacket(ccsds::SequenceFlags seqFlags, uint16_t sequenceCount, uint8_t memoryId, + uint32_t currentAddr, uint16_t length, uint8_t* updateData) { + if (length > CHUNK_MAX) { + sif::error << "WriteMemory::WriteMemory: Invalid length" << std::endl; + return SerializeIF::BUFFER_TOO_SHORT; + } + spParams.creator.setSeqFlags(seqFlags); + spParams.creator.setSeqCount(sequenceCount); + auto res = initPacket(memoryId, currentAddr, length, updateData); + if (res != returnvalue::OK) { + return res; + } + res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + return calcAndSetCrc(); + } + // Although the space packet has space left for 1010 bytes of data to supervisor can only process + // update packets with a maximum of 512 bytes. + static const uint16_t CHUNK_MAX = 512; + + private: + static const uint16_t META_DATA_LENGTH = 8; + uint8_t n = 1; + + ReturnValue_t initPacket(uint8_t memoryId, uint32_t currentAddr, uint16_t updateDataLen, + uint8_t* updateData) { + uint8_t* data = payloadStart; + if (updateDataLen % 2 != 0) { + setLenFromPayloadLen(META_DATA_LENGTH + updateDataLen + 1); + } else { + setLenFromPayloadLen(META_DATA_LENGTH + updateDataLen); + } + // To avoid crashes in this unexpected case + ReturnValue_t result = checkPayloadLen(); + if (result != returnvalue::OK) { + return result; + } + size_t serializedSize = MIN_TMTC_LEN; + SerializeAdapter::serialize(&memoryId, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&n, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(¤tAddr, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&updateDataLen, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + std::memcpy(data, updateData, updateDataLen); + if (updateDataLen % 2 != 0) { + // The data field must be two bytes aligned. Thus, in case the number of bytes to write is odd + // a value of zero is added here + data[updateDataLen] = 0; + } + return returnvalue::OK; + } +}; + +/** + * @brief This class packages the space packet to wipe or dump parts of the MRAM. + */ +class MramCmd : public TcBase { + public: + enum class MramAction { WIPE, DUMP }; + + /** + * @brief Constructor + * + * @param start Start address of the MRAM section to wipe or dump + * @param stop End address of the MRAM section to wipe or dump + * @param action Dump or wipe MRAM + * + * @note The content at the stop address is excluded from the dump or wipe operation. + */ + MramCmd(TcParams params) : TcBase(params, Apid::DATA_LOGGER) { setLenFromPayloadLen(6); } + + ReturnValue_t buildPacket(uint32_t start, uint32_t stop, MramAction action) { + if (action == MramAction::WIPE) { + setServiceId(static_cast(tc::DataLoggerServiceId::WIPE_MRAM)); + } else if (action == MramAction::DUMP) { + setServiceId(static_cast(tc::DataLoggerServiceId::DUMP_MRAM)); + } else { + sif::debug << "WipeMram: Invalid action specified"; + } + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(start, stop); + return calcAndSetCrc(); + } + + private: + uint32_t start = 0; + uint32_t stop = 0; + + void initPacket(uint32_t start, uint32_t stop) { + uint8_t concatBuffer[6]; + concatBuffer[0] = static_cast(start >> 16); + concatBuffer[1] = static_cast(start >> 8); + concatBuffer[2] = static_cast(start); + concatBuffer[3] = static_cast(stop >> 16); + concatBuffer[4] = static_cast(stop >> 8); + concatBuffer[5] = static_cast(stop); + std::memcpy(payloadStart, concatBuffer, sizeof(concatBuffer)); + } +}; + +/** + * @brief This class can be used to package erase memory command + */ +class EraseMemory : public TcBase { + public: + EraseMemory(TcParams params) + : TcBase(params, Apid::MEM_MAN, static_cast(tc::MemManId::ERASE), PAYLOAD_LENGTH) {} + + ReturnValue_t buildPacket(uint8_t memoryId, uint32_t startAddress, uint32_t length) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(memoryId, startAddress, length); + return calcAndSetCrc(); + } + + private: + static const uint16_t PAYLOAD_LENGTH = 10; // length without CRC field + + uint8_t n = 1; + + void initPacket(uint8_t memoryId, uint32_t startAddress, uint32_t length) { + uint8_t* data = payloadStart; + size_t serializedSize = 6; + SerializeAdapter::serialize(&memoryId, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&n, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&startAddress, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + SerializeAdapter::serialize(&length, &data, &serializedSize, spParams.maxSize, + SerializeIF::Endianness::BIG); + } +}; + +class VerificationReport { + public: + VerificationReport(TmBase& readerBase) : readerBase(readerBase) {} + + virtual ~VerificationReport() = default; + + virtual ReturnValue_t parse(bool checkCrc) { + if (checkCrc and readerBase.checkCrc() != returnvalue::OK) { + return result::CRC_FAILURE; + } + if (readerBase.getModuleApid() != Apid::TMTC_MAN) { + return result::INVALID_APID; + } + if (readerBase.getBufSize() < MIN_TMTC_LEN + PAYLOAD_LEN or + readerBase.getPayloadLen() < PAYLOAD_LEN) { + sif::error << "VerificationReport: Invalid verification report, payload too small" + << std::endl; + return result::BUF_TOO_SMALL; + } + const uint8_t* payloadStart = readerBase.getPayloadStart(); + size_t remLen = PAYLOAD_LEN; + ReturnValue_t result = SerializeAdapter::deSerialize(&refApid, &payloadStart, &remLen, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "VerificationReport: Failed to deserialize reference APID field" << std::endl; + return result; + } + result = SerializeAdapter::deSerialize(&refServiceId, &payloadStart, &remLen, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "VerificationReport: Failed to deserialize reference Service ID field" + << std::endl; + return result; + } + result = SerializeAdapter::deSerialize(&refSeqCount, &payloadStart, &remLen, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "VerificationReport: Failed to deserialize reference sequence count field" + << std::endl; + return result; + } + result = SerializeAdapter::deSerialize(&statusCode, &payloadStart, &remLen, + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::warning << "VerificationReport: Failed to deserialize status code field" << std::endl; + return result; + } + return returnvalue::OK; + } + + /** + * @brief Gets the APID of command which caused the transmission of this verification report. + */ + uint8_t getRefModuleApid() const { return refApid; } + + uint8_t getRefServiceId() const { return refServiceId; } + + uint16_t getRefSequenceCount() const { return refSeqCount; } + + uint32_t getStatusCode() const { return statusCode; } + + virtual void printStatusInformation(const char* prefix) const { + bool codeHandled = true; + if (statusCode < 0x100) { + GeneralStatusCode code = static_cast(getStatusCode()); + switch (code) { + case GeneralStatusCode::OK: { + sif::warning << prefix << "Ok" << std::endl; + break; + } + case GeneralStatusCode::INIT_ERROR: { + sif::warning << prefix << "Init error" << std::endl; + break; + } + case GeneralStatusCode::BAD_PARAM: { + sif::warning << prefix << "Bad param" << std::endl; + break; + } + case GeneralStatusCode::NOT_INITIALIZED: { + sif::warning << prefix << "Not initialized" << std::endl; + break; + } + case GeneralStatusCode::BAD_PERIPH_ID: { + sif::warning << prefix << "Bad periph ID" << std::endl; + break; + } + case GeneralStatusCode::TIMEOUT: { + sif::warning << prefix << "Timeout" << std::endl; + break; + } + case GeneralStatusCode::RX_ERROR: { + sif::warning << prefix << "RX error" << std::endl; + break; + } + case GeneralStatusCode::TX_ERROR: { + sif::warning << prefix << "TX error" << std::endl; + break; + } + case GeneralStatusCode::BUF_EMPTY: { + sif::warning << prefix << "Buf empty" << std::endl; + break; + } + case GeneralStatusCode::NAK: { + sif::warning << prefix << "Nak, default error code" << std::endl; + break; + } + case GeneralStatusCode::ARB_LOST: { + sif::warning << prefix << "Arb lost" << std::endl; + break; + } + case GeneralStatusCode::BUSY: { + sif::warning << prefix << "Busy" << std::endl; + break; + } + case GeneralStatusCode::NOT_IMPL: { + sif::warning << prefix << "Not implemented" << std::endl; + break; + } + case GeneralStatusCode::ALIGNMENT_ERROR: { + sif::warning << prefix << "Alignment error" << std::endl; + break; + } + case GeneralStatusCode::PERIPH_ERROR: { + sif::warning << prefix << "Periph error" << std::endl; + break; + } + case GeneralStatusCode::FAILED_LATCH: { + sif::warning << prefix << "Failed latch" << std::endl; + break; + } + case GeneralStatusCode::GPIO_HIGH: { + sif::warning << prefix << "GPIO high" << std::endl; + break; + } + case GeneralStatusCode::GPIO_LOW: { + sif::warning << prefix << "GPIO low" << std::endl; + break; + } + case GeneralStatusCode::TEST_PASSED: { + sif::warning << prefix << "Test passed" << std::endl; + break; + } + case GeneralStatusCode::TEST_FAILED: { + sif::warning << prefix << "Test failed" << std::endl; + break; + } + default: { + codeHandled = false; + break; + } + } + } else if (statusCode < 0x200 and statusCode >= 0x100) { + BootManStatusCode code = static_cast(statusCode); + switch (code) { + case BootManStatusCode::NOTHING_TODO: { + sif::warning << prefix << "Nothing to do" << std::endl; + break; + } + case BootManStatusCode::POWER_FAULT: { + sif::warning << prefix << "Power fault" << std::endl; + break; + } + case BootManStatusCode::INVALID_LENGTH: { + sif::warning << prefix << "Invalid length" << std::endl; + break; + } + case BootManStatusCode::OUT_OF_RANGE: { + sif::warning << prefix << "Out of range, lenght check of parameter failed" << std::endl; + break; + } + case BootManStatusCode::OUT_OF_HEAP_MEMORY: { + sif::warning << prefix << "Out of heap memory" << std::endl; + break; + } + case BootManStatusCode::INVALID_STATE_TRANSITION: { + sif::warning << prefix << "Invalid state transition" << std::endl; + break; + } + case BootManStatusCode::MPSOC_ALREADY_BOOTING: { + sif::warning << prefix << "MPSoC already booting" << std::endl; + break; + } + case BootManStatusCode::MPSOC_ALREADY_OPERATIONAL: { + sif::warning << prefix << "MPSoC already operational" << std::endl; + break; + } + case BootManStatusCode::MPSOC_BOOT_FAILED: { + sif::warning << prefix << "MPSoC boot failed" << std::endl; + break; + } + default: { + codeHandled = false; + break; + } + } + } else if (statusCode < 0x300 and statusCode >= 0x200) { + MemManStatusCode code = static_cast(statusCode); + switch (code) { + case MemManStatusCode::SP_NOT_AVAILABLE: { + sif::warning << prefix << "SP not available" << std::endl; + break; + } + case MemManStatusCode::SP_DATA_INSUFFICIENT: { + sif::warning << prefix << "SP data insufficient" << std::endl; + break; + } + case MemManStatusCode::SP_MEMORY_ID_INVALID: { + sif::warning << prefix << "SP data insufficient" << std::endl; + break; + } + case MemManStatusCode::MPSOC_NOT_IN_RESET: { + sif::warning << prefix << "MPSoC not in reset" << std::endl; + break; + } + case MemManStatusCode::FLASH_INIT_FAILED: { + sif::warning << prefix << "Flash init failed" << std::endl; + break; + } + case MemManStatusCode::FLASH_ERASE_FAILED: { + sif::warning << prefix << "Flash erase failed" << std::endl; + break; + } + case MemManStatusCode::FLASH_WRITE_FAILED: { + sif::warning << prefix << "Flash write failed" << std::endl; + break; + } + case MemManStatusCode::FLASH_VERIFY_FAILED: { + sif::warning << prefix << "Flash verify failed" << std::endl; + break; + } + case MemManStatusCode::CANNOT_ACCESS_TM: { + sif::warning << prefix << "Can not access tm" << std::endl; + break; + } + case MemManStatusCode::CANNOT_SEND_TM: { + sif::warning << prefix << "Can not access tm" << std::endl; + break; + } + default: { + codeHandled = false; + break; + } + } + } else if (statusCode < 0x400 and statusCode >= 0x300) { + PowerManStatusCode code = static_cast(statusCode); + switch (code) { + case PowerManStatusCode::PG_LOW: { + sif::warning << prefix << "PG low" << std::endl; + break; + } + case PowerManStatusCode::PG_5V_LOW: { + sif::warning << prefix << "PG 5V low" << std::endl; + break; + } + case PowerManStatusCode::PG_0V85_LOW: { + sif::warning << prefix << "PG 0V85 low" << std::endl; + break; + } + case PowerManStatusCode::PG_1V8_LOW: { + sif::warning << prefix << "PG 1V8 low" << std::endl; + break; + } + case PowerManStatusCode::PG_MISC_LOW: { + sif::warning << prefix << "PG misc low" << std::endl; + break; + } + case PowerManStatusCode::PG_3V3_LOW: { + sif::warning << prefix << "PG 3V3 low" << std::endl; + break; + } + case PowerManStatusCode::PG_MB_VAIO_LOW: { + sif::warning << prefix << "PG mb vaio low" << std::endl; + break; + } + case PowerManStatusCode::PG_MB_MPSOCIO_LOW: { + sif::warning << prefix << "PG mb mpsocio low" << std::endl; + break; + } + default: { + codeHandled = false; + break; + } + } + } else if (statusCode >= 0x600) { + TmtcManStatusCode code = static_cast(statusCode); + switch (code) { + case TmtcManStatusCode::BUF_FULL: { + sif::warning << prefix << "TMTC MAN: Buffer full" << std::endl; + break; + } + case TmtcManStatusCode::WRONG_APID: { + sif::warning << prefix << "TMTC MAN: Wrong APID" << std::endl; + break; + } + case TmtcManStatusCode::WRONG_SERVICE_ID: { + sif::warning << prefix << "TMTC MAN: Wrong Service ID" << std::endl; + break; + } + case TmtcManStatusCode::TC_DELIVERY_ACCEPTED: { + sif::warning << prefix << "TMTC MAN: TC accepted" << std::endl; + break; + } + case TmtcManStatusCode::TC_DELIVERY_REJECTED: { + sif::warning << prefix << "TMTC MAN: TC rejected" << std::endl; + break; + } + case TmtcManStatusCode::TC_PACKET_LEN_INCORRECT: { + sif::warning << prefix << "TMTC MAN: TC packet lenght incorrect" << std::endl; + break; + } + case TmtcManStatusCode::BAD_CRC: { + sif::warning << prefix << "TMTC MAN: Bad CRC" << std::endl; + break; + } + case TmtcManStatusCode::BAD_DEST: { + sif::warning << prefix << "TMTC MAN: Bad destination" << std::endl; + break; + } + case TmtcManStatusCode::BAD_SP_HEADER: { + sif::warning << prefix << "TMTC MAN: Bad SP header" << std::endl; + break; + } + default: { + codeHandled = false; + break; + } + } + } + if (not codeHandled) { + sif::warning << prefix << "Invalid or unimplemented status code: 0x" << std::hex + << std::setfill('0') << std::setw(4) << static_cast(statusCode) + << std::dec << std::endl; + } + } + + protected: + TmBase& readerBase; + uint8_t refApid = 0; + uint8_t refServiceId = 0; + uint16_t refSeqCount = 0; + uint32_t statusCode = 0; + static const size_t PAYLOAD_LEN = 8; +}; + +class AcknowledgmentReport : public VerificationReport { + public: + AcknowledgmentReport(TmBase& readerBase) : VerificationReport(readerBase) {} + + ReturnValue_t parse(bool checkCrc) override { + if (readerBase.getServiceId() != static_cast(tm::TmtcId::ACK) and + readerBase.getServiceId() != static_cast(tm::TmtcId::NAK)) { + return result::INVALID_SERVICE_ID; + } + return VerificationReport::parse(checkCrc); + } + + void printStatusInformationAck() { + VerificationReport::printStatusInformation(STATUS_PRINTOUT_PREFIX); + } + + private: + static constexpr char STATUS_PRINTOUT_PREFIX[] = "SUPV NAK Status: "; +}; + +class ExecutionReport : public VerificationReport { + public: + ExecutionReport(TmBase& readerBase) : VerificationReport(readerBase) {} + + TmBase& getReader() { return readerBase; } + + ReturnValue_t parse(bool checkCrc) override { + if (readerBase.getServiceId() != static_cast(tm::TmtcId::EXEC_ACK) and + readerBase.getServiceId() != static_cast(tm::TmtcId::EXEC_NAK)) { + return returnvalue::FAILED; + } + return VerificationReport::parse(checkCrc); + } + + void printStatusInformationExe() { + VerificationReport::printStatusInformation(STATUS_PRINTOUT_PREFIX); + } + + private: + static constexpr char STATUS_PRINTOUT_PREFIX[] = "SUPV EXE NAK Status: "; +}; + +class UpdateStatusReport { + public: + UpdateStatusReport(TmBase& tmReader) : tmReader(tmReader) {} + + ReturnValue_t parse(bool checkCrc) { + if (checkCrc and tmReader.checkCrc() != returnvalue::OK) { + return result::CRC_FAILURE; + } + if (tmReader.getModuleApid() != Apid::MEM_MAN) { + return result::INVALID_APID; + } + if (tmReader.getBufSize() < MIN_TMTC_LEN + PAYLOAD_LEN or + tmReader.getPayloadLen() < PAYLOAD_LEN) { + sif::error << "VerificationReport: Invalid verification report, payload too small" + << std::endl; + return result::BUF_TOO_SMALL; + } + size_t remLen = tmReader.getPayloadLen(); + const uint8_t* dataFieldPtr = tmReader.getPayloadStart(); + SerializeAdapter::deSerialize(&memoryId, &dataFieldPtr, &remLen, SerializeIF::Endianness::BIG); + SerializeAdapter::deSerialize(&n, &dataFieldPtr, &remLen, SerializeIF::Endianness::BIG); + SerializeAdapter::deSerialize(&startAddress, &dataFieldPtr, &remLen, + SerializeIF::Endianness::BIG); + SerializeAdapter::deSerialize(&length, &dataFieldPtr, &remLen, SerializeIF::Endianness::BIG); + SerializeAdapter::deSerialize(&crc, &dataFieldPtr, &remLen, SerializeIF::Endianness::BIG); + return returnvalue::OK; + } + + ReturnValue_t verifyCrc(uint16_t goodCrc) const { + if (crc != goodCrc) { + return result::UPDATE_CRC_FAILURE; + } + return returnvalue::OK; + } + + uint16_t getCrc() const { return crc; } + + private: + TmBase& tmReader; + + // Nominal size of the space packet + static const uint16_t PAYLOAD_LEN = 12; // header, data field and crc + + uint8_t memoryId = 0; + uint8_t n = 0; + uint32_t startAddress = 0; + uint32_t length = 0; + uint16_t crc = 0; +}; + +/** + * @brief This dataset stores the boot status report of the supervisor. + */ +class BootStatusReport : public StaticLocalDataSet { + public: + BootStatusReport(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, BOOT_REPORT_SET_ID) {} + + BootStatusReport(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, BOOT_REPORT_SET_ID)) {} + + /** Information about boot status of MPSoC */ + lp_var_t socState = lp_var_t(sid.objectId, PoolIds::BR_SOC_STATE, this); + lp_var_t powerCycles = lp_var_t(sid.objectId, PoolIds::POWER_CYCLES, this); + /** Time the MPSoC needs for last boot */ + lp_var_t bootAfterMs = lp_var_t(sid.objectId, PoolIds::BOOT_AFTER_MS, this); + /** The currently set boot timeout */ + lp_var_t bootTimeoutMs = + lp_var_t(sid.objectId, PoolIds::BOOT_TIMEOUT_POOL_VAR_MS, this); + lp_var_t activeNvm = lp_var_t(sid.objectId, PoolIds::ACTIVE_NVM, this); + /** States of the boot partition pins */ + lp_var_t bp0State = lp_var_t(sid.objectId, PoolIds::BP0_STATE, this); + lp_var_t bp1State = lp_var_t(sid.objectId, PoolIds::BP1_STATE, this); + lp_var_t bp2State = lp_var_t(sid.objectId, PoolIds::BP2_STATE, this); + lp_var_t bootState = lp_var_t(sid.objectId, PoolIds::BOOT_STATE, this); + lp_var_t bootCycles = lp_var_t(sid.objectId, PoolIds::BOOT_CYCLES, this); +}; + +/** + * @brief This dataset stores the housekeeping data of the supervisor. + */ +class HkSet : public StaticLocalDataSet { + public: + enum class SocState { OFF = 0, BOOTING = 1, OPERATIONAL = 3, SHUTDOWN = 4 }; + + HkSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, HK_SET_ID) {} + + HkSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, HK_SET_ID)) {} + + lp_var_t tempPs = lp_var_t(sid.objectId, PoolIds::TEMP_PS, this); + lp_var_t tempPl = lp_var_t(sid.objectId, PoolIds::TEMP_PS, this); + lp_var_t tempSup = lp_var_t(sid.objectId, PoolIds::TEMP_SUP, this); + lp_var_t uptime = lp_var_t(sid.objectId, PoolIds::UPTIME, this); + lp_var_t cpuLoad = lp_var_t(sid.objectId, PoolIds::CPULOAD, this); + lp_var_t availableHeap = lp_var_t(sid.objectId, PoolIds::AVAILABLEHEAP, this); + lp_var_t numTcs = lp_var_t(sid.objectId, PoolIds::NUM_TCS, this); + lp_var_t numTms = lp_var_t(sid.objectId, PoolIds::NUM_TMS, this); + lp_var_t socState = lp_var_t(sid.objectId, PoolIds::HK_SOC_STATE, this); + lp_var_t nvm0_1_state = lp_var_t(sid.objectId, PoolIds::NVM0_1_STATE, this); + lp_var_t nvm3_state = lp_var_t(sid.objectId, PoolIds::NVM3_STATE, this); + lp_var_t missionIoState = + lp_var_t(sid.objectId, PoolIds::MISSION_IO_STATE, this); + lp_var_t fmcState = lp_var_t(sid.objectId, PoolIds::FMC_STATE, this); +}; + +/** + * @brief This dataset stores the last requested latchup status report. + */ +class LatchupStatusReport : public StaticLocalDataSet { + public: + LatchupStatusReport(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, LATCHUP_RPT_ID) {} + + LatchupStatusReport(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, LATCHUP_RPT_ID)) {} + + lp_var_t id = lp_var_t(sid.objectId, PoolIds::LATCHUP_ID, this); + lp_var_t cnt0 = lp_var_t(sid.objectId, PoolIds::CNT0, this); + lp_var_t cnt1 = lp_var_t(sid.objectId, PoolIds::CNT1, this); + lp_var_t cnt2 = lp_var_t(sid.objectId, PoolIds::CNT2, this); + lp_var_t cnt3 = lp_var_t(sid.objectId, PoolIds::CNT3, this); + lp_var_t cnt4 = lp_var_t(sid.objectId, PoolIds::CNT4, this); + lp_var_t cnt5 = lp_var_t(sid.objectId, PoolIds::CNT5, this); + lp_var_t cnt6 = lp_var_t(sid.objectId, PoolIds::CNT6, this); + lp_var_t timeMsec = + lp_var_t(sid.objectId, PoolIds::LATCHUP_RPT_TIME_MSEC, this); + lp_var_t timeSec = lp_var_t(sid.objectId, PoolIds::LATCHUP_RPT_TIME_SEC, this); + lp_var_t timeMin = lp_var_t(sid.objectId, PoolIds::LATCHUP_RPT_TIME_MIN, this); + lp_var_t timeHour = + lp_var_t(sid.objectId, PoolIds::LATCHUP_RPT_TIME_HOUR, this); + lp_var_t timeDay = lp_var_t(sid.objectId, PoolIds::LATCHUP_RPT_TIME_DAY, this); + lp_var_t timeMon = lp_var_t(sid.objectId, PoolIds::LATCHUP_RPT_TIME_MON, this); + lp_var_t timeYear = + lp_var_t(sid.objectId, PoolIds::LATCHUP_RPT_TIME_YEAR, this); + lp_var_t isSynced = lp_var_t(sid.objectId, PoolIds::LATCHUP_RPT_IS_SET, this); + + static const uint8_t IS_SET_BIT_POS = 15; +}; + +/** + * @brief This dataset stores the logging report. + */ +class CountersReport : public StaticLocalDataSet { + public: + CountersReport(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, LOGGING_RPT_ID) {} + + CountersReport(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, LOGGING_RPT_ID)) {} + + lp_var_t signature = + lp_var_t(sid.objectId, PoolIds::LATCHUP_HAPPENED_CNTS, this); + lp_vec_t latchupHappenCnts = + lp_vec_t(sid.objectId, PoolIds::LATCHUP_HAPPENED_CNTS, this); + lp_var_t adcDeviationTriggersCnt = + lp_var_t(sid.objectId, PoolIds::ADC_DEVIATION_TRIGGERS_CNT, this); + lp_var_t tcReceivedCnt = + lp_var_t(sid.objectId, PoolIds::TC_RECEIVED_CNT, this); + lp_var_t tmAvailableCnt = + lp_var_t(sid.objectId, PoolIds::TM_AVAILABLE_CNT, this); + lp_var_t supervisorBoots = + lp_var_t(sid.objectId, PoolIds::SUPERVISOR_BOOTS, this); + lp_var_t mpsocBoots = lp_var_t(sid.objectId, PoolIds::MPSOC_BOOTS, this); + lp_var_t mpsocBootFailedAttempts = + lp_var_t(sid.objectId, PoolIds::MPSOC_BOOT_FAILED_ATTEMPTS, this); + lp_var_t mpsocPowerup = lp_var_t(sid.objectId, PoolIds::MPSOC_POWER_UP, this); + lp_var_t mpsocUpdates = lp_var_t(sid.objectId, PoolIds::MPSOC_UPDATES, this); + lp_var_t mpsocHeartbeatResets = + lp_var_t(sid.objectId, PoolIds::MPSOC_HEARTBEAT_RESETS, this); + lp_var_t cpuWdtResets = + lp_var_t(sid.objectId, PoolIds::MPSOC_HEARTBEAT_RESETS, this); + lp_var_t psHeartbeatsLost = + lp_var_t(sid.objectId, PoolIds::PS_HEARTBEATS_LOST, this); + lp_var_t plHeartbeatsLost = + lp_var_t(sid.objectId, PoolIds::PL_HEARTBEATS_LOST, this); + lp_var_t ebTaskLost = lp_var_t(sid.objectId, PoolIds::EB_TASK_LOST, this); + lp_var_t bmTaskLost = lp_var_t(sid.objectId, PoolIds::BM_TASK_LOST, this); + lp_var_t lmTaskLost = lp_var_t(sid.objectId, PoolIds::LM_TASK_LOST, this); + lp_var_t amTaskLost = lp_var_t(sid.objectId, PoolIds::AM_TASK_LOST, this); + lp_var_t tctmmTaskLost = + lp_var_t(sid.objectId, PoolIds::TCTMM_TASK_LOST, this); + lp_var_t mmTaskLost = lp_var_t(sid.objectId, PoolIds::MM_TASK_LOST, this); + lp_var_t hkTaskLost = lp_var_t(sid.objectId, PoolIds::HK_TASK_LOST, this); + lp_var_t dlTaskLost = lp_var_t(sid.objectId, PoolIds::DL_TASK_LOST, this); + lp_vec_t rwsTasksLost = + lp_vec_t(sid.objectId, PoolIds::RWS_TASKS_LOST, this); + + void printSet() { + for (unsigned i = 0; i < 7; i++) { + sif::info << "LoggingReport: Latchup happened count " << i << ": " + << this->latchupHappenCnts[i] << std::endl; + } + sif::info << "LoggingReport: ADC deviation triggers count: " << this->adcDeviationTriggersCnt + << std::endl; + sif::info << "LoggingReport: TC received count: " << this->tcReceivedCnt << std::endl; + sif::info << "LoggingReport: TM available count: " << this->tmAvailableCnt << std::endl; + sif::info << "LoggingReport: Supervisor boots: " << this->supervisorBoots << std::endl; + sif::info << "LoggingReport: MPSoC boots: " << this->mpsocBoots << std::endl; + sif::info << "LoggingReport: MPSoC boot failed attempts: " << this->mpsocBootFailedAttempts + << std::endl; + sif::info << "LoggingReport: MPSoC power up: " << this->mpsocPowerup << std::endl; + sif::info << "LoggingReport: MPSoC updates: " << this->mpsocUpdates << std::endl; + } +}; + +/** + * @brief This dataset stores the ADC report. + */ +class AdcReport : public StaticLocalDataSet<3> { + public: + AdcReport(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, ADC_REPORT_SET_ID) {} + + AdcReport(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, ADC_REPORT_SET_ID)) {} + + lp_vec_t adcRaw = lp_vec_t(sid.objectId, PoolIds::ADC_RAW, this); + lp_vec_t adcEng = lp_vec_t(sid.objectId, PoolIds::ADC_ENG, this); + + void printSet() { + sif::info << "---- Adc Report: Raw values ----" << std::endl; + for (unsigned i = 0; i < 16; i++) { + sif::info << "AdcReport: ADC raw " << i << ": " << std::dec << this->adcRaw[i] << std::endl; + } + for (unsigned i = 0; i < 16; i++) { + sif::info << "AdcReport: ADC eng " << i << ": " << std::dec << this->adcEng[i] << std::endl; + } + } +}; + +namespace notimpl { + +/** + * @brief This class packages the space packet to perform the factory reset. The op parameter is + * optional. + * + * @details: Without OP: All MRAM entries will be cleared. + * OP = 0x01: Only the mirror entries will be wiped. + * OP = 0x02: Only the circular entries will be wiped. + */ +class FactoryReset : public TcBase { + public: + enum class Op { CLEAR_ALL, MIRROR_ENTRIES, CIRCULAR_ENTRIES }; + + /** + * @brief Constructor + * + * @param op + */ + FactoryReset(TcParams params) : TcBase(params, Apid::TMTC_MAN, 0x11, 1) {} + + ReturnValue_t buildPacket(Op op) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(op); + return calcAndSetCrc(); + } + + private: + void initPacket(Op op) { + size_t packetDataLen = 2; + switch (op) { + case Op::MIRROR_ENTRIES: + payloadStart[0] = 1; + packetDataLen = 3; + break; + case Op::CIRCULAR_ENTRIES: + payloadStart[0] = 2; + packetDataLen = 3; + break; + default: + break; + } + spParams.setFullPayloadLen(packetDataLen); + } +}; + +/** + * @brief This class creates the command to enable or disable the NVMs connected to the + * supervisor. + */ +class EnableNvms : public TcBase { + public: + /** + * @brief Constructor + * + * @param mem The memory to boot from: NVM0 (0), NVM1 (1) + * @param bp0 Partition pin 0 + * @param bp1 Partition pin 1 + * @param bp2 Partition pin 2 + */ + EnableNvms(TcParams params) : TcBase(params, Apid::TMTC_MAN, 0x06, 2) {} + + ReturnValue_t buildPacket(uint8_t nvm01, uint8_t nvm3) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + initPacket(nvm01, nvm3); + return calcAndSetCrc(); + } + + private: + void initPacket(uint8_t nvm01, uint8_t nvm3) { + payloadStart[0] = nvm01; + payloadStart[1] = nvm3; + } +}; + +/** + * @brief This class creates the space packet to enable the auto TM generation + */ +class EnableAutoTm : public TcBase { + public: + EnableAutoTm(TcParams params) : TcBase(params) { spParams.setFullPayloadLen(PAYLOAD_LENGTH + 2); } + + ReturnValue_t buildPacket() { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + payloadStart[0] = ENABLE; + return calcAndSetCrc(); + } + + private: + static const uint16_t PAYLOAD_LENGTH = 1; // length without CRC field + static const uint8_t ENABLE = 1; +}; + +/** + * @brief This class creates the space packet to disable the auto TM generation + */ +class DisableAutoTm : public TcBase { + public: + DisableAutoTm(TcParams params) : TcBase(params) { + spParams.setFullPayloadLen(PAYLOAD_LENGTH + 2); + // spParams.creator.setApid(APID_AUTO_TM); + // spParams.creator.setSeqCount(DEFAULT_SEQUENCE_COUNT); + } + + ReturnValue_t buildPacket() { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + payloadStart[0] = DISABLE; + return calcAndSetCrc(); + } + + private: + static const uint16_t PAYLOAD_LENGTH = 1; // length without CRC field + static const uint8_t DISABLE = 0; +}; + +/** + * @brief This class creates the space packet to request the logging data from the supervisor + */ +class RequestLoggingData : public TcBase { + public: + /** + * Subapid + */ + enum class Sa : uint8_t { + REQUEST_COUNTERS = 1, /**< REQUEST_COUNTERS */ + REQUEST_EVENT_BUFFERS = 2, /**< REQUEST_EVENT_BUFFERS */ + CLEAR_COUNTERS = 3, /**< CLEAR_COUNTERS */ + SET_LOGGING_TOPIC = 4 /**< SET_LOGGING_TOPIC */ + }; + + RequestLoggingData(TcParams params) : TcBase(params) { + spParams.setFullPayloadLen(PAYLOAD_LENGTH + 2); + // spParams.creator.setApid(APID_REQUEST_LOGGING_DATA); + // spParams.creator.setSeqCount(DEFAULT_SEQUENCE_COUNT); + } + + /** + * @param sa + * @param tpc Topic + * @return + */ + ReturnValue_t buildPacket(Sa sa, uint8_t tpc = 0) { + auto res = checkSizeAndSerializeHeader(); + if (res != returnvalue::OK) { + return res; + } + payloadStart[0] = static_cast(sa); + payloadStart[1] = tpc; + return calcAndSetCrc(); + } + + private: + static const uint16_t PAYLOAD_LENGTH = 2; // length without CRC field + static const uint8_t TPC_OFFSET = 1; +}; + +typedef struct { + // The most significant bit of msec value is set to 0x80 to indicate that full + // time and data information is transmitted, when the time has been synced with + // the reference. If the time has not been synced with reference, then the most + // significant bit is set to 0x00. Only the most significant bit is used for + // this purpose (bit 15 of the field tm_msec) + uint16_t tm_msec; // miliseconds 0-999; + uint8_t tm_sec; // seconds after the minute, 0 to 60 + // (0 - 60 allows for the occasional leap second) + uint8_t tm_min; // minutes after the hour, 0 to 59 + uint8_t tm_hour; // hours since midnight, 0 to 23 + uint8_t tm_mday; // day of the month, 1 to 31 + uint8_t tm_mon; // months 1 to 12 + uint8_t tm_year; // years since 1900 +} tas_time_t; + +} // namespace notimpl + +} // namespace supv + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_PLOCSVPDEFINITIONS_H_ */ diff --git a/linux/power/CMakeLists.txt b/linux/power/CMakeLists.txt new file mode 100644 index 0000000..b58849a --- /dev/null +++ b/linux/power/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PUBLIC CspComIF.cpp) diff --git a/linux/power/CspComIF.cpp b/linux/power/CspComIF.cpp new file mode 100644 index 0000000..1b1cd0a --- /dev/null +++ b/linux/power/CspComIF.cpp @@ -0,0 +1,396 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace GOMSPACE; + +CspComIF::CspComIF(object_id_t objectId, const char* routeTaskName, uint32_t routerRealTimePriority) + : SystemObject(objectId), + routerRealTimePriority(routerRealTimePriority), + routerTaskName(routeTaskName) {} + +CspComIF::~CspComIF() {} + +ReturnValue_t CspComIF::initializeInterface(CookieIF* cookie) { + if (cookie == nullptr) { + return NULLPOINTER; + } + + CspCookie* cspCookie = dynamic_cast(cookie); + if (cspCookie == nullptr) { + return NULLPOINTER; + } + + /* Perform CAN and CSP initialization only once */ + if (cspDeviceMap.empty()) { + sif::info << "Performing " << canInterface << " initialization.." << std::endl; + + /* Define the memory to allocate for the CSP stack */ + int buf_count = 10; + int buf_size = 300; + /* Init CSP and CSP buffer system */ + if (csp_init(cspOwnAddress) != CSP_ERR_NONE || + csp_buffer_init(buf_count, buf_size) != CSP_ERR_NONE) { + sif::error << "Failed to init CSP\r\n" << std::endl; + return returnvalue::FAILED; + } + + int promisc = 0; // Set filter mode on + csp_iface_t* csp_if_ptr = &csp_if; + csp_if_ptr = csp_can_socketcan_init(canInterface, bitrate, promisc); + + /* Set default route and start router */ + uint8_t address = CSP_DEFAULT_ROUTE; + uint8_t netmask = 0; + uint8_t mac = CSP_NODE_MAC; + int result = csp_rtable_set(address, netmask, csp_if_ptr, mac); + if (result != CSP_ERR_NONE) { + sif::error << "Failed to add can interface to router table" << std::endl; + return returnvalue::FAILED; + } + + /* Start the route task */ + result = startRouterTask(); + if (result != returnvalue::OK) { + sif::error << "Failed to start csp route task" << std::endl; + return returnvalue::FAILED; + } + sif::info << canInterface << " initialized successfully" << std::endl; + } + + uint8_t cspAddress = cspCookie->getCspAddress(); + uint16_t maxReplyLength = cspCookie->getMaxReplyLength(); + if (cspDeviceMap.find(cspAddress) == cspDeviceMap.end()) { + /* Insert device information in CSP map */ + cspDeviceMap.emplace(cspAddress, ReplyInfo(maxReplyLength)); + } + return returnvalue::OK; +} + +ReturnValue_t CspComIF::sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) { + int result; + if (cookie == nullptr) { + return returnvalue::FAILED; + } + CspCookie* cspCookie = dynamic_cast(cookie); + if (cspCookie == nullptr) { + return returnvalue::FAILED; + } + + uint8_t cspPort; + uint16_t querySize = 0; + if (cspCookie->getRequest() == GOMSPACE::SpecialRequestTypes::DEFAULT_COM_IF) { + /* Extract csp port and bytes to query from command buffer */ + result = getPortAndQuerySize(&sendData, &sendLen, &cspPort, &querySize); + if (result != returnvalue::OK) { + return result; + } + } else { + cspPort = cspCookie->getCspPort(); + querySize = cspCookie->getReplyLen(); + } + if (querySize > cspCookie->getMaxReplyLength()) { + sif::error << "Query size " << querySize << " is larger than maximum allowed " + << cspCookie->getMaxReplyLength() << std::endl; + return returnvalue::FAILED; + } + uint8_t cspAddress = cspCookie->getCspAddress(); + auto iter = cspDeviceMap.find(cspAddress); + if (iter == cspDeviceMap.end()) { + return returnvalue::FAILED; + } + switch (cspPort) { + case (CspPorts::CSP_PING): { + initiatePingRequest(cspAddress, querySize); + break; + } + case (CspPorts::CSP_REBOOT): { + csp_reboot(cspAddress); + break; + } + case (CspPorts::P60_PORT_GNDWDT_RESET_ENUM): + case (CspPorts::P60_PORT_RPARAM_ENUM): { + if (cspCookie->getRequest() != SpecialRequestTypes::DEFAULT_COM_IF) { + param_index_t requestStruct{}; + requestStruct.physaddr = iter->second.replyBuf.data(); + auto req = cspCookie->getRequest(); + if (req == GOMSPACE::SpecialRequestTypes::GET_PDU_HK) { + if (!p60pdu_get_hk(&requestStruct, cspAddress, cspCookie->getTimeout())) { + return returnvalue::FAILED; + } + + } else if (req == GOMSPACE::SpecialRequestTypes::GET_ACU_HK) { + if (!p60acu_get_hk(&requestStruct, cspAddress, cspCookie->getTimeout())) { + return returnvalue::FAILED; + } + } else if (req == GOMSPACE::SpecialRequestTypes::GET_P60DOCK_HK) { + if (!p60dock_get_hk(&requestStruct, cspAddress, cspCookie->getTimeout())) { + return returnvalue::FAILED; + } + } else if (req == GOMSPACE::SpecialRequestTypes::GET_PDU_CONFIG) { + requestStruct.table = p60pdu_config; + requestStruct.mem_id = P60PDU_PARAM; + requestStruct.count = p60pdu_config_count; + requestStruct.size = P60PDU_PARAM_SIZE; + result = rparam_get_full_table(&requestStruct, cspAddress, P60_PORT_RPARAM, + requestStruct.mem_id, cspCookie->getTimeout()); + if (result != 0) { + return returnvalue::FAILED; + } + } else if (req == GOMSPACE::SpecialRequestTypes::GET_ACU_CONFIG) { + requestStruct.table = p60acu_config; + requestStruct.mem_id = P60ACU_PARAM; + requestStruct.count = p60acu_config_count; + requestStruct.size = P60ACU_PARAM_SIZE; + result = rparam_get_full_table(&requestStruct, cspAddress, P60_PORT_RPARAM, + requestStruct.mem_id, cspCookie->getTimeout()); + if (result != 0) { + return returnvalue::FAILED; + } + } else if (req == GOMSPACE::SpecialRequestTypes::GET_P60DOCK_CONFIG) { + requestStruct.table = p60dock_config; + requestStruct.mem_id = P60DOCK_PARAM; + requestStruct.count = p60dock_config_count; + requestStruct.size = P60DOCK_PARAM_SIZE; + result = rparam_get_full_table(&requestStruct, cspAddress, P60_PORT_RPARAM, + requestStruct.mem_id, cspCookie->getTimeout()); + if (result != 0) { + return returnvalue::FAILED; + } + } else if (req == GOMSPACE::SpecialRequestTypes::SAVE_TABLE) { + if (sendLen < 2) { + return returnvalue::FAILED; + } + const TableInfo* tableInfo = reinterpret_cast(sendData); + result = gs_rparam_save(cspAddress, cspCookie->getTimeout(), tableInfo->sourceTable, + tableInfo->targetTable); + if (result != 0) { + return returnvalue::FAILED; + } + } else if (req == GOMSPACE::SpecialRequestTypes::LOAD_TABLE) { + if (sendLen < 2) { + return returnvalue::FAILED; + } + const TableInfo* tableInfo = reinterpret_cast(sendData); + result = gs_rparam_load(cspAddress, cspCookie->getTimeout(), tableInfo->sourceTable, + tableInfo->targetTable); + if (result != 0) { + return returnvalue::FAILED; + } + } + } else { + /* No CSP fixed port was selected. Send data to the specified port and + * wait for querySize number of bytes */ + result = cspTransfer(cspAddress, cspPort, sendData, sendLen, querySize); + if (result != returnvalue::OK) { + return returnvalue::FAILED; + } + } + iter->second.replyLen = querySize; + break; + } + default: + sif::error << "CspComIF: Invalid port specified" << std::endl; + break; + } + return returnvalue::OK; +} + +ReturnValue_t CspComIF::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } + +ReturnValue_t CspComIF::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t CspComIF::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) { + if (cookie == NULL) { + return returnvalue::FAILED; + } + CspCookie* cspCookie = dynamic_cast(cookie); + if (cspCookie == NULL) { + return returnvalue::FAILED; + } + + uint8_t cspAddress = cspCookie->getCspAddress(); + auto iter = cspDeviceMap.find(cspAddress); + if (iter == cspDeviceMap.end()) { + return returnvalue::FAILED; + } + *buffer = iter->second.replyBuf.data(); + *size = iter->second.replyLen; + + return returnvalue::OK; +} + +ReturnValue_t CspComIF::cspTransfer(uint8_t cspAddress, uint8_t cspPort, const uint8_t* cmdBuffer, + int cmdLen, uint16_t querySize) { + uint32_t timeout_ms = 1000; + uint16_t bytesRead = 0; + int32_t expectedSize = static_cast(querySize); + auto iter = cspDeviceMap.find(cspAddress); + if (iter == cspDeviceMap.end()) { + sif::error << "CSP device with address " << cspAddress << " no found in" + << " device map" << std::endl; + return returnvalue::FAILED; + } + uint8_t* replyBuffer = iter->second.replyBuf.data(); + + csp_conn_t* conn = csp_connect(CSP_PRIO_HIGH, cspAddress, cspPort, 0, CSP_O_NONE); + + csp_packet_t* commandPacket = (csp_packet_t*)csp_buffer_get(cmdLen); + if (commandPacket == NULL) { + sif::error << "CspComIF::cspTransfer: Failed to get memory for a csp packet from the csp " + << "stack" << std::endl; + csp_close(conn); + return returnvalue::FAILED; + } + + memcpy(commandPacket->data, cmdBuffer, cmdLen); + commandPacket->length = cmdLen; + + if (!csp_send(conn, commandPacket, timeout_ms)) { + csp_buffer_free(commandPacket); + sif::error << "CspComIF::cspTransfer: Failed to send csp packet" << std::endl; + csp_close(conn); + return returnvalue::FAILED; + } + + /* Return when no reply is expected */ + if (expectedSize == 0) { + return returnvalue::OK; + } + + csp_packet_t* reply; + reply = csp_read(conn, timeout_ms); + if (reply == NULL) { + sif::error << "CspComIF::cspTransfer: Failed to read csp packet" << std::endl; + csp_close(conn); + return returnvalue::FAILED; + } + memcpy(replyBuffer, reply->data, reply->length); + expectedSize = expectedSize - reply->length; + bytesRead += reply->length; + csp_buffer_free(reply); + while (expectedSize > 0) { + reply = csp_read(conn, timeout_ms); + if (reply == NULL) { + sif::error << "CspComIF::cspTransfer: Failed to read csp packet" << std::endl; + csp_close(conn); + return returnvalue::FAILED; + } + if ((reply->length + bytesRead) > iter->second.replyBuf.size()) { + sif::error << "CspComIF::cspTransfer: Reply buffer to short" << std::endl; + csp_buffer_free(reply); + csp_close(conn); + return returnvalue::FAILED; + } + memcpy(replyBuffer + bytesRead, reply->data, reply->length); + expectedSize = expectedSize - reply->length; + bytesRead += reply->length; + csp_buffer_free(reply); + } + + if (expectedSize != 0) { + sif::error << "CspComIF::cspTransfer: Received more bytes than requested" << std::endl; + sif::debug << "CspComIF::cspTransfer: Received bytes: " << bytesRead << std::endl; + csp_close(conn); + return returnvalue::FAILED; + } + + csp_close(conn); + + return returnvalue::OK; +} + +ReturnValue_t CspComIF::getPortAndQuerySize(const uint8_t** sendData, size_t* sendLen, + uint8_t* cspPort, uint16_t* querySize) { + ReturnValue_t result = + SerializeAdapter::deSerialize(cspPort, sendData, sendLen, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::error << "CspComIF: Failed to deserialize CSP port from command " + << "buffer" << std::endl; + return returnvalue::FAILED; + } + SerializeAdapter::deSerialize(querySize, sendData, sendLen, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::error << "CspComIF: Failed to deserialize querySize from command " + << "buffer" << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void CspComIF::initiatePingRequest(uint8_t cspAddress, uint16_t querySize) { + uint32_t timeout_ms = 500; + uint32_t replyTime = csp_ping(cspAddress, timeout_ms, querySize, CSP_O_NONE); + sif::info << "Ping address: " << cspAddress << ", reply after " << replyTime << " ms" + << std::endl; + auto iter = cspDeviceMap.find(cspAddress); + if (iter == cspDeviceMap.end()) { + return; + } + /* Store reply time in reply buffer * */ + uint8_t* replyBuffer = iter->second.replyBuf.data(); + memcpy(replyBuffer, &replyTime, sizeof(replyTime)); + iter->second.replyLen = sizeof(replyTime); +} + +ReturnValue_t CspComIF::startRouterTask() { + pthread_attr_t attr; + int res = pthread_attr_init(&attr); + if (res) { + return returnvalue::FAILED; + } + + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + res = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + if (res != 0) { + return returnvalue::FAILED; + } + + // Set scheduling policy to SCHED_RR + res = pthread_attr_setschedpolicy(&attr, SCHED_RR); + if (res) { + pthread_attr_destroy(&attr); + return returnvalue::FAILED; + } + + struct sched_param sched_param; + sched_param.sched_priority = routerRealTimePriority; + res = pthread_attr_setschedparam(&attr, &sched_param); + if (res) { + pthread_attr_destroy(&attr); + return returnvalue::FAILED; + } + + res = pthread_create(&routerTaskHandle, &attr, routerWorkWrapper, NULL); + if (res) { + pthread_attr_destroy(&attr); + return returnvalue::FAILED; + } + + res = pthread_setname_np(routerTaskHandle, routerTaskName); + if (res) { + pthread_attr_destroy(&attr); + return returnvalue::FAILED; + } + pthread_attr_destroy(&attr); + return returnvalue::OK; +} + +void* CspComIF::routerWorkWrapper(void* args) { + /* Here there be routing */ + while (1) { + csp_route_work(FIFO_TIMEOUT); + } + return nullptr; +} diff --git a/linux/power/CspComIF.h b/linux/power/CspComIF.h new file mode 100644 index 0000000..f50d295 --- /dev/null +++ b/linux/power/CspComIF.h @@ -0,0 +1,93 @@ +#ifndef LINUX_POWER_CSPCOMIF_H_ +#define LINUX_POWER_CSPCOMIF_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * @brief This class serves as the communication interface to devices + * supporting the CSP protocol. As physical layer can0 is used + * in this implementation. + * @author J. Meier + */ +class CspComIF : public DeviceCommunicationIF, public SystemObject { + public: + CspComIF(object_id_t objectId, const char *routeTaskName, uint32_t routerRealTimePriority); + virtual ~CspComIF(); + + ReturnValue_t initializeInterface(CookieIF *cookie) override; + ReturnValue_t sendMessage(CookieIF *cookie, const uint8_t *sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF *cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF *cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF *cookie, uint8_t **readData, size_t *readLen) override; + + private: +#ifdef CSP_USE_RDP + //! If RDP is enabled, the router needs to awake some times to check timeouts + static constexpr uint32_t FIFO_TIMEOUT = 100; +#else + //! If no RDP, the router can sleep untill data arrives + static constexpr uint32_t FIFO_TIMEOUT = CSP_MAX_DELAY; +#endif + /** + * @brief This function initiates the CSP transfer. + * + * @param cspAddress The CSP address of the target device. + * @param cspPort The port of the target device. + * @param timeout The timeout to wait for csp_send and csp_read + * functions. Specifies how long the functions wait + * for a successful operation. + * @param cmdBuffer The data to send. + * @param cmdLen The number of bytes to send. + * @param querySize The size of the requested message. + */ + ReturnValue_t cspTransfer(uint8_t cspAddress, uint8_t cspPort, const uint8_t *cmdBuffer, + int cmdLen, uint16_t querySize); + + typedef uint8_t node_t; + struct ReplyInfo { + ReplyInfo(size_t maxLen) : replyBuf(maxLen) {}; + std::vector replyBuf; + size_t replyLen = 0; + }; + using VectorBufferMap = std::unordered_map; + + /* In this map assigns reply buffers to a CSP device */ + VectorBufferMap cspDeviceMap; + + /* This is the CSP address of the OBC. */ + node_t cspOwnAddress = 1; + + pthread_t routerTaskHandle{}; + uint32_t routerRealTimePriority = 0; + const char *routerTaskName; + + /* Interface struct for csp protocol stack */ + csp_iface_t csp_if; + char canInterface[5] = "can0"; + int bitrate = 1000; + + /** + * @brief Function to extract the csp port and the query size from the + * command buffer. + */ + ReturnValue_t getPortAndQuerySize(const uint8_t **sendData, size_t *sendLen, uint8_t *cspPort, + uint16_t *querySize); + + /** + * @brief This function initiates the ping request. + */ + void initiatePingRequest(uint8_t cspAddress, uint16_t querySize); + + ReturnValue_t startRouterTask(); + static void *routerWorkWrapper(void *args); +}; + +#endif /* LINUX_POWER_CSPCOMIF_H_ */ diff --git a/linux/scheduling.cpp b/linux/scheduling.cpp new file mode 100644 index 0000000..28d1509 --- /dev/null +++ b/linux/scheduling.cpp @@ -0,0 +1,49 @@ +#include "scheduling.h" + +#include +#include +#include + +#include "OBSWConfig.h" +#include "ObjectFactory.h" +#include "eive/objects.h" + +PosixThreadArgs scheduling::RR_SCHEDULING = {.policy = SchedulingPolicy::RR}; +PosixThreadArgs scheduling::NORMAL_SCHEDULING; + +void scheduling::scheduleScexReader(TaskFactory& factory, PeriodicTaskIF*& scexReaderTask) { + using namespace scheduling; + ReturnValue_t result = returnvalue::OK; +#if OBSW_PRINT_MISSED_DEADLINES == 1 + void (*missedDeadlineFunc)(void) = TaskFactory::printMissedDeadline; +#else + void (*missedDeadlineFunc)(void) = nullptr; +#endif + + result = returnvalue::OK; + scexReaderTask = + factory.createPeriodicTask("SCEX_UART_READER", 20, PeriodicTaskIF::MINIMUM_STACK_SIZE, 2.0, + missedDeadlineFunc, &NORMAL_SCHEDULING); + result = scexReaderTask->addComponent(objects::SCEX_UART_READER); + if (result != returnvalue::OK) { + printAddObjectError("SCEX_UART_READER", objects::SCEX_UART_READER); + } +} + +void scheduling::addMpsocSupvHandlers(PeriodicTaskIF* plTask) { + plTask->addComponent(objects::PLOC_SUPERVISOR_HANDLER, DeviceHandlerIF::PERFORM_OPERATION); + plTask->addComponent(objects::PLOC_SUPERVISOR_HANDLER, DeviceHandlerIF::SEND_WRITE); + plTask->addComponent(objects::PLOC_SUPERVISOR_HANDLER, DeviceHandlerIF::GET_WRITE); + plTask->addComponent(objects::PLOC_SUPERVISOR_HANDLER, DeviceHandlerIF::SEND_READ); + plTask->addComponent(objects::PLOC_SUPERVISOR_HANDLER, DeviceHandlerIF::GET_READ); + plTask->addComponent(objects::PLOC_SUPERVISOR_HANDLER, DeviceHandlerIF::SEND_READ); + plTask->addComponent(objects::PLOC_SUPERVISOR_HANDLER, DeviceHandlerIF::GET_READ); + + plTask->addComponent(objects::PLOC_MPSOC_HANDLER, DeviceHandlerIF::PERFORM_OPERATION); + plTask->addComponent(objects::PLOC_MPSOC_HANDLER, DeviceHandlerIF::SEND_WRITE); + plTask->addComponent(objects::PLOC_MPSOC_HANDLER, DeviceHandlerIF::GET_WRITE); + plTask->addComponent(objects::PLOC_MPSOC_HANDLER, DeviceHandlerIF::SEND_READ); + plTask->addComponent(objects::PLOC_MPSOC_HANDLER, DeviceHandlerIF::GET_READ); + plTask->addComponent(objects::PLOC_MPSOC_HANDLER, DeviceHandlerIF::SEND_READ); + plTask->addComponent(objects::PLOC_MPSOC_HANDLER, DeviceHandlerIF::GET_READ); +} diff --git a/linux/scheduling.h b/linux/scheduling.h new file mode 100644 index 0000000..82cf4dc --- /dev/null +++ b/linux/scheduling.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace scheduling { + +extern PosixThreadArgs RR_SCHEDULING; +extern PosixThreadArgs NORMAL_SCHEDULING; + +void scheduleScexReader(TaskFactory& factory, PeriodicTaskIF*& scexReaderTask); +void addMpsocSupvHandlers(PeriodicTaskIF* task); +} // namespace scheduling diff --git a/linux/tcs/CMakeLists.txt b/linux/tcs/CMakeLists.txt new file mode 100644 index 0000000..3721f02 --- /dev/null +++ b/linux/tcs/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PUBLIC Max31865RtdPolling.cpp) diff --git a/linux/tcs/Max31865RtdPolling.cpp b/linux/tcs/Max31865RtdPolling.cpp new file mode 100644 index 0000000..f8ec7ed --- /dev/null +++ b/linux/tcs/Max31865RtdPolling.cpp @@ -0,0 +1,473 @@ +#include +#include +#include +#include + +#define OBSW_RTD_AUTO_MODE 1 + +#if OBSW_RTD_AUTO_MODE == 1 +static constexpr uint8_t BASE_CFG = (MAX31865::Bias::ON << MAX31865::CfgBitPos::BIAS_SEL) | + (MAX31865::Wires::FOUR_WIRE << MAX31865::CfgBitPos::WIRE_SEL) | + (MAX31865::ConvMode::AUTO << MAX31865::CfgBitPos::CONV_MODE); +#else +static constexpr uint8_t BASE_CFG = + (MAX31865::Bias::OFF << MAX31865::CfgBitPos::BIAS_SEL) | + (MAX31865::Wires::FOUR_WIRE << MAX31865::CfgBitPos::WIRE_SEL) | + (MAX31865::ConvMode::NORM_OFF << MAX31865::CfgBitPos::CONV_MODE); +#endif + +Max31865RtdPolling::Max31865RtdPolling(object_id_t objectId, SpiComIF* lowLevelComIF, + GpioIF* gpioIF) + : SystemObject(objectId), rtds(EiveMax31855::NUM_RTDS), comIF(lowLevelComIF), gpioIF(gpioIF) { + readerLock = MutexFactory::instance()->createMutex(); +} + +ReturnValue_t Max31865RtdPolling::performOperation(uint8_t operationCode) { + using namespace MAX31865; + ReturnValue_t result = returnvalue::OK; + static_cast(result); + // Measured to take 0-1 ms in debug build + // Stopwatch watch; + periodicInitHandling(); +#if OBSW_RTD_AUTO_MODE == 0 + // 10 ms delay for VBIAS startup + TaskFactory::delayTask(10); + result = periodicReadReqHandling(); + if (result != returnvalue::OK) { + return result; + } + // After requesting, 65 milliseconds delay required + TaskFactory::delayTask(65); +#endif + + return periodicReadHandling(); +} + +bool Max31865RtdPolling::rtdIsActive(uint8_t idx) { + if (rtds[idx]->on and rtds[idx]->db.active and rtds[idx]->db.configured) { + return true; + } + return false; +} + +ReturnValue_t Max31865RtdPolling::periodicInitHandling() { + using namespace MAX31865; + ReturnValue_t result = returnvalue::OK; + for (auto& rtd : rtds) { + if (rtd == nullptr) { + continue; + } + bool mustPerformInitHandling = false; + bool doWriteLowThreshold = false; + bool doWriteHighThreshold = false; + { + MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (mg.getLockResult() != returnvalue::OK) { + sif::warning << "Max31865RtdReader::periodicInitHandling: Mutex lock failed" << std::endl; + continue; + } + mustPerformInitHandling = + (rtd->on or rtd->db.active) and not rtd->db.configured and rtd->cd.hasTimedOut(); + doWriteHighThreshold = rtd->writeHighThreshold; + doWriteLowThreshold = rtd->writeLowThreshold; + } + if (mustPerformInitHandling) { + // Please note that using the manual CS lock wrapper here is problematic. Might be a SPI + // or hardware specific issue where the CS needs to be pulled high and then low again + // between transfers + result = writeCfgReg(rtd->spiCookie, BASE_CFG); + if (result != returnvalue::OK) { + handleSpiError(rtd, result, "writeCfgReg"); + continue; + } + if (doWriteLowThreshold) { + result = writeLowThreshold(rtd->spiCookie, rtd->lowThreshold); + if (result != returnvalue::OK) { + handleSpiError(rtd, result, "writeLowThreshold"); + } + } + if (doWriteHighThreshold) { + result = writeHighThreshold(rtd->spiCookie, rtd->highThreshold); + if (result != returnvalue::OK) { + handleSpiError(rtd, result, "writeHighThreshold"); + } + } + result = clearFaultStatus(rtd->spiCookie); + if (result != returnvalue::OK) { + handleSpiError(rtd, result, "clearFaultStatus"); + } + MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + rtd->db.configured = true; + rtd->db.active = true; + } + } + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::periodicReadReqHandling() { + using namespace MAX31865; + updateActiveRtdsArray(); + // Now request one shot config for all active RTDs + for (auto& rtd : rtds) { + if (rtd == nullptr) { + continue; + } + if (activeRtdsArray[rtd->idx]) { + ReturnValue_t result = writeCfgReg(rtd->spiCookie, BASE_CFG | (1 << CfgBitPos::ONE_SHOT)); + if (result != returnvalue::OK) { + handleSpiError(rtd, result, "writeCfgReg"); + // Release mutex ASAP + return returnvalue::FAILED; + } + } + } + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::periodicReadHandling() { + using namespace MAX31865; + auto result = returnvalue::OK; + updateActiveRtdsArray(); + // Now read the RTD values + for (auto& rtd : rtds) { + if (rtd == nullptr) { + continue; + } + if (activeRtdsArray[rtd->idx]) { + // Please note that using the manual CS lock wrapper here is problematic. Might be a SPI + // or hardware specific issue where the CS needs to be pulled high and then low again + // between transfers + uint16_t rtdVal = 0; + bool faultBitSet = false; + result = writeCfgReg(rtd->spiCookie, BASE_CFG); + if (result != returnvalue::OK) { + handleSpiError(rtd, result, "writeCfgReg"); + continue; + } + result = readRtdVal(rtd->spiCookie, rtdVal, faultBitSet); + // sif::debug << "RTD Val: " << rtdVal << std::endl; + if (result != returnvalue::OK) { + handleSpiError(rtd, result, "readRtdVal"); + continue; + } + MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (faultBitSet) { + rtd->db.faultBitSet = faultBitSet; + } + rtd->db.adcCode = rtdVal; + } + } +#if OBSW_RTD_AUTO_MODE == 0 + for (auto& rtd : rtds) { + if (rtd == nullptr) { + continue; + } + // Even if a device was made inactive, turn off the bias here. If it was turned off, not + // necessary anymore.. + if (rtd->on) { + result = writeBiasSel(Bias::OFF, rtd->spiCookie, BASE_CFG); + } + } +#endif + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::initializeInterface(CookieIF* cookie) { + if (cookie == nullptr) { + throw std::invalid_argument("Invalid MAX31865 Reader Cookie"); + } + auto* rtdCookie = dynamic_cast(cookie); + ReturnValue_t result = comIF->initializeInterface(rtdCookie->spiCookie); + if (result != returnvalue::OK) { + return result; + } + if (rtdCookie->idx > EiveMax31855::NUM_RTDS) { + throw std::invalid_argument("Invalid RTD index"); + } + rtds[rtdCookie->idx] = rtdCookie; + MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (dbLen == 0) { + dbLen = rtdCookie->db.getSerializedSize(); + } + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::sendMessage(CookieIF* cookie, const uint8_t* sendData, + size_t sendLen) { + if (cookie == nullptr) { + return returnvalue::FAILED; + } + auto* rtdCookie = dynamic_cast(cookie); + if (rtdCookie == nullptr) { + return returnvalue::FAILED; + } + // Empty command.. don't fail for now + if (sendLen < 1) { + return returnvalue::OK; + } + MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (mg.getLockResult() != returnvalue::OK) { + sif::warning << "Max31865RtdReader::sendMessage: Mutex lock failed" << std::endl; + return returnvalue::FAILED; + } + uint8_t cmdRaw = sendData[0]; + if (cmdRaw > EiveMax31855::RtdCommands::NUM_CMDS) { + sif::warning << "Max31865RtdReader::sendMessage: Invalid command" << std::endl; + return returnvalue::FAILED; + } + + auto thresholdHandler = [&]() { + rtdCookie->lowThreshold = (sendData[1] << 8) | sendData[2]; + rtdCookie->highThreshold = (sendData[3] << 8) | sendData[4]; + rtdCookie->writeLowThreshold = true; + rtdCookie->writeHighThreshold = true; + }; + + auto cmd = static_cast(sendData[0]); + switch (cmd) { + case (EiveMax31855::RtdCommands::ON): { + if (not rtdCookie->on) { + rtdCookie->cd.setTimeout(MAX31865::WARMUP_MS); + rtdCookie->on = true; + rtdCookie->db.active = false; + rtdCookie->db.configured = false; + if (sendLen == 5) { + thresholdHandler(); + } + } + break; + } + case (EiveMax31855::RtdCommands::ACTIVE): { + if (not rtdCookie->on) { + rtdCookie->cd.setTimeout(MAX31865::WARMUP_MS); + rtdCookie->on = true; + rtdCookie->db.active = true; + rtdCookie->db.configured = false; + } else { + rtdCookie->db.active = true; + } + if (sendLen == 5) { + thresholdHandler(); + } + break; + } + case (EiveMax31855::RtdCommands::OFF): { + rtdCookie->on = false; + rtdCookie->db.active = false; + rtdCookie->db.configured = false; + break; + } + case (EiveMax31855::RtdCommands::HIGH_TRESHOLD): { + if (sendLen == 3) { + rtdCookie->highThreshold = (sendData[1] << 8) | sendData[2]; + rtdCookie->writeHighThreshold = true; + } else { + return returnvalue::FAILED; + } + break; + } + case (EiveMax31855::RtdCommands::LOW_THRESHOLD): { + if (sendLen == 3) { + rtdCookie->lowThreshold = (sendData[1] << 8) | sendData[2]; + rtdCookie->writeLowThreshold = true; + } else { + return returnvalue::FAILED; + } + break; + } + case (EiveMax31855::RtdCommands::CFG): { + ReturnValue_t result = writeCfgReg(rtdCookie->spiCookie, BASE_CFG); + if (result != returnvalue::OK) { + handleSpiError(rtdCookie, result, "writeCfgReg"); + } + break; + } + default: { + // TODO: Only implement if needed + break; + } + } + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } + +ReturnValue_t Max31865RtdPolling::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, + size_t* size) { + auto* rtdCookie = dynamic_cast(cookie); + if (rtdCookie == nullptr) { + return returnvalue::FAILED; + } + MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (mg.getLockResult() != returnvalue::OK) { + // TODO: Emit warning + return returnvalue::FAILED; + } + uint8_t* exchangePtr = rtdCookie->exchangeBuf.data(); + size_t serLen = 0; + auto result = rtdCookie->db.serialize(&exchangePtr, &serLen, rtdCookie->exchangeBuf.size(), + SerializeIF::Endianness::MACHINE); + if (result != returnvalue::OK) { + // TODO: Emit warning + return returnvalue::FAILED; + } + *buffer = reinterpret_cast(rtdCookie->exchangeBuf.data()); + *size = serLen; + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::writeCfgReg(SpiCookie* cookie, uint8_t cfg) { + using namespace MAX31865; + return writeNToReg(cookie, CONFIG, 1, &cfg, nullptr); +} + +ReturnValue_t Max31865RtdPolling::writeBiasSel(MAX31865::Bias bias, SpiCookie* cookie, + uint8_t baseCfg) { + using namespace MAX31865; + if (bias == MAX31865::Bias::OFF) { + baseCfg &= ~(1 << CfgBitPos::BIAS_SEL); + } else { + baseCfg |= (1 << CfgBitPos::BIAS_SEL); + } + return writeCfgReg(cookie, baseCfg); +} + +ReturnValue_t Max31865RtdPolling::clearFaultStatus(SpiCookie* cookie) { + using namespace MAX31865; + // Read back the current configuration to avoid overwriting it when clearing te fault status + uint8_t currentCfg = 0; + auto result = readCfgReg(cookie, currentCfg); + if (result != returnvalue::OK) { + return result; + } + // Clear bytes 5, 3 and 2 which need to be 0 + currentCfg &= ~0x2C; + currentCfg |= (1 << CfgBitPos::FAULT_STATUS_CLEAR); + return writeCfgReg(cookie, currentCfg); +} + +ReturnValue_t Max31865RtdPolling::readCfgReg(SpiCookie* cookie, uint8_t& cfg) { + using namespace MAX31865; + uint8_t* replyPtr = nullptr; + auto result = readNFromReg(cookie, CONFIG, 1, &replyPtr); + if (result == returnvalue::OK) { + cfg = replyPtr[0]; + } + return result; +} + +ReturnValue_t Max31865RtdPolling::writeLowThreshold(SpiCookie* cookie, uint16_t val) { + using namespace MAX31865; + uint8_t cmd[2] = {static_cast((val >> 8) & 0xff), static_cast(val & 0xff)}; + return writeNToReg(cookie, LOW_THRESHOLD, 2, cmd, nullptr); +} + +ReturnValue_t Max31865RtdPolling::writeHighThreshold(SpiCookie* cookie, uint16_t val) { + using namespace MAX31865; + uint8_t cmd[2] = {static_cast((val >> 8) & 0xff), static_cast(val & 0xff)}; + return writeNToReg(cookie, HIGH_THRESHOLD, 2, cmd, nullptr); +} + +ReturnValue_t Max31865RtdPolling::readLowThreshold(SpiCookie* cookie, uint16_t& lowThreshold) { + using namespace MAX31865; + uint8_t* replyPtr = nullptr; + auto result = readNFromReg(cookie, LOW_THRESHOLD, 2, &replyPtr); + if (result == returnvalue::OK) { + lowThreshold = (replyPtr[0] << 8) | replyPtr[1]; + } + return result; +} + +ReturnValue_t Max31865RtdPolling::readHighThreshold(SpiCookie* cookie, uint16_t& highThreshold) { + using namespace MAX31865; + uint8_t* replyPtr = nullptr; + auto result = readNFromReg(cookie, HIGH_THRESHOLD, 2, &replyPtr); + if (result == returnvalue::OK) { + highThreshold = (replyPtr[0] << 8) | replyPtr[1]; + } + return result; +} + +ReturnValue_t Max31865RtdPolling::writeNToReg(SpiCookie* cookie, uint8_t reg, size_t n, + uint8_t* cmd, uint8_t** reply) { + using namespace MAX31865; + if (n > cmdBuf.size() - 1) { + return returnvalue::FAILED; + } + cmdBuf[0] = reg | WRITE_BIT; + for (size_t idx = 0; idx < n; idx++) { + cmdBuf[idx + 1] = cmd[idx]; + } + return comIF->sendMessage(cookie, cmdBuf.data(), n + 1); +} + +ReturnValue_t Max31865RtdPolling::readRtdVal(SpiCookie* cookie, uint16_t& val, bool& faultBitSet) { + using namespace MAX31865; + uint8_t* replyPtr = nullptr; + auto result = readNFromReg(cookie, RTD, 2, &replyPtr); + if (result != returnvalue::OK) { + return result; + } + if (replyPtr[1] & 0b0000'0001) { + faultBitSet = true; + } + // Shift 1 to the right to remove fault bit + val = ((replyPtr[0] << 8) | replyPtr[1]) >> 1; + return result; +} + +ReturnValue_t Max31865RtdPolling::readNFromReg(SpiCookie* cookie, uint8_t reg, size_t n, + uint8_t** reply) { + using namespace MAX31865; + if (n > 4) { + return returnvalue::FAILED; + } + // Clear write bit in any case + reg &= ~WRITE_BIT; + cmdBuf[0] = reg; + std::memset(cmdBuf.data() + 1, 0, n); + ReturnValue_t result = comIF->sendMessage(cookie, cmdBuf.data(), n + 1); + if (result != returnvalue::OK) { + return returnvalue::FAILED; + } + + size_t dummyLen = 0; + uint8_t* replyPtr = nullptr; + result = comIF->readReceivedMessage(cookie, &replyPtr, &dummyLen); + if (result != returnvalue::OK) { + return result; + } + if (reply != nullptr) { + *reply = replyPtr + 1; + } + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::updateActiveRtdsArray() { + MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (mg.getLockResult() != returnvalue::OK) { + sif::warning << "Max31865RtdReader::periodicReadHandling: Mutex lock failed" << std::endl; + return returnvalue::FAILED; + } + for (const auto& rtd : rtds) { + activeRtdsArray[rtd->idx] = rtdIsActive(rtd->idx); + } + return returnvalue::OK; +} + +ReturnValue_t Max31865RtdPolling::handleSpiError(Max31865ReaderCookie* cookie, ReturnValue_t result, + const char* ctx) { + cookie->db.spiErrorCount.value += 1; + sif::warning << "Max31865RtdReader::handleSpiError: " << ctx << " | Failed with result " << result + << std::endl; + return result; +} + +ReturnValue_t Max31865RtdPolling::initialize() { + csLock = comIF->getCsMutex(); + return SystemObject::initialize(); +} diff --git a/linux/tcs/Max31865RtdPolling.h b/linux/tcs/Max31865RtdPolling.h new file mode 100644 index 0000000..04260a5 --- /dev/null +++ b/linux/tcs/Max31865RtdPolling.h @@ -0,0 +1,93 @@ +#ifndef LINUX_DEVICES_MAX31865RTDREADER_H_ +#define LINUX_DEVICES_MAX31865RTDREADER_H_ + +#include +#include +#include +#include +#include +#include + +#include "devConf.h" +#include "fsfw/devicehandlers/DeviceCommunicationIF.h" + +struct Max31865ReaderCookie : public CookieIF { + Max31865ReaderCookie() {}; + Max31865ReaderCookie(object_id_t handlerId_, uint8_t idx_, const std::string& locString_, + SpiCookie* spiCookie_) + : idx(idx_), handlerId(handlerId_), locString(locString_), spiCookie(spiCookie_) {} + + uint8_t idx = 0; + object_id_t handlerId = objects::NO_OBJECT; + + std::string locString = ""; + std::array exchangeBuf{}; + Countdown cd = Countdown(MAX31865::WARMUP_MS); + + bool on = false; + bool writeLowThreshold = false; + bool writeHighThreshold = false; + uint16_t lowThreshold = 0; + uint16_t highThreshold = 0; + SpiCookie* spiCookie = nullptr; + + // Exchange data buffer struct + EiveMax31855::ReadOutStruct db; +}; + +class Max31865RtdPolling : public SystemObject, + public ExecutableObjectIF, + public DeviceCommunicationIF { + public: + Max31865RtdPolling(object_id_t objectId, SpiComIF* lowLevelComIF, GpioIF* gpioIF); + + ReturnValue_t performOperation(uint8_t operationCode) override; + ReturnValue_t initialize() override; + + private: + std::vector rtds; + std::array cmdBuf = {}; + std::array activeRtdsArray{}; + size_t dbLen = 0; + MutexIF* readerLock; + static constexpr MutexIF::TimeoutType LOCK_TYPE = MutexIF::TimeoutType::WAITING; + static constexpr uint32_t LOCK_TIMEOUT = 20; + static constexpr char LOCK_CTX[] = "Max31865RtdPolling"; + + SpiComIF* comIF; + GpioIF* gpioIF; + MutexIF::TimeoutType csTimeoutType = MutexIF::TimeoutType::WAITING; + uint32_t csTimeoutMs = spi::RTD_CS_TIMEOUT; + MutexIF* csLock = nullptr; + + ReturnValue_t periodicInitHandling(); + ReturnValue_t periodicReadReqHandling(); + ReturnValue_t periodicReadHandling(); + + bool rtdIsActive(uint8_t idx); + ReturnValue_t writeCfgReg(SpiCookie* cookie, uint8_t cfg); + ReturnValue_t writeBiasSel(MAX31865::Bias bias, SpiCookie* cookie, uint8_t baseCfg); + ReturnValue_t readCfgReg(SpiCookie* cookie, uint8_t& cfg); + ReturnValue_t readRtdVal(SpiCookie* cookie, uint16_t& val, bool& faultBitSet); + ReturnValue_t writeLowThreshold(SpiCookie* cookie, uint16_t val); + ReturnValue_t writeHighThreshold(SpiCookie* cookie, uint16_t val); + ReturnValue_t readLowThreshold(SpiCookie* cookie, uint16_t& val); + ReturnValue_t readHighThreshold(SpiCookie* cookie, uint16_t& val); + ReturnValue_t clearFaultStatus(SpiCookie* cookie); + + ReturnValue_t readNFromReg(SpiCookie* cookie, uint8_t reg, size_t n, uint8_t** reply); + ReturnValue_t writeNToReg(SpiCookie* cookie, uint8_t reg, size_t n, uint8_t* cmd, + uint8_t** reply); + + ReturnValue_t initializeInterface(CookieIF* cookie) override; + ReturnValue_t sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) override; + ReturnValue_t getSendSuccess(CookieIF* cookie) override; + ReturnValue_t requestReceiveMessage(CookieIF* cookie, size_t requestLen) override; + ReturnValue_t readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) override; + + ReturnValue_t updateActiveRtdsArray(); + + ReturnValue_t handleSpiError(Max31865ReaderCookie* cookie, ReturnValue_t result, const char* ctx); +}; + +#endif /* LINUX_DEVICES_MAX31865RTDREADER_H_ */ diff --git a/linux/utility/CMakeLists.txt b/linux/utility/CMakeLists.txt new file mode 100644 index 0000000..f0d4e69 --- /dev/null +++ b/linux/utility/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${OBSW_NAME} PUBLIC utility.cpp) diff --git a/linux/utility/utility.cpp b/linux/utility/utility.cpp new file mode 100644 index 0000000..2f51aea --- /dev/null +++ b/linux/utility/utility.cpp @@ -0,0 +1,27 @@ + +#include "utility.h" + +#include + +#include "FSFWConfig.h" +#include "OBSWConfig.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "fsfw/timemanager/Clock.h" + +void utility::handleSystemError(int retcode, std::string context) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::warning << context << ": System call failed with code " << retcode << ": " + << strerror(retcode) << std::endl; +#endif +} + +bool utility::timeSanityCheck() { + timeval currentTime = {}; + Clock::getUptime(¤tTime); + Clock::TimeOfDay_t currTimeOfDay = {}; + Clock::convertTimevalToTimeOfDay(¤tTime, &currTimeOfDay); + if (currTimeOfDay.year == 2000) { + return false; + } + return true; +} diff --git a/linux/utility/utility.h b/linux/utility/utility.h new file mode 100644 index 0000000..7f314f8 --- /dev/null +++ b/linux/utility/utility.h @@ -0,0 +1,16 @@ +#ifndef LINUX_UTILITY_UTILITY_H_ +#define LINUX_UTILITY_UTILITY_H_ + +#include + +#include "fsfw/returnvalues/returnvalue.h" + +namespace utility { + +void handleSystemError(int retcode, std::string function); + +bool timeSanityCheck(); + +} // namespace utility + +#endif /* LINUX_UTILITY_UTILITY_H_ */ diff --git a/misc/archive/GPIORPi.cpp b/misc/archive/GPIORPi.cpp new file mode 100644 index 0000000..34d0f69 --- /dev/null +++ b/misc/archive/GPIORPi.cpp @@ -0,0 +1,35 @@ +#include "GPIORPi.h" +#include +#include +#include + +ReturnValue_t gpio::createRpiGpioConfig(GpioCookie* cookie, gpioId_t gpioId, int bcmPin, + std::string consumer, gpio::Direction direction, int initValue) { + if(cookie == nullptr) { + return HasReturnvaluesIF::RETURN_FAILED; + } + + GpiodRegular config; + /* Default chipname for Raspberry Pi. There is still gpiochip1 for expansion, but most users + will not need this */ + config.chipname = "gpiochip0"; + + config.consumer = consumer; + config.direction = direction; + config.initValue = initValue; + + /* Sanity check for the BCM pins before assigning it */ + if(bcmPin > 27) { +#if FSFW_VERBOSE_LEVEL >= 1 +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "createRpiGpioConfig: BCM pin " << bcmPin << " invalid!" << std::endl; +#else + sif::printError("createRpiGpioConfig: BCM pin %d invalid!\n", bcmPin); +#endif /* FSFW_CPP_OSTREAM_ENABLED == 1 */ +#endif /* FSFW_VERBOSE_LEVEL >= 1 */ + return HasReturnvaluesIF::RETURN_FAILED; + } + config.lineNum = bcmPin; + cookie->addGpio(gpioId, config); + return HasReturnvaluesIF::RETURN_OK; +} diff --git a/misc/archive/GPIORPi.h b/misc/archive/GPIORPi.h new file mode 100644 index 0000000..a2c1187 --- /dev/null +++ b/misc/archive/GPIORPi.h @@ -0,0 +1,26 @@ +#ifndef BSP_RPI_GPIO_GPIORPI_H_ +#define BSP_RPI_GPIO_GPIORPI_H_ + +#include +#include + +class GpioCookie; + +namespace gpio { + +/** + * Create a GpioConfig_t. This function does a sanity check on the BCM pin number and fails if the + * BCM pin is invalid. + * @param cookie Adds the configuration to this cookie directly + * @param gpioId ID which identifies the GPIO configuration + * @param bcmPin Raspberry Pi BCM pin + * @param consumer Information string + * @param direction GPIO direction + * @param initValue Intial value for output pins, 0 for low, 1 for high + * @return + */ +ReturnValue_t createRpiGpioConfig(GpioCookie* cookie, gpioId_t gpioId, int bcmPin, + std::string consumer, gpio::Direction direction, int initValue); +} + +#endif /* BSP_RPI_GPIO_GPIORPI_H_ */ diff --git a/misc/archive/GyroL3GD20Handler.cpp b/misc/archive/GyroL3GD20Handler.cpp new file mode 100644 index 0000000..a2850df --- /dev/null +++ b/misc/archive/GyroL3GD20Handler.cpp @@ -0,0 +1,260 @@ +//#include "GyroL3GD20Handler.h" +//#include +// +//#include +// +//GyroHandlerL3GD20H::GyroHandlerL3GD20H(object_id_t objectId, object_id_t deviceCommunication, +// CookieIF *comCookie): +// DeviceHandlerBase(objectId, deviceCommunication, comCookie), +// dataset(this) { +//#if L3GD20_GYRO_DEBUG == 1 +// debugDivider = new PeriodicOperationDivider(5); +//#endif +//} +// +//GyroHandlerL3GD20H::~GyroHandlerL3GD20H() {} +// +//void GyroHandlerL3GD20H::doStartUp() { +// if(internalState == InternalState::NONE) { +// internalState = InternalState::CONFIGURE; +// } +// +// if(internalState == InternalState::CONFIGURE) { +// if(commandExecuted) { +// internalState = InternalState::CHECK_REGS; +// commandExecuted = false; +// } +// } +// +// if(internalState == InternalState::CHECK_REGS) { +// if(commandExecuted) { +// internalState = InternalState::NORMAL; +//#if OBSW_SWITCH_TO_NORMAL_MODE_AFTER_STARTUP == 1 +// setMode(MODE_NORMAL); +//#else +// setMode(_MODE_TO_ON); +//#endif +// commandExecuted = false; +// } +// } +//} +// +//void GyroHandlerL3GD20H::doShutDown() { +// setMode(_MODE_POWER_DOWN); +//} +// +//ReturnValue_t GyroHandlerL3GD20H::buildTransitionDeviceCommand(DeviceCommandId_t *id) { +// switch(internalState) { +// case(InternalState::NONE): +// case(InternalState::NORMAL): { +// return HasReturnvaluesIF::RETURN_OK; +// } +// case(InternalState::CONFIGURE): { +// *id = L3GD20H::CONFIGURE_CTRL_REGS; +// uint8_t command [5]; +// command[0] = L3GD20H::CTRL_REG_1_VAL; +// command[1] = L3GD20H::CTRL_REG_2_VAL; +// command[2] = L3GD20H::CTRL_REG_3_VAL; +// command[3] = L3GD20H::CTRL_REG_4_VAL; +// command[4] = L3GD20H::CTRL_REG_5_VAL; +// return buildCommandFromCommand(*id, command, 5); +// } +// case(InternalState::CHECK_REGS): { +// *id = L3GD20H::READ_REGS; +// return buildCommandFromCommand(*id, nullptr, 0); +// } +// default: +// /* Might be a configuration error. */ +// sif::debug << "GyroHandler::buildTransitionDeviceCommand: Unknown internal state!" << +// std::endl; +// return HasReturnvaluesIF::RETURN_OK; +// } +// return HasReturnvaluesIF::RETURN_OK; +//} +// +//ReturnValue_t GyroHandlerL3GD20H::buildNormalDeviceCommand(DeviceCommandId_t *id) { +// *id = L3GD20H::READ_REGS; +// return buildCommandFromCommand(*id, nullptr, 0); +//} +// +//ReturnValue_t GyroHandlerL3GD20H::buildCommandFromCommand( +// DeviceCommandId_t deviceCommand, const uint8_t *commandData, +// size_t commandDataLen) { +// switch(deviceCommand) { +// case(L3GD20H::READ_REGS): { +// commandBuffer[0] = L3GD20H::READ_START | L3GD20H::AUTO_INCREMENT_MASK | +// L3GD20H::READ_MASK; +// +// std::memset(commandBuffer + 1, 0, L3GD20H::READ_LEN); +// rawPacket = commandBuffer; +// rawPacketLen = L3GD20H::READ_LEN + 1; +// break; +// } +// case(L3GD20H::CONFIGURE_CTRL_REGS): { +// commandBuffer[0] = L3GD20H::CTRL_REG_1 | L3GD20H::AUTO_INCREMENT_MASK; +// if(commandData == nullptr or commandDataLen != 5) { +// return DeviceHandlerIF::INVALID_COMMAND_PARAMETER; +// } +// +// ctrlReg1Value = commandData[0]; +// ctrlReg2Value = commandData[1]; +// ctrlReg3Value = commandData[2]; +// ctrlReg4Value = commandData[3]; +// ctrlReg5Value = commandData[4]; +// +// bool fsH = ctrlReg4Value & L3GD20H::SET_FS_1; +// bool fsL = ctrlReg4Value & L3GD20H::SET_FS_0; +// +// if(not fsH and not fsL) { +// scaleFactor = static_cast(L3GD20H::RANGE_DPS_00) / INT16_MAX; +// } +// else if(not fsH and fsL) { +// scaleFactor = static_cast(L3GD20H::RANGE_DPS_01) / INT16_MAX; +// } +// else { +// scaleFactor = static_cast(L3GD20H::RANGE_DPS_11) / INT16_MAX; +// } +// +// commandBuffer[1] = ctrlReg1Value; +// commandBuffer[2] = ctrlReg2Value; +// commandBuffer[3] = ctrlReg3Value; +// commandBuffer[4] = ctrlReg4Value; +// commandBuffer[5] = ctrlReg5Value; +// +// rawPacket = commandBuffer; +// rawPacketLen = 6; +// break; +// } +// case(L3GD20H::READ_CTRL_REGS): { +// commandBuffer[0] = L3GD20H::READ_START | L3GD20H::AUTO_INCREMENT_MASK | +// L3GD20H::READ_MASK; +// +// std::memset(commandBuffer + 1, 0, 5); +// rawPacket = commandBuffer; +// rawPacketLen = 6; +// break; +// } +// default: +// return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; +// } +// return HasReturnvaluesIF::RETURN_OK; +//} +// +//ReturnValue_t GyroHandlerL3GD20H::scanForReply(const uint8_t *start, size_t len, +// DeviceCommandId_t *foundId, size_t *foundLen) { +// /* For SPI, the ID will always be the one of the last sent command. */ +// *foundId = this->getPendingCommand(); +// *foundLen = this->rawPacketLen; +// +// /* Data with SPI Interface has always this answer */ +// if (start[0] == 0b11111111) { +// return HasReturnvaluesIF::RETURN_OK; +// } +// return DeviceHandlerIF::INVALID_DATA; +//} +// +//ReturnValue_t GyroHandlerL3GD20H::interpretDeviceReply(DeviceCommandId_t id, +// const uint8_t *packet) { +// ReturnValue_t result = HasReturnvaluesIF::RETURN_OK; +// switch(id) { +// case(L3GD20H::CONFIGURE_CTRL_REGS): { +// commandExecuted = true; +// break; +// } +// case(L3GD20H::READ_CTRL_REGS): { +// if(packet[1] == ctrlReg1Value and packet[2] == ctrlReg2Value and +// packet[3] == ctrlReg3Value and packet[4] == ctrlReg4Value and +// packet[5] == ctrlReg5Value) { +// commandExecuted = true; +// } +// else { +// /* Attempt reconfiguration. */ +// internalState = InternalState::CONFIGURE; +// return DeviceHandlerIF::DEVICE_REPLY_INVALID; +// } +// break; +// } +// case(L3GD20H::READ_REGS): { +// if(packet[1] != ctrlReg1Value and packet[2] != ctrlReg2Value and +// packet[3] != ctrlReg3Value and packet[4] != ctrlReg4Value and +// packet[5] != ctrlReg5Value) { +// return DeviceHandlerIF::DEVICE_REPLY_INVALID; +// } +// else { +// if(internalState == InternalState::CHECK_REGS) { +// commandExecuted = true; +// } +// } +// +// statusReg = packet[L3GD20H::STATUS_IDX]; +// +// float angVelocX = (packet[L3GD20H::OUT_X_H] << 8 | +// packet[L3GD20H::OUT_X_L]) * scaleFactor; +// float angVelocY = (packet[L3GD20H::OUT_Y_H] << 8 | +// packet[L3GD20H::OUT_Y_L]) * scaleFactor; +// float angVelocZ = (packet[L3GD20H::OUT_Z_H] << 8 | +// packet[L3GD20H::OUT_Z_L]) * scaleFactor; +// +// int8_t temperaturOffset = (-1) * packet[L3GD20H::TEMPERATURE_IDX]; +// float temperature = 25.0 + temperaturOffset; +//#if L3GD20_GYRO_DEBUG == 1 +// if(debugDivider->checkAndIncrement()) { +// /* Set terminal to utf-8 if there is an issue with micro printout. */ +//#if FSFW_CPP_OSTREAM_ENABLED == 1 +// sif::info << "GyroHandlerL3GD20H: Angular velocities in degrees per second:" << +// std::endl; +// sif::info << "X: " << angVelocX << " \xC2\xB0" << std::endl; +// sif::info << "Y: " << angVelocY << " \xC2\xB0" << std::endl; +// sif::info << "Z: " << angVelocZ << " \xC2\xB0" << std::endl; +//#else +// sif::printInfo("GyroHandlerL3GD20H: Angular velocities in degrees per second:\n"); +// sif::printInfo("X: %f " "\xC2\xB0" "T\n", angVelocX); +// sif::printInfo("Y: %f " "\xC2\xB0" "T\n", angVelocY); +// sif::printInfo("Z: %f " "\xC2\xB0" "T\n", angVelocZ); +//#endif +// } +//#endif +// +// PoolReadGuard readSet(&dataset); +// if(readSet.getReadResult() == HasReturnvaluesIF::RETURN_OK) { +// dataset.angVelocX = angVelocX; +// dataset.angVelocY = angVelocY; +// dataset.angVelocZ = angVelocZ; +// dataset.temperature = temperature; +// dataset.setValidity(true, true); +// } +// break; +// } +// default: +// return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; +// } +// return result; +//} +// +// +//uint32_t GyroHandlerL3GD20H::getTransitionDelayMs(Mode_t from, Mode_t to) { +// return 10000; +//} +// +//ReturnValue_t GyroHandlerL3GD20H::initializeLocalDataPool( +// localpool::DataPool &localDataPoolMap, LocalDataPoolManager &poolManager) { +// localDataPoolMap.emplace(L3GD20H::ANG_VELOC_X, +// new PoolEntry({0.0})); +// localDataPoolMap.emplace(L3GD20H::ANG_VELOC_Y, +// new PoolEntry({0.0})); +// localDataPoolMap.emplace(L3GD20H::ANG_VELOC_Z, +// new PoolEntry({0.0})); +// localDataPoolMap.emplace(L3GD20H::TEMPERATURE, +// new PoolEntry({0.0})); +// return HasReturnvaluesIF::RETURN_OK; +//} +// +//void GyroHandlerL3GD20H::fillCommandAndReplyMap() { +// insertInCommandAndReplyMap(L3GD20H::READ_REGS, 1, &dataset); +// insertInCommandAndReplyMap(L3GD20H::CONFIGURE_CTRL_REGS, 1); +// insertInCommandAndReplyMap(L3GD20H::READ_CTRL_REGS, 1); +//} +// +//void GyroHandlerL3GD20H::modeChanged() { +// internalState = InternalState::NONE; +//} diff --git a/misc/archive/GyroL3GD20Handler.h b/misc/archive/GyroL3GD20Handler.h new file mode 100644 index 0000000..b2e03b3 --- /dev/null +++ b/misc/archive/GyroL3GD20Handler.h @@ -0,0 +1,80 @@ +//#ifndef MISSION_DEVICES_GYROL3GD20HANDLER_H_ +//#define MISSION_DEVICES_GYROL3GD20HANDLER_H_ +// +//#include "devicedefinitions/GyroL3GD20Definitions.h" +//#include +// +//#include +//#include +// +// +///** +// * @brief Device Handler for the L3GD20H gyroscope sensor +// * (https://www.st.com/en/mems-and-sensors/l3gd20h.html) +// * @details +// * Advanced documentation: +// * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/L3GD20H_Gyro +// * +// * Data is read big endian with the smallest possible range of 245 degrees per second. +// */ +//class GyroHandlerL3GD20H: public DeviceHandlerBase { +//public: +// GyroHandlerL3GD20H(object_id_t objectId, object_id_t deviceCommunication, +// CookieIF* comCookie); +// virtual ~GyroHandlerL3GD20H(); +// +//protected: +// +// /* DeviceHandlerBase overrides */ +// ReturnValue_t buildTransitionDeviceCommand( +// DeviceCommandId_t *id) override; +// void doStartUp() override; +// void doShutDown() override; +// ReturnValue_t buildNormalDeviceCommand( +// DeviceCommandId_t *id) override; +// ReturnValue_t buildCommandFromCommand( +// DeviceCommandId_t deviceCommand, const uint8_t *commandData, +// size_t commandDataLen) override; +// ReturnValue_t scanForReply(const uint8_t *start, size_t len, +// DeviceCommandId_t *foundId, size_t *foundLen) override; +// ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, +// const uint8_t *packet) override; +// +// void fillCommandAndReplyMap() override; +// void modeChanged() override; +// uint32_t getTransitionDelayMs(Mode_t from, Mode_t to) override; +// ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, +// LocalDataPoolManager &poolManager) override; +// +//private: +// GyroPrimaryDataset dataset; +// +// enum class InternalState { +// NONE, +// CONFIGURE, +// CHECK_REGS, +// NORMAL +// }; +// InternalState internalState = InternalState::NONE; +// bool commandExecuted = false; +// +// uint8_t statusReg = 0; +// +// uint8_t ctrlReg1Value = L3GD20H::CTRL_REG_1_VAL; +// uint8_t ctrlReg2Value = L3GD20H::CTRL_REG_2_VAL; +// uint8_t ctrlReg3Value = L3GD20H::CTRL_REG_3_VAL; +// uint8_t ctrlReg4Value = L3GD20H::CTRL_REG_4_VAL; +// uint8_t ctrlReg5Value = L3GD20H::CTRL_REG_5_VAL; +// +// uint8_t commandBuffer[L3GD20H::READ_LEN + 1]; +// +// float scaleFactor = static_cast(L3GD20H::RANGE_DPS_00) / INT16_MAX; +// +//#if L3GD20_GYRO_DEBUG == 1 +// PeriodicOperationDivider* debugDivider = nullptr; +//#endif +//}; +// +// +// +//#endif /* MISSION_DEVICES_GYROL3GD20HANDLER_H_ */ diff --git a/misc/archive/Makefile b/misc/archive/Makefile new file mode 100644 index 0000000..b1ccd74 --- /dev/null +++ b/misc/archive/Makefile @@ -0,0 +1,387 @@ +#------------------------------------------------------------------------------- +# Makefile for EIVE OBSW +#------------------------------------------------------------------------------- +# User-modifiable options +#------------------------------------------------------------------------------- +# Fundamentals on the build process of C/C++ Software: +# https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html + +# Make documentation: https://www.gnu.org/software/make/manual/make.pdf +# Online: https://www.gnu.org/software/make/manual/make.html +# General rules: http://make.mad-scientist.net/papers/rules-of-makefiles/#rule3 +SHELL = /bin/sh + +# Chip & board used for compilation +# (can be overriden by adding CHIP=chip and BOARD=board to the command-line) +BOARD_FILE_ROOT = bsp_q7s +BOARD = linux +OS_FSFW = linux +CUSTOM_DEFINES += -D$(OS_FSFW) +CUSTOM_DEFINES += -DLINUX + +# General folder paths +FRAMEWORK_PATH = fsfw +MISSION_PATH = mission +CONFIG_PATH = fsfwconfig +TEST_PATH = test +UNITTEST_PATH = unittest +LIBCSP_PATH = libcsp + +# Board specific paths +BSP_PATH = $(BOARD_FILE_ROOT) +BOARDTEST_PATH = $(BOARD_FILE_ROOT)/boardtest + +# Output file basename +BASENAME = eiveobsw +BINARY_NAME = $(BASENAME)-$(BOARD) +# Output files will be put in this directory inside +OUTPUT_FOLDER = $(OS_FSFW) + +# Default debug output +DEBUG_LEVEL = -g3 + +# Optimization level. -O0 default, -Os for size, -O3 for speed and size. +OPTIMIZATION = -O0 + +ifdef GCOV +CUSTOM_DEFINES += -DGCOV +endif + +# Output directories +BUILDPATH = _bin +DEPENDPATH = _dep +OBJECTPATH = _obj +ifeq ($(MAKECMDGOALS), release) +BUILD_FOLDER = mission +else +BUILD_FOLDER = devel +endif + +DEPENDDIR = $(DEPENDPATH)/$(OUTPUT_FOLDER)/$(BUILD_FOLDER) +OBJDIR = $(OBJECTPATH)/$(OUTPUT_FOLDER)/$(BUILD_FOLDER) +BINDIR = $(BUILDPATH)/$(OUTPUT_FOLDER)/$(BUILD_FOLDER) + +CLEANDEP = $(DEPENDPATH)/$(OUTPUT_FOLDER) +CLEANOBJ = $(OBJECTPATH)/$(OUTPUT_FOLDER) +CLEANBIN1 = $(BUILDPATH)/$(OUTPUT_FOLDER)/mission +CLEANBIN2 = $(BUILDPATH)/$(OUTPUT_FOLDER)/devel + +#------------------------------------------------------------------------------- +# Tools and Includes +#------------------------------------------------------------------------------- + +# Tool suffix when cross-compiling +ifdef HOST_LINUX +CROSS_COMPILE = +else +CROSS_COMPILE = arm-linux-gnueabihf- +endif + +ifdef WINDOWS +# C Compiler +CC = $(CROSS_COMPILE)gcc.exe + +# C++ compiler +CXX = $(CROSS_COMPILE)g++.exe + +# Additional Tools +SIZE = $(CROSS_COMPILE)size.exe +STRIP = $(CROSS_COMPILE)strip.exe +CP = $(CROSS_COMPILE)objcopy.exe +else +# C Compiler +CC = $(CROSS_COMPILE)gcc + +# C++ compiler +CXX = $(CROSS_COMPILE)g++ + +# Additional Tools +SIZE = $(CROSS_COMPILE)size +STRIP = $(CROSS_COMPILE)strip +CP = $(CROSS_COMPILE)objcopy +endif + +HEXCOPY = $(CP) -O ihex +BINCOPY = $(CP) -O binary +# files to be compiled, will be filled in by include makefiles +# := assignment here is neccessary as initialization so that the += +# operator can be used in the submakefiles to achieve immediate evaluation. +# See: http://make.mad-scientist.net/evaluation-and-expansion/ +CSRC := +CXXSRC := +ASRC := +INCLUDES := + +# Directories where $(directoryname).mk files should be included from +SUBDIRS := $(FRAMEWORK_PATH) $(TEST_PATH) $(BSP_PATH) $(UNITTEST_PATH)\ + $(CONFIG_PATH) $(MISSION_PATH) $(LIBCSP_PATH) +# INCLUDES += framework/test/catch2 + +# ETL library include. +ETL_PATH = etl/include +I_INCLUDES += -I$(ETL_PATH) + +I_INCLUDES += $(addprefix -I, $(INCLUDES)) + +# This is a hack from http://make.mad-scientist.net/the-eval-function/ +# +# The problem is, that included makefiles should be aware of their relative path +# but not need to guess or hardcode it. So we set $(CURRENTPATH) for them. If +# we do this globally and the included makefiles want to include other makefiles as +# well, they would overwrite $(CURRENTPATH), screwing the include after them. +# +# By using a for-loop with an eval'd macro, we can generate the code to include all +# sub-makefiles (with the correct $(CURRENTPATH) set) before actually evaluating +# (and by this possibly changing $(CURRENTPATH)) them. +# +# This works recursively, if an included makefile wants to include, it can safely set +# $(SUBDIRS) (which has already been evaluated here) and do +# "$(foreach S,$(SUBDIRS),$(eval $(INCLUDE_FILE)))" +# $(SUBDIRS) must be relative to the project root, so to include subdir foo, set +# $(SUBDIRS) = $(CURRENTPATH)/foo. +define INCLUDE_FILE +CURRENTPATH := $S +include $(S)/$(notdir $S).mk +endef +$(foreach S,$(SUBDIRS),$(eval $(INCLUDE_FILE))) + + +#------------------------------------------------------------------------------- +# Source Files +#------------------------------------------------------------------------------- +# Additional source files which were not includes by other .mk +# files are added here. +# To help Makefile find source files, the source location paths +# can be added by using the VPATH variable +# See: https://www.gnu.org/software/make/manual/html_node/General-Search.html +# It is recommended to only use VPATH to add source locations +# See: http://make.mad-scientist.net/papers/how-not-to-use-vpath/ + +# Please ensure that no files are included by both .mk file and here ! + +# All C Sources included by .mk files are assigned here +# Add the objects to sources so dependency handling works +C_OBJECTS += $(CSRC:.c=.o) + +# Objects built from Assembly source files +ASM_OBJECTS = $(ASRC:.S=.o) + +# Objects built from C++ source files +CXX_OBJECTS += $(CXXSRC:.cpp=.o) + +#------------------------------------------------------------------------------- +# Build Configuration + Output +#------------------------------------------------------------------------------- + +TARGET = Debug +DEBUG_MESSAGE = Off +OPTIMIZATION_MESSAGE = Off + +# Define Messages +MSG_INFO = Software: EIVE OBSW Linux. +MSG_LINKING = Linking: +MSG_COMPILING = Compiling: +MSG_ASSEMBLING = Assembling: +MSG_BINARY = Generate binary: +MSG_OPTIMIZATION = Optimization: $(OPTIMIZATION), $(OPTIMIZATION_MESSAGE) +MSG_TARGET = Target Build: $(TARGET) +MSG_DEBUG = FSFW Debugging: $(DEBUG_MESSAGE) +MSG_COMIF = TMTC Communication Interface: $(COMIF_MESSAGE) + +# See: https://stackoverflow.com/questions/6687630/how-to-remove-unused-c-c-symbols-with-gcc-and-ld +# Used to throw away unused code. Reduces code size significantly ! +# -Wl,--gc-sections: needs to be passed to the linker to throw aways unused code +ifdef KEEP_UNUSED_CODE +PROTOTYPE_OPTIMIZATION = +DEAD_CODE_REMOVAL = +else +PROTOTYPE_OPTIMIZATION = -ffunction-sections -fdata-sections +DEAD_CODE_REMOVAL = -Wl,--gc-sections +# Link time optimization +# See https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html for reference +# Link time is larger and size of object files can not be retrieved +# but resulting binary is smaller. Could be used in mission/deployment build +# Requires -ffunction-section in linker call +LINK_TIME_OPTIMIZATION = -flto +OPTIMIZATION += $(PROTOTYPE_OPTIMIZATION) +endif + +# Dependency Flags +# These flags tell the compiler to build dependencies +# See: https://www.gnu.org/software/make/manual/html_node/Automatic-Prerequisites.html +# Using following guide: +# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ +DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPENDDIR)/$*.d + +# Flags for the compiler call +# - std: Which C++ version to use. Common versions: c++11, c++14 and c++17 +# - Wall: enable all warnings +# - Wextra: enable extra warnings +# - g: defines debug level +# - fmessage-length: to control the formatting algorithm for diagnostic messages; +# =0 means no line-wrapping is done; each error message appears on a single line +# - fno-exceptions: stops generating extra code needed to propagate exceptions, +# which can produce significant data size overhead +WARNING_FLAGS = -Wall -Wshadow=local -Wextra -Wimplicit-fallthrough=1 \ + -Wno-unused-parameter + +CXXDEFINES := $(CUSTOM_DEFINES) +CFLAGS += +CXXFLAGS += -I. $(DEBUG_LEVEL) $(OPTIMIZATION) $(DEPFLAGS) $(WARNING_FLAGS) \ + $(I_INCLUDES) -fmessage-length=0 $(CXXDEFINES) +CPPFLAGS += -std=c++17 -fno-exceptions +ASFLAGS = -Wall $(DEBUG_LEVEL) $(OPTIMIZATION) $(I_INCLUDES) -D__ASSEMBLY__ + +# Flags for the linker call +# LINK_INCLUDES specify the path to used libraries and the linker script +# LINK_LIBRARIES link HCC and HAL library and enable float support +LINK_FLAGS = $(DEBUG_LEVEL) $(DEAD_CODE_REMOVAL) $(OPTIMIZATION) -pthread +LINK_INCLUDES = +LINK_LIBRARIES = -lrt + +# Gnu Coverage Tools Flags +ifdef GCOV +GCOV_CXXFLAGS = -fprofile-arcs -ftest-coverage +CXXFLAGS += $(GCOV_CXXFLAGS) +GCOV_LINKER_LIBS = -lgcov -fprofile-arcs -ftest-coverage +LINK_LIBRARIES += $(GCOV_LINKER_LIBS) +endif + +#------------------------------------------------------------------------------- +# Rules +#------------------------------------------------------------------------------- +# Makefile rules: https://www.gnu.org/software/make/manual/html_node/Rules.html +# This is the primary section which defines the ruleset to build +# the executable from the sources. + +default: all + +# In this section, the binaries are built for all selected memories + +all: executable + +# Cleans all files +hardclean: + -rm -rf $(BUILDPATH) + -rm -rf $(OBJECTPATH) + -rm -rf $(DEPENDPATH) + +# Only clean files for current build +clean: + @echo $(DEPFILES) + -rm -rf $(CLEANOBJ) + -rm -rf $(CLEANBIN) + -rm -rf $(CLEANDEP) + +# Only clean binaries. Useful for changing the binary type when object files +# are already compiled so complete rebuild is not necessary +cleanbin: + -rm -rf $(BUILDPATH)/$(OUTPUT_FOLDER) + +# Build target configuration +release: OPTIMIZATION = -O2 $(PROTOTYPE_OPTIMIZATION) $(LINK_TIME_OPTIMIZATION) +release: LINK_TIME_OPTIMIZATION = -flto +release: TARGET = Release +release: OPTIMIZATION_MESSAGE = On with Link Time Optimization +release: DEBUG_LEVEL = -g0 + +debug: CXXDEFINES += -DDEBUG +debug: DEBUG_MESSAGE = On + +debug release: executable + +executable: $(BINDIR)/$(BINARY_NAME).elf + @echo + @echo $(MSG_INFO) + @echo $(MSG_TARGET) + @echo $(MSG_OPTIMIZATION) + @echo $(MSG_DEBUG) + +# For debugging. +# $(info $${C_OBJECTS} is [${C_OBJECTS}]) +# $(info $${CXX_OBJECTS} is [${CXX_OBJECTS}]) + +C_OBJECTS_PREFIXED = $(addprefix $(OBJDIR)/, $(C_OBJECTS)) +CXX_OBJECTS_PREFIXED = $(addprefix $(OBJDIR)/, $(CXX_OBJECTS)) +ASM_OBJECTS_PREFIXED = $(addprefix $(OBJDIR)/, $(ASM_OBJECTS)) +ALL_OBJECTS_PREFIXED = $(ASM_OBJECTS_PREFIXED) $(C_OBJECTS_PREFIXED) \ + $(CXX_OBJECTS_PREFIXED) + +# Useful for debugging the Makefile +# Also see: https://www.oreilly.com/openbook/make3/book/ch12.pdf +# $$(info $${ALL_OBJECTS_PREFIXED} is [${ALL_OBJECTS_PREFIXED}]) + +# Automatic variables are used here extensively. Some of them +# are escaped($$) to suppress immediate evaluation. The most important ones are: +# $@: Name of Target (left side of rule) +# $<: Name of the first prerequisite (right side of rule) +# @^: List of all prerequisite, omitting duplicates +# @D: Directory and file-within-directory part of $@ + +# Generates binary and displays all build properties +# -p with mkdir ignores error and creates directory when needed. + +# SHOW_DETAILS = 1 + +$(BINDIR)/$(BINARY_NAME).elf: $(ALL_OBJECTS_PREFIXED) + @echo $(MSG_LINKING) Target $@ + @mkdir -p $(@D) +ifdef SHOW_DETAILS + $(CXX) $(LINK_FLAGS) $(LINK_INCLUDES) -o $@ $^ $(LINK_LIBRARIES) +else + @$(CXX) $(LINK_FLAGS) $(LINK_INCLUDES) -o $@ $^ $(LINK_LIBRARIES) +endif +ifeq ($(BUILD_FOLDER), release) +# With Link Time Optimization, section size is not available + $(SIZE) $@ +else + @$(SIZE) $^ $@ +endif + + +$(OBJDIR)/%.o: %.cpp +$(OBJDIR)/%.o: %.cpp $(DEPENDDIR)/%.d | $(DEPENDDIR) + @echo $(I_INCLUDES) + @echo + @echo $(MSG_COMPILING) $< + @mkdir -p $(@D) +ifdef SHOW_DETAILS + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< +else + @$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< +endif + + +$(OBJDIR)/%.o: %.c +$(OBJDIR)/%.o: %.c $(DEPENDDIR)/%.d | $(DEPENDDIR) + @echo + @echo $(MSG_COMPILING) $< + @mkdir -p $(@D) +ifdef SHOW_DETAILS + $(CC) $(CXXFLAGS) $(CFLAGS) -c -o $@ $< +else + @$(CC) $(CXXFLAGS) $(CFLAGS) -c -o $@ $< +endif + + +#------------------------------------------------------------------------------- +# Dependency Handling +#------------------------------------------------------------------------------- + +# Dependency Handling according to following guide: +# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ +$(DEPENDDIR): + @mkdir -p $(@D) +DEPENDENCY_RELATIVE = $(CSRC:.c=.d) $(CXXSRC:.cpp=.d) +# This is the list of all dependencies +DEPFILES = $(addprefix $(DEPENDDIR)/, $(DEPENDENCY_RELATIVE)) +# Create subdirectories for dependencies +$(DEPFILES): + @mkdir -p $(@D) + + +# Include all dependencies +include $(wildcard $(DEPFILES)) + +# .PHONY tells make that these targets aren't files +.PHONY: clean sdramCfg release debug all hardclean diff --git a/misc/archive/Makefile-Hosted b/misc/archive/Makefile-Hosted new file mode 100644 index 0000000..3a93f62 --- /dev/null +++ b/misc/archive/Makefile-Hosted @@ -0,0 +1,440 @@ +#------------------------------------------------------------------------------- +# Makefile for SOURCE OBSW +#------------------------------------------------------------------------------- +# User-modifiable options +#------------------------------------------------------------------------------- +# Fundamentals on the build process of C/C++ Software: +# https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html + +# Make documentation: https://www.gnu.org/software/make/manual/make.pdf +# Online: https://www.gnu.org/software/make/manual/make.html +# General rules: http://make.mad-scientist.net/papers/rules-of-makefiles/#rule3 +SHELL = /bin/sh + +CUSTOM_DEFINES := + +# Chip & board used for compilation +# (can be overriden by adding CHIP=chip and BOARD=board to the command-line) +BOARD_FILE_ROOT = bsp_hosted +BOARD = host + +DETECTED_OS := +# Copied from stackoverflow, can be used to differentiate between Windows +# and Linux +ifeq ($(OS),Windows_NT) + DETECTED_OS = WINDOWS + CUSTOM_DEFINES += -DWIN32 + ifeq ($(PROCESSOR_ARCHITEW6432),AMD64) + CUSTOM_DEFINES += -DAMD64 + else + ifeq ($(PROCESSOR_ARCHITECTURE),AMD64) + CUSTOM_DEFINES += -DAMD64 + endif + ifeq ($(PROCESSOR_ARCHITECTURE),x86) + CUSTOM_DEFINES += -DIA32 + endif + endif +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Linux) + DETECTED_OS = LINUX + CUSTOM_DEFINES += -DLINUX + endif + ifeq ($(UNAME_S),Darwin) + CUSTOM_DEFINES += -DOSX + endif + UNAME_P := $(shell uname -p) + ifeq ($(UNAME_P),x86_64) + CUSTOM_DEFINES += -DAMD64 + endif + ifneq ($(filter %86,$(UNAME_P)),) + CUSTOM_DEFINES += -DIA32 + endif + ifneq ($(filter arm%,$(UNAME_P)),) + CUSTOM_DEFINES += -DARM + endif +endif + +ifeq (${DETECTED_OS}, WINDOWS) +OS_FSFW = host +else +OS_FSFW = linux +endif + +CUSTOM_DEFINES += -D$(OS_FSFW) + +# General folder paths +FRAMEWORK_PATH = fsfw +MISSION_PATH = mission +CONFIG_PATH = $(BOARD_FILE_ROOT)/fsfwconfig +TEST_PATH = test +UNITTEST_PATH = unittest + +# Board specific paths +BSP_PATH = $(BOARD_FILE_ROOT) +BOARDTEST_PATH = $(BOARD_FILE_ROOT)/boardtest + +# Output file basename +BASENAME = eiveobsw +BINARY_NAME = $(BASENAME)-$(BOARD) +# Output files will be put in this directory inside +OUTPUT_FOLDER = $(OS_FSFW) + +# Default debug output +DEBUG_LEVEL = -g3 + +# Optimization level. -O0 default, -Os for size, -O3 for speed and size. +OPTIMIZATION = -O0 + +ifdef GCOV +CUSTOM_DEFINES += -DGCOV +endif + +# Output directories +BUILDPATH = _bin +DEPENDPATH = _dep +OBJECTPATH = _obj +ifeq ($(MAKECMDGOALS),release) +BUILD_FOLDER = mission +else +BUILD_FOLDER = devel +endif + +DEPENDDIR = $(DEPENDPATH)/$(OUTPUT_FOLDER)/$(BUILD_FOLDER) +OBJDIR = $(OBJECTPATH)/$(OUTPUT_FOLDER)/$(BUILD_FOLDER) +BINDIR = $(BUILDPATH)/$(OUTPUT_FOLDER)/$(BUILD_FOLDER) + +CLEANDEP = $(DEPENDPATH)/$(OUTPUT_FOLDER) +CLEANOBJ = $(OBJECTPATH)/$(OUTPUT_FOLDER) +CLEANBIN = $(BUILDPATH)/$(OUTPUT_FOLDER) + +#------------------------------------------------------------------------------- +# Tools and Includes +#------------------------------------------------------------------------------- + +# Tool suffix when cross-compiling +CROSS_COMPILE = + +# C Compiler +CC = $(CROSS_COMPILE)gcc + +# C++ compiler +CXX = $(CROSS_COMPILE)g++ + +# Additional Tools +SIZE = $(CROSS_COMPILE)size +STRIP = $(CROSS_COMPILE)strip +CP = $(CROSS_COMPILE)objcopy + +HEXCOPY = $(CP) -O ihex +BINCOPY = $(CP) -O binary +# files to be compiled, will be filled in by include makefiles +# := assignment here is neccessary as initialization so that the += +# operator can be used in the submakefiles to achieve immediate evaluation. +# See: http://make.mad-scientist.net/evaluation-and-expansion/ +CSRC := +CXXSRC := +ASRC := +INCLUDES := + +# Directories where $(directoryname).mk files should be included from +SUBDIRS := $(FRAMEWORK_PATH) $(TEST_PATH) $(BSP_PATH) \ + $(CONFIG_PATH) $(MISSION_PATH) +# $(MISSION_PATH) $(CONFIG_PATH) +# INCLUDES += framework/test/catch2 + +# ETL library include. +ETL_PATH = etl/include +I_INCLUDES += -I$(ETL_PATH) + +I_INCLUDES += $(addprefix -I, $(INCLUDES)) + +# This is a hack from http://make.mad-scientist.net/the-eval-function/ +# +# The problem is, that included makefiles should be aware of their relative path +# but not need to guess or hardcode it. So we set $(CURRENTPATH) for them. If +# we do this globally and the included makefiles want to include other makefiles as +# well, they would overwrite $(CURRENTPATH), screwing the include after them. +# +# By using a for-loop with an eval'd macro, we can generate the code to include all +# sub-makefiles (with the correct $(CURRENTPATH) set) before actually evaluating +# (and by this possibly changing $(CURRENTPATH)) them. +# +# This works recursively, if an included makefile wants to include, it can safely set +# $(SUBDIRS) (which has already been evaluated here) and do +# "$(foreach S,$(SUBDIRS),$(eval $(INCLUDE_FILE)))" +# $(SUBDIRS) must be relative to the project root, so to include subdir foo, set +# $(SUBDIRS) = $(CURRENTPATH)/foo. +define INCLUDE_FILE +CURRENTPATH := $S +include $(S)/$(notdir $S).mk +endef +$(foreach S,$(SUBDIRS),$(eval $(INCLUDE_FILE))) + + +#------------------------------------------------------------------------------- +# Source Files +#------------------------------------------------------------------------------- +# Additional source files which were not includes by other .mk +# files are added here. +# To help Makefile find source files, the source location paths +# can be added by using the VPATH variable +# See: https://www.gnu.org/software/make/manual/html_node/General-Search.html +# It is recommended to only use VPATH to add source locations +# See: http://make.mad-scientist.net/papers/how-not-to-use-vpath/ + +# Please ensure that no files are included by both .mk file and here ! + +# VPATH += mission/pus/ + +#ifeq ($(DETECTED_OS), LINUX) +#CXXSRC += $(FRAMEWORK_PATH)/osal/linux/TcUnixUdpPollingTask.cpp +#CXXSRC += $(FRAMEWORK_PATH)/osal/linux/TmTcUnixUdpBridge.cpp +#endif + +# All C Sources included by .mk files are assigned here +# Add the objects to sources so dependency handling works +C_OBJECTS += $(CSRC:.c=.o) + +# Objects built from Assembly source files +ASM_OBJECTS = $(ASRC:.S=.o) + +# Objects built from C++ source files +CXX_OBJECTS += $(CXXSRC:.cpp=.o) + +#------------------------------------------------------------------------------- +# Build Configuration + Output +#------------------------------------------------------------------------------- + +TARGET = Debug +DEBUG_MESSAGE = Off +OPTIMIZATION_MESSAGE = Off + +# Define Messages +MSG_INFO = Software: Hosted EIVE OBSW. +MSG_LINKING = Linking: +MSG_COMPILING = Compiling: +MSG_ASSEMBLING = Assembling: +MSG_BINARY = Generate binary: +MSG_OPTIMIZATION = Optimization: $(OPTIMIZATION), $(OPTIMIZATION_MESSAGE) +MSG_TARGET = Target Build: $(TARGET) +MSG_DEBUG = FSFW Debugging: $(DEBUG_MESSAGE) +MSG_COMIF = TMTC Communication Interface: $(COMIF_MESSAGE) + +# See: https://stackoverflow.com/questions/6687630/how-to-remove-unused-c-c-symbols-with-gcc-and-ld +# Used to throw away unused code. Reduces code size significantly ! +# -Wl,--gc-sections: needs to be passed to the linker to throw aways unused code +ifdef KEEP_UNUSED_CODE +PROTOTYPE_OPTIMIZATION = +DEAD_CODE_REMOVAL = +else +PROTOTYPE_OPTIMIZATION = -ffunction-sections -fdata-sections +DEAD_CODE_REMOVAL = -Wl,--gc-sections +# Link time optimization +# See https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html for reference +# Link time is larger and size of object files can not be retrieved +# but resulting binary is smaller. Could be used in mission/deployment build +# Requires -ffunction-section in linker call +OPTIMIZATION += $(PROTOTYPE_OPTIMIZATION) +endif + +# Dependency Flags +# These flags tell the compiler to build dependencies +# See: https://www.gnu.org/software/make/manual/html_node/Automatic-Prerequisites.html +# Using following guide: +# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ +DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPENDDIR)/$*.d + +# Flags for the compiler call +# - std: Which C++ version to use. Common versions: c++11, c++14 and c++17 +# - Wall: enable all warnings +# - Wextra: enable extra warnings +# - g: defines debug level +# - fmessage-length: to control the formatting algorithm for diagnostic messages; +# =0 means no line-wrapping, each error message appears on a single line +# - fno-exceptions: stops generating extra code needed to propagate exceptions, +# which can produce significant data size overhead +WARNING_FLAGS = -Wall -Wshadow=local -Wextra -Wimplicit-fallthrough=1 \ + -Wno-unused-parameter + +CXXDEFINES := $(CUSTOM_DEFINES) +CFLAGS += +CXXFLAGS += -I. $(DEBUG_LEVEL) $(WARNIING_FLAGS) $(I_INCLUDES) $(DEPFLAGS) \ + -fmessage-length=0 $(CXXDEFINES) $(OPTIMIZATION) +CPPFLAGS += -std=c++2a -fno-exceptions +ASFLAGS = -Wall -g $(OPTIMIZATION) $(I_INCLUDES) -D__ASSEMBLY__ + +# Flags for the linker call +# LINK_INCLUDES specify the path to used libraries and the linker script +# LINK_LIBRARIES link HCC and HAL library and enable float support +LDFLAGS := -g3 -pthread $(DEAD_CODE_REMOVAL) $(OPTIMIZATION) +LINK_LIBRARIES := + +ifeq ($(OS),Windows_NT) +LINK_LIBRARIES += -lwsock32 -lws2_32 +else +LINK_LIBRARIES += -lrt +endif + +# Gnu Coverage Tools Flags +ifdef GCOV +GCOV_CXXFLAGS = -fprofile-arcs -ftest-coverage +CXXFLAGS += $(GCOV_CXXFLAGS) +GCOV_LINKER_LIBS = -lgcov -fprofile-arcs -ftest-coverage +LINK_LIBRARIES += $(GCOV_LINKER_LIBS) +endif + +#------------------------------------------------------------------------------- +# Rules +#------------------------------------------------------------------------------- +# Makefile rules: https://www.gnu.org/software/make/manual/html_node/Rules.html +# This is the primary section which defines the ruleset to build +# the executable from the sources. + +default: all + +# Cleans all files +hardclean: + -rm -rf $(BUILDPATH) + -rm -rf $(OBJECTPATH) + -rm -rf $(DEPENDPATH) + +# Only clean files for current build +clean: + -rm -rf $(CLEANOBJ) + -rm -rf $(CLEANBIN) + -rm -rf $(CLEANDEP) + +# Only clean binaries. Useful for changing the binary type when object files are already compiled +# so complete rebuild is not necessary +cleanbin: + -rm -rf $(BUILDPATH)/$(OUTPUT_FOLDER) + +# In this section, the binaries are built for all selected memories + +all: executable + +# Build target configuration +release: OPTIMIZATION = -Os $(PROTOTYPE_OPTIMIZATION) $(LINK_TIME_OPTIMIZATION) +# Problematic on MinGW +ifneq ($(OS),Windows_NT) +release: LINK_TIME_OPTIMIZATION = -flto +endif +release: TARGET = Release +release: OPTIMIZATION_MESSAGE = On with Link Time Optimization +release: DEBUG_LEVEL = -g0 + +debug: CXXDEFINES += -DDEBUG +debug: DEBUG_MESSAGE = On + +ifndef KEEP_UNUSED_CODE +debug release: OPTIMIZATION_MESSAGE = Off with unused code removal +else +debug release: OPTIMIZATION_MESSAGE = Off +endif + +debug release: executable + +# executable: $(BINDIR)/$(BINARY_NAME).bin +ifeq ($(OS),Windows_NT) +executable: $(BINDIR)/$(BINARY_NAME).exe +else +executable: $(BINDIR)/$(BINARY_NAME).elf +endif + @echo + @echo $(MSG_INFO) + @echo $(MSG_TARGET) + @echo $(MSG_OPTIMIZATION) + @echo $(MSG_DEBUG) + +C_OBJECTS_PREFIXED = $(addprefix $(OBJDIR)/, $(C_OBJECTS)) +CXX_OBJECTS_PREFIXED = $(addprefix $(OBJDIR)/, $(CXX_OBJECTS)) +ASM_OBJECTS_PREFIXED = $(addprefix $(OBJDIR)/, $(ASM_OBJECTS)) +ALL_OBJECTS_PREFIXED = $(ASM_OBJECTS_PREFIXED) $(C_OBJECTS_PREFIXED) \ + $(CXX_OBJECTS_PREFIXED) + +# Useful for debugging the Makefile +# Also see: https://www.oreilly.com/openbook/make3/book/ch12.pdf +# $$(info $${CXXDEFINES} is [${CXXDEFINES}]) +# $$(info $${CXXSRC} is [${CXXSRC}]) + +# Automatic variables are used here extensively. Some of them +# are escaped($$) to suppress immediate evaluation. The most important ones are: +# $@: Name of Target (left side of rule) +# $<: Name of the first prerequisite (right side of rule) +# @^: List of all prerequisite, omitting duplicates +# @D: Directory and file-within-directory part of $@ + +# Generates binary and displays all build properties +# -p with mkdir ignores error and creates directory when needed. + +# SHOW_DETAILS = 1 + +ifeq ($(OS),Windows_NT) +$(BINDIR)/$(BINARY_NAME).exe: $(ALL_OBJECTS_PREFIXED) +else +$(BINDIR)/$(BINARY_NAME).elf: $(ALL_OBJECTS_PREFIXED) +endif + @echo + @echo $(MSG_LINKING) Target $@ + @mkdir -p $(@D) + @$(CXX) $(LDFLAGS) $(LINK_INCLUDES) -o $@ $^ $(LINK_LIBRARIES) +ifeq ($(BUILD_FOLDER), mission) +# With Link Time Optimization, section size is not available + $(SIZE) $@ +else + $(SIZE) $^ $@ +endif + +# Build new objects for changed dependencies. +$(OBJDIR)/%.o: %.cpp +$(OBJDIR)/%.o: %.cpp $(DEPENDDIR)/%.d | $(DEPENDDIR) + @echo + @echo $(MSG_COMPILING) $< + @mkdir -p $(@D) +ifdef SHOW_DETAILS + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< +else + @$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< +endif + +$(OBJDIR)/%.o: %.c +$(OBJDIR)/%.o: %.c $(DEPENDDIR)/%.d | $(DEPENDDIR) + @echo + @echo $(MSG_COMPILING) $< + @mkdir -p $(@D) +ifdef SHOW_DETAILS + $(CC) $(CXXFLAGS) $(CFLAGS) -c -o $@ $< +else + @$(CC) $(CXXFLAGS) $(CFLAGS) -c -o $@ $< +endif + +$(OBJDIR)/%.o: %.S Makefile + @echo + @echo $(MSG_ASSEMBLING) $< + @mkdir -p $(@D) +ifdef SHOW_DETAILS + $(CC) $(ASFLAGS) -c -o $@ $< +else + @$(CC) $(ASFLAGS) -c -o $@ $< +endif + +#------------------------------------------------------------------------------- +# Dependency Handling +#------------------------------------------------------------------------------- + +# Dependency Handling according to following guide: +# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ +$(DEPENDDIR): + @mkdir -p $(@D) +DEPENDENCY_RELATIVE = $(CSRC:.c=.d) $(CXXSRC:.cpp=.d) +# This is the list of all dependencies +DEPFILES = $(addprefix $(DEPENDDIR)/, $(DEPENDENCY_RELATIVE)) +# Create subdirectories for dependencies +$(DEPFILES): + @mkdir -p $(@D) +# Include all dependencies +include $(wildcard $(DEPFILES)) + +# .PHONY tells make that these targets aren't files +.PHONY: clean sdramCfg release debug all hardclean diff --git a/misc/archive/RPiGPIO.cpp b/misc/archive/RPiGPIO.cpp new file mode 100644 index 0000000..7ffb024 --- /dev/null +++ b/misc/archive/RPiGPIO.cpp @@ -0,0 +1,123 @@ +#include "RPiGPIO.h" + +#include +#include +#include +#include +#include + + +int RPiGPIO::enablePin(int pin) { + char buffer[BUFFER_MAX]; + ssize_t bytes_written; + int fd; + + fd = open("/sys/class/gpio/export", O_WRONLY); + if (fd == -1) { + sif::error << "Failed to open export of pin " << pin << " for writing!" << std::endl; + return -1; + } + + bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin); + write(fd, buffer, bytes_written); + close(fd); + return 0; +} + +int RPiGPIO::disablePin(int pin) { + char buffer[BUFFER_MAX]; + ssize_t bytes_written; + int fd; + + fd = open("/sys/class/gpio/unexport", O_WRONLY); + if (fd == -1) { + sif::error << "Failed to open unexport of pin " << pin << " for writing!" << std::endl; + return -1; + } + + bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin); + write(fd, buffer, bytes_written); + close(fd); + return(0); +} + +int RPiGPIO::pinDirection(int pin, Directions dir) { + + char path[DIRECTION_MAX]; + + snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/direction", pin); + int fd = open(path, O_WRONLY); + if (fd == -1) { + sif::error << "Failed to open gpio " << pin << " direction for writing!" << std::endl; + return -1; + } + + int result = 0; + if(dir == Directions::IN) { + result = write(fd, "in", IN_WRITE_SIZE); + } + else { + result = write(fd, "out", OUT_WRITE_SIZE); + } + + if (result != 0) { + sif::error << "Failed to set direction!" << std::endl; + return -2; + } + + close(fd); + return 0; +} + +int RPiGPIO::readPin(int pin) { + char path[VALUE_MAX]; + char value_str[3]; + + snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin); + int fd = open(path, O_RDONLY); + if (fd == -1) { + sif::error << "RPiGPIO::readPin: Failed to open GPIO pin " << pin << "!" << std::endl; + return -1; + } + + if (read(fd, value_str, 3) == -1) { + sif::error << "Failed to read value!" << std::endl; + return -1; + } + + close(fd); + char* endPtr = nullptr; + + return strtol(value_str, &endPtr, 10); +} + +int RPiGPIO::writePin(int pin, States state) { + + char path[VALUE_MAX]; + int fd; + + snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, O_WRONLY); + if (fd == -1) { + sif::error << "RPiGPIO::writePin: Failed to open GPIO pin " << pin << "!" << std::endl; + return -1; + } + + int result = 0; + if(state == States::LOW) { + result = write(fd, "0", 1); + } + else { + result = write(fd, "1", 1); + } + + + if (result != 0) { + sif::error << "Failed to write pin " << pin << " value to " << static_cast(state) + << "!" << std::endl; + return -1; + } + + close(fd); + return 0; +} diff --git a/misc/archive/RPiGPIO.h b/misc/archive/RPiGPIO.h new file mode 100644 index 0000000..400a37d --- /dev/null +++ b/misc/archive/RPiGPIO.h @@ -0,0 +1,41 @@ +#ifndef BSP_RPI_BOARDTEST_RPIGPIO_H_ +#define BSP_RPI_BOARDTEST_RPIGPIO_H_ + +#include + +/** + * @brief Really simple C++ GPIO wrapper for the Raspberry Pi, using the sysfs interface. + * Use BCM pins notation (https://pinout.xyz/#) + * + */ +class RPiGPIO { +public: + enum Directions { + IN = 0, + OUT = 1 + }; + + enum States { + LOW = 0, + HIGH = 1 + }; + + static int enablePin(int pin); + static int disablePin(int pin); + static int pinDirection(int pin, Directions dir); + static int readPin(int pin); + static int writePin(int pin, States state); + +private: + + + static constexpr uint8_t BUFFER_MAX = 3; + static constexpr uint8_t DIRECTION_MAX = 35; + static constexpr uint8_t VALUE_MAX = 30; + + static constexpr uint8_t IN_WRITE_SIZE = 3; + static constexpr uint8_t OUT_WRITE_SIZE = 4; +}; + + +#endif /* BSP_RPI_BOARDTEST_RPIGPIO_H_ */ diff --git a/misc/eclipse/.cproject b/misc/eclipse/.cproject new file mode 100644 index 0000000..154cb27 --- /dev/null +++ b/misc/eclipse/.cproject @@ -0,0 +1,1549 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/misc/eclipse/.project b/misc/eclipse/.project new file mode 100644 index 0000000..86d9858 --- /dev/null +++ b/misc/eclipse/.project @@ -0,0 +1,27 @@ + + + eive-obsw + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/misc/eclipse/host/eive-linux-host-debug-cmake.launch b/misc/eclipse/host/eive-linux-host-debug-cmake.launch new file mode 100644 index 0000000..bf44dfc --- /dev/null +++ b/misc/eclipse/host/eive-linux-host-debug-cmake.launch @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/eclipse/host/eive-linux-host-release-cmake.launch b/misc/eclipse/host/eive-linux-host-release-cmake.launch new file mode 100644 index 0000000..f2647a4 --- /dev/null +++ b/misc/eclipse/host/eive-linux-host-release-cmake.launch @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/eclipse/host/eive-mingw-debug-cmake.launch b/misc/eclipse/host/eive-mingw-debug-cmake.launch new file mode 100644 index 0000000..9109d83 --- /dev/null +++ b/misc/eclipse/host/eive-mingw-debug-cmake.launch @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/eclipse/host/eive-mingw-release-cmake.launch b/misc/eclipse/host/eive-mingw-release-cmake.launch new file mode 100644 index 0000000..7547141 --- /dev/null +++ b/misc/eclipse/host/eive-mingw-release-cmake.launch @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/eclipse/host/eive-unittest.launch b/misc/eclipse/host/eive-unittest.launch new file mode 100644 index 0000000..38577b7 --- /dev/null +++ b/misc/eclipse/host/eive-unittest.launch @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mission/CMakeLists.txt b/mission/CMakeLists.txt new file mode 100644 index 0000000..bb7cf14 --- /dev/null +++ b/mission/CMakeLists.txt @@ -0,0 +1,17 @@ +add_subdirectory(controller) +add_subdirectory(utility) +add_subdirectory(memory) +add_subdirectory(tmtc) +add_subdirectory(system) +add_subdirectory(com) +add_subdirectory(acs) +add_subdirectory(power) +add_subdirectory(tcs) +add_subdirectory(payload) +add_subdirectory(cfdp) +add_subdirectory(config) + +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE SolarArrayDeploymentHandler.cpp genericFactory.cpp + pollingSeqTables.cpp scheduling.cpp) diff --git a/mission/SolarArrayDeploymentHandler.cpp b/mission/SolarArrayDeploymentHandler.cpp new file mode 100644 index 0000000..b8ec4d7 --- /dev/null +++ b/mission/SolarArrayDeploymentHandler.cpp @@ -0,0 +1,519 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "devices/gpioIds.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/objectmanager/ObjectManager.h" +#include "fsfw_hal/common/gpio/GpioCookie.h" + +static constexpr bool DEBUG_MODE = true; + +SolarArrayDeploymentHandler::SolarArrayDeploymentHandler(object_id_t setObjectId_, + GpioIF& gpioInterface, + PowerSwitchIF& mainLineSwitcher_, + power::Switches mainLineSwitch_, + gpioId_t deplSA1, gpioId_t deplSA2, + SdCardMountedIF& sdcMountedIF) + : SystemObject(setObjectId_), + gpioInterface(gpioInterface), + deplSA1(deplSA1), + deplSA2(deplSA2), + mainLineSwitcher(mainLineSwitcher_), + mainLineSwitch(mainLineSwitch_), + sdcMan(sdcMountedIF), + actionHelper(this, nullptr) { + auto mqArgs = MqArgs(setObjectId_, static_cast(this)); + commandQueue = QueueFactory::instance()->createMessageQueue( + cmdQueueSize, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); +} + +SolarArrayDeploymentHandler::~SolarArrayDeploymentHandler() = default; + +ReturnValue_t SolarArrayDeploymentHandler::performOperation(uint8_t operationCode) { + using namespace std::filesystem; +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "SA DEPL"); +#endif + if (opDivider.checkAndIncrement()) { + auto activeSdc = sdcMan.getActiveSdCard(); + std::error_code e; + if (activeSdc and activeSdc.value() == sd::SdCard::SLOT_0 and + sdcMan.isSdCardUsable(activeSdc.value())) { + if (exists(SD_0_DEPL_FILE, e)) { + // perform autonomous deployment handling + performAutonomousDepl(sd::SdCard::SLOT_0, dryRunStringInFile(SD_0_DEPL_FILE)); + } + } else if (activeSdc and activeSdc.value() == sd::SdCard::SLOT_1 and + sdcMan.isSdCardUsable(activeSdc.value())) { + if (exists(SD_1_DEPL_FILE, e)) { + // perform autonomous deployment handling + performAutonomousDepl(sd::SdCard::SLOT_1, dryRunStringInFile(SD_1_DEPL_FILE)); + } + } else { + // TODO: This is FDIR domain. If both SD cards are not available for whatever reason, + // there is not much we can do except somehow use the scratch buffer which is + // not non-volatile. Implementation effort is considerable as well. + } + } + readCommandQueue(); + handleStateMachine(); + return returnvalue::OK; +} + +void SolarArrayDeploymentHandler::handleStateMachine() { + if (stateMachine == MAIN_POWER_ON) { + mainLineSwitcher.sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_ON); + mainSwitchCountdown.setTimeout(mainLineSwitcher.getSwitchDelayMs()); + stateMachine = WAIT_MAIN_POWER_ON; + sif::info << "S/A Deployment: Deployment power line on" << std::endl; + } + if (stateMachine == MAIN_POWER_OFF) { + // These should never fail + allOff(); + stateMachine = WAIT_MAIN_POWER_OFF; + sif::info << "S/A Deployment: Deployment power line off" << std::endl; + } + if (stateMachine == WAIT_MAIN_POWER_ON) { + if (checkMainPowerOn()) { + if (DEBUG_MODE) { + sif::debug << "SA DEPL FSM: WAIT_MAIN_POWER_ON done -> SWITCH_DEPL_GPIOS" << std::endl; + } + stateMachine = SWITCH_DEPL_GPIOS; + } + } + if (stateMachine == WAIT_MAIN_POWER_OFF) { + if (checkMainPowerOff()) { + if (DEBUG_MODE) { + sif::debug << "SA DEPL FSM: WAIT_MAIN_POWER_OFF done -> FSM DONE" << std::endl; + } + sif::info << "S/A Deployment: FSM done" << std::endl; + finishFsm(returnvalue::OK); + } + } + if (stateMachine == SWITCH_DEPL_GPIOS) { + burnCountdown.setTimeout(fsmInfo.burnCountdownMs); + // This should never fail + channelAlternationCd.resetTimer(); + if (not fsmInfo.dryRun) { + if (fsmInfo.initChannel == 0) { + sa2Off(); + sa1On(); + fsmInfo.alternationDummy = true; + } else { + sa1Off(); + sa2On(); + fsmInfo.alternationDummy = false; + } + } + sif::info << "S/A Deployment: Burning" << std::endl; + triggerEvent(BURN_PHASE_START, fsmInfo.burnCountdownMs, fsmInfo.dryRun); + channelAlternationCd.resetTimer(); + stateMachine = BURNING; + } + if (stateMachine == BURNING) { + saGpioAlternation(); + if (burnCountdown.hasTimedOut()) { + if (DEBUG_MODE) { + sif::debug << "SA DEPL FSM: BURNING done -> WAIT_MAIN_POWER_OFF" << std::endl; + } + allOff(); + triggerEvent(BURN_PHASE_DONE, fsmInfo.burnCountdownMs, fsmInfo.dryRun); + stateMachine = WAIT_MAIN_POWER_OFF; + } + } +} + +ReturnValue_t SolarArrayDeploymentHandler::performAutonomousDepl(sd::SdCard sdCard, bool dryRun) { + using namespace std::filesystem; + using namespace std; + auto initFile = [](const char* filename) { + ofstream of(filename); + of << "phase: init\n"; + of << "secs_since_start: 0\n"; + }; + std::error_code e; + if (sdCard == sd::SdCard::SLOT_0) { + if (not exists(SD_0_DEPLY_INFO, e)) { + initFile(SD_0_DEPLY_INFO); + } + if (not autonomousDeplForFile(sd::SdCard::SLOT_0, SD_0_DEPLY_INFO, dryRun)) { + initFile(SD_0_DEPLY_INFO); + } + } else if (sdCard == sd::SdCard::SLOT_1) { + if (not exists(SD_1_DEPLY_INFO, e)) { + initFile(SD_1_DEPLY_INFO); + } + if (not autonomousDeplForFile(sd::SdCard::SLOT_1, SD_1_DEPLY_INFO, dryRun)) { + initFile(SD_1_DEPLY_INFO); + } + } + return returnvalue::OK; +} + +bool SolarArrayDeploymentHandler::autonomousDeplForFile(sd::SdCard sdCard, const char* infoFile, + bool dryRun) { + using namespace std; + std::error_code e; + ifstream file(infoFile); + if (file.bad()) { + return false; + } + string line; + string word; + unsigned int lineNum = 0; + AutonomousDeplState deplState = AutonomousDeplState::INIT; + bool stateSwitch = false; + uint32_t secsSinceBoot = 0; + while (std::getline(file, line)) { + std::istringstream iss(line); + if (lineNum == 0) { + iss >> word; + if (word.find("phase:") == string::npos) { + return false; + } + iss >> word; + if (word.find(PHASE_INIT_STR) != string::npos) { + deplState = AutonomousDeplState::INIT; + } else if (word.find(PHASE_FIRST_BURN_STR) != string::npos) { + deplState = AutonomousDeplState::FIRST_BURN; + } else if (word.find(PHASE_WAIT_STR) != string::npos) { + deplState = AutonomousDeplState::WAIT; + } else if (word.find(PHASE_SECOND_BURN_STR) != string::npos) { + deplState = AutonomousDeplState::SECOND_BURN; + } else if (word.find(PHASE_DONE) != string::npos) { + deplState = AutonomousDeplState::DONE; + } else { + return false; + } + } else if (lineNum == 1) { + iss >> word; + if (iss.bad()) { + return false; + } + if (word.find("secs_since_start:") == string::npos) { + return false; + } + + iss >> secsSinceBoot; + if (iss.bad()) { + return false; + } + if (not initUptime) { + initUptime = secsSinceBoot; + } + + auto switchCheck = [&](AutonomousDeplState expected) { + if (deplState != expected) { + deplState = expected; + stateSwitch = true; + } + }; + if ((secsSinceBoot >= FIRST_BURN_START_TIME) and (secsSinceBoot < FIRST_BURN_END_TIME)) { + switchCheck(AutonomousDeplState::FIRST_BURN); + } else if ((secsSinceBoot >= WAIT_START_TIME) and (secsSinceBoot < WAIT_END_TIME)) { + switchCheck(AutonomousDeplState::WAIT); + } else if ((secsSinceBoot >= SECOND_BURN_START_TIME) and + (secsSinceBoot < SECOND_BURN_END_TIME)) { + switchCheck(AutonomousDeplState::SECOND_BURN); + } else if (secsSinceBoot >= SECOND_BURN_END_TIME) { + switchCheck(AutonomousDeplState::DONE); + } + } + lineNum++; + } + if (initUptime) { + secsSinceBoot = initUptime.value(); + } + // Uptime has increased by X seconds so we need to update the uptime count inside the file + secsSinceBoot += Clock::getUptime().tv_sec; + if (stateSwitch or firstAutonomousCycle) { + if (deplState == AutonomousDeplState::FIRST_BURN or + deplState == AutonomousDeplState::SECOND_BURN) { + startFsmOn(config::SA_DEPL_BURN_TIME_SECS, (config::SA_DEPL_BURN_TIME_SECS / 2) * 1000, 0, + dryRun); + } else if (deplState == AutonomousDeplState::WAIT or deplState == AutonomousDeplState::DONE or + deplState == AutonomousDeplState::INIT) { + startFsmOff(); + } + } + if (deplState == AutonomousDeplState::DONE) { + std::filesystem::remove(infoFile, e); + if (sdCard == sd::SdCard::SLOT_0) { + std::filesystem::remove(SD_0_DEPL_FILE, e); + } else { + std::filesystem::remove(SD_1_DEPL_FILE, e); + } + triggerEvent(AUTONOMOUS_DEPLOYMENT_COMPLETED); + } else { + std::ofstream of(infoFile); + if (of.bad()) { + return false; + } + of << "phase: "; + if (deplState == AutonomousDeplState::INIT) { + of << PHASE_INIT_STR << "\n"; + } else if (deplState == AutonomousDeplState::FIRST_BURN) { + of << PHASE_FIRST_BURN_STR << "\n"; + } else if (deplState == AutonomousDeplState::WAIT) { + of << PHASE_WAIT_STR << "\n"; + } else if (deplState == AutonomousDeplState::SECOND_BURN) { + of << PHASE_SECOND_BURN_STR << "\n"; + } + of << "secs_since_start: " << std::to_string(secsSinceBoot) << "\n"; + } + if (firstAutonomousCycle) { + firstAutonomousCycle = false; + } + return true; +} + +bool SolarArrayDeploymentHandler::checkMainPowerOn() { return checkMainPower(true); } + +bool SolarArrayDeploymentHandler::checkMainPowerOff() { return checkMainPower(false); } + +bool SolarArrayDeploymentHandler::checkMainPower(bool onOff) { + if ((onOff and mainLineSwitcher.getSwitchState(mainLineSwitch) == PowerSwitchIF::SWITCH_ON) or + (not onOff and + mainLineSwitcher.getSwitchState(mainLineSwitch) == PowerSwitchIF::SWITCH_OFF)) { + return true; + } + if (mainSwitchCountdown.hasTimedOut()) { + if (onOff) { + triggerEvent(MAIN_SWITCH_ON_TIMEOUT); + } else { + triggerEvent(MAIN_SWITCH_OFF_TIMEOUT); + } + if (retryCounter < 3) { + if (onOff) { + stateMachine = MAIN_POWER_ON; + } else { + stateMachine = MAIN_POWER_OFF; + } + retryCounter++; + } else { + finishFsm(MAIN_SWITCH_TIMEOUT_FAILURE); + } + } + return false; +} + +bool SolarArrayDeploymentHandler::startFsmOn(uint32_t burnCountdownSecs, + uint32_t channelAlternationIntervalMs, + uint8_t initChannel, bool dryRun) { + if (stateMachine != StateMachine::IDLE) { + return false; + } + channelAlternationCd.setTimeout(channelAlternationIntervalMs); + if (burnCountdownSecs > config::SA_DEPL_MAX_BURN_TIME) { + burnCountdownSecs = config::SA_DEPL_MAX_BURN_TIME; + } + fsmInfo.dryRun = dryRun; + fsmInfo.burnCountdownMs = burnCountdownSecs * 1000; + fsmInfo.initChannel = initChannel; + stateMachine = StateMachine::MAIN_POWER_ON; + retryCounter = 0; + return true; +} + +void SolarArrayDeploymentHandler::startFsmOff() { + if (stateMachine != StateMachine::IDLE) { + // off commands override the state machine. Cancel any active action commands. + finishFsm(returnvalue::FAILED); + } + retryCounter = 0; + stateMachine = StateMachine::MAIN_POWER_OFF; +} + +void SolarArrayDeploymentHandler::finishFsm(ReturnValue_t resultForActionHelper) { + retryCounter = 0; + stateMachine = StateMachine::IDLE; + fsmInfo.dryRun = false; + fsmInfo.alternationDummy = false; + if (actionActive) { + bool success = false; + if (resultForActionHelper == returnvalue::OK or + resultForActionHelper == HasActionsIF::EXECUTION_FINISHED) { + success = true; + } + actionHelper.finish(success, rememberCommanderId, activeCmd, resultForActionHelper); + } +} + +void SolarArrayDeploymentHandler::allOff() { + deploymentTransistorsOff(); + mainLineSwitcher.sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF); + mainSwitchCountdown.setTimeout(mainLineSwitcher.getSwitchDelayMs()); +} + +bool SolarArrayDeploymentHandler::dryRunStringInFile(const char* filename) { + std::ifstream ifile(filename); + if (ifile.bad()) { + return false; + } + std::string line; + while (getline(ifile, line)) { + if (line.find("dryrun") != std::string::npos) { + return true; + } + } + return false; +} + +ReturnValue_t SolarArrayDeploymentHandler::executeAction(ActionId_t actionId, + MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + ReturnValue_t result = returnvalue::OK; + if (actionId == DEPLOY_SOLAR_ARRAYS_MANUALLY) { + ManualDeploymentCommand cmd; + if (size < cmd.getSerializedSize()) { + return HasActionsIF::INVALID_PARAMETERS; + } + result = cmd.deSerialize(&data, &size, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + uint32_t burnCountdown = cmd.getBurnTimeSecs(); + if (not startFsmOn(burnCountdown, cmd.getSwitchIntervalMs(), cmd.getInitChannel(), + cmd.isDryRun())) { + return HasActionsIF::IS_BUSY; + } + actionActive = true; + rememberCommanderId = commandedBy; + return result; + } else if (actionId == SWITCH_OFF_DEPLOYMENT) { + startFsmOff(); + actionActive = true; + rememberCommanderId = commandedBy; + return result; + } else { + return HasActionsIF::INVALID_ACTION_ID; + } + return result; +} + +ReturnValue_t SolarArrayDeploymentHandler::saGpioAlternation() { + ReturnValue_t status = returnvalue::OK; + ReturnValue_t result; + if (channelAlternationCd.hasTimedOut() and not fsmInfo.dryRun) { + if (fsmInfo.alternationDummy) { + result = sa1Off(); + if (result != returnvalue::OK) { + status = result; + } + TaskFactory::delayTask(1); + result = sa2On(); + if (result != returnvalue::OK) { + status = result; + } + } else { + result = sa2Off(); + if (result != returnvalue::OK) { + status = result; + } + TaskFactory::delayTask(1); + result = sa1On(); + if (result != returnvalue::OK) { + status = result; + } + } + fsmInfo.alternationDummy = not fsmInfo.alternationDummy; + channelAlternationCd.resetTimer(); + } + return status; +} + +ReturnValue_t SolarArrayDeploymentHandler::deploymentTransistorsOff() { + ReturnValue_t status = returnvalue::OK; + ReturnValue_t result = sa1Off(); + if (result != returnvalue::OK) { + status = result; + } + result = sa2Off(); + if (result != returnvalue::OK) { + status = result; + } + return status; +} + +ReturnValue_t SolarArrayDeploymentHandler::sa1On() { + ReturnValue_t result = gpioInterface.pullHigh(deplSA1); + if (result != returnvalue::OK) { + sif::warning << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar" + " array deployment switch 1 high" + << std::endl; + // If gpio switch high failed, state machine is reset to wait for a command re-initiating + // the deployment sequence. + triggerEvent(DEPL_SA1_GPIO_SWTICH_ON_FAILED); + } + return result; +} + +ReturnValue_t SolarArrayDeploymentHandler::sa1Off() { + ReturnValue_t result = gpioInterface.pullLow(deplSA1); + if (result != returnvalue::OK) { + sif::warning << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar" + " array deployment switch 1 low" + << std::endl; + // If gpio switch high failed, state machine is reset to wait for a command re-initiating + // the deployment sequence. + triggerEvent(DEPL_SA1_GPIO_SWTICH_OFF_FAILED); + } + return result; +} + +ReturnValue_t SolarArrayDeploymentHandler::sa2On() { + ReturnValue_t result = gpioInterface.pullHigh(deplSA2); + if (result != returnvalue::OK) { + sif::warning << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar" + " array deployment switch 2 high" + << std::endl; + // If gpio switch high failed, state machine is reset to wait for a command re-initiating + // the deployment sequence. + triggerEvent(DEPL_SA2_GPIO_SWTICH_ON_FAILED); + } + return result; +} + +ReturnValue_t SolarArrayDeploymentHandler::sa2Off() { + ReturnValue_t result = gpioInterface.pullLow(deplSA2); + if (result != returnvalue::OK) { + sif::warning << "SolarArrayDeploymentHandler::handleStateMachine: Failed to pull solar" + " array deployment switch 2 low" + << std::endl; + // If gpio switch high failed, state machine is reset to wait for a command re-initiating + // the deployment sequence. + triggerEvent(DEPL_SA2_GPIO_SWTICH_OFF_FAILED); + } + return result; +} + +void SolarArrayDeploymentHandler::readCommandQueue() { + CommandMessage command; + ReturnValue_t result = commandQueue->receiveMessage(&command); + if (result != returnvalue::OK) { + return; + } + + result = actionHelper.handleActionMessage(&command); + if (result == returnvalue::OK) { + return; + } +} + +MessageQueueId_t SolarArrayDeploymentHandler::getCommandQueue() const { + return commandQueue->getId(); +} + +ReturnValue_t SolarArrayDeploymentHandler::initialize() { + ReturnValue_t result = actionHelper.initialize(commandQueue); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + return SystemObject::initialize(); +} diff --git a/mission/SolarArrayDeploymentHandler.h b/mission/SolarArrayDeploymentHandler.h new file mode 100644 index 0000000..6be3aff --- /dev/null +++ b/mission/SolarArrayDeploymentHandler.h @@ -0,0 +1,257 @@ +#ifndef MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_ +#define MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_ + +#include +#include + +#include + +#include "devices/powerSwitcherList.h" +#include "eive/definitions.h" +#include "events/subsystemIdRanges.h" +#include "fsfw/action/HasActionsIF.h" +#include "fsfw/devicehandlers/CookieIF.h" +#include "fsfw/devicehandlers/DeviceHandlerIF.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/power/PowerSwitchIF.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/serialize/SerialLinkedListAdapter.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw/timemanager/Countdown.h" +#include "fsfw_hal/common/gpio/GpioIF.h" +#include "mission/memory/SdCardMountedIF.h" +#include "returnvalues/classIds.h" + +enum DeploymentChannels : uint8_t { SA_1 = 1, SA_2 = 2 }; + +class ManualDeploymentCommand : public SerialLinkedListAdapter { + public: + ManualDeploymentCommand() { setLinks(); } + + void setLinks() { + setStart(&burnTimeSecs); + burnTimeSecs.setNext(&switchIntervalMs); + switchIntervalMs.setNext(&initChannel); + initChannel.setNext(&dryRun); + } + + uint32_t getBurnTimeSecs() const { return burnTimeSecs.entry; } + + uint32_t getSwitchIntervalMs() const { return switchIntervalMs.entry; }; + + uint8_t getInitChannel() const { return initChannel.entry; }; + + bool isDryRun() const { return dryRun.entry; } + + private: + SerializeElement burnTimeSecs; + SerializeElement switchIntervalMs; + SerializeElement initChannel; + SerializeElement dryRun; +}; + +/** + * @brief This class is used to control the solar array deployment. + * + * @author J. Meier + */ +class SolarArrayDeploymentHandler : public ExecutableObjectIF, + public SystemObject, + public HasActionsIF { + public: + //! Manual deployment of the solar arrays. Burn time, channel switch interval, initial + //! burn channel and dry run flag are supplied as parameters. There are following cases to + //! consider. + //! + //! - Channel switch interval greater or equal to burn time: Only burn one channel. The init + //! burn channel parameter can be used to select which channel is burned. + //! - Channel switch interval half of burn time: Burn each channel for half of the burn time. + //! + //! The dry run flag can be used to avoid actually toggling IO pins and only test the + //! application logic. + static constexpr DeviceCommandId_t DEPLOY_SOLAR_ARRAYS_MANUALLY = 0x05; + static constexpr DeviceCommandId_t SWITCH_OFF_DEPLOYMENT = 0x06; + + static constexpr uint32_t FIRST_BURN_START_TIME = config::SA_DEPL_INIT_BUFFER_SECS; + static constexpr uint32_t FIRST_BURN_END_TIME = + FIRST_BURN_START_TIME + config::SA_DEPL_BURN_TIME_SECS; + static constexpr uint32_t WAIT_START_TIME = FIRST_BURN_END_TIME; + static constexpr uint32_t WAIT_END_TIME = WAIT_START_TIME + config::SA_DEPL_WAIT_TIME_SECS; + static constexpr uint32_t SECOND_BURN_START_TIME = WAIT_END_TIME; + static constexpr uint32_t SECOND_BURN_END_TIME = + SECOND_BURN_START_TIME + config::SA_DEPL_WAIT_TIME_SECS; + + static constexpr char SD_0_DEPL_FILE[] = "/mnt/sd0/conf/deployment"; + static constexpr char SD_1_DEPL_FILE[] = "/mnt/sd1/conf/deployment"; + static constexpr char SD_0_DEPLY_INFO[] = "/mnt/sd0/conf/deployment_info.txt"; + static constexpr char SD_1_DEPLY_INFO[] = "/mnt/sd1/conf/deployment_info.txt"; + + static constexpr char PHASE_INIT_STR[] = "init"; + static constexpr char PHASE_FIRST_BURN_STR[] = "first_burn"; + static constexpr char PHASE_WAIT_STR[] = "wait"; + static constexpr char PHASE_SECOND_BURN_STR[] = "second_burn"; + static constexpr char PHASE_DONE[] = "done"; + /** + * @brief constructor + * + * @param setObjectId The object id of the SolarArrayDeploymentHandler. + * @param gpioDriverId The id of the gpio com if. + * @param gpioCookie GpioCookie holding information about the gpios used to switch the + * transistors. + * @param mainLineSwitcherObjectId The object id of the object responsible for switching + * the 8V power source. This is normally the PCDU. + * @param mainLineSwitch The id of the main line switch. This is defined in + * powerSwitcherList.h. + * @param deplSA1 gpioId of the GPIO controlling the deployment 1 transistor. + * @param deplSA2 gpioId of the GPIO controlling the deployment 2 transistor. + * @param burnTimeMs Time duration the power will be applied to the burn wires. + */ + SolarArrayDeploymentHandler(object_id_t setObjectId, GpioIF& gpio, + PowerSwitchIF& mainLineSwitcher, power::Switches mainLineSwitch, + gpioId_t deplSA1, gpioId_t deplSA2, SdCardMountedIF& sdcMountedIF); + + virtual ~SolarArrayDeploymentHandler(); + + virtual ReturnValue_t performOperation(uint8_t operationCode = 0) override; + + virtual MessageQueueId_t getCommandQueue() const override; + virtual ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + virtual ReturnValue_t initialize() override; + + private: + enum AutonomousDeplState { INIT, FIRST_BURN, WAIT, SECOND_BURN, DONE }; + + enum StateMachine { + IDLE, + MAIN_POWER_ON, + MAIN_POWER_OFF, + WAIT_MAIN_POWER_ON, + WAIT_MAIN_POWER_OFF, + SWITCH_DEPL_GPIOS, + BURNING + }; + + struct FsmInfo { + // Not required anymore + // DeploymentChannels channel; + bool dryRun; + bool alternationDummy = false; + uint8_t initChannel = 0; + uint32_t burnCountdownMs = config::SA_DEPL_MAX_BURN_TIME; + }; + + static const uint8_t INTERFACE_ID = CLASS_ID::SA_DEPL_HANDLER; + static const ReturnValue_t COMMAND_NOT_SUPPORTED = MAKE_RETURN_CODE(0xA0); + static const ReturnValue_t DEPLOYMENT_ALREADY_EXECUTING = MAKE_RETURN_CODE(0xA1); + static const ReturnValue_t MAIN_SWITCH_TIMEOUT_FAILURE = MAKE_RETURN_CODE(0xA2); + static const ReturnValue_t SWITCHING_DEPL_SA1_FAILED = MAKE_RETURN_CODE(0xA3); + static const ReturnValue_t SWITCHING_DEPL_SA2_FAILED = MAKE_RETURN_CODE(0xA4); + + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::SA_DEPL_HANDLER; + + //! [EXPORT] : [COMMENT] P1: Burn duration in milliseconds, P2: Dry run flag + static constexpr Event BURN_PHASE_START = event::makeEvent(SUBSYSTEM_ID, 0, severity::INFO); + //! [EXPORT] : [COMMENT] P1: Burn duration in milliseconds, P2: Dry run flag + static constexpr Event BURN_PHASE_DONE = event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO); + static constexpr Event MAIN_SWITCH_ON_TIMEOUT = event::makeEvent(SUBSYSTEM_ID, 2, severity::LOW); + static constexpr Event MAIN_SWITCH_OFF_TIMEOUT = event::makeEvent(SUBSYSTEM_ID, 3, severity::LOW); + static constexpr Event DEPL_SA1_GPIO_SWTICH_ON_FAILED = + event::makeEvent(SUBSYSTEM_ID, 4, severity::HIGH); + static constexpr Event DEPL_SA2_GPIO_SWTICH_ON_FAILED = + event::makeEvent(SUBSYSTEM_ID, 5, severity::HIGH); + static constexpr Event DEPL_SA1_GPIO_SWTICH_OFF_FAILED = + event::makeEvent(SUBSYSTEM_ID, 6, severity::HIGH); + static constexpr Event DEPL_SA2_GPIO_SWTICH_OFF_FAILED = + event::makeEvent(SUBSYSTEM_ID, 7, severity::HIGH); + static constexpr Event AUTONOMOUS_DEPLOYMENT_COMPLETED = + event::makeEvent(SUBSYSTEM_ID, 8, severity::INFO); + + FsmInfo fsmInfo; + StateMachine stateMachine = IDLE; + bool actionActive = false; + bool firstAutonomousCycle = true; + ActionId_t activeCmd = HasActionsIF::INVALID_ACTION_ID; + std::optional initUptime; +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter = 0; +#endif + PeriodicOperationDivider opDivider = PeriodicOperationDivider(5); + uint8_t retryCounter = 3; + + bool startFsmOn(uint32_t burnCountdownSecs, uint32_t channelAlternationIntervalMs, + uint8_t initChannel, bool dryRun); + void startFsmOff(); + + void finishFsm(ReturnValue_t resultForActionHelper); + + ReturnValue_t performAutonomousDepl(sd::SdCard sdCard, bool dryRun); + bool dryRunStringInFile(const char* filename); + bool autonomousDeplForFile(sd::SdCard sdCard, const char* filename, bool dryRun); + /** + * This countdown is used to check if the PCDU sets the 8V line on in the intended time. + */ + Countdown mainSwitchCountdown; + + /** + * This countdown is used to wait for the burn wire being successful cut. + */ + Countdown burnCountdown; + + // Only initial value, new approach is to burn each channel half of the total burn time. + Countdown channelAlternationCd = + Countdown(config::LEGACY_SA_DEPL_CHANNEL_ALTERNATION_INTERVAL_SECS * 1000); + + /** + * The message queue id of the component commanding an action will be stored in this variable. + * This is necessary to send later the action finish replies. + */ + MessageQueueId_t rememberCommanderId = 0; + + /** Size of command queue */ + size_t cmdQueueSize = 20; + GpioIF& gpioInterface; + gpioId_t deplSA1; + gpioId_t deplSA2; + + /** + * After initialization this pointer will hold the reference to the main line switcher object. + */ + PowerSwitchIF& mainLineSwitcher; + + /** Switch number of the 8V power switch */ + uint8_t mainLineSwitch; + + SdCardMountedIF& sdcMan; + + ActionHelper actionHelper; + + /** Queue to receive messages from other objects. */ + MessageQueueIF* commandQueue = nullptr; + + void readCommandQueue(); + + /** + * @brief This function performs actions dependent on the current state. + */ + void handleStateMachine(); + + /** + * @brief This function polls the 8V switch state and changes the state machine when the + * switch has been enabled. + */ + bool checkMainPowerOn(); + bool checkMainPowerOff(); + bool checkMainPower(bool onOff); + + void allOff(); + + ReturnValue_t deploymentTransistorsOff(); + ReturnValue_t saGpioAlternation(); + ReturnValue_t sa1On(); + ReturnValue_t sa1Off(); + ReturnValue_t sa2On(); + ReturnValue_t sa2Off(); +}; + +#endif /* MISSION_DEVICES_SOLARARRAYDEPLOYMENT_H_ */ diff --git a/mission/acs/CMakeLists.txt b/mission/acs/CMakeLists.txt new file mode 100644 index 0000000..e518d94 --- /dev/null +++ b/mission/acs/CMakeLists.txt @@ -0,0 +1,18 @@ +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE GyrAdis1650XHandler.cpp + GyrL3gCustomHandler.cpp + ImtqHandler.cpp + MgmLis3CustomHandler.cpp + MgmRm3100CustomHandler.cpp + RwHandler.cpp + SusHandler.cpp + gyroAdisHelpers.cpp + imtqHelpers.cpp + rwHelpers.cpp + defs.cpp) + +# Dependency on proprietary library +if(TGT_BSP MATCHES "arm/q7s") + add_subdirectory(str) +endif() diff --git a/mission/acs/GyrAdis1650XHandler.cpp b/mission/acs/GyrAdis1650XHandler.cpp new file mode 100644 index 0000000..fe10c21 --- /dev/null +++ b/mission/acs/GyrAdis1650XHandler.cpp @@ -0,0 +1,225 @@ +#include "GyrAdis1650XHandler.h" + +#include "fsfw/action/HasActionsIF.h" +#include "fsfw/datapool/PoolReadGuard.h" +#include "mission/acs/acsBoardPolling.h" + +GyrAdis1650XHandler::GyrAdis1650XHandler(object_id_t objectId, object_id_t deviceCommunication, + CookieIF *comCookie, adis1650x::Type type) + : DeviceHandlerBase(objectId, deviceCommunication, comCookie), + adisType(type), + primaryDataset(this), + configDataset(this), + breakCountdown() { + adisRequest.type = adisType; +} + +void GyrAdis1650XHandler::doStartUp() { + if (internalState == InternalState::NONE) { + internalState = InternalState::STARTUP; + commandExecuted = false; + } + // Initial 310 ms start up time after power-up + if (internalState == InternalState::STARTUP) { + if (not commandExecuted) { + warningSwitch = true; + breakCountdown.setTimeout(adis1650x::START_UP_TIME); + updatePeriodicReply(true, adis1650x::REPLY); + commandExecuted = true; + } + } + if (internalState == InternalState::STARTUP_DONE) { + setMode(MODE_ON); + commandExecuted = false; + internalState = InternalState::NONE; + } +} + +void GyrAdis1650XHandler::doShutDown() { + if (internalState != InternalState::SHUTDOWN) { + commandExecuted = false; + PoolReadGuard pg(&primaryDataset); + primaryDataset.setValidity(false, true); + internalState = InternalState::SHUTDOWN; + } + if (internalState == InternalState::SHUTDOWN and commandExecuted) { + updatePeriodicReply(false, adis1650x::REPLY); + internalState = InternalState::NONE; + commandExecuted = false; + setMode(MODE_OFF); + } +} + +ReturnValue_t GyrAdis1650XHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = adis1650x::REQUEST; + return preparePeriodicRequest(acs::SimpleSensorMode::NORMAL); +} + +ReturnValue_t GyrAdis1650XHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + switch (internalState) { + case (InternalState::STARTUP): { + if (breakCountdown.isBusy()) { + return NOTHING_TO_SEND; + } + *id = adis1650x::REQUEST; + adisRequest.cfg.decRateReg = adis1650x::DEC_RATE; + return preparePeriodicRequest(acs::SimpleSensorMode::NORMAL); + } + case (InternalState::SHUTDOWN): { + *id = adis1650x::REQUEST; + return preparePeriodicRequest(acs::SimpleSensorMode::OFF); + } + default: { + return NOTHING_TO_SEND; + } + } + return returnvalue::OK; +} + +ReturnValue_t GyrAdis1650XHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return NOTHING_TO_SEND; +} + +void GyrAdis1650XHandler::fillCommandAndReplyMap() { + insertInCommandMap(adis1650x::REQUEST); + insertInReplyMap(adis1650x::REPLY, 5, nullptr, 0, true); +} + +ReturnValue_t GyrAdis1650XHandler::scanForReply(const uint8_t *start, size_t remainingSize, + DeviceCommandId_t *foundId, size_t *foundLen) { + if (breakCountdown.isBusy() or getMode() == _MODE_WAIT_OFF or getMode() == _MODE_WAIT_ON or + getMode() == _MODE_POWER_DOWN) { + return IGNORE_FULL_PACKET; + } + if (internalState == InternalState::STARTUP) { + internalState = InternalState::STARTUP_DONE; + } + *foundLen = remainingSize; + if (remainingSize != sizeof(acs::Adis1650XReply)) { + return returnvalue::FAILED; + } + *foundId = adis1650x::REPLY; + if (internalState == InternalState::SHUTDOWN) { + commandExecuted = true; + } + return returnvalue::OK; +} + +ReturnValue_t GyrAdis1650XHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + using namespace adis1650x; + const acs::Adis1650XReply *reply = reinterpret_cast(packet); + if (internalState == InternalState::STARTUP and reply->cfgWasSet) { + switch (adisType) { + case (adis1650x::Type::ADIS16507): { + if (reply->cfg.prodId != adis1650x::PROD_ID_16507) { + sif::error << "GyroADIS1650XHandler::interpretDeviceReply: Invalid product ID " + << reply->cfg.prodId << "for ADIS type 16507" << std::endl; + return returnvalue::FAILED; + } + break; + } + case (adis1650x::Type::ADIS16505): { + if (reply->cfg.prodId != adis1650x::PROD_ID_16505) { + sif::error << "GyroADIS1650XHandler::interpretDeviceReply: Invalid product ID " + << reply->cfg.prodId << "for ADIS type 16505" << std::endl; + return returnvalue::FAILED; + } + break; + } + } + PoolReadGuard rg(&configDataset); + configDataset.setValidity(true, true); + configDataset.mscCtrlReg = reply->cfg.mscCtrlReg; + configDataset.rangMdl = reply->cfg.rangMdl; + configDataset.diagStatReg = reply->cfg.diagStat; + configDataset.filterSetting = reply->cfg.filterSetting; + configDataset.decRateReg = reply->cfg.decRateReg; + commandExecuted = true; + } + if (reply->dataWasSet) { + { + PoolReadGuard rg(&configDataset); + configDataset.diagStatReg = reply->cfg.diagStat; + } + auto sensitivity = reply->data.sensitivity; + auto accelSensitivity = reply->data.accelScaling; + PoolReadGuard pg(&primaryDataset); + primaryDataset.setValidity(true, true); + primaryDataset.angVelocX = static_cast(reply->data.angVelocities[0]) * sensitivity; + primaryDataset.angVelocY = static_cast(reply->data.angVelocities[1]) * sensitivity; + primaryDataset.angVelocZ = static_cast(reply->data.angVelocities[2]) * sensitivity; + // TODO: Check whether we need to divide by INT16_MAX + primaryDataset.accelX = static_cast(reply->data.accelerations[0]) * accelSensitivity; + primaryDataset.accelY = static_cast(reply->data.accelerations[1]) * accelSensitivity; + primaryDataset.accelZ = static_cast(reply->data.accelerations[2]) * accelSensitivity; + primaryDataset.temperature.value = static_cast(reply->data.temperatureRaw) * 0.1; + } + return returnvalue::OK; +} + +LocalPoolDataSetBase *GyrAdis1650XHandler::getDataSetHandle(sid_t sid) { + if (sid.ownerSetId == adis1650x::ADIS_DATASET_ID) { + return &primaryDataset; + } else if (sid.ownerSetId == adis1650x::ADIS_CFG_DATASET_ID) { + return &configDataset; + } + return nullptr; +} + +uint32_t GyrAdis1650XHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 6000; } + +void GyrAdis1650XHandler::prepareWriteCommand(uint8_t startReg, uint8_t valueOne, + uint8_t valueTwo) { + uint8_t secondReg = startReg + 1; + startReg |= adis1650x::WRITE_MASK; + secondReg |= adis1650x::WRITE_MASK; + cmdBuf[0] = startReg; + cmdBuf[1] = valueOne; + cmdBuf[2] = secondReg; + cmdBuf[3] = valueTwo; + this->rawPacketLen = 4; + this->rawPacket = cmdBuf.data(); +} + +ReturnValue_t GyrAdis1650XHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(adis1650x::ANG_VELOC_X, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::ANG_VELOC_Y, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::ANG_VELOC_Z, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::ACCELERATION_X, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::ACCELERATION_Y, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::ACCELERATION_Z, new PoolEntry({0.0})); + localDataPoolMap.emplace(adis1650x::TEMPERATURE, new PoolEntry({0.0})); + + localDataPoolMap.emplace(adis1650x::DIAG_STAT_REGISTER, new PoolEntry()); + localDataPoolMap.emplace(adis1650x::FILTER_SETTINGS, new PoolEntry()); + localDataPoolMap.emplace(adis1650x::RANG_MDL, &rangMdl); + localDataPoolMap.emplace(adis1650x::MSC_CTRL_REGISTER, new PoolEntry()); + localDataPoolMap.emplace(adis1650x::DEC_RATE_REGISTER, new PoolEntry()); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(primaryDataset.getSid(), false, 5.0)); + return returnvalue::OK; +} + +adis1650x::BurstModes GyrAdis1650XHandler::getBurstMode() { + using namespace adis1650x; + configDataset.mscCtrlReg.read(); + uint16_t currentCtrlReg = configDataset.mscCtrlReg.value; + configDataset.mscCtrlReg.commit(); + return adis1650x::burstModeFromMscCtrl(currentCtrlReg); +} + +void GyrAdis1650XHandler::enablePeriodicPrintouts(bool enable, uint8_t divider) { + periodicPrintout = enable; + debugDivider.setDivider(divider); +} + +ReturnValue_t GyrAdis1650XHandler::preparePeriodicRequest(acs::SimpleSensorMode mode) { + adisRequest.mode = mode; + rawPacket = reinterpret_cast(&adisRequest); + rawPacketLen = sizeof(acs::Adis1650XRequest); + return returnvalue::OK; +} diff --git a/mission/acs/GyrAdis1650XHandler.h b/mission/acs/GyrAdis1650XHandler.h new file mode 100644 index 0000000..308d472 --- /dev/null +++ b/mission/acs/GyrAdis1650XHandler.h @@ -0,0 +1,70 @@ +#ifndef MISSION_DEVICES_GYROADIS16507HANDLER_H_ +#define MISSION_DEVICES_GYROADIS16507HANDLER_H_ + +#include +#include + +#include "FSFWConfig.h" +#include "OBSWConfig.h" +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "fsfw/globalfunctions/PeriodicOperationDivider.h" + +/** + * @brief Device handle for the ADIS16507 Gyroscope by Analog Devices + * @details + * Flight manual: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/ADIS16507_Gyro + */ +class GyrAdis1650XHandler : public DeviceHandlerBase { + public: + GyrAdis1650XHandler(object_id_t objectId, object_id_t deviceCommunication, CookieIF *comCookie, + adis1650x::Type type); + + void enablePeriodicPrintouts(bool enable, uint8_t divider); + + // DeviceHandlerBase abstract function implementation + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + void fillCommandAndReplyMap() override; + ReturnValue_t scanForReply(const uint8_t *start, size_t remainingSize, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; + + private: + std::array cmdBuf; + acs::Adis1650XRequest adisRequest; + adis1650x::Type adisType; + AdisGyroPrimaryDataset primaryDataset; + AdisGyroConfigDataset configDataset; + double sensitivity = adis1650x::SENSITIVITY_UNSET; + + bool warningSwitch = true; + + enum class InternalState { NONE, STARTUP, STARTUP_DONE, SHUTDOWN }; + + InternalState internalState = InternalState::STARTUP; + bool commandExecuted = false; + + PoolEntry rangMdl = PoolEntry(); + + adis1650x::BurstModes getBurstMode(); + + Countdown breakCountdown; + void prepareWriteCommand(uint8_t startReg, uint8_t valueOne, uint8_t valueTwo); + ReturnValue_t preparePeriodicRequest(acs::SimpleSensorMode mode); + + ReturnValue_t handleSensorData(const uint8_t *packet); + + bool periodicPrintout = false; + PeriodicOperationDivider debugDivider = PeriodicOperationDivider(3); +}; + +#endif /* MISSION_DEVICES_GYROADIS16507HANDLER_H_ */ diff --git a/mission/acs/GyrL3gCustomHandler.cpp b/mission/acs/GyrL3gCustomHandler.cpp new file mode 100644 index 0000000..ebc6ba4 --- /dev/null +++ b/mission/acs/GyrL3gCustomHandler.cpp @@ -0,0 +1,185 @@ + +#include +#include + +#include + +#include "fsfw/datapool/PoolReadGuard.h" + +GyrL3gCustomHandler::GyrL3gCustomHandler(object_id_t objectId, object_id_t deviceCommunication, + CookieIF *comCookie, uint32_t transitionDelayMs) + : DeviceHandlerBase(objectId, deviceCommunication, comCookie), + transitionDelayMs(transitionDelayMs), + dataset(this) {} + +GyrL3gCustomHandler::~GyrL3gCustomHandler() = default; + +void GyrL3gCustomHandler::doStartUp() { + if (internalState != InternalState::STARTUP) { + internalState = InternalState::STARTUP; + updatePeriodicReply(true, l3gd20h::REPLY); + commandExecuted = false; + } + + if (internalState == InternalState::STARTUP) { + if (commandExecuted) { + setMode(MODE_ON); + internalState = InternalState::NONE; + commandExecuted = false; + } + } +} + +void GyrL3gCustomHandler::doShutDown() { + if (internalState != InternalState::SHUTDOWN) { + internalState = InternalState::SHUTDOWN; + PoolReadGuard pg(&dataset); + dataset.setValidity(false, true); + commandExecuted = false; + } + if (internalState == InternalState::SHUTDOWN and commandExecuted) { + internalState = InternalState::NONE; + updatePeriodicReply(false, l3gd20h::REPLY); + commandExecuted = false; + setMode(MODE_OFF); + } +} + +ReturnValue_t GyrL3gCustomHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + switch (internalState) { + case (InternalState::NONE): + case (InternalState::NORMAL): { + return NOTHING_TO_SEND; + } + case (InternalState::STARTUP): { + *id = l3gd20h::REQUEST; + gyrRequest.ctrlRegs[0] = l3gd20h::CTRL_REG_1_VAL; + gyrRequest.ctrlRegs[1] = l3gd20h::CTRL_REG_2_VAL; + gyrRequest.ctrlRegs[2] = l3gd20h::CTRL_REG_3_VAL; + gyrRequest.ctrlRegs[3] = l3gd20h::CTRL_REG_4_VAL; + gyrRequest.ctrlRegs[4] = l3gd20h::CTRL_REG_5_VAL; + return doPeriodicRequest(acs::SimpleSensorMode::NORMAL); + } + case (InternalState::SHUTDOWN): { + *id = l3gd20h::REQUEST; + return doPeriodicRequest(acs::SimpleSensorMode::OFF); + } + default: +#if FSFW_CPP_OSTREAM_ENABLED == 1 + /* Might be a configuration error. */ + sif::warning << "GyroL3GD20Handler::buildTransitionDeviceCommand: " + "Unknown internal state!" + << std::endl; +#else + sif::printDebug( + "GyroL3GD20Handler::buildTransitionDeviceCommand: " + "Unknown internal state!\n"); +#endif + return returnvalue::OK; + } + return returnvalue::OK; +} + +ReturnValue_t GyrL3gCustomHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = l3gd20h::REQUEST; + return doPeriodicRequest(acs::SimpleSensorMode::NORMAL); +} + +ReturnValue_t GyrL3gCustomHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + switch (deviceCommand) { + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::OK; +} + +ReturnValue_t GyrL3gCustomHandler::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + if (getMode() == _MODE_WAIT_OFF or getMode() == _MODE_WAIT_ON or getMode() == _MODE_POWER_DOWN) { + return IGNORE_FULL_PACKET; + } + *foundLen = len; + if (len != sizeof(acs::GyroL3gReply)) { + return returnvalue::FAILED; + } + *foundId = adis1650x::REPLY; + if (internalState == InternalState::SHUTDOWN) { + commandExecuted = true; + } + return returnvalue::OK; +} + +ReturnValue_t GyrL3gCustomHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + const acs::GyroL3gReply *reply = reinterpret_cast(packet); + if (reply->cfgWasSet) { + if (internalState == InternalState::STARTUP) { + commandExecuted = true; + } + PoolReadGuard pg(&dataset); + dataset.setValidity(true, true); + dataset.angVelocX = static_cast(reply->angVelocities[0]) * reply->sensitivity; + dataset.angVelocY = static_cast(reply->angVelocities[1]) * reply->sensitivity; + dataset.angVelocZ = static_cast(reply->angVelocities[2]) * reply->sensitivity; + if (std::abs(dataset.angVelocX.value) > absLimitX) { + dataset.angVelocX.setValid(false); + } + if (std::abs(dataset.angVelocY.value) > absLimitY) { + dataset.angVelocY.setValid(false); + } + if (std::abs(dataset.angVelocZ.value) > absLimitZ) { + dataset.angVelocZ.setValid(false); + } + dataset.temperature = 25.0 - reply->tempOffsetRaw; + } + return returnvalue::OK; +} + +uint32_t GyrL3gCustomHandler::getTransitionDelayMs(Mode_t from, Mode_t to) { + return this->transitionDelayMs; +} + +ReturnValue_t GyrL3gCustomHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(l3gd20h::ANG_VELOC_X, new PoolEntry({0.0})); + localDataPoolMap.emplace(l3gd20h::ANG_VELOC_Y, new PoolEntry({0.0})); + localDataPoolMap.emplace(l3gd20h::ANG_VELOC_Z, new PoolEntry({0.0})); + localDataPoolMap.emplace(l3gd20h::TEMPERATURE, new PoolEntry({0.0})); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(dataset.getSid(), false, 10.0)); + return returnvalue::OK; +} + +void GyrL3gCustomHandler::fillCommandAndReplyMap() { + insertInCommandMap(l3gd20h::REQUEST); + insertInReplyMap(l3gd20h::REPLY, 5, nullptr, 0, true); +} + +void GyrL3gCustomHandler::modeChanged() { internalState = InternalState::NONE; } + +void GyrL3gCustomHandler::setAbsoluteLimits(float limitX, float limitY, float limitZ) { + this->absLimitX = limitX; + this->absLimitY = limitY; + this->absLimitZ = limitZ; +} + +void GyrL3gCustomHandler::enablePeriodicPrintouts(bool enable, uint8_t divider) { + periodicPrintout = enable; + debugDivider.setDivider(divider); +} + +LocalPoolDataSetBase *GyrL3gCustomHandler::getDataSetHandle(sid_t sid) { + if (sid == dataset.getSid()) { + return &dataset; + } + return nullptr; +} + +ReturnValue_t GyrL3gCustomHandler::doPeriodicRequest(acs::SimpleSensorMode mode) { + gyrRequest.mode = mode; + rawPacket = reinterpret_cast(&gyrRequest); + rawPacketLen = sizeof(acs::GyroL3gRequest); + return returnvalue::OK; +} diff --git a/mission/acs/GyrL3gCustomHandler.h b/mission/acs/GyrL3gCustomHandler.h new file mode 100644 index 0000000..08d1b70 --- /dev/null +++ b/mission/acs/GyrL3gCustomHandler.h @@ -0,0 +1,85 @@ +#ifndef MISSION_DEVICES_GYROL3GD20HANDLER_H_ +#define MISSION_DEVICES_GYROL3GD20HANDLER_H_ + +#include +#include +#include +#include + +/** + * @brief Device Handler for the L3GD20H gyroscope sensor + * (https://www.st.com/en/mems-and-sensors/l3gd20h.html) + * @details + * Advanced documentation: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/L3GD20H_Gyro + * + * Data is read big endian with the smallest possible range of 245 degrees per second. + */ +class GyrL3gCustomHandler : public DeviceHandlerBase { + public: + GyrL3gCustomHandler(object_id_t objectId, object_id_t deviceCommunication, CookieIF *comCookie, + uint32_t transitionDelayMs); + virtual ~GyrL3gCustomHandler(); + + void enablePeriodicPrintouts(bool enable, uint8_t divider); + + /** + * Set the absolute limit for the values on the axis in degrees per second. + * The dataset values will be marked as invalid if that limit is exceeded + * @param xLimit + * @param yLimit + * @param zLimit + */ + void setAbsoluteLimits(float limitX, float limitY, float limitZ); + + protected: + /* DeviceHandlerBase overrides */ + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + virtual ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + + void fillCommandAndReplyMap() override; + void modeChanged() override; + virtual uint32_t getTransitionDelayMs(Mode_t from, Mode_t to) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; + + private: + uint32_t transitionDelayMs = 0; + GyroPrimaryDataset dataset; + + float absLimitX = l3gd20h::RANGE_DPS_00; + float absLimitY = l3gd20h::RANGE_DPS_00; + float absLimitZ = l3gd20h::RANGE_DPS_00; + + enum class InternalState { NONE, STARTUP, SHUTDOWN, NORMAL }; + InternalState internalState = InternalState::NONE; + bool commandExecuted = false; + + uint8_t statusReg = 0; + + uint8_t ctrlReg1Value = l3gd20h::CTRL_REG_1_VAL; + uint8_t ctrlReg2Value = l3gd20h::CTRL_REG_2_VAL; + uint8_t ctrlReg3Value = l3gd20h::CTRL_REG_3_VAL; + uint8_t ctrlReg4Value = l3gd20h::CTRL_REG_4_VAL; + uint8_t ctrlReg5Value = l3gd20h::CTRL_REG_5_VAL; + + acs::GyroL3gRequest gyrRequest{}; + + // Set default value + float sensitivity = l3gd20h::SENSITIVITY_00; + + bool periodicPrintout = false; + PeriodicOperationDivider debugDivider = PeriodicOperationDivider(3); + + ReturnValue_t doPeriodicRequest(acs::SimpleSensorMode mode); +}; + +#endif /* MISSION_DEVICES_GYROL3GD20HANDLER_H_ */ diff --git a/mission/acs/ImtqHandler.cpp b/mission/acs/ImtqHandler.cpp new file mode 100644 index 0000000..74f9a69 --- /dev/null +++ b/mission/acs/ImtqHandler.cpp @@ -0,0 +1,2425 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "mission/config/torquer.h" + +static constexpr bool ACTUATION_WIRETAPPING = false; + +ImtqHandler::ImtqHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + power::Switch_t pwrSwitcher, bool enableHkSets) + : DeviceHandlerBase(objectId, comIF, comCookie), + enableHkSets(enableHkSets), + statusSet(this), + dipoleSet(*this), + rawMtmNoTorque(this), + hkDatasetNoTorque(this), + rawMtmWithTorque(this), + hkDatasetWithTorque(this), + calMtmMeasurementSet(this), + posXselfTestDataset(this), + negXselfTestDataset(this), + posYselfTestDataset(this), + negYselfTestDataset(this), + posZselfTestDataset(this), + negZselfTestDataset(this), + switcher(pwrSwitcher) { + if (comCookie == nullptr) { + sif::error << "IMTQHandler: Invalid com cookie" << std::endl; + } +} + +ReturnValue_t ImtqHandler::performOperation(uint8_t opCode) { + uint8_t dhbOpCode = DeviceHandlerIF::PERFORM_OPERATION; + auto actuateStep = [&]() { + if (ignoreActForRestOfComSteps) { + requestStep = imtq::RequestType::DO_NOTHING; + } else { + requestStep = imtq::RequestType::ACTUATE; + } + }; + switch (static_cast(opCode)) { + case (imtq::ComStep::DHB_OP): { + break; + } + case (imtq::ComStep::START_MEASURE_SEND): { + requestStep = imtq::RequestType::MEASURE_NO_ACTUATION; + dhbOpCode = DeviceHandlerIF::SEND_WRITE; + break; + } + case (imtq::ComStep::START_MEASURE_GET): { + requestStep = imtq::RequestType::MEASURE_NO_ACTUATION; + dhbOpCode = DeviceHandlerIF::GET_WRITE; + break; + } + case (imtq::ComStep::READ_MEASURE_SEND): { + requestStep = imtq::RequestType::MEASURE_NO_ACTUATION; + dhbOpCode = DeviceHandlerIF::SEND_READ; + break; + } + case (imtq::ComStep::READ_MEASURE_GET): { + requestStep = imtq::RequestType::MEASURE_NO_ACTUATION; + dhbOpCode = DeviceHandlerIF::GET_READ; + break; + } + case (imtq::ComStep::START_ACTUATE_SEND): { + if (manualTorqueCmdActive) { + if (manuallyCommandedTorqueDuration.isBusy()) { + ignoreActForRestOfComSteps = true; + requestStep = imtq::RequestType::DO_NOTHING; + } else { + manualTorqueCmdActive = false; + PoolReadGuard pg(&dipoleSet); + dipoleSet.dipoles[0] = 0; + dipoleSet.dipoles[1] = 0; + dipoleSet.dipoles[2] = 0; + dipoleSet.currentTorqueDurationMs = 0; + requestStep = imtq::RequestType::ACTUATE; + ignoreActForRestOfComSteps = false; + } + } else { + requestStep = imtq::RequestType::ACTUATE; + } + dhbOpCode = DeviceHandlerIF::SEND_WRITE; + break; + } + case (imtq::ComStep::START_ACTUATE_GET): { + actuateStep(); + dhbOpCode = DeviceHandlerIF::GET_WRITE; + break; + } + case (imtq::ComStep::READ_ACTUATE_SEND): { + actuateStep(); + dhbOpCode = DeviceHandlerIF::SEND_READ; + break; + } + case (imtq::ComStep::READ_ACTUATE_GET): { + actuateStep(); + dhbOpCode = DeviceHandlerIF::GET_READ; + break; + } + default: { + sif::error << "ImtqHandler: Unexpected COM step" << std::endl; + break; + } + } + return DeviceHandlerBase::performOperation(dhbOpCode); +} + +ImtqHandler::~ImtqHandler() = default; + +void ImtqHandler::doStartUp() { + if (internalState != InternalState::STARTUP) { + commandExecuted = false; + updatePeriodicReply(true, imtq::cmdIds::REPLY_NO_TORQUE); + updatePeriodicReply(true, imtq::cmdIds::REPLY_WITH_TORQUE); + internalState = InternalState::STARTUP; + } + if (internalState == InternalState::STARTUP) { + if (commandExecuted) { + if (goToNormalMode) { + setMode(MODE_NORMAL); + } else { + setMode(_MODE_TO_ON); + } + commandExecuted = false; + } + } +} + +void ImtqHandler::doShutDown() { + if (internalState != InternalState::SHUTDOWN) { + commandExecuted = false; + internalState = InternalState::SHUTDOWN; + } + if (internalState == InternalState::SHUTDOWN and commandExecuted) { + updatePeriodicReply(false, imtq::cmdIds::REPLY_NO_TORQUE); + updatePeriodicReply(false, imtq::cmdIds::REPLY_WITH_TORQUE); + specialRequestActive = false; + firstReplyCycle = true; + internalState = InternalState::NONE; + commandExecuted = false; + statusSet.setValidity(false, true); + rawMtmNoTorque.setValidity(false, true); + rawMtmWithTorque.setValidity(false, true); + hkDatasetNoTorque.setValidity(false, true); + hkDatasetWithTorque.setValidity(false, true); + calMtmMeasurementSet.setValidity(false, true); + setMode(_MODE_POWER_DOWN); + } +} + +ReturnValue_t ImtqHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + switch (requestStep) { + case (imtq::RequestType::MEASURE_NO_ACTUATION): { + *id = imtq::cmdIds::REQUEST; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (imtq::RequestType::ACTUATE): { + *id = imtq::cmdIds::START_ACTUATION_DIPOLE; + return buildCommandFromCommand(*id, nullptr, 0); + } + default: { + *id = imtq::cmdIds::REQUEST; + request.requestType = imtq::RequestType::DO_NOTHING; + request.specialRequest = imtq::SpecialRequest::NONE; + expectedReply = DeviceHandlerIF::NO_COMMAND_ID; + rawPacket = reinterpret_cast(&request); + rawPacketLen = sizeof(imtq::Request); + return returnvalue::OK; + } + } + return NOTHING_TO_SEND; +} + +ReturnValue_t ImtqHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + if (internalState == InternalState::STARTUP or internalState == InternalState::SHUTDOWN) { + *id = imtq::cmdIds::REQUEST; + return buildCommandFromCommand(*id, nullptr, 0); + } + return NOTHING_TO_SEND; +} + +ReturnValue_t ImtqHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + auto genericSpecialRequest = [&](imtq::SpecialRequest specialRequest) { + request.requestType = imtq::RequestType::MEASURE_NO_ACTUATION; + request.specialRequest = specialRequest; + expectedReply = imtq::cmdIds::REPLY_NO_TORQUE; + specialRequestActive = true; + rawPacket = reinterpret_cast(&request); + rawPacketLen = sizeof(imtq::Request); + }; + switch (deviceCommand) { + case (imtq::cmdIds::POS_X_SELF_TEST): { + genericSpecialRequest(imtq::SpecialRequest::DO_SELF_TEST_POS_X); + return returnvalue::OK; + } + case (imtq::cmdIds::NEG_X_SELF_TEST): { + genericSpecialRequest(imtq::SpecialRequest::DO_SELF_TEST_NEG_X); + return returnvalue::OK; + } + case (imtq::cmdIds::POS_Y_SELF_TEST): { + genericSpecialRequest(imtq::SpecialRequest::DO_SELF_TEST_POS_Y); + return returnvalue::OK; + } + case (imtq::cmdIds::NEG_Y_SELF_TEST): { + genericSpecialRequest(imtq::SpecialRequest::DO_SELF_TEST_NEG_Y); + return returnvalue::OK; + } + case (imtq::cmdIds::POS_Z_SELF_TEST): { + genericSpecialRequest(imtq::SpecialRequest::DO_SELF_TEST_POS_Z); + return returnvalue::OK; + } + case (imtq::cmdIds::NEG_Z_SELF_TEST): { + genericSpecialRequest(imtq::SpecialRequest::DO_SELF_TEST_NEG_Z); + return returnvalue::OK; + } + case (imtq::cmdIds::GET_SELF_TEST_RESULT): { + genericSpecialRequest(imtq::SpecialRequest::GET_SELF_TEST_RESULT); + return returnvalue::OK; + } + case (imtq::cmdIds::REQUEST): { + request.requestType = imtq::RequestType::MEASURE_NO_ACTUATION; + request.specialRequest = imtq::SpecialRequest::NONE; + // 6 ms integration time instead of 10 ms. + request.integrationTimeSel = 2; + expectedReply = imtq::cmdIds::REPLY_NO_TORQUE; + if (internalState == InternalState::SHUTDOWN) { + request.mode = acs::SimpleSensorMode::OFF; + } else { + request.mode = acs::SimpleSensorMode::NORMAL; + } + rawPacket = reinterpret_cast(&request); + rawPacketLen = sizeof(imtq::Request); + return returnvalue::OK; + } + case (imtq::cmdIds::START_ACTUATION_DIPOLE): { + if (commandData != nullptr && commandDataLen < 8) { + return DeviceHandlerIF::INVALID_COMMAND_PARAMETER; + } + { + // Do this in any case to read values which might be commanded by the ACS controller. + PoolReadGuard pg(&dipoleSet); + // Commands override anything which was set in the software + if (commandData != nullptr) { + dipoleSet.setValidityBufferGeneration(false); + ReturnValue_t result = dipoleSet.deSerialize(&commandData, &commandDataLen, + SerializeIF::Endianness::NETWORK); + dipoleSet.setValidityBufferGeneration(true); + if (result != returnvalue::OK) { + return result; + } + manualTorqueCmdActive = true; + manuallyCommandedTorqueDuration.setTimeout(dipoleSet.currentTorqueDurationMs.value); + } + } + + expectedReply = imtq::cmdIds::REPLY_WITH_TORQUE; + request.requestType = imtq::RequestType::ACTUATE; + request.specialRequest = imtq::SpecialRequest::NONE; + std::memcpy(request.dipoles, dipoleSet.dipoles.value, sizeof(request.dipoles)); + request.torqueDuration = dipoleSet.currentTorqueDurationMs.value; + if (ACTUATION_WIRETAPPING) { + sif::debug << "Actuating IMTQ with parameters x = " << dipoleSet.dipoles[0] + << ", y = " << dipoleSet.dipoles[1] << ", z = " << dipoleSet.dipoles[2] + << ", duration = " << dipoleSet.currentTorqueDurationMs.value << std::endl; + } + MutexGuard mg(torquer::lazyLock(), torquer::LOCK_TYPE, torquer::LOCK_TIMEOUT, + torquer::LOCK_CTX); + torquer::TORQUEING = true; + torquer::TORQUE_COUNTDOWN.setTimeout(dipoleSet.currentTorqueDurationMs.value); + rawPacket = reinterpret_cast(&request); + rawPacketLen = sizeof(imtq::Request); + return returnvalue::OK; + } + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::FAILED; +} + +void ImtqHandler::fillCommandAndReplyMap() { + insertInCommandMap(imtq::cmdIds::REQUEST); + insertInCommandMap(imtq::cmdIds::START_ACTUATION_DIPOLE); + insertInReplyMap(imtq::cmdIds::REPLY_NO_TORQUE, 5, nullptr, 0, true); + insertInReplyMap(imtq::cmdIds::REPLY_WITH_TORQUE, 20, nullptr, 0, true); + insertInCommandMap(imtq::cmdIds::POS_X_SELF_TEST); + insertInCommandMap(imtq::cmdIds::NEG_X_SELF_TEST); + insertInCommandMap(imtq::cmdIds::POS_Y_SELF_TEST); + insertInCommandMap(imtq::cmdIds::NEG_Y_SELF_TEST); + insertInCommandMap(imtq::cmdIds::POS_Z_SELF_TEST); + insertInCommandMap(imtq::cmdIds::NEG_Z_SELF_TEST); + insertInCommandMap(imtq::cmdIds::GET_SELF_TEST_RESULT); +} + +ReturnValue_t ImtqHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + if (getMode() == _MODE_WAIT_OFF or getMode() == _MODE_WAIT_ON or getMode() == _MODE_POWER_DOWN) { + return IGNORE_FULL_PACKET; + } + if (internalState == InternalState::SHUTDOWN) { + commandExecuted = true; + } + if (remainingSize > 0) { + *foundLen = remainingSize; + *foundId = expectedReply; + return returnvalue::OK; + } + return returnvalue::FAILED; +} + +ReturnValue_t ImtqHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) { + ReturnValue_t result; + ReturnValue_t status = returnvalue::OK; + if (getMode() != MODE_NORMAL) { + if (expectedReply == imtq::cmdIds::REPLY_NO_TORQUE) { + ImtqRepliesDefault replies(packet); + if (replies.devWasConfigured() and internalState == InternalState::STARTUP) { + commandExecuted = true; + } + } + return returnvalue::OK; + } + if (expectedReply == imtq::cmdIds::REPLY_NO_TORQUE) { + // sif::debug << "handle measure" << std::endl; + ImtqRepliesDefault replies(packet); + if (replies.devWasConfigured() and internalState == InternalState::STARTUP) { + commandExecuted = true; + } + if (specialRequestActive) { + if (replies.wasSpecialRequestRead()) { + uint8_t* specialRequest = replies.getSpecialRequest(); + imtq::CC::CC cc = static_cast(specialRequest[0]); + result = parseStatusByte(cc, packet); + if (result != returnvalue::OK) { + status = result; + } + if (cc == imtq::CC::CC::GET_SELF_TEST_RESULT) { + handleSelfTestReply(specialRequest); + } + // For a special request, the other stuff was not read, so return here. + return status; + } else { + sif::warning << "IMTQ: Possible timing issue, special request was not read" << std::endl; + } + specialRequestActive = false; + } + if (not replies.wasEngHkRead() and not firstReplyCycle) { + sif::warning << "IMTQ: Possible timing issue, ENG HK was not read" << std::endl; + } + // Still read it, even if it is old. Better than nothing + uint8_t* engHkReply = replies.getEngHk(); + result = parseStatusByte(imtq::CC::GET_ENG_HK_DATA, engHkReply); + if (result == returnvalue::OK) { + fillEngHkDataset(hkDatasetNoTorque, engHkReply); + } else { + status = result; + } + + if (not replies.wasGetSystemStateRead() and not firstReplyCycle) { + sif::warning << "IMTQ: Possible timing issue, system state was not read" << std::endl; + } + uint8_t* sysStateReply = replies.getSystemState(); + result = parseStatusByte(imtq::CC::GET_SYSTEM_STATE, sysStateReply); + if (result == returnvalue::OK) { + fillSystemStateIntoDataset(sysStateReply); + } else { + status = result; + } + + if (not replies.wasGetRawMgmMeasurementRead() and not firstReplyCycle) { + sif::warning << "IMTQ: Possible timing issue, raw MGM measurement was not read" << std::endl; + } + uint8_t* rawMgmMeasurement = replies.getRawMgmMeasurement(); + result = parseStatusByte(imtq::CC::GET_RAW_MTM_MEASUREMENT, rawMgmMeasurement); + if (result == returnvalue::OK) { + fillRawMtmDataset(rawMtmNoTorque, rawMgmMeasurement); + } else { + status = result; + } + + if (not replies.wasCalibMgmMeasurementRead() and not firstReplyCycle) { + sif::warning << "IMTQ: Possible timing issue, calib MGM measurement was not read" + << std::endl; + } + uint8_t* calibMgmMeasurement = replies.getCalibMgmMeasurement(); + result = parseStatusByte(imtq::CC::GET_CAL_MTM_MEASUREMENT, calibMgmMeasurement); + if (result == returnvalue::OK) { + fillCalibratedMtmDataset(calibMgmMeasurement); + } else { + status = result; + } + } else if (expectedReply == imtq::cmdIds::REPLY_WITH_TORQUE) { + // sif::debug << "handle measure with torque" << std::endl; + ImtqRepliesWithTorque replies(packet); + if (replies.wasDipoleActuationRead()) { + parseStatusByte(imtq::CC::START_ACTUATION_DIPOLE, replies.getDipoleActuation()); + } else if (not firstReplyCycle) { + sif::warning << "IMTQ: Possible timing issue, start actuation dipole status was not read" + << std::endl; + } + + if (not replies.wasGetRawMgmMeasurementRead() and not firstReplyCycle) { + sif::warning << "IMTQ: Possible timing issue, was MGM measurement with torque was not read" + << std::endl; + } + uint8_t* rawMgmMeasurement = replies.getRawMgmMeasurement(); + result = parseStatusByte(imtq::CC::GET_RAW_MTM_MEASUREMENT, rawMgmMeasurement); + if (result == returnvalue::OK) { + fillRawMtmDataset(rawMtmWithTorque, rawMgmMeasurement); + } else { + status = result; + } + + if (not replies.wasEngHkRead() and not firstReplyCycle) { + sif::warning << "IMTQ: Possible timing issue, engineering HK with torque was not read" + << std::endl; + } + uint8_t* engHkReply = replies.getEngHk(); + result = parseStatusByte(imtq::CC::GET_ENG_HK_DATA, engHkReply); + if (result != returnvalue::OK) { + return result; + } else { + status = result; + } + fillEngHkDataset(hkDatasetWithTorque, engHkReply); + if (firstReplyCycle) { + firstReplyCycle = false; + } + } + return status; +} + +LocalPoolDataSetBase* ImtqHandler::getDataSetHandle(sid_t sid) { + if (sid == hkDatasetNoTorque.getSid()) { + return &hkDatasetNoTorque; + } else if (sid == dipoleSet.getSid()) { + return &dipoleSet; + } else if (sid == statusSet.getSid()) { + return &statusSet; + } else if (sid == hkDatasetWithTorque.getSid()) { + return &hkDatasetWithTorque; + } else if (sid == rawMtmWithTorque.getSid()) { + return &rawMtmWithTorque; + } else if (sid == calMtmMeasurementSet.getSid()) { + return &calMtmMeasurementSet; + } else if (sid == rawMtmNoTorque.getSid()) { + return &rawMtmNoTorque; + } else if (sid == posXselfTestDataset.getSid()) { + return &posXselfTestDataset; + } else if (sid == negXselfTestDataset.getSid()) { + return &negXselfTestDataset; + } else if (sid == posYselfTestDataset.getSid()) { + return &posYselfTestDataset; + } else if (sid == negYselfTestDataset.getSid()) { + return &negYselfTestDataset; + } else if (sid == posZselfTestDataset.getSid()) { + return &posZselfTestDataset; + } else if (sid == negZselfTestDataset.getSid()) { + return &negZselfTestDataset; + } else { + sif::error << "ImtqHandler::getDataSetHandle: Invalid SID" << std::endl; + return nullptr; + } +} + +uint32_t ImtqHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 5000; } + +ReturnValue_t ImtqHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + /** Entries of housekeeping dataset */ + localDataPoolMap.emplace(imtq::STATUS_BYTE_MODE, &statusMode); + localDataPoolMap.emplace(imtq::STATUS_BYTE_CONF, &statusConfig); + localDataPoolMap.emplace(imtq::STATUS_BYTE_ERROR, &statusError); + localDataPoolMap.emplace(imtq::STATUS_BYTE_UPTIME, &statusUptime); + + // ENG HK No Torque + localDataPoolMap.emplace(imtq::DIGITAL_VOLTAGE_MV, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::ANALOG_VOLTAGE_MV, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::DIGITAL_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::ANALOG_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::COIL_CURRENTS, &coilCurrentsMilliampsNoTorque); + localDataPoolMap.emplace(imtq::COIL_TEMPERATURES, &coilTempsNoTorque); + localDataPoolMap.emplace(imtq::MCU_TEMPERATURE, new PoolEntry({0})); + + // ENG HK With Torque + localDataPoolMap.emplace(imtq::DIGITAL_VOLTAGE_MV_WT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::ANALOG_VOLTAGE_MV_WT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::DIGITAL_CURRENT_WT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::ANALOG_CURRENT_WT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::COIL_CURRENTS_WT, &coilCurrentsMilliampsWithTorque); + localDataPoolMap.emplace(imtq::COIL_TEMPERATURES_WT, &coilTempsWithTorque); + localDataPoolMap.emplace(imtq::MCU_TEMPERATURE_WT, new PoolEntry({0})); + + localDataPoolMap.emplace(imtq::DIPOLES_ID, &dipolesPoolEntry); + localDataPoolMap.emplace(imtq::CURRENT_TORQUE_DURATION, &torqueDurationEntry); + + /** Entries of calibrated MTM measurement dataset */ + localDataPoolMap.emplace(imtq::MGM_CAL_NT, &mgmCalEntry); + localDataPoolMap.emplace(imtq::ACTUATION_CAL_STATUS, new PoolEntry({0})); + + /** Entries of raw MTM measurement dataset */ + localDataPoolMap.emplace(imtq::MTM_RAW, &mtmRawNoTorque); + localDataPoolMap.emplace(imtq::ACTUATION_RAW_STATUS, &actStatusNoTorque); + + localDataPoolMap.emplace(imtq::MTM_RAW_WT, &mtmRawWithTorque); + localDataPoolMap.emplace(imtq::ACTUATION_RAW_STATUS_WT, &actStatusWithTorque); + + /** INIT measurements for positive X axis test */ + localDataPoolMap.emplace(imtq::INIT_POS_X_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_X_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** INIT measurements for negative X axis test */ + localDataPoolMap.emplace(imtq::INIT_NEG_X_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_X_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** INIT measurements for positive Y axis test */ + localDataPoolMap.emplace(imtq::INIT_POS_Y_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Y_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** INIT measurements for negative Y axis test */ + localDataPoolMap.emplace(imtq::INIT_NEG_Y_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Y_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** INIT measurements for positive Z axis test */ + localDataPoolMap.emplace(imtq::INIT_POS_Z_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_POS_Z_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** INIT measurements for negative Z axis test */ + localDataPoolMap.emplace(imtq::INIT_NEG_Z_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::INIT_NEG_Z_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + localDataPoolMap.emplace(imtq::POS_X_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_X_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + localDataPoolMap.emplace(imtq::NEG_X_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_X_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + localDataPoolMap.emplace(imtq::POS_Y_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Y_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + localDataPoolMap.emplace(imtq::NEG_Y_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Y_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + localDataPoolMap.emplace(imtq::POS_Z_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::POS_Z_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + localDataPoolMap.emplace(imtq::NEG_Z_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::NEG_Z_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** FINA measurements for positive X axis test */ + localDataPoolMap.emplace(imtq::FINA_POS_X_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_X_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** FINA measurements for negative X axis test */ + localDataPoolMap.emplace(imtq::FINA_NEG_X_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_X_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** FINA measurements for positive Y axis test */ + localDataPoolMap.emplace(imtq::FINA_POS_Y_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Y_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** FINA measurements for negative Y axis test */ + localDataPoolMap.emplace(imtq::FINA_NEG_Y_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Y_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** FINA measurements for positive Z axis test */ + localDataPoolMap.emplace(imtq::FINA_POS_Z_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_POS_Z_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + /** FINA measurements for negative Z axis test */ + localDataPoolMap.emplace(imtq::FINA_NEG_Z_ERR, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_RAW_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_RAW_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_RAW_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_CAL_MAG_X, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_CAL_MAG_Y, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_CAL_MAG_Z, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_COIL_X_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_COIL_Y_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_COIL_Z_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_COIL_X_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_COIL_Y_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(imtq::FINA_NEG_Z_COIL_Z_TEMPERATURE, new PoolEntry({0})); + + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(hkDatasetNoTorque.getSid(), enableHkSets, 60.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(hkDatasetWithTorque.getSid(), enableHkSets, 60.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(rawMtmNoTorque.getSid(), false, 10.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(rawMtmWithTorque.getSid(), false, 10.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(calMtmMeasurementSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(statusSet.getSid(), false, 10.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(dipoleSet.getSid(), false, 10.0)); + return DeviceHandlerBase::initializeLocalDataPool(localDataPoolMap, poolManager); +} + +ReturnValue_t ImtqHandler::getSelfTestCommandId(DeviceCommandId_t* id) { + DeviceCommandId_t commandId = getPendingCommand(); + switch (commandId) { + case imtq::cmdIds::POS_X_SELF_TEST: + case imtq::cmdIds::NEG_X_SELF_TEST: + case imtq::cmdIds::POS_Y_SELF_TEST: + case imtq::cmdIds::NEG_Y_SELF_TEST: + case imtq::cmdIds::POS_Z_SELF_TEST: + case imtq::cmdIds::NEG_Z_SELF_TEST: + *id = commandId; + break; + default: + sif::error << "IMTQHandler::getSelfTestCommandId: Reply does not match to pending " + << "command" << std::endl; + // return UNEXPECTED_SELF_TEST_REPLY; + } + return returnvalue::OK; +} + +ReturnValue_t ImtqHandler::parseStatusByte(imtq::CC::CC command, const uint8_t* packet) { + uint8_t cmdErrorField = packet[1] & 0xF; + if (cmdErrorField == 0) { + return returnvalue::OK; + } + sif::error << std::hex; + switch (cmdErrorField) { + case 1: + sif::error << "IMTQ::parseStatusByte: Command 0x" << std::setw(2) << command + << " rejected without reason" << std::endl; + return imtq::REJECTED_WITHOUT_REASON; + case 2: + sif::error << "IMTQ::parseStatusByte: Command 0x" << std::setw(2) << command + << " has invalid command code" << std::endl; + return imtq::INVALID_COMMAND_CODE; + case 3: + sif::error << "IMTQ::parseStatusByte: Command 0x" << std::setw(2) << command + << " has missing parameter" << std::endl; + return imtq::PARAMETER_MISSING; + case 4: + sif::error << "IMTQ::parseStatusByte: Command 0x" << std::setw(2) << command + << " has invalid parameter" << std::endl; + return imtq::PARAMETER_INVALID; + case 5: + sif::error << "IMTQ::parseStatusByte: CC 0x" << std::setw(2) << command << " unavailable" + << std::endl; + return imtq::CC_UNAVAILABLE; + case 7: + sif::error << "IMTQ::parseStatusByte: Internal processing error for command 0x" + << std::setw(2) << command << std::endl; + return imtq::INTERNAL_PROCESSING_ERROR; + default: + sif::error << "IMTQ::parseStatusByte: CMD error field for command 0x" << std::setw(2) + << command << " contains unknown error code 0x" << static_cast(cmdErrorField) + << std::endl; + return imtq::CMD_ERR_UNKNOWN; + } + sif::error << std::dec; +} + +void ImtqHandler::fillEngHkDataset(imtq::HkDataset& hkDataset, const uint8_t* packet) { + PoolReadGuard rg(&hkDataset); + uint8_t offset = 2; + hkDataset.digitalVoltageMv = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + hkDataset.analogVoltageMv = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + hkDataset.digitalCurrentmA = (*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + hkDataset.analogCurrentmA = (*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + hkDataset.coilCurrentsMilliamps[0] = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + hkDataset.coilCurrentsMilliamps[1] = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + hkDataset.coilCurrentsMilliamps[2] = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + hkDataset.coilTemperatures[0] = (*(packet + offset + 1) << 8 | *(packet + offset)); + offset += 2; + hkDataset.coilTemperatures[1] = (*(packet + offset + 1) << 8 | *(packet + offset)); + offset += 2; + hkDataset.coilTemperatures[2] = (*(packet + offset + 1) << 8 | *(packet + offset)); + offset += 2; + size_t dummy = 2; + SerializeAdapter::deSerialize(&hkDataset.mcuTemperature.value, packet + offset, &dummy, + SerializeIF::Endianness::LITTLE); + + hkDataset.setValidity(true, true); + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "IMTQ digital voltage: " << hkDataset.digitalVoltageMv << " mV" << std::endl; + sif::info << "IMTQ analog voltage: " << hkDataset.analogVoltageMv << " mV" << std::endl; + sif::info << "IMTQ digital current: " << hkDataset.digitalCurrentmA << " mA" << std::endl; + sif::info << "IMTQ analog current: " << hkDataset.analogCurrentmA << " mA" << std::endl; + sif::info << "IMTQ coil X current: " << hkDataset.coilCurrentsMilliamps[0] << " mA" + << std::endl; + sif::info << "IMTQ coil Y current: " << hkDataset.coilCurrentsMilliamps[1] << " mA" + << std::endl; + sif::info << "IMTQ coil Z current: " << hkDataset.coilCurrentsMilliamps[2] << " mA" + << std::endl; + sif::info << "IMTQ coil X temperature: " << hkDataset.coilTemperatures[0] << " °C" << std::endl; + sif::info << "IMTQ coil Y temperature: " << hkDataset.coilTemperatures[1] << " °C" << std::endl; + sif::info << "IMTQ coil Z temperature: " << hkDataset.coilTemperatures[2] << " °C" << std::endl; + sif::info << "IMTQ coil MCU temperature: " << hkDataset.mcuTemperature << " °C" << std::endl; +#endif + } +} + +void ImtqHandler::setToGoToNormal(bool enable) { this->goToNormalMode = enable; } + +void ImtqHandler::fillCalibratedMtmDataset(const uint8_t* packet) { + PoolReadGuard rg(&calMtmMeasurementSet); + calMtmMeasurementSet.setValidity(true, true); + int8_t offset = 2; + calMtmMeasurementSet.mgmXyz[0] = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + calMtmMeasurementSet.mgmXyz[1] = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + calMtmMeasurementSet.mgmXyz[2] = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + calMtmMeasurementSet.coilActuationStatus = (*(packet + offset + 3) << 24) | + (*(packet + offset + 2) << 16) | + (*(packet + offset + 1) << 8) | (*(packet + offset)); + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "IMTQ calibrated MTM measurement X: " << calMtmMeasurementSet.mgmXyz[0] << " nT" + << std::endl; + sif::info << "IMTQ calibrated MTM measurement Y: " << calMtmMeasurementSet.mgmXyz[1] << " nT" + << std::endl; + sif::info << "IMTQ calibrated MTM measurement Z: " << calMtmMeasurementSet.mgmXyz[2] << " nT" + << std::endl; + sif::info << "IMTQ coil actuation status during MTM measurement: " + << (unsigned int)calMtmMeasurementSet.coilActuationStatus.value << std::endl; +#endif + } +} + +void ImtqHandler::fillRawMtmDataset(imtq::RawMtmMeasurementSet& set, const uint8_t* packet) { + PoolReadGuard rg(&set); + if (rg.getReadResult() != returnvalue::OK) { + sif::error << "ImtqHandler::fillRawMtmDataset: Read failure" << std::endl; + } + unsigned int offset = 2; + size_t deSerLen = 16; + const uint8_t* dataStart = packet + offset; + int32_t xRaw = 0; + int32_t yRaw = 0; + int32_t zRaw = 0; + uint32_t coilActStatus = 0; + auto res = + SerializeAdapter::deSerialize(&xRaw, &dataStart, &deSerLen, SerializeIF::Endianness::LITTLE); + if (res != returnvalue::OK) { + return; + } + res = + SerializeAdapter::deSerialize(&yRaw, &dataStart, &deSerLen, SerializeIF::Endianness::LITTLE); + if (res != returnvalue::OK) { + return; + } + res = + SerializeAdapter::deSerialize(&zRaw, &dataStart, &deSerLen, SerializeIF::Endianness::LITTLE); + if (res != returnvalue::OK) { + return; + } + res = SerializeAdapter::deSerialize(&coilActStatus, &dataStart, &deSerLen, + SerializeIF::Endianness::LITTLE); + if (res != returnvalue::OK) { + return; + } + set.mtmRawNt[0] = static_cast(xRaw) * 7.5; + set.mtmRawNt[1] = static_cast(yRaw) * 7.5; + set.mtmRawNt[2] = static_cast(zRaw) * 7.5; + set.coilActuationStatus = static_cast(coilActStatus); + set.setValidity(true, true); + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Set ID: " << set.getSid().ownerSetId << std::endl; + sif::info << "IMTQ raw MTM measurement X: " << set.mtmRawNt[0] << " nT" << std::endl; + sif::info << "IMTQ raw MTM measurement Y: " << set.mtmRawNt[1] << " nT" << std::endl; + sif::info << "IMTQ raw MTM measurement Z: " << set.mtmRawNt[2] << " nT" << std::endl; + sif::info << "IMTQ coil actuation status during MTM measurement: " + << (unsigned int)set.coilActuationStatus.value << std::endl; +#endif + } +} + +void ImtqHandler::handleSelfTestReply(const uint8_t* packet) { + uint16_t offset = 2; + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + + switch (*(packet + imtq::MAIN_STEP_OFFSET)) { + case imtq::selfTest::step::X_POSITIVE: { + handlePositiveXSelfTestReply(packet); + break; + } + case imtq::selfTest::step::X_NEGATIVE: { + handleNegativeXSelfTestReply(packet); + break; + } + case imtq::selfTest::step::Y_POSITIVE: { + handlePositiveYSelfTestReply(packet); + break; + } + case imtq::selfTest::step::Y_NEGATIVE: { + handleNegativeYSelfTestReply(packet); + break; + } + case imtq::selfTest::step::Z_POSITIVE: { + handlePositiveZSelfTestReply(packet); + break; + } + case imtq::selfTest::step::Z_NEGATIVE: { + handleNegativeZSelfTestReply(packet); + break; + } + default: + break; + } +} + +void ImtqHandler::handlePositiveXSelfTestReply(const uint8_t* packet) { + PoolReadGuard rg(&posXselfTestDataset); + + uint16_t offset = 2; + /** Init measurements */ + posXselfTestDataset.initErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + posXselfTestDataset.initRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.initRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.initRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.initCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.initCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.initCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.initCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.initCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.initCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.initCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posXselfTestDataset.initCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posXselfTestDataset.initCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** +X measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + posXselfTestDataset.err = *(packet + offset); + offset += 2; // STEP byte will not be stored + posXselfTestDataset.rawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.rawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.rawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.calMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.calMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.calMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.coilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.coilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.coilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.coilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posXselfTestDataset.coilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posXselfTestDataset.coilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** FINA measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + posXselfTestDataset.finaErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + posXselfTestDataset.finaRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.finaRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.finaRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posXselfTestDataset.finaCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.finaCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.finaCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posXselfTestDataset.finaCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.finaCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.finaCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posXselfTestDataset.finaCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posXselfTestDataset.finaCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posXselfTestDataset.finaCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "IMTQ self test (INIT) err: " + << static_cast(posXselfTestDataset.initErr.value) << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field X: " << posXselfTestDataset.initRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Y: " << posXselfTestDataset.initRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Z: " << posXselfTestDataset.initRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field X: " + << posXselfTestDataset.initCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Y: " + << posXselfTestDataset.initCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Z: " + << posXselfTestDataset.initCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) coil X current: " << posXselfTestDataset.initCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y current: " << posXselfTestDataset.initCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z current: " << posXselfTestDataset.initCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil X temperature: " + << posXselfTestDataset.initCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y temperature: " + << posXselfTestDataset.initCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z temperature: " + << posXselfTestDataset.initCoilZTemperature << " °C" << std::endl; + + sif::info << "IMTQ self test (+X) err: " + << static_cast(posXselfTestDataset.err.value) << std::endl; + sif::info << "IMTQ self test (+X) raw magnetic field X: " << posXselfTestDataset.rawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (+X) raw magnetic field Y: " << posXselfTestDataset.rawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (+X) raw magnetic field Z: " << posXselfTestDataset.rawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (+X) calibrated magnetic field X: " << posXselfTestDataset.calMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (+X) calibrated magnetic field Y: " << posXselfTestDataset.calMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (+X) calibrated magnetic field Z: " << posXselfTestDataset.calMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (+X) coil X current: " << posXselfTestDataset.coilXCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+X) coil Y current: " << posXselfTestDataset.coilYCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+X) coil Z current: " << posXselfTestDataset.coilZCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+X) coil X temperature: " << posXselfTestDataset.coilXTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (+X) coil Y temperature: " << posXselfTestDataset.coilYTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (+X) coil Z temperature: " << posXselfTestDataset.coilZTemperature + << " °C" << std::endl; + + sif::info << "IMTQ self test (FINA) err: " + << static_cast(posXselfTestDataset.finaErr.value) << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field X: " << posXselfTestDataset.finaRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Y: " << posXselfTestDataset.finaRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Z: " << posXselfTestDataset.finaRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field X: " + << posXselfTestDataset.finaCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Y: " + << posXselfTestDataset.finaCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Z: " + << posXselfTestDataset.finaCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) coil X current: " << posXselfTestDataset.finaCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y current: " << posXselfTestDataset.finaCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z current: " << posXselfTestDataset.finaCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil X temperature: " + << posXselfTestDataset.finaCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y temperature: " + << posXselfTestDataset.finaCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z temperature: " + << posXselfTestDataset.finaCoilZTemperature << " °C" << std::endl; +#endif + } +} + +void ImtqHandler::handleNegativeXSelfTestReply(const uint8_t* packet) { + PoolReadGuard rg(&posXselfTestDataset); + + uint16_t offset = 2; + /** Init measurements */ + negXselfTestDataset.initErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + negXselfTestDataset.initRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.initRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.initRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.initCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.initCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.initCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.initCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.initCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.initCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.initCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negXselfTestDataset.initCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negXselfTestDataset.initCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** +X measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + negXselfTestDataset.err = *(packet + offset); + offset += 2; // STEP byte will not be stored + negXselfTestDataset.rawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.rawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.rawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.calMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.calMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.calMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.coilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.coilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.coilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.coilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negXselfTestDataset.coilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negXselfTestDataset.coilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** FINA measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + negXselfTestDataset.finaErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + negXselfTestDataset.finaRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.finaRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.finaRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negXselfTestDataset.finaCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.finaCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.finaCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negXselfTestDataset.finaCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.finaCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.finaCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negXselfTestDataset.finaCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negXselfTestDataset.finaCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negXselfTestDataset.finaCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "IMTQ self test (INIT) err: " + << static_cast(negXselfTestDataset.initErr.value) << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field X: " << negXselfTestDataset.initRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Y: " << negXselfTestDataset.initRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Z: " << negXselfTestDataset.initRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field X: " + << negXselfTestDataset.initCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Y: " + << negXselfTestDataset.initCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Z: " + << negXselfTestDataset.initCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) coil X current: " << negXselfTestDataset.initCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y current: " << negXselfTestDataset.initCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z current: " << negXselfTestDataset.initCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil X temperature: " + << negXselfTestDataset.initCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y temperature: " + << negXselfTestDataset.initCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z temperature: " + << negXselfTestDataset.initCoilZTemperature << " °C" << std::endl; + + sif::info << "IMTQ self test (-X) err: " + << static_cast(negXselfTestDataset.err.value) << std::endl; + sif::info << "IMTQ self test (-X) raw magnetic field X: " << negXselfTestDataset.rawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (-X) raw magnetic field Y: " << negXselfTestDataset.rawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (-X) raw magnetic field Z: " << negXselfTestDataset.rawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (-X) calibrated magnetic field X: " << negXselfTestDataset.calMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (-X) calibrated magnetic field Y: " << negXselfTestDataset.calMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (-X) calibrated magnetic field Z: " << negXselfTestDataset.calMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (-X) coil X current: " << negXselfTestDataset.coilXCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-X) coil Y current: " << negXselfTestDataset.coilYCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-X) coil Z current: " << negXselfTestDataset.coilZCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-X) coil X temperature: " << negXselfTestDataset.coilXTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (-X) coil Y temperature: " << negXselfTestDataset.coilYTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (-X) coil Z temperature: " << negXselfTestDataset.coilZTemperature + << " °C" << std::endl; + + sif::info << "IMTQ self test (FINA) err: " + << static_cast(negXselfTestDataset.finaErr.value) << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field X: " << negXselfTestDataset.finaRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Y: " << negXselfTestDataset.finaRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Z: " << negXselfTestDataset.finaRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field X: " + << negXselfTestDataset.finaCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Y: " + << negXselfTestDataset.finaCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Z: " + << negXselfTestDataset.finaCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) coil X current: " << negXselfTestDataset.finaCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y current: " << negXselfTestDataset.finaCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z current: " << negXselfTestDataset.finaCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil X temperature: " + << negXselfTestDataset.finaCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y temperature: " + << negXselfTestDataset.finaCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z temperature: " + << negXselfTestDataset.finaCoilZTemperature << " °C" << std::endl; +#endif + } +} + +void ImtqHandler::handlePositiveYSelfTestReply(const uint8_t* packet) { + PoolReadGuard rg(&posXselfTestDataset); + + uint16_t offset = 2; + /** Init measurements */ + posYselfTestDataset.initErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + posYselfTestDataset.initRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.initRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.initRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.initCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.initCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.initCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.initCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.initCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.initCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.initCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posYselfTestDataset.initCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posYselfTestDataset.initCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** +X measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + posYselfTestDataset.err = *(packet + offset); + offset += 2; // STEP byte will not be stored + posYselfTestDataset.rawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.rawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.rawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.calMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.calMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.calMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.coilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.coilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.coilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.coilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posYselfTestDataset.coilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posYselfTestDataset.coilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** FINA measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + posYselfTestDataset.finaErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + posYselfTestDataset.finaRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.finaRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.finaRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posYselfTestDataset.finaCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.finaCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.finaCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posYselfTestDataset.finaCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.finaCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.finaCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posYselfTestDataset.finaCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posYselfTestDataset.finaCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posYselfTestDataset.finaCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "IMTQ self test (INIT) err: " + << static_cast(posYselfTestDataset.initErr.value) << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field X: " << posYselfTestDataset.initRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Y: " << posYselfTestDataset.initRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Z: " << posYselfTestDataset.initRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field X: " + << posYselfTestDataset.initCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Y: " + << posYselfTestDataset.initCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Z: " + << posYselfTestDataset.initCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) coil X current: " << posYselfTestDataset.initCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y current: " << posYselfTestDataset.initCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z current: " << posYselfTestDataset.initCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil X temperature: " + << posYselfTestDataset.initCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y temperature: " + << posYselfTestDataset.initCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z temperature: " + << posYselfTestDataset.initCoilZTemperature << " °C" << std::endl; + + sif::info << "IMTQ self test (+Y) err: " + << static_cast(posYselfTestDataset.err.value) << std::endl; + sif::info << "IMTQ self test (+Y) raw magnetic field X: " << posYselfTestDataset.rawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (+Y) raw magnetic field Y: " << posYselfTestDataset.rawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (+Y) raw magnetic field Z: " << posYselfTestDataset.rawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (+Y) calibrated magnetic field X: " << posYselfTestDataset.calMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (+Y) calibrated magnetic field Y: " << posYselfTestDataset.calMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (+Y) calibrated magnetic field Z: " << posYselfTestDataset.calMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (+Y) coil X current: " << posYselfTestDataset.coilXCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+Y) coil Y current: " << posYselfTestDataset.coilYCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+Y) coil Z current: " << posYselfTestDataset.coilZCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+Y) coil X temperature: " << posYselfTestDataset.coilXTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (+Y) coil Y temperature: " << posYselfTestDataset.coilYTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (+Y) coil Z temperature: " << posYselfTestDataset.coilZTemperature + << " °C" << std::endl; + + sif::info << "IMTQ self test (FINA) err: " + << static_cast(posYselfTestDataset.finaErr.value) << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field X: " << posYselfTestDataset.finaRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Y: " << posYselfTestDataset.finaRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Z: " << posYselfTestDataset.finaRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field X: " + << posYselfTestDataset.finaCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Y: " + << posYselfTestDataset.finaCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Z: " + << posYselfTestDataset.finaCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) coil X current: " << posYselfTestDataset.finaCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y current: " << posYselfTestDataset.finaCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z current: " << posYselfTestDataset.finaCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil X temperature: " + << posYselfTestDataset.finaCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y temperature: " + << posYselfTestDataset.finaCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z temperature: " + << posYselfTestDataset.finaCoilZTemperature << " °C" << std::endl; +#endif + } +} + +void ImtqHandler::handleNegativeYSelfTestReply(const uint8_t* packet) { + PoolReadGuard rg(&posXselfTestDataset); + + uint16_t offset = 2; + /** Init measurements */ + posZselfTestDataset.initErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + negYselfTestDataset.initRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.initRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.initRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.initCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.initCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.initCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.initCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.initCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.initCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.initCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negYselfTestDataset.initCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negYselfTestDataset.initCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** +X measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + negYselfTestDataset.err = *(packet + offset); + offset += 2; // STEP byte will not be stored + negYselfTestDataset.rawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.rawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.rawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.calMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.calMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.calMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.coilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.coilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.coilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.coilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negYselfTestDataset.coilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negYselfTestDataset.coilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** FINA measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + negYselfTestDataset.finaErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + negYselfTestDataset.finaRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.finaRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.finaRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negYselfTestDataset.finaCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.finaCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.finaCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negYselfTestDataset.finaCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.finaCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.finaCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negYselfTestDataset.finaCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negYselfTestDataset.finaCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negYselfTestDataset.finaCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "IMTQ self test (INIT) err: " + << static_cast(negYselfTestDataset.initErr.value) << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field X: " << negYselfTestDataset.initRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Y: " << negYselfTestDataset.initRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Z: " << negYselfTestDataset.initRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field X: " + << negYselfTestDataset.initCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Y: " + << negYselfTestDataset.initCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Z: " + << negYselfTestDataset.initCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) coil X current: " << negYselfTestDataset.initCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y current: " << negYselfTestDataset.initCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z current: " << negYselfTestDataset.initCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil X temperature: " + << negYselfTestDataset.initCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y temperature: " + << negYselfTestDataset.initCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z temperature: " + << negYselfTestDataset.initCoilZTemperature << " °C" << std::endl; + + sif::info << "IMTQ self test (-Y) err: " + << static_cast(negYselfTestDataset.err.value) << std::endl; + sif::info << "IMTQ self test (-Y) raw magnetic field X: " << negYselfTestDataset.rawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (-Y) raw magnetic field Y: " << negYselfTestDataset.rawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (-Y) raw magnetic field Z: " << negYselfTestDataset.rawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (-Y) calibrated magnetic field X: " << negYselfTestDataset.calMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (-Y) calibrated magnetic field Y: " << negYselfTestDataset.calMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (-Y) calibrated magnetic field Z: " << negYselfTestDataset.calMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (-Y) coil X current: " << negYselfTestDataset.coilXCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-Y) coil Y current: " << negYselfTestDataset.coilYCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-Y) coil Z current: " << negYselfTestDataset.coilZCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-Y) coil X temperature: " << negYselfTestDataset.coilXTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (-Y) coil Y temperature: " << negYselfTestDataset.coilYTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (-Y) coil Z temperature: " << negYselfTestDataset.coilZTemperature + << " °C" << std::endl; + + sif::info << "IMTQ self test (FINA) err: " + << static_cast(negYselfTestDataset.finaErr.value) << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field X: " << negYselfTestDataset.finaRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Y: " << negYselfTestDataset.finaRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Z: " << negYselfTestDataset.finaRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field X: " + << negYselfTestDataset.finaCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Y: " + << negYselfTestDataset.finaCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Z: " + << negYselfTestDataset.finaCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) coil X current: " << negYselfTestDataset.finaCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y current: " << negYselfTestDataset.finaCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z current: " << negYselfTestDataset.finaCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil X temperature: " + << negYselfTestDataset.finaCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y temperature: " + << negYselfTestDataset.finaCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z temperature: " + << negYselfTestDataset.finaCoilZTemperature << " °C" << std::endl; +#endif + } +} + +void ImtqHandler::handlePositiveZSelfTestReply(const uint8_t* packet) { + PoolReadGuard rg(&posXselfTestDataset); + + uint16_t offset = 2; + /** Init measurements */ + posZselfTestDataset.initErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + posZselfTestDataset.initRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.initRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.initRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.initCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.initCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.initCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.initCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.initCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.initCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.initCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posZselfTestDataset.initCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posZselfTestDataset.initCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** +X measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + posZselfTestDataset.err = *(packet + offset); + offset += 2; // STEP byte will not be stored + posZselfTestDataset.rawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.rawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.rawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.calMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.calMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.calMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.coilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.coilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.coilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.coilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posZselfTestDataset.coilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posZselfTestDataset.coilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** FINA measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + posZselfTestDataset.finaErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + posZselfTestDataset.finaRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.finaRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.finaRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + posZselfTestDataset.finaCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.finaCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.finaCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + posZselfTestDataset.finaCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.finaCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.finaCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + posZselfTestDataset.finaCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posZselfTestDataset.finaCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + posZselfTestDataset.finaCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "IMTQ self test (INIT) err: " + << static_cast(posZselfTestDataset.initErr.value) << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field X: " << posZselfTestDataset.initRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Y: " << posZselfTestDataset.initRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Z: " << posZselfTestDataset.initRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field X: " + << posZselfTestDataset.initCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Y: " + << posZselfTestDataset.initCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Z: " + << posZselfTestDataset.initCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) coil X current: " << posZselfTestDataset.initCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y current: " << posZselfTestDataset.initCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z current: " << posZselfTestDataset.initCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil X temperature: " + << posZselfTestDataset.initCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y temperature: " + << posZselfTestDataset.initCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z temperature: " + << posZselfTestDataset.initCoilZTemperature << " °C" << std::endl; + + sif::info << "IMTQ self test (+Z) err: " + << static_cast(posZselfTestDataset.err.value) << std::endl; + sif::info << "IMTQ self test (+Z) raw magnetic field X: " << posZselfTestDataset.rawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (+Z) raw magnetic field Y: " << posZselfTestDataset.rawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (+Z) raw magnetic field Z: " << posZselfTestDataset.rawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (+Z) calibrated magnetic field X: " << posZselfTestDataset.calMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (+Z) calibrated magnetic field Y: " << posZselfTestDataset.calMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (+Z) calibrated magnetic field Z: " << posZselfTestDataset.calMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (+Z) coil X current: " << posZselfTestDataset.coilXCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+Z) coil Y current: " << posZselfTestDataset.coilYCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+Z) coil Z current: " << posZselfTestDataset.coilZCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (+Z) coil X temperature: " << posZselfTestDataset.coilXTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (+Z) coil Y temperature: " << posZselfTestDataset.coilYTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (+Z) coil Z temperature: " << negYselfTestDataset.coilZTemperature + << " °C" << std::endl; + + sif::info << "IMTQ self test (FINA) err: " + << static_cast(posZselfTestDataset.finaErr.value) << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field X: " << posZselfTestDataset.finaRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Y: " << posZselfTestDataset.finaRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Z: " << posZselfTestDataset.finaRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field X: " + << posZselfTestDataset.finaCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Y: " + << posZselfTestDataset.finaCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Z: " + << posZselfTestDataset.finaCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) coil X current: " << posZselfTestDataset.finaCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y current: " << posZselfTestDataset.finaCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z current: " << posZselfTestDataset.finaCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil X temperature: " + << posZselfTestDataset.finaCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y temperature: " + << posZselfTestDataset.finaCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z temperature: " + << posZselfTestDataset.finaCoilZTemperature << " °C" << std::endl; +#endif + } +} + +void ImtqHandler::handleNegativeZSelfTestReply(const uint8_t* packet) { + PoolReadGuard rg(&posXselfTestDataset); + + uint16_t offset = 2; + /** Init measurements */ + negZselfTestDataset.initErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + negZselfTestDataset.initRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.initRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.initRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.initCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.initCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.initCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.initCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.initCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.initCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.initCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negZselfTestDataset.initCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negZselfTestDataset.initCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** +X measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + negZselfTestDataset.err = *(packet + offset); + offset += 2; // STEP byte will not be stored + negZselfTestDataset.rawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.rawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.rawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.calMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.calMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.calMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.coilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.coilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.coilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.coilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negZselfTestDataset.coilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negZselfTestDataset.coilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + /** FINA measurements */ + checkErrorByte(*(packet + offset), *(packet + offset + 1)); + negZselfTestDataset.finaErr = *(packet + offset); + offset += 2; // STEP byte will not be stored + negZselfTestDataset.finaRawMagX = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.finaRawMagY = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.finaRawMagZ = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset)) * + 7.5; + offset += 4; + negZselfTestDataset.finaCalMagX = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.finaCalMagY = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.finaCalMagZ = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + negZselfTestDataset.finaCoilXCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.finaCoilYCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.finaCoilZCurrent = + static_cast(*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1; + offset += 2; + negZselfTestDataset.finaCoilXTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negZselfTestDataset.finaCoilYTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 2; + negZselfTestDataset.finaCoilZTemperature = *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "IMTQ self test (INIT) err: " + << static_cast(negZselfTestDataset.initErr.value) << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field X: " << negZselfTestDataset.initRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Y: " << negZselfTestDataset.initRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) raw magnetic field Z: " << negZselfTestDataset.initRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field X: " + << negZselfTestDataset.initCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Y: " + << negZselfTestDataset.initCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) calibrated magnetic field Z: " + << negZselfTestDataset.initCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (INIT) coil X current: " << negZselfTestDataset.initCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y current: " << negZselfTestDataset.initCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z current: " << negZselfTestDataset.initCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (INIT) coil X temperature: " + << negZselfTestDataset.initCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Y temperature: " + << negZselfTestDataset.initCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (INIT) coil Z temperature: " + << negZselfTestDataset.initCoilZTemperature << " °C" << std::endl; + + sif::info << "IMTQ self test (-Z) err: " + << static_cast(negZselfTestDataset.err.value) << std::endl; + sif::info << "IMTQ self test (-Z) raw magnetic field X: " << negZselfTestDataset.rawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (-Z) raw magnetic field Y: " << negZselfTestDataset.rawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (-Z) raw magnetic field Z: " << negZselfTestDataset.rawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (-Z) calibrated magnetic field X: " << negZselfTestDataset.calMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (-Z) calibrated magnetic field Y: " << negZselfTestDataset.calMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (-Z) calibrated magnetic field Z: " << negZselfTestDataset.calMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (-Z) coil X current: " << negZselfTestDataset.coilXCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-Z) coil Y current: " << negZselfTestDataset.coilYCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-Z) coil Z current: " << negZselfTestDataset.coilZCurrent << " mA" + << std::endl; + sif::info << "IMTQ self test (-Z) coil X temperature: " << negZselfTestDataset.coilXTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (-Z) coil Y temperature: " << negZselfTestDataset.coilYTemperature + << " °C" << std::endl; + sif::info << "IMTQ self test (-Z) coil Z temperature: " << negYselfTestDataset.coilZTemperature + << " °C" << std::endl; + + sif::info << "IMTQ self test (FINA) err: " + << static_cast(negZselfTestDataset.finaErr.value) << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field X: " << negZselfTestDataset.finaRawMagX + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Y: " << negZselfTestDataset.finaRawMagY + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) raw magnetic field Z: " << negZselfTestDataset.finaRawMagZ + << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field X: " + << negZselfTestDataset.finaCalMagX << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Y: " + << negZselfTestDataset.finaCalMagY << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) calibrated magnetic field Z: " + << negZselfTestDataset.finaCalMagZ << " nT" << std::endl; + sif::info << "IMTQ self test (FINA) coil X current: " << negZselfTestDataset.finaCoilXCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y current: " << negZselfTestDataset.finaCoilYCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z current: " << negZselfTestDataset.finaCoilZCurrent + << " mA" << std::endl; + sif::info << "IMTQ self test (FINA) coil X temperature: " + << negZselfTestDataset.finaCoilXTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Y temperature: " + << negZselfTestDataset.finaCoilYTemperature << " °C" << std::endl; + sif::info << "IMTQ self test (FINA) coil Z temperature: " + << negZselfTestDataset.finaCoilZTemperature << " °C" << std::endl; +#endif + } +} + +void ImtqHandler::setDebugMode(bool enable) { this->debugMode = enable; } + +void ImtqHandler::checkErrorByte(const uint8_t errorByte, const uint8_t step) { + std::string stepString(""); + if (step < 8) { + stepString = makeStepString(step); + } else { + /** This should normally never happen */ + sif::debug << "IMTQHandler::checkErrorByte: Invalid step" << std::endl; + return; + } + + if (errorByte == 0) { + return; + } + + if (errorByte & imtq::I2C_FAILURE_MASK) { + triggerEvent(SELF_TEST_I2C_FAILURE, step); + sif::error << "IMTQHandler::checkErrorByte: Self test I2C failure for step " << stepString + << std::endl; + } + if (errorByte & imtq::SPI_FAILURE_MASK) { + triggerEvent(SELF_TEST_SPI_FAILURE, step); + sif::error << "IMTQHandler::checkErrorByte: Self test SPI failure for step " << stepString + << std::endl; + } + if (errorByte & imtq::ADC_FAILURE_MASK) { + triggerEvent(SELF_TEST_ADC_FAILURE, step); + sif::error << "IMTQHandler::checkErrorByte: Self test ADC failure for step " << stepString + << std::endl; + } + if (errorByte & imtq::PWM_FAILURE_MASK) { + triggerEvent(SELF_TEST_PWM_FAILURE, step); + sif::error << "IMTQHandler::checkErrorByte: Self test PWM failure for step " << stepString + << std::endl; + } + if (errorByte & imtq::TC_FAILURE_MASK) { + triggerEvent(SELF_TEST_TC_FAILURE, step); + sif::error << "IMTQHandler::checkErrorByte: Self test TC failure (system failure) for step " + << stepString << std::endl; + } + if (errorByte & imtq::MTM_RANGE_FAILURE_MASK) { + triggerEvent(SELF_TEST_TC_FAILURE, step); + sif::error << "IMTQHandler::checkErrorByte: Self test MTM range failure for step " << stepString + << std::endl; + } + if (errorByte & imtq::COIL_CURRENT_FAILURE_MASK) { + triggerEvent(SELF_TEST_COIL_CURRENT_FAILURE, step); + sif::error << "IMTQHandler::checkErrorByte: Self test coil current outside of expected " + "range for step " + << stepString << std::endl; + } + + if (errorByte & imtq::INVALID_ERROR_BYTE) { + triggerEvent(INVALID_ERROR_BYTE, step); + sif::error << "IMTQHandler::checkErrorByte: Self test result of step " << stepString + << " has invalid error byte" << std::endl; + } +} + +void ImtqHandler::fillSystemStateIntoDataset(const uint8_t* packet) { + PoolReadGuard pg(&statusSet); + statusSet.statusByteMode.value = packet[2]; + statusSet.statusByteError.value = packet[3]; + statusSet.statusByteConfig.value = packet[4]; + size_t dummy = 0; + SerializeAdapter::deSerialize(&statusSet.statusByteUptime.value, packet + 5, &dummy, + SerializeIF::Endianness::LITTLE); + statusSet.setValidity(true, true); +} + +std::string ImtqHandler::makeStepString(const uint8_t step) { + std::string stepString(""); + switch (step) { + case imtq::selfTest::step::INIT: + stepString = std::string("INIT"); + break; + case imtq::selfTest::step::X_POSITIVE: + stepString = std::string("+X"); + break; + case imtq::selfTest::step::X_NEGATIVE: + stepString = std::string("-X"); + break; + case imtq::selfTest::step::Y_POSITIVE: + stepString = std::string("+Y"); + break; + case imtq::selfTest::step::Y_NEGATIVE: + stepString = std::string("-Y"); + break; + case imtq::selfTest::step::Z_POSITIVE: + stepString = std::string("+Z"); + break; + case imtq::selfTest::step::Z_NEGATIVE: + stepString = std::string("-Z"); + break; + case imtq::selfTest::step::FINA: + stepString = std::string("FINA"); + break; + default: + sif::error << "IMTQHandler::checkErrorByte: Received packet with invalid step information" + << std::endl; + break; + } + return stepString; +} + +ReturnValue_t ImtqHandler::getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches) { + if (switcher != power::NO_SWITCH) { + *numberOfSwitches = 1; + *switches = &switcher; + return returnvalue::OK; + } + return DeviceHandlerBase::NO_SWITCH; +} diff --git a/mission/acs/ImtqHandler.h b/mission/acs/ImtqHandler.h new file mode 100644 index 0000000..b435e49 --- /dev/null +++ b/mission/acs/ImtqHandler.h @@ -0,0 +1,209 @@ +#ifndef MISSION_DEVICES_IMTQHANDLER_H_ +#define MISSION_DEVICES_IMTQHANDLER_H_ + +#include +#include +#include + +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +/** + * @brief This is the device handler for the ISIS Magnetorquer iMTQ. + * + * @author J. Meier + */ +class ImtqHandler : public DeviceHandlerBase { + public: + enum NormalPollingMode { UNCALIBRATED = 0, CALIBRATED = 1, BOTH = 2 }; + + ImtqHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + power::Switch_t pwrSwitcher, bool enableHkSets); + virtual ~ImtqHandler(); + + void setPollingMode(NormalPollingMode pollMode); + + /** + * @brief Sets mode to MODE_NORMAL. Can be used for debugging. + */ + void setToGoToNormal(bool enable); + + void setDebugMode(bool enable); + + protected: + ReturnValue_t performOperation(uint8_t opCode); + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + virtual LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + ReturnValue_t getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches) override; + + private: + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::IMTQ_HANDLER; + + //! [EXPORT] : [COMMENT] Get self test result returns I2C failure + //! P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> + //! -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA + static const Event SELF_TEST_I2C_FAILURE = MAKE_EVENT(1, severity::LOW); + //! [EXPORT] : [COMMENT] Get self test result returns SPI failure. This concerns the MTM + //! connectivity. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 + //! -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA + static const Event SELF_TEST_SPI_FAILURE = MAKE_EVENT(2, severity::LOW); + //! [EXPORT] : [COMMENT] Get self test result returns failure in measurement of current and + //! temperature. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 + //! -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA + static const Event SELF_TEST_ADC_FAILURE = MAKE_EVENT(3, severity::LOW); + //! [EXPORT] : [COMMENT] Get self test result returns PWM failure which concerns the coil + //! actuation. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> + //! +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA + static const Event SELF_TEST_PWM_FAILURE = MAKE_EVENT(4, severity::LOW); + //! [EXPORT] : [COMMENT] Get self test result returns TC failure (system failure) + //! P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, 3 -> +Y, 4 -> + //! -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA + static const Event SELF_TEST_TC_FAILURE = MAKE_EVENT(5, severity::LOW); + //! [EXPORT] : [COMMENT] Get self test result returns failure that MTM values were outside of the + //! expected range. P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 -> +X, 2 -> -X, + //! 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA + static const Event SELF_TEST_MTM_RANGE_FAILURE = MAKE_EVENT(6, severity::LOW); + //! [EXPORT] : [COMMENT] Get self test result returns failure indicating that the coil current was + //! outside of the expected range P1: Indicates on which axis the failure occurred. 0 -> INIT, 1 + //! -> +X, 2 -> -X, 3 -> +Y, 4 -> -Y, 5 -> +Z, 6 -> -Z, 7 -> FINA + static const Event SELF_TEST_COIL_CURRENT_FAILURE = MAKE_EVENT(7, severity::LOW); + //! [EXPORT] : [COMMENT] Received invalid error byte. This indicates an error of the communication + //! link between IMTQ and OBC. + static const Event INVALID_ERROR_BYTE = MAKE_EVENT(8, severity::LOW); + + enum class InternalState { NONE, STARTUP, SHUTDOWN } internalState = InternalState::NONE; + bool commandExecuted = false; + bool enableHkSets = false; + + imtq::Request request{}; + + imtq::StatusDataset statusSet; + imtq::DipoleActuationSet dipoleSet; + imtq::RawMtmMeasurementNoTorque rawMtmNoTorque; + imtq::HkDatasetNoTorque hkDatasetNoTorque; + + imtq::RawMtmMeasurementWithTorque rawMtmWithTorque; + imtq::HkDatasetWithTorque hkDatasetWithTorque; + + imtq::CalibratedMtmMeasurementSet calMtmMeasurementSet; + imtq::PosXSelfTestSet posXselfTestDataset; + imtq::NegXSelfTestSet negXselfTestDataset; + imtq::PosYSelfTestSet posYselfTestDataset; + imtq::NegYSelfTestSet negYselfTestDataset; + imtq::PosZSelfTestSet posZselfTestDataset; + imtq::NegZSelfTestSet negZselfTestDataset; + bool manualTorqueCmdActive = false; + bool ignoreActForRestOfComSteps = false; + Countdown manuallyCommandedTorqueDuration = Countdown(); + + NormalPollingMode pollingMode = NormalPollingMode::UNCALIBRATED; + + PoolEntry statusMode = PoolEntry({0}); + PoolEntry statusError = PoolEntry({0}); + PoolEntry statusConfig = PoolEntry({0}); + PoolEntry statusUptime = PoolEntry({0}); + + PoolEntry mgmCalEntry = PoolEntry(3); + PoolEntry dipolesPoolEntry = PoolEntry({0, 0, 0}, false); + PoolEntry torqueDurationEntry = PoolEntry({0}, false); + PoolEntry coilCurrentsMilliampsNoTorque = PoolEntry(3); + PoolEntry coilCurrentsMilliampsWithTorque = PoolEntry(3); + PoolEntry coilTempsNoTorque = PoolEntry(3); + PoolEntry coilTempsWithTorque = PoolEntry(3); + PoolEntry mtmRawNoTorque = PoolEntry(3); + PoolEntry actStatusNoTorque = PoolEntry(1); + PoolEntry mtmRawWithTorque = PoolEntry(3); + PoolEntry actStatusWithTorque = PoolEntry(1); + + power::Switch_t switcher = power::NO_SWITCH; + + DeviceCommandId_t expectedReply = DeviceHandlerIF::NO_COMMAND_ID; + bool goToNormalMode = false; + bool debugMode = false; + bool specialRequestActive = false; + bool firstReplyCycle = true; + + imtq::RequestType requestStep = imtq::RequestType::MEASURE_NO_ACTUATION; + + /** + * @brief In case of a status reply to a single axis self test command, this function + * searches for the actual pending command. + */ + ReturnValue_t getSelfTestCommandId(DeviceCommandId_t* id); + + /** + * @brief Each reply contains a status byte giving information about a request. This function + * parses this byte and returns the associated failure message. + * + * @param packet Pointer to the received message containing the status byte. + * + * @return The return code derived from the received status byte. + */ + ReturnValue_t parseStatusByte(imtq::CC::CC command, const uint8_t* packet); + + /** + * @brief This function fills the engineering housekeeping dataset with the received data. + + * @param packet Pointer to the received data. + * + */ + void fillEngHkDataset(imtq::HkDataset& hkDataset, const uint8_t* packet); + + void fillSystemStateIntoDataset(const uint8_t* packet); + + /** + * @brief This function parses the reply containing the calibrated MTM measurement and writes + * the values to the appropriate dataset. + * @param packet Pointer to the reply data. + */ + void fillCalibratedMtmDataset(const uint8_t* packet); + + /** + * @brief This function copies the raw MTM measurements to the MTM raw dataset. + * @param packet Pointer to the reply data requested with the GET_RAW_MTM_MEASUREMENTS + * command. + */ + void fillRawMtmDataset(imtq::RawMtmMeasurementSet& set, const uint8_t* packet); + + /** + * @brief This function handles all self test results. This comprises parsing the error byte + * and step byte and calling the function to fill the respective dataset. + */ + void handleSelfTestReply(const uint8_t* packet); + + /** + * @brief The following functions fill the respective dataset of the single axis self tests. + * @param packet Pointer to the reply data holding the self test result. + */ + void handlePositiveXSelfTestReply(const uint8_t* packet); + void handleNegativeXSelfTestReply(const uint8_t* packet); + void handlePositiveYSelfTestReply(const uint8_t* packet); + void handleNegativeYSelfTestReply(const uint8_t* packet); + void handlePositiveZSelfTestReply(const uint8_t* packet); + void handleNegativeZSelfTestReply(const uint8_t* packet); + + // ReturnValue_t buildDipoleActuationCommand(); + /** + * @brief This function checks the error byte of a self test measurement. + * + * @param errorByte The received error byte to check + * @param step The step of the error byte. + */ + void checkErrorByte(const uint8_t errorByte, const uint8_t step); + + std::string makeStepString(const uint8_t step); +}; + +#endif /* MISSION_DEVICES_IMTQHANDLER_H_ */ diff --git a/mission/acs/MgmLis3CustomHandler.cpp b/mission/acs/MgmLis3CustomHandler.cpp new file mode 100644 index 0000000..4b2e992 --- /dev/null +++ b/mission/acs/MgmLis3CustomHandler.cpp @@ -0,0 +1,151 @@ +#include "MgmLis3CustomHandler.h" + +#include + +#include "fsfw/datapool/PoolReadGuard.h" + +MgmLis3CustomHandler::MgmLis3CustomHandler(object_id_t objectId, object_id_t deviceCommunication, + CookieIF *comCookie, uint32_t transitionDelay) + : DeviceHandlerBase(objectId, deviceCommunication, comCookie), + dataset(this), + transitionDelay(transitionDelay) {} + +MgmLis3CustomHandler::~MgmLis3CustomHandler() = default; + +void MgmLis3CustomHandler::doStartUp() { + if (internalState != InternalState::STARTUP) { + commandExecuted = false; + updatePeriodicReply(true, REPLY); + internalState = InternalState::STARTUP; + } + if (internalState == InternalState::STARTUP) { + if (commandExecuted) { + setMode(MODE_ON); + internalState = InternalState::NONE; + commandExecuted = false; + } + } +} + +void MgmLis3CustomHandler::doShutDown() { + if (internalState != InternalState::SHUTDOWN) { + PoolReadGuard pg(&dataset); + dataset.setValidity(false, true); + internalState = InternalState::SHUTDOWN; + commandExecuted = false; + } + if (internalState == InternalState::SHUTDOWN and commandExecuted) { + updatePeriodicReply(false, REPLY); + commandExecuted = false; + internalState = InternalState::NONE; + setMode(MODE_OFF); + } +} + +ReturnValue_t MgmLis3CustomHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + if (internalState == InternalState::STARTUP) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::NORMAL); + } else if (internalState == InternalState::SHUTDOWN) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::OFF); + } + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmLis3CustomHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::NORMAL); +} + +ReturnValue_t MgmLis3CustomHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmLis3CustomHandler::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + if (getMode() == _MODE_WAIT_OFF or getMode() == _MODE_WAIT_ON) { + return IGNORE_FULL_PACKET; + } + if (len != sizeof(acs::MgmLis3Reply)) { + *foundLen = len; + return returnvalue::FAILED; + } + *foundId = REPLY; + *foundLen = len; + if (internalState == InternalState::SHUTDOWN) { + commandExecuted = true; + } + return returnvalue::OK; +} +ReturnValue_t MgmLis3CustomHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + const auto *reply = reinterpret_cast(packet); + if (reply->dataWasSet) { + if (internalState == InternalState::STARTUP) { + commandExecuted = true; + } + PoolReadGuard pg(&dataset); + for (uint8_t idx = 0; idx < 3; idx++) { + dataset.fieldStrengths[idx] = + reply->mgmValuesRaw[idx] * reply->sensitivity * mgmLis3::GAUSS_TO_MICROTESLA_FACTOR; + } + + dataset.setValidity(true, true); + if (std::abs(dataset.fieldStrengths[0]) > absLimitX or + std::abs(dataset.fieldStrengths[1]) > absLimitY or + std::abs(dataset.fieldStrengths[2]) > absLimitZ) { + dataset.fieldStrengths.setValid(false); + } + dataset.temperature = 25.0 + ((static_cast(reply->temperatureRaw)) / 8.0); + } + return returnvalue::OK; +} + +void MgmLis3CustomHandler::fillCommandAndReplyMap() { + insertInCommandMap(REQUEST); + insertInReplyMap(REPLY, 5, nullptr, 0, true); +} + +void MgmLis3CustomHandler::setToGoToNormalMode(bool enable) { this->goToNormalMode = enable; } + +uint32_t MgmLis3CustomHandler::getTransitionDelayMs(Mode_t from, Mode_t to) { + return transitionDelay; +} + +void MgmLis3CustomHandler::modeChanged(void) { internalState = InternalState::NONE; } + +ReturnValue_t MgmLis3CustomHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(mgmLis3::FIELD_STRENGTHS, &mgmXYZ); + localDataPoolMap.emplace(mgmLis3::TEMPERATURE_CELCIUS, &temperature); + poolManager.subscribeForRegularPeriodicPacket({dataset.getSid(), false, 10.0}); + return returnvalue::OK; +} + +void MgmLis3CustomHandler::setAbsoluteLimits(float xLimit, float yLimit, float zLimit) { + this->absLimitX = xLimit; + this->absLimitY = yLimit; + this->absLimitZ = zLimit; +} + +void MgmLis3CustomHandler::enablePeriodicPrintouts(bool enable, uint8_t divider) { + periodicPrintout = enable; + debugDivider.setDivider(divider); +} + +ReturnValue_t MgmLis3CustomHandler::prepareRequest(acs::SimpleSensorMode mode) { + request.mode = mode; + rawPacket = reinterpret_cast(&request); + rawPacketLen = sizeof(acs::MgmLis3Request); + return returnvalue::OK; +} + +LocalPoolDataSetBase *MgmLis3CustomHandler::getDataSetHandle(sid_t sid) { + if (sid == dataset.getSid()) { + return &dataset; + } + return nullptr; +} diff --git a/mission/acs/MgmLis3CustomHandler.h b/mission/acs/MgmLis3CustomHandler.h new file mode 100644 index 0000000..06ab6d7 --- /dev/null +++ b/mission/acs/MgmLis3CustomHandler.h @@ -0,0 +1,103 @@ +#ifndef MISSION_DEVICES_MGMLIS3CUSTOMHANDLER_H_ +#define MISSION_DEVICES_MGMLIS3CUSTOMHANDLER_H_ + +#include + +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "fsfw/globalfunctions/PeriodicOperationDivider.h" +#include "mission/acs/acsBoardPolling.h" + +class PeriodicOperationDivider; + +/** + * @brief Device handler object for the LIS3MDL 3-axis magnetometer + * by STMicroeletronics + * @details + * Datasheet can be found online by googling LIS3MDL. + * Flight manual: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/LIS3MDL_MGM + * @author L. Loidold, R. Mueller + */ +class MgmLis3CustomHandler : public DeviceHandlerBase { + public: + static constexpr DeviceCommandId_t REQUEST = 0x70; + static constexpr DeviceCommandId_t REPLY = 0x77; + + static const uint8_t INTERFACE_ID = CLASS_ID::MGM_LIS3MDL; + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::MGM_LIS3MDL; + + MgmLis3CustomHandler(uint32_t objectId, object_id_t deviceCommunication, CookieIF *comCookie, + uint32_t transitionDelay); + virtual ~MgmLis3CustomHandler(); + + void enablePeriodicPrintouts(bool enable, uint8_t divider); + /** + * Set the absolute limit for the values on the axis in microtesla. The dataset values will + * be marked as invalid if that limit is exceeded + * @param xLimit + * @param yLimit + * @param zLimit + */ + void setAbsoluteLimits(float xLimit, float yLimit, float zLimit); + void setToGoToNormalMode(bool enable); + + protected: + /** DeviceHandlerBase overrides */ + void doShutDown() override; + void doStartUp() override; + virtual uint32_t getTransitionDelayMs(Mode_t from, Mode_t to) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + /** + * This implementation is tailored towards space applications and will flag values larger + * than 100 microtesla on X,Y and 150 microtesla on Z as invalid + * @param id + * @param packet + * @return + */ + virtual ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + void modeChanged(void) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; + + private: + mgmLis3::MgmPrimaryDataset dataset; + acs::MgmLis3Request request{}; + + uint32_t transitionDelay; + + float absLimitX = 100; + float absLimitY = 100; + float absLimitZ = 150; + + uint8_t statusRegister = 0; + bool goToNormalMode = false; + + enum class InternalState { + NONE, + STARTUP, + SHUTDOWN, + }; + + InternalState internalState = InternalState::NONE; + bool commandExecuted = false; + + PoolEntry mgmXYZ = PoolEntry(3); + PoolEntry temperature = PoolEntry(); + /*------------------------------------------------------------------------*/ + /* Device specific commands and variables */ + /*------------------------------------------------------------------------*/ + + bool periodicPrintout = false; + PeriodicOperationDivider debugDivider = PeriodicOperationDivider(3); + + ReturnValue_t prepareRequest(acs::SimpleSensorMode mode); +}; + +#endif /* MISSION_DEVICES_MGMLIS3CUSTOMHANDLER_H_ */ diff --git a/mission/acs/MgmRm3100CustomHandler.cpp b/mission/acs/MgmRm3100CustomHandler.cpp new file mode 100644 index 0000000..24c9493 --- /dev/null +++ b/mission/acs/MgmRm3100CustomHandler.cpp @@ -0,0 +1,140 @@ +#include "MgmRm3100CustomHandler.h" + +#include "fsfw/datapool/PoolReadGuard.h" +#include "fsfw/devicehandlers/DeviceHandlerMessage.h" +#include "fsfw/globalfunctions/bitutility.h" +#include "fsfw/objectmanager/SystemObjectIF.h" +#include "fsfw/returnvalues/returnvalue.h" + +MgmRm3100CustomHandler::MgmRm3100CustomHandler(object_id_t objectId, + object_id_t deviceCommunication, CookieIF *comCookie, + uint32_t transitionDelay) + : DeviceHandlerBase(objectId, deviceCommunication, comCookie), + primaryDataset(this), + transitionDelay(transitionDelay) {} + +MgmRm3100CustomHandler::~MgmRm3100CustomHandler() = default; + +void MgmRm3100CustomHandler::doStartUp() { + if (internalState != InternalState::STARTUP) { + commandExecuted = false; + updatePeriodicReply(true, REPLY); + internalState = InternalState::STARTUP; + } + if (internalState == InternalState::STARTUP) { + if (commandExecuted) { + commandExecuted = false; + internalState = InternalState::NONE; + setMode(MODE_ON); + } + } +} + +void MgmRm3100CustomHandler::doShutDown() { + if (internalState != InternalState::SHUTDOWN) { + commandExecuted = false; + PoolReadGuard pg(&primaryDataset); + primaryDataset.setValidity(false, true); + internalState = InternalState::SHUTDOWN; + } + if (internalState == InternalState::SHUTDOWN and commandExecuted) { + updatePeriodicReply(false, REPLY); + setMode(MODE_OFF); + commandExecuted = false; + } +} + +ReturnValue_t MgmRm3100CustomHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + if (internalState == InternalState::STARTUP) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::NORMAL); + } else if (internalState == InternalState::SHUTDOWN) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::OFF); + } + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmRm3100CustomHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return NOTHING_TO_SEND; +} + +ReturnValue_t MgmRm3100CustomHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::NORMAL); +} + +ReturnValue_t MgmRm3100CustomHandler::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + if (getMode() == _MODE_WAIT_OFF or getMode() == _MODE_WAIT_ON) { + return IGNORE_FULL_PACKET; + } + if (len != sizeof(acs::MgmRm3100Reply)) { + *foundLen = len; + return returnvalue::FAILED; + } + *foundId = REPLY; + *foundLen = len; + if (internalState == InternalState::SHUTDOWN) { + commandExecuted = true; + } + return returnvalue::OK; +} + +ReturnValue_t MgmRm3100CustomHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + const auto *reply = reinterpret_cast(packet); + if (reply->dataWasRead) { + if (internalState == InternalState::STARTUP) { + commandExecuted = true; + } + + PoolReadGuard pg(&primaryDataset); + primaryDataset.setValidity(true, true); + for (uint8_t idx = 0; idx < 3; idx++) { + primaryDataset.fieldStrengths[idx] = reply->mgmValuesRaw[idx] * reply->scaleFactors[idx]; + } + } + return returnvalue::OK; +} + +void MgmRm3100CustomHandler::fillCommandAndReplyMap() { + insertInCommandMap(REQUEST); + insertInReplyMap(REPLY, 5, nullptr, 0, true); +} + +void MgmRm3100CustomHandler::modeChanged() { internalState = InternalState::NONE; } + +ReturnValue_t MgmRm3100CustomHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(mgmRm3100::FIELD_STRENGTHS, &mgmXYZ); + poolManager.subscribeForRegularPeriodicPacket({primaryDataset.getSid(), false, 10.0}); + return returnvalue::OK; +} + +uint32_t MgmRm3100CustomHandler::getTransitionDelayMs(Mode_t from, Mode_t to) { + return this->transitionDelay; +} + +void MgmRm3100CustomHandler::setToGoToNormalMode(bool enable) { goToNormalModeAtStartup = enable; } + +void MgmRm3100CustomHandler::enablePeriodicPrintouts(bool enable, uint8_t divider) { + periodicPrintout = enable; + debugDivider.setDivider(divider); +} + +ReturnValue_t MgmRm3100CustomHandler::prepareRequest(acs::SimpleSensorMode mode) { + request.mode = mode; + rawPacket = reinterpret_cast(&request); + rawPacketLen = sizeof(acs::MgmRm3100Request); + return returnvalue::OK; +} + +LocalPoolDataSetBase *MgmRm3100CustomHandler::getDataSetHandle(sid_t sid) { + if (sid == primaryDataset.getSid()) { + return &primaryDataset; + } + return nullptr; +} diff --git a/mission/acs/MgmRm3100CustomHandler.h b/mission/acs/MgmRm3100CustomHandler.h new file mode 100644 index 0000000..3cb4559 --- /dev/null +++ b/mission/acs/MgmRm3100CustomHandler.h @@ -0,0 +1,97 @@ +#ifndef MISSION_DEVICES_MGMRM3100CUSTOMHANDLER_H_ +#define MISSION_DEVICES_MGMRM3100CUSTOMHANDLER_H_ + +#include + +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "fsfw/globalfunctions/PeriodicOperationDivider.h" +#include "mission/acs/acsBoardPolling.h" + +/** + * @brief Device Handler for the RM3100 geomagnetic magnetometer sensor + * (https://www.pnicorp.com/rm3100/) + * @details + * Flight manual: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/RM3100_MGM + */ +class MgmRm3100CustomHandler : public DeviceHandlerBase { + public: + static constexpr DeviceCommandId_t REQUEST = 0x70; + static constexpr DeviceCommandId_t REPLY = 0x77; + + static const uint8_t INTERFACE_ID = CLASS_ID::MGM_RM3100; + + //! [EXPORT] : [COMMENT] P1: TMRC value which was set, P2: 0 + static constexpr Event TMRC_SET = + event::makeEvent(SUBSYSTEM_ID::MGM_RM3100, 0x00, severity::INFO); + + //! [EXPORT] : [COMMENT] Cycle counter set. P1: First two bytes new Cycle Count X + //! P1: Second two bytes new Cycle Count Y + //! P2: New cycle count Z + static constexpr Event cycleCountersSet = + event::makeEvent(SUBSYSTEM_ID::MGM_RM3100, 0x01, severity::INFO); + + MgmRm3100CustomHandler(object_id_t objectId, object_id_t deviceCommunication, CookieIF *comCookie, + uint32_t transitionDelay); + virtual ~MgmRm3100CustomHandler(); + + void enablePeriodicPrintouts(bool enable, uint8_t divider); + /** + * Configure device handler to go to normal mode after startup immediately + * @param enable + */ + void setToGoToNormalMode(bool enable); + + protected: + /* DeviceHandlerBase overrides */ + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + + void fillCommandAndReplyMap() override; + void modeChanged(void) override; + virtual uint32_t getTransitionDelayMs(Mode_t from, Mode_t to) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; + + private: + enum class InternalState { NONE, STARTUP, SHUTDOWN }; + InternalState internalState = InternalState::NONE; + bool commandExecuted = false; + mgmRm3100::Rm3100PrimaryDataset primaryDataset; + acs::MgmRm3100Request request{}; + + // uint8_t cmmRegValue = mgmRm3100::CMM_VALUE; + // uint8_t tmrcRegValue = mgmRm3100::TMRC_DEFAULT_VALUE; + // uint16_t cycleCountRegValueX = mgmRm3100::CYCLE_COUNT_VALUE; + // uint16_t cycleCountRegValueY = mgmRm3100::CYCLE_COUNT_VALUE; + // uint16_t cycleCountRegValueZ = mgmRm3100::CYCLE_COUNT_VALUE; + float scaleFactorX = 1.0 / mgmRm3100::DEFAULT_GAIN; + float scaleFactorY = 1.0 / mgmRm3100::DEFAULT_GAIN; + float scaleFactorZ = 1.0 / mgmRm3100::DEFAULT_GAIN; + + bool goToNormalModeAtStartup = false; + uint32_t transitionDelay; + PoolEntry mgmXYZ = PoolEntry(3); + bool periodicPrintout = false; + + ReturnValue_t handleCycleCountConfigCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, size_t commandDataLen); + ReturnValue_t handleCycleCommand(bool oneCycleValue, const uint8_t *commandData, + size_t commandDataLen); + + ReturnValue_t handleTmrcConfigCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen); + + ReturnValue_t prepareRequest(acs::SimpleSensorMode mode); + PeriodicOperationDivider debugDivider = PeriodicOperationDivider(3); +}; + +#endif /* MISSION_DEVICEHANDLING_MgmRm3100CustomHandler_H_ */ diff --git a/mission/acs/RwHandler.cpp b/mission/acs/RwHandler.cpp new file mode 100644 index 0000000..7cdc8b9 --- /dev/null +++ b/mission/acs/RwHandler.cpp @@ -0,0 +1,598 @@ +#include "RwHandler.h" + +#include +#include +#include +#include +#include + +#include "OBSWConfig.h" + +RwHandler::RwHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + GpioIF* gpioComIF, gpioId_t enableGpio, uint8_t rwIdx) + : DeviceHandlerBase(objectId, comIF, comCookie), + gpioComIF(gpioComIF), + enableGpio(enableGpio), + statusSet(this), + lastResetStatusSet(this), + tmDataset(this), + rwSpeedActuationSet(*this), + rwIdx(rwIdx) { + if (comCookie == nullptr) { + sif::error << "RwHandler: Invalid com cookie" << std::endl; + } + if (gpioComIF == nullptr) { + sif::error << "RwHandler: Invalid gpio communication interface" << std::endl; + } + currentRequest.rwIdx = rwIdx; +} + +RwHandler::~RwHandler() {} + +void RwHandler::doStartUp() { + internalState = InternalState::DEFAULT; + + if (gpioComIF->pullHigh(enableGpio) != returnvalue::OK) { + sif::error << "RW Handler " << rwIdx << ": Failed to pull ENABLE pin high for startup"; + } + currentRequest.mode = acs::SimpleSensorMode::NORMAL; + updatePeriodicReply(true, rws::REPLY_ID); + statusSet.setReportingEnabled(true); + setMode(_MODE_TO_ON); +} + +void RwHandler::doShutDown() { + if (internalState != InternalState::SHUTDOWN) { + internalState = InternalState::SHUTDOWN; + shutdownState = ShutdownState::SET_SPEED_ZERO; + offTransitionCountdown.resetTimer(); + } + if ((internalState == InternalState::SHUTDOWN) and + (shutdownState == ShutdownState::DONE or offTransitionCountdown.hasTimedOut())) { + if (gpioComIF->pullLow(enableGpio) != returnvalue::OK) { + sif::error << "RW Handler " << rwIdx << ": Failed to pull ENABLE pin low for shutdown"; + } + updatePeriodicReply(false, rws::REPLY_ID); + { + PoolReadGuard pg(&statusSet); + statusSet.currSpeed = 0.0; + statusSet.referenceSpeed = 0.0; + statusSet.state = 0; + statusSet.setValidity(false, true); + statusSet.setReportingEnabled(false); + } + { + PoolReadGuard pg(&tmDataset); + tmDataset.setValidity(false, true); + } + internalState = InternalState::DEFAULT; + shutdownState = ShutdownState::NONE; + // The power switch is handled by the assembly, so we can go off here directly. + setMode(MODE_OFF); + } +} + +ReturnValue_t RwHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + switch (internalState) { + case InternalState::DEFAULT: { + *id = rws::REQUEST_ID; + break; + } + default: + sif::debug << "RwHandler::buildNormalDeviceCommand: Invalid internal step" << std::endl; + break; + } + return buildCommandFromCommand(*id, NULL, 0); +} + +ReturnValue_t RwHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + if (internalState == InternalState::SHUTDOWN) { + if (shutdownState == ShutdownState::SET_SPEED_ZERO) { + { + PoolReadGuard pg(&rwSpeedActuationSet); + rwSpeedActuationSet.setRwSpeed(0, 10); + } + *id = rws::REQUEST_ID; + return buildCommandFromCommand(*id, nullptr, 0); + } else if (shutdownState == ShutdownState::STOP_POLLING) { + currentRequest.mode = acs::SimpleSensorMode::OFF; + *id = rws::REQUEST_ID; + return buildCommandFromCommand(*id, nullptr, 0); + } + } + return NOTHING_TO_SEND; +} + +ReturnValue_t RwHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + + switch (deviceCommand) { + case (rws::SET_SPEED): + case (rws::REQUEST_ID): { + if (commandData != nullptr && commandDataLen != 6) { + sif::error << "RwHandler::buildCommandFromCommand: Received set speed command with" + << " invalid length" << std::endl; + return SET_SPEED_COMMAND_INVALID_LENGTH; + } + + { + PoolReadGuard pg(&rwSpeedActuationSet); + // Commands override anything which was set in the software + if (commandData != nullptr) { + rwSpeedActuationSet.setValidityBufferGeneration(false); + result = rwSpeedActuationSet.deSerialize(&commandData, &commandDataLen, + SerializeIF::Endianness::NETWORK); + rwSpeedActuationSet.setValidityBufferGeneration(true); + if (result != returnvalue::OK) { + return result; + } + } + } + if (ACTUATION_WIRETAPPING) { + int32_t speed = 0; + uint16_t rampTime = 0; + rwSpeedActuationSet.getRwSpeed(speed, rampTime); + sif::debug << "Actuating RW " << static_cast(rwIdx) << " with speed = " << speed + << " and rampTime = " << rampTime << std::endl; + } + result = checkSpeedAndRampTime(); + if (result != returnvalue::OK) { + return result; + } + // set speed flag. + currentRequest.setSpeed = true; + rwSpeedActuationSet.getRwSpeed(currentRequest.currentRwSpeed, currentRequest.currentRampTime); + currentRequest.specialRequest = rws::SpecialRwRequest::REQUEST_NONE; + rawPacket = reinterpret_cast(¤tRequest); + rawPacketLen = sizeof(rws::RwRequest); + return returnvalue::OK; + } + case (rws::RESET_MCU): { + currentRequest.setSpeed = false; + currentRequest.specialRequest = rws::SpecialRwRequest::RESET_MCU; + internalState = InternalState::RESET_MCU; + rawPacket = reinterpret_cast(¤tRequest); + rawPacketLen = sizeof(rws::RwRequest); + return returnvalue::OK; + } + + case (rws::INIT_RW_CONTROLLER): { + currentRequest.setSpeed = false; + currentRequest.specialRequest = rws::SpecialRwRequest::INIT_RW_CONTROLLER; + internalState = InternalState::INIT_RW_CONTROLLER; + rawPacket = reinterpret_cast(¤tRequest); + rawPacketLen = sizeof(rws::RwRequest); + return returnvalue::OK; + } + case (rws::GET_TM): { + currentRequest.setSpeed = false; + currentRequest.specialRequest = rws::SpecialRwRequest::GET_TM; + internalState = InternalState::GET_TM; + rawPacket = reinterpret_cast(¤tRequest); + rawPacketLen = sizeof(rws::RwRequest); + return returnvalue::OK; + } + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::FAILED; +} + +void RwHandler::fillCommandAndReplyMap() { + insertInCommandMap(rws::REQUEST_ID); + insertInReplyMap(rws::REPLY_ID, 5, nullptr, 0, true); + + insertInCommandMap(rws::RESET_MCU); + insertInCommandMap(rws::SET_SPEED); + insertInCommandAndReplyMap(rws::INIT_RW_CONTROLLER, 5, nullptr, 0, false, true, rws::REPLY_ID); + insertInCommandAndReplyMap(rws::GET_TM, 5, nullptr, 0, false, true, rws::REPLY_ID); +} + +ReturnValue_t RwHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + if (getMode() == _MODE_WAIT_OFF or getMode() == _MODE_WAIT_ON) { + return IGNORE_FULL_PACKET; + } + if (remainingSize > 0) { + *foundLen = remainingSize; + *foundId = rws::REPLY_ID; + } + if (internalState == InternalState::SHUTDOWN and shutdownState == ShutdownState::STOP_POLLING) { + shutdownState = ShutdownState::DONE; + } + return returnvalue::OK; +} + +ReturnValue_t RwHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) { + RwReplies replies(packet); + ReturnValue_t result = returnvalue::OK; + ReturnValue_t status; + auto checkPacket = [&](DeviceCommandId_t currentId, const uint8_t* packetPtr) { + // This is just the packet length of the frame from the RW. The actual + // data is one more flag byte to show whether the value was read at least once. + auto packetLen = rws::idToPacketLen(currentId); + // arrayprinter::print(packetPtr, packetLen); + uint16_t replyCrc = 0; + size_t dummy = 0; + SerializeAdapter::deSerialize(&replyCrc, packetPtr + packetLen - 2, &dummy, + SerializeIF::Endianness::LITTLE); + if (CRC::crc16ccitt(packetPtr, packetLen - 2) != replyCrc) { + sif::error << "RwHandler::interpretDeviceReply: CRC error for ID " << id << std::endl; + return CRC_ERROR; + } + if (packetPtr[1] == rws::STATE_ERROR) { + sif::error << "RwHandler::interpretDeviceReply: Command execution failed. Command id: " << id + << std::endl; + result = EXECUTION_FAILED; + } + return returnvalue::OK; + }; + if (replies.wasSetSpeedReplyRead()) { + status = checkPacket(rws::DeviceCommandId::SET_SPEED, replies.getSetSpeedReply()); + if (status != returnvalue::OK) { + result = status; + } + } + + if (replies.wasRwStatusRead()) { + status = checkPacket(rws::DeviceCommandId::GET_RW_STATUS, replies.getRwStatusReply()); + if (status == returnvalue::OK) { + handleGetRwStatusReply(replies.getRwStatusReply()); + } else { + result = status; + } + } + + if (replies.wasGetLastStatusReplyRead()) { + status = checkPacket(rws::DeviceCommandId::GET_LAST_RESET_STATUS, + replies.getGetLastResetStatusReply()); + if (status == returnvalue::OK) { + handleResetStatusReply(replies.getGetLastResetStatusReply()); + } else { + result = status; + } + } + + if (replies.wasClearLastRsetStatusReplyRead()) { + status = checkPacket(rws::DeviceCommandId::CLEAR_LAST_RESET_STATUS, + replies.getClearLastResetStatusReply()); + if (status != returnvalue::OK) { + result = status; + } + } + + if (replies.wasReadTemperatureReplySet()) { + status = checkPacket(rws::DeviceCommandId::GET_TEMPERATURE, replies.getReadTemperatureReply()); + if (status == returnvalue::OK) { + handleTemperatureReply(replies.getReadTemperatureReply()); + } else { + result = status; + } + } + if (internalState == InternalState::GET_TM) { + if (replies.wasHkDataReplyRead()) { + status = checkPacket(rws::DeviceCommandId::GET_TM, replies.getHkDataReply()); + if (status == returnvalue::OK) { + handleGetTelemetryReply(replies.getHkDataReply()); + } else { + result = status; + } + internalState = InternalState::DEFAULT; + } + } + if (internalState == InternalState::INIT_RW_CONTROLLER) { + if (replies.wasInitRwControllerReplyRead()) { + status = + checkPacket(rws::DeviceCommandId::INIT_RW_CONTROLLER, replies.getInitRwControllerReply()); + if (status != returnvalue::OK) { + result = status; + } + internalState = InternalState::DEFAULT; + } + } + if (internalState == InternalState::RESET_MCU) { + internalState = InternalState::DEFAULT; + } + return result; +} + +uint32_t RwHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 5000; } + +ReturnValue_t RwHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(rws::RW_SPEED, &rwSpeed); + localDataPoolMap.emplace(rws::RAMP_TIME, &rampTime); + + localDataPoolMap.emplace(rws::TEMPERATURE_C, new PoolEntry({0})); + + localDataPoolMap.emplace(rws::CURR_SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(rws::REFERENCE_SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(rws::STATE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::CLC_MODE, new PoolEntry({0})); + + localDataPoolMap.emplace(rws::LAST_RESET_STATUS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::CURRRENT_RESET_STATUS, new PoolEntry({0})); + + localDataPoolMap.emplace(rws::TM_LAST_RESET_STATUS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_MCU_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::PRESSURE_SENSOR_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::PRESSURE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_RW_STATE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_CLC_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_RW_CURR_SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(rws::TM_RW_REF_SPEED, new PoolEntry({0})); + localDataPoolMap.emplace(rws::INVALID_CRC_PACKETS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::INVALID_LEN_PACKETS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::INVALID_CMD_PACKETS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::EXECUTED_REPLIES, new PoolEntry({0})); + localDataPoolMap.emplace(rws::COMMAND_REPLIES, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_BYTES_WRITTEN, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_BYTES_READ, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_PARITY_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_NOISE_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_FRAME_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_REG_OVERRUN_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::UART_TOTAL_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::SPI_BYTES_WRITTEN, new PoolEntry({0})); + localDataPoolMap.emplace(rws::SPI_BYTES_READ, new PoolEntry({0})); + localDataPoolMap.emplace(rws::SPI_REG_OVERRUN_ERRORS, new PoolEntry({0})); + localDataPoolMap.emplace(rws::SPI_TOTAL_ERRORS, new PoolEntry({0})); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(statusSet.getSid(), false, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(tmDataset.getSid(), false, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(lastResetStatusSet.getSid(), false, 30.0)); + return returnvalue::OK; +} + +ReturnValue_t RwHandler::checkSpeedAndRampTime() { + int32_t speed = 0; + uint16_t rampTime = 0; + rwSpeedActuationSet.getRwSpeed(speed, rampTime); + if ((speed < -65000 || speed > 65000 || (speed > -1000 && speed < 1000)) && (speed != 0)) { + sif::error << "RwHandler::checkSpeedAndRampTime: Command has invalid speed" << std::endl; + return INVALID_SPEED; + } + + if (rampTime < 10 || rampTime > 20000) { + sif::error << "RwHandler::checkSpeedAndRampTime: Command has invalid ramp time" << std::endl; + return INVALID_RAMP_TIME; + } + + return returnvalue::OK; +} + +void RwHandler::handleResetStatusReply(const uint8_t* packet) { + PoolReadGuard rg(&lastResetStatusSet); + uint8_t offset = 2; + uint8_t resetStatus = packet[offset]; + if (resetStatus != 0) { + // internalState = InternalState::CLEAR_RESET_STATUS; + lastResetStatusSet.lastNonClearedResetStatus = resetStatus; + triggerEvent(rws::RESET_OCCURED, resetStatus, 0); + } + lastResetStatusSet.currentResetStatus = resetStatus; + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "RwHandler::handleResetStatusReply: Last reset status: " + << static_cast(lastResetStatusSet.lastNonClearedResetStatus.value) + << std::endl; + sif::info << "RwHandler::handleResetStatusReply: Current reset status: " + << static_cast(lastResetStatusSet.currentResetStatus.value) + << std::endl; +#endif + } +} + +void RwHandler::handleGetRwStatusReply(const uint8_t* packet) { + PoolReadGuard rg0(&statusSet); + PoolReadGuard rg1(&tmDataset); + uint8_t offset = 2; + statusSet.currSpeed = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + tmDataset.rwCurrSpeed = statusSet.currSpeed; + tmDataset.rwCurrSpeed.setValid(true); + offset += 4; + statusSet.referenceSpeed = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + tmDataset.rwRefSpeed = statusSet.referenceSpeed; + tmDataset.rwRefSpeed.setValid(true); + offset += 4; + statusSet.state = *(packet + offset); + tmDataset.rwState = statusSet.state; + tmDataset.rwState.setValid(true); + offset += 1; + statusSet.clcMode = *(packet + offset); + tmDataset.rwClcMode = statusSet.clcMode; + tmDataset.rwClcMode.setValid(true); + + statusSet.setValidity(true, true); + + if (internalState == InternalState::SHUTDOWN and std::abs(tmDataset.rwCurrSpeed.value) <= 2 and + shutdownState == ShutdownState::SET_SPEED_ZERO) { + // Finish transition to off. + shutdownState = ShutdownState::STOP_POLLING; + } + + if (statusSet.state == rws::STATE_ERROR) { + // Trigger FDIR reaction, first recovery, then faulty if it doesnt fix the issue. + triggerEvent(DeviceHandlerIF::DEVICE_WANTS_HARD_REBOOT, statusSet.state.value, 0); + sif::error << "RwHandler::handleGetRwStatusReply: Reaction wheel in error state" << std::endl; + } + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "RwHandler::handleGetRwStatusReply: Current speed is: " << statusSet.currSpeed + << " * 0.1 RPM" << std::endl; + sif::info << "RwHandler::handleGetRwStatusReply: Reference speed is: " + << statusSet.referenceSpeed << " * 0.1 RPM" << std::endl; + sif::info << "RwHandler::handleGetRwStatusReply: State is: " + << (unsigned int)statusSet.state.value << std::endl; + sif::info << "RwHandler::handleGetRwStatusReply: clc mode is: " + << (unsigned int)statusSet.clcMode.value << std::endl; +#endif + } +} + +void RwHandler::handleTemperatureReply(const uint8_t* packet) { + PoolReadGuard rg(&statusSet); + uint8_t offset = 2; + statusSet.temperatureCelcius = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "RwHandler::handleTemperatureReply: Temperature: " << statusSet.temperatureCelcius + << " °C" << std::endl; +#endif + } +} + +LocalPoolDataSetBase* RwHandler::getDataSetHandle(sid_t sid) { + switch (sid.ownerSetId) { + case (rws::SetIds::STATUS_SET_ID): { + return &statusSet; + } + case (rws::SetIds::LAST_RESET_ID): { + return &lastResetStatusSet; + } + case (rws::SetIds::SPEED_CMD_SET): { + return &rwSpeedActuationSet; + } + case (rws::SetIds::TM_SET_ID): { + return &tmDataset; + } + } + return nullptr; +} + +void RwHandler::handleGetTelemetryReply(const uint8_t* packet) { + PoolReadGuard rg(&tmDataset); + uint8_t offset = 2; + tmDataset.lastResetStatus = *(packet + offset); + offset += 1; + tmDataset.mcuTemperature = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.pressureSensorTemperature = *(packet + offset + 3) << 24 | + *(packet + offset + 2) << 16 | *(packet + offset + 1) << 8 | + *(packet + offset); + offset += 4; + tmDataset.pressure = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.rwState = *(packet + offset); + offset += 1; + tmDataset.rwClcMode = *(packet + offset); + offset += 1; + tmDataset.rwCurrSpeed = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.rwRefSpeed = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.numOfInvalidCrcPackets = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.numOfInvalidLenPackets = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.numOfInvalidCmdPackets = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.numOfCmdExecutedReplies = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.numOfCmdReplies = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.uartNumOfBytesWritten = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.uartNumOfBytesRead = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.uartNumOfParityErrors = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.uartNumOfNoiseErrors = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.uartNumOfFrameErrors = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.uartNumOfRegisterOverrunErrors = *(packet + offset + 3) << 24 | + *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.uartTotalNumOfErrors = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.spiNumOfBytesWritten = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.spiNumOfBytesRead = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.spiNumOfRegisterOverrunErrors = *(packet + offset + 3) << 24 | + *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + offset += 4; + tmDataset.spiTotalNumOfErrors = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16 | + *(packet + offset + 1) << 8 | *(packet + offset); + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "RwHandler::handleGetTelemetryReply: Last reset status: " + << static_cast(tmDataset.lastResetStatus.value) << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: MCU temperature: " << tmDataset.mcuTemperature + << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Pressure sensor temperature: " + << tmDataset.pressureSensorTemperature << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Pressure " << tmDataset.pressure << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: State: " + << static_cast(tmDataset.rwState.value) << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: CLC mode: " + << static_cast(tmDataset.rwClcMode.value) << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Current speed: " << tmDataset.rwCurrSpeed + << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Reference speed: " << tmDataset.rwRefSpeed + << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Number of invalid CRC packets: " + << tmDataset.numOfInvalidCrcPackets << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Number of invalid length packets: " + << tmDataset.numOfInvalidLenPackets << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Number of invalid command packets: " + << tmDataset.numOfInvalidCmdPackets << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Number of command executed replies: " + << tmDataset.numOfCmdExecutedReplies << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: Number of command replies: " + << tmDataset.numOfCmdReplies << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: UART number of bytes written: " + << tmDataset.uartNumOfBytesWritten << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: UART number of bytes read: " + << tmDataset.uartNumOfBytesRead << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: UART number of parity errors: " + << tmDataset.uartNumOfParityErrors << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: UART number of noise errors: " + << tmDataset.uartNumOfNoiseErrors << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: UART number of frame errors: " + << tmDataset.uartNumOfFrameErrors << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: UART number of register overrun errors: " + << tmDataset.uartNumOfRegisterOverrunErrors << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: UART number of total errors: " + << tmDataset.uartTotalNumOfErrors << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: SPI number of bytes written: " + << tmDataset.spiNumOfBytesWritten << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: SPI number of bytes read: " + << tmDataset.spiNumOfBytesRead << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: SPI number of register overrun errors: " + << tmDataset.spiNumOfRegisterOverrunErrors << std::endl; + sif::info << "RwHandler::handleGetTelemetryReply: SPI number of register total errors: " + << tmDataset.spiTotalNumOfErrors << std::endl; +#endif + } +} + +void RwHandler::setDebugMode(bool enable) { this->debugMode = enable; } diff --git a/mission/acs/RwHandler.h b/mission/acs/RwHandler.h new file mode 100644 index 0000000..87689fe --- /dev/null +++ b/mission/acs/RwHandler.h @@ -0,0 +1,137 @@ +#ifndef MISSION_DEVICES_RWHANDLER_H_ +#define MISSION_DEVICES_RWHANDLER_H_ + +#include +#include +#include +#include + +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +static constexpr bool ACTUATION_WIRETAPPING = false; + +class GpioIF; + +/** + * @brief This is the device handler for the reaction wheel from nano avionics. + * + * @details Datasheet: https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/ + * Arbeitsdaten/08_Used%20Components/Nanoavionics_Reactionwheels&fileid=181622 + * + * @note Values are transferred in little endian format. + * + * @author J. Meier + */ +class RwHandler : public DeviceHandlerBase { + public: + /** + * @brief Constructor + * + * @param objectId + * @param comIF + * @param comCookie + * @param gpioComIF Pointer to gpio communication interface + * @param enablePin GPIO connected to the enable pin of the reaction wheels. Must be pulled + * to high to enable the device. + */ + RwHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, GpioIF* gpioComIF, + gpioId_t enableGpio, uint8_t rwIdx); + + void setDebugMode(bool enable); + + virtual ~RwHandler(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::RW_HANDLER; + //! [EXPORT] : [COMMENT] Action Message with invalid speed was received. Valid speeds must be in + //! the range of [-65000; 1000] or [1000; 65000] + static const ReturnValue_t INVALID_SPEED = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] Action Message with invalid ramp time was received. + static const ReturnValue_t INVALID_RAMP_TIME = MAKE_RETURN_CODE(0xA1); + //! [EXPORT] : [COMMENT] Received set speed command has invalid length. Should be 6. + static const ReturnValue_t SET_SPEED_COMMAND_INVALID_LENGTH = MAKE_RETURN_CODE(0xA2); + //! [EXPORT] : [COMMENT] Command execution failed + static const ReturnValue_t EXECUTION_FAILED = MAKE_RETURN_CODE(0xA3); + //! [EXPORT] : [COMMENT] Reaction wheel reply has invalid crc + static const ReturnValue_t CRC_ERROR = MAKE_RETURN_CODE(0xA4); + static const ReturnValue_t VALUE_NOT_READ = MAKE_RETURN_CODE(0xA5); + + GpioIF* gpioComIF = nullptr; + gpioId_t enableGpio = gpio::NO_GPIO; + bool debugMode = false; + Countdown offTransitionCountdown = Countdown(5000); + rws::RwRequest currentRequest; + + rws::StatusSet statusSet; + rws::LastResetSatus lastResetStatusSet; + rws::TmDataset tmDataset; + rws::RwSpeedActuationSet rwSpeedActuationSet; + + uint8_t commandBuffer[32]; + uint8_t rwIdx; + + PoolEntry rwSpeed = PoolEntry({0}); + PoolEntry rampTime = PoolEntry({10}); + + enum class InternalState { DEFAULT, GET_TM, INIT_RW_CONTROLLER, RESET_MCU, SHUTDOWN }; + enum class ShutdownState { + NONE, + SET_SPEED_ZERO, + STOP_POLLING, + DONE, + } shutdownState = ShutdownState::NONE; + + InternalState internalState = InternalState::DEFAULT; + + /** + * @brief This function checks if the receiced speed and ramp time to set are in a valid + * range. + * @return returnvalue::OK if successful, otherwise error code. + */ + ReturnValue_t checkSpeedAndRampTime(); + + /** + * @brief This function writes the last reset status retrieved with the get last reset status + * command into the reset status dataset. + * + * @param packet Pointer to the buffer holding the reply data. + */ + void handleResetStatusReply(const uint8_t* packet); + + /** + * @brief This function handles the reply of the get temperature command. + * + * @param packet Pointer to the reply data + */ + void handleTemperatureReply(const uint8_t* packet); + + /** + * @brief This function fills the status set with the data from the get-status-reply. + */ + void handleGetRwStatusReply(const uint8_t* packet); + + /** + * @brief This function fills the tmDataset with the reply data requested with get telemetry + * command. + */ + void handleGetTelemetryReply(const uint8_t* packet); +}; + +#endif /* MISSION_DEVICES_RWHANDLER_H_ */ diff --git a/mission/acs/SusHandler.cpp b/mission/acs/SusHandler.cpp new file mode 100644 index 0000000..193bdb5 --- /dev/null +++ b/mission/acs/SusHandler.cpp @@ -0,0 +1,163 @@ +#include "SusHandler.h" + +#include + +#include + +#include "fsfw/datapool/PoolReadGuard.h" + +SusHandler::SusHandler(object_id_t objectId, uint8_t susIdx, object_id_t deviceCommunication, + CookieIF *comCookie) + : DeviceHandlerBase(objectId, deviceCommunication, comCookie), dataset(this), susIdx(susIdx) {} + +SusHandler::~SusHandler() = default; + +void SusHandler::doStartUp() { + if (internalState != InternalState::STARTUP) { + commandExecuted = false; + updatePeriodicReply(true, REPLY); + internalState = InternalState::STARTUP; + } + if (internalState == InternalState::STARTUP) { + if (commandExecuted) { + if (waitingForRecovery) { + waitingForRecovery = false; + } + setMode(MODE_ON); + internalState = InternalState::NONE; + commandExecuted = false; + } + } +} + +void SusHandler::doShutDown() { + if (internalState != InternalState::SHUTDOWN) { + PoolReadGuard pg(&dataset); + dataset.tempC = thermal::INVALID_TEMPERATURE; + dataset.setValidity(false, true); + internalState = InternalState::SHUTDOWN; + commandExecuted = false; + } + if (internalState == InternalState::SHUTDOWN and commandExecuted) { + updatePeriodicReply(false, REPLY); + commandExecuted = false; + internalState = InternalState::NONE; + setMode(MODE_OFF); + } +} + +ReturnValue_t SusHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + if (internalState == InternalState::STARTUP) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::NORMAL); + } else if (internalState == InternalState::SHUTDOWN) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::OFF); + } + return NOTHING_TO_SEND; +} + +ReturnValue_t SusHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = REQUEST; + return prepareRequest(acs::SimpleSensorMode::NORMAL); +} + +ReturnValue_t SusHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + return NOTHING_TO_SEND; +} + +ReturnValue_t SusHandler::scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) { + if (getMode() == _MODE_WAIT_OFF or getMode() == _MODE_WAIT_ON) { + return IGNORE_FULL_PACKET; + } + if (len != sizeof(acs::SusReply)) { + *foundLen = len; + return returnvalue::FAILED; + } + *foundId = REPLY; + *foundLen = len; + if (internalState == InternalState::SHUTDOWN) { + commandExecuted = true; + } + return returnvalue::OK; +} +ReturnValue_t SusHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + const auto *reply = reinterpret_cast(packet); + if (!reply->dataWasSet) { + return returnvalue::OK; + } + if (internalState == InternalState::STARTUP) { + commandExecuted = true; + } + PoolReadGuard pg(&dataset); + // Simple FDIR variant to make the handler more robust to invalid messages which + // appear sometimes for the SUS device: Allow invalid message up to a certain threshold + // before triggering FDIR reactions. + if (reply->tempRaw == 0xfff and not waitingForRecovery) { + if (invalidMsgCounter == 0 and invalidMsgPeriodCounter == 0) { + triggerEvent(TEMPERATURE_ALL_ONES_START); + } else if (invalidMsgCounter == susMax1227::MAX_INVALID_MSG_COUNT) { + triggerEvent(DeviceHandlerIF::DEVICE_WANTS_HARD_REBOOT); + waitingForRecovery = true; + } + invalidMsgCounter++; + dataset.setValidity(false, true); + dataset.tempC = thermal::INVALID_TEMPERATURE; + std::memset(dataset.channels.value, 0, sizeof(dataset.channels.value)); + return returnvalue::OK; + } + if (invalidMsgCounter > 0) { + invalidMsgPeriodCounter++; + if (invalidMsgCounter > invalidMsgCounterMax) { + invalidMsgCounterMax = invalidMsgCounter; + } + invalidMsgCounter = 0; + invalidMsgCountdown.resetTimer(); + } + if (invalidMsgCountdown.hasTimedOut() and invalidMsgPeriodCounter > 0) { + triggerEvent(TEMPERATURE_ALL_ONES_RECOVERY, invalidMsgPeriodCounter, invalidMsgCounterMax); + invalidMsgPeriodCounter = 0; + invalidMsgCounterMax = 0; + } + dataset.setValidity(true, true); + dataset.tempC = max1227::getTemperature(reply->tempRaw); + std::memcpy(dataset.channels.value, reply->channelsRaw, sizeof(reply->channelsRaw)); + return returnvalue::OK; +} + +void SusHandler::fillCommandAndReplyMap() { + insertInCommandMap(REQUEST); + insertInReplyMap(REPLY, 5, nullptr, 0, true); +} + +void SusHandler::setToGoToNormalMode(bool enable) { this->goToNormalMode = enable; } + +uint32_t SusHandler::getTransitionDelayMs(Mode_t from, Mode_t to) { return transitionDelay; } + +void SusHandler::modeChanged(void) { internalState = InternalState::NONE; } + +ReturnValue_t SusHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(susMax1227::TEMPERATURE_C, &tempC); + localDataPoolMap.emplace(susMax1227::CHANNEL_VEC, &channelVec); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(dataset.getSid(), false, 5.0)); + return returnvalue::OK; +} + +ReturnValue_t SusHandler::prepareRequest(acs::SimpleSensorMode mode) { + request.mode = mode; + rawPacket = reinterpret_cast(&request); + rawPacketLen = sizeof(acs::SusRequest); + return returnvalue::OK; +} + +LocalPoolDataSetBase *SusHandler::getDataSetHandle(sid_t sid) { + if (sid == dataset.getSid()) { + return &dataset; + } + return nullptr; +} diff --git a/mission/acs/SusHandler.h b/mission/acs/SusHandler.h new file mode 100644 index 0000000..43c49dc --- /dev/null +++ b/mission/acs/SusHandler.h @@ -0,0 +1,84 @@ +#ifndef MISSION_DEVICES_SusHandler_H_ +#define MISSION_DEVICES_SusHandler_H_ + +#include +#include +#include + +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "fsfw/globalfunctions/PeriodicOperationDivider.h" +#include "mission/acs/acsBoardPolling.h" + +class SusHandler : public DeviceHandlerBase { + public: + static constexpr DeviceCommandId_t REQUEST = 0x70; + static constexpr DeviceCommandId_t REPLY = 0x77; + + static const uint8_t INTERFACE_ID = CLASS_ID::SUS_HANDLER; + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::SUS_HANDLER; + + //! [EXPORT] : [COMMENT] Detected invalid values, starting invalid message counting + static constexpr Event TEMPERATURE_ALL_ONES_START = + event::makeEvent(SUBSYSTEM_ID, 0, severity::MEDIUM); + //! [EXPORT] : [COMMENT] Detected valid values for a prolonged time again, resetting all counters. + //! P1: Number of periods with invalid messages. + //! P2: Maximum invalid message counter. + static constexpr Event TEMPERATURE_ALL_ONES_RECOVERY = + event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO); + + SusHandler(uint32_t objectId, uint8_t susIdx, object_id_t deviceCommunication, + CookieIF *comCookie); + virtual ~SusHandler(); + + void enablePeriodicPrintouts(bool enable, uint8_t divider); + void setToGoToNormalMode(bool enable); + + protected: + /** DeviceHandlerBase overrides */ + void doShutDown() override; + void doStartUp() override; + uint32_t getTransitionDelayMs(Mode_t from, Mode_t to) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void fillCommandAndReplyMap() override; + void modeChanged(void) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override; + + private: + susMax1227::SusDataset dataset; + acs::SusRequest request{}; + uint8_t susIdx; + + // After 1 minute, trigger the event for the invalid messages. + Countdown invalidMsgCountdown = Countdown(60000); + bool waitingForRecovery = true; + uint32_t invalidMsgCounter = 0; + uint32_t invalidMsgCounterMax = 0; + uint32_t invalidMsgPeriodCounter = 0; + + uint32_t transitionDelay = 1000; + bool goToNormalMode = false; + + PoolEntry tempC = PoolEntry({0.0}); + PoolEntry channelVec = PoolEntry({0, 0, 0, 0, 0, 0}); + + enum class InternalState { + NONE, + STARTUP, + SHUTDOWN, + }; + + InternalState internalState = InternalState::NONE; + bool commandExecuted = false; + + ReturnValue_t prepareRequest(acs::SimpleSensorMode mode); +}; + +#endif /* MISSION_DEVICES_SusHandler_H_ */ diff --git a/mission/acs/acsBoardPolling.h b/mission/acs/acsBoardPolling.h new file mode 100644 index 0000000..9f794fb --- /dev/null +++ b/mission/acs/acsBoardPolling.h @@ -0,0 +1,93 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_ACSPOLLING_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_ACSPOLLING_H_ + +#include + +#include "fsfw/thermal/tcsDefinitions.h" +#include "gyroAdisHelpers.h" + +namespace acs { + +struct Adis1650XConfig { + uint16_t diagStat; + uint16_t filterSetting; + uint16_t rangMdl; + uint16_t mscCtrlReg; + uint16_t decRateReg; + uint16_t prodId; +}; + +struct Adis1650XRequest { + SimpleSensorMode mode; + adis1650x::Type type; + Adis1650XConfig cfg; +}; + +struct Adis1650XData { + double sensitivity = 0.0; + // Angular velocities in all axes (X, Y and Z) + int16_t angVelocities[3]{}; + double accelScaling = 0.0; + // Accelerations in all axes + int16_t accelerations[3]{}; + int16_t temperatureRaw = thermal::INVALID_TEMPERATURE; +}; + +struct Adis1650XReply { + bool cfgWasSet = false; + Adis1650XConfig cfg; + bool dataWasSet = false; + Adis1650XData data; +}; + +struct GyroL3gRequest { + SimpleSensorMode mode = SimpleSensorMode::OFF; + uint8_t ctrlRegs[5]{}; +}; + +struct GyroL3gReply { + bool cfgWasSet = false; + uint8_t statusReg; + // Angular velocities in all axes (X, Y and Z) + int16_t angVelocities[3]{}; + int8_t tempOffsetRaw = 0; + uint8_t ctrlRegs[5]{}; + float sensitivity = 0.0; +}; + +struct MgmRm3100Request { + SimpleSensorMode mode = SimpleSensorMode::OFF; +}; + +struct MgmRm3100Reply { + bool dataWasRead = false; + float scaleFactors[3]{}; + int32_t mgmValuesRaw[3]{}; +}; + +struct MgmLis3Request { + SimpleSensorMode mode = SimpleSensorMode::OFF; +}; + +struct MgmLis3Reply { + bool dataWasSet = false; + float sensitivity = 0.0; + int16_t mgmValuesRaw[3]{}; + bool temperatureWasSet = false; + int16_t temperatureRaw = thermal::INVALID_TEMPERATURE; +}; + +struct SusRequest { + SimpleSensorMode mode = SimpleSensorMode::OFF; +}; + +struct SusReply { + bool cfgWasSet = false; + bool dataWasSet = false; + uint16_t tempRaw = 0; + uint16_t channelsRaw[6]{}; +}; + +} // namespace acs + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_ACSPOLLING_H_ */ diff --git a/mission/acs/archive/GPSHyperionHandler.cpp b/mission/acs/archive/GPSHyperionHandler.cpp new file mode 100644 index 0000000..16451ae --- /dev/null +++ b/mission/acs/archive/GPSHyperionHandler.cpp @@ -0,0 +1,204 @@ +#include "GPSHyperionHandler.h" + +#include + +#include "fsfw/datapool/PoolReadGuard.h" +#include "fsfw/timemanager/Clock.h" +#include "lwgps/lwgps.h" + +#if FSFW_DEV_HYPERION_GPS_CREATE_NMEA_CSV == 1 +#include +#include +#endif + +GPSHyperionHandler::GPSHyperionHandler(object_id_t objectId, object_id_t deviceCommunication, + CookieIF *comCookie, bool debugHyperionGps) + : DeviceHandlerBase(objectId, deviceCommunication, comCookie), + gpsSet(this), + debugHyperionGps(debugHyperionGps) { + lwgps_init(&gpsData); +} + +GPSHyperionHandler::~GPSHyperionHandler() {} + +void GPSHyperionHandler::doStartUp() { + if (internalState == InternalStates::NONE) { + commandExecuted = false; + internalState = InternalStates::WAIT_FIRST_MESSAGE; + } + + if (internalState == InternalStates::WAIT_FIRST_MESSAGE) { + if (commandExecuted) { + internalState = InternalStates::IDLE; + setMode(MODE_ON); + commandExecuted = false; + } + } +} + +void GPSHyperionHandler::doShutDown() { + internalState = InternalStates::NONE; + commandExecuted = false; + setMode(_MODE_POWER_DOWN); +} + +ReturnValue_t GPSHyperionHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t GPSHyperionHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t GPSHyperionHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + // By default, send nothing + rawPacketLen = 0; + switch (deviceCommand) { + case (GpsHyperion::TRIGGER_RESET_PIN): { + if (resetCallback != nullptr) { + PoolReadGuard pg(&gpsSet); + // Set HK entries invalid + gpsSet.setValidity(false, true); + resetCallback(resetCallbackArgs); + return HasActionsIF::EXECUTION_FINISHED; + } + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + } + return returnvalue::OK; +} + +ReturnValue_t GPSHyperionHandler::scanForReply(const uint8_t *start, size_t len, + DeviceCommandId_t *foundId, size_t *foundLen) { + // Pass data to GPS library + if (len > 0) { + // sif::debug << "GPSHandler::scanForReply: Received " << len << " bytes" << std::endl; + if (internalState == InternalStates::WAIT_FIRST_MESSAGE) { + // TODO: Check whether data is valid by checking whether NMEA start string is valid? + commandExecuted = true; + } + int result = lwgps_process(&gpsData, start, len); + if (result != 1) { + sif::warning << "GPSHandler::scanForReply: Issue processing GPS data with lwgps" << std::endl; + } else { + // The data from the device will generally be read all at once. Therefore, we + // can set all field here + PoolReadGuard pg(&gpsSet); + if (pg.getReadResult() != returnvalue::OK) { +#if FSFW_VERBOSE_LEVEL >= 1 + sif::warning << "GPSHyperionHandler::scanForReply: Reading dataset failed" << std::endl; +#endif + } + // Print messages + if (gpsData.is_valid) { + // Set all entries valid now, set invalid on case basis if values are sanitized + gpsSet.setValidity(true, true); + } + // Negative latitude -> South direction + gpsSet.latitude.value = gpsData.latitude; + // Negative longitude -> West direction + gpsSet.longitude.value = gpsData.longitude; + if (gpsData.altitude > 600000.0 or gpsData.altitude < 400000.0) { + gpsSet.altitude.setValid(false); + } else { + gpsSet.altitude.setValid(true); + gpsSet.altitude.value = gpsData.altitude; + } + gpsSet.fixMode.value = gpsData.fix_mode; + gpsSet.satInUse.value = gpsData.sats_in_use; + Clock::TimeOfDay_t timeStruct = {}; + timeStruct.day = gpsData.date; + timeStruct.hour = gpsData.hours; + timeStruct.minute = gpsData.minutes; + timeStruct.month = gpsData.month; + timeStruct.second = gpsData.seconds; + // Convert two-digit year to full year (AD) + timeStruct.year = gpsData.year + 2000; + timeval timeval = {}; + Clock::convertTimeOfDayToTimeval(&timeStruct, &timeval); + gpsSet.year = timeStruct.year; + gpsSet.month = gpsData.month; + gpsSet.day = gpsData.date; + gpsSet.hours = gpsData.hours; + gpsSet.minutes = gpsData.minutes; + gpsSet.seconds = gpsData.seconds; + gpsSet.unixSeconds = timeval.tv_sec; + if (debugHyperionGps) { + sif::info << "GPS Data" << std::endl; + printf("Valid status: %d\n", gpsData.is_valid); + printf("Latitude: %f degrees\n", gpsData.latitude); + printf("Longitude: %f degrees\n", gpsData.longitude); + printf("Altitude: %f meters\n", gpsData.altitude); + } +#if FSFW_DEV_HYPERION_GPS_CREATE_NMEA_CSV == 1 + std::string filename = "/mnt/sd0/gps_log.txt"; + std::ofstream gpsFile; + if (not std::filesystem::exists(filename)) { + gpsFile.open(filename, std::ofstream::out); + } + gpsFile.open(filename, std::ofstream::out | std::ofstream::app); + gpsFile.write("\n", 1); + gpsFile.write(reinterpret_cast(start), len); +#endif + } + *foundLen = len; + *foundId = GpsHyperion::GPS_REPLY; + } + return returnvalue::OK; +} + +ReturnValue_t GPSHyperionHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + return returnvalue::OK; +} + +uint32_t GPSHyperionHandler::getTransitionDelayMs(Mode_t from, Mode_t to) { return 5000; } + +ReturnValue_t GPSHyperionHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(GpsHyperion::ALTITUDE, new PoolEntry({0.0})); + localDataPoolMap.emplace(GpsHyperion::LONGITUDE, new PoolEntry({0.0})); + localDataPoolMap.emplace(GpsHyperion::LATITUDE, new PoolEntry({0.0})); + localDataPoolMap.emplace(GpsHyperion::YEAR, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::MONTH, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::DAY, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::HOURS, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::MINUTES, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::SECONDS, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::UNIX_SECONDS, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::SATS_IN_USE, new PoolEntry()); + localDataPoolMap.emplace(GpsHyperion::FIX_MODE, new PoolEntry()); + poolManager.subscribeForPeriodicPacket(gpsSet.getSid(), false, 30.0, false); + return returnvalue::OK; +} + +void GPSHyperionHandler::fillCommandAndReplyMap() { + // Reply length does not matter, packets should always arrive periodically + insertInReplyMap(GpsHyperion::GPS_REPLY, 4, &gpsSet, 0, true); + insertInCommandMap(GpsHyperion::TRIGGER_RESET_PIN); +} + +void GPSHyperionHandler::modeChanged() { internalState = InternalStates::NONE; } + +void GPSHyperionHandler::setResetPinTriggerFunction(gpioResetFunction_t resetCallback, void *args) { + this->resetCallback = resetCallback; + resetCallbackArgs = args; +} + +void GPSHyperionHandler::debugInterface(uint8_t positionTracker, object_id_t objectId, + uint32_t parameter) {} + +ReturnValue_t GPSHyperionHandler::initialize() { + ReturnValue_t result = DeviceHandlerBase::initialize(); + if (result != returnvalue::OK) { + return result; + } + // Enable reply immediately for now + return updatePeriodicReply(true, GpsHyperion::GPS_REPLY); +} + +ReturnValue_t GPSHyperionHandler::acceptExternalDeviceCommands() { + return DeviceHandlerBase::acceptExternalDeviceCommands(); +} diff --git a/mission/acs/archive/GPSHyperionHandler.h b/mission/acs/archive/GPSHyperionHandler.h new file mode 100644 index 0000000..cbc36fb --- /dev/null +++ b/mission/acs/archive/GPSHyperionHandler.h @@ -0,0 +1,62 @@ +#ifndef MISSION_DEVICES_GPSHYPERIONHANDLER_H_ +#define MISSION_DEVICES_GPSHYPERIONHANDLER_H_ + +#include + +#include "fsfw/FSFW.h" +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "lwgps/lwgps.h" + +/** + * @brief Device handler for the Hyperion HT-GPS200 device + * @details + * Flight manual: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/Hyperion_HT-GPS200 + */ +class GPSHyperionHandler : public DeviceHandlerBase { + public: + GPSHyperionHandler(object_id_t objectId, object_id_t deviceCommunication, CookieIF *comCookie, + bool debugHyperionGps = false); + virtual ~GPSHyperionHandler(); + + using gpioResetFunction_t = ReturnValue_t (*)(void *args); + + void setResetPinTriggerFunction(gpioResetFunction_t resetCallback, void *args); + ReturnValue_t acceptExternalDeviceCommands() override; + + ReturnValue_t initialize() override; + + protected: + gpioResetFunction_t resetCallback = nullptr; + void *resetCallbackArgs = nullptr; + + enum class InternalStates { NONE, WAIT_FIRST_MESSAGE, IDLE }; + InternalStates internalState = InternalStates::NONE; + bool commandExecuted = false; + + /* DeviceHandlerBase overrides */ + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + + void fillCommandAndReplyMap() override; + void modeChanged() override; + uint32_t getTransitionDelayMs(Mode_t from, Mode_t to) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + virtual void debugInterface(uint8_t positionTracker = 0, object_id_t objectId = 0, + uint32_t parameter = 0) override; + + private: + lwgps_t gpsData = {}; + GpsPrimaryDataset gpsSet; + bool debugHyperionGps = false; +}; + +#endif /* MISSION_DEVICES_GPSHYPERIONHANDLER_H_ */ diff --git a/mission/acs/archive/LegacySusHandler.cpp b/mission/acs/archive/LegacySusHandler.cpp new file mode 100644 index 0000000..52a43de --- /dev/null +++ b/mission/acs/archive/LegacySusHandler.cpp @@ -0,0 +1,233 @@ +#include +#include +#include + +#include "OBSWConfig.h" + +LegacySusHandler::LegacySusHandler(object_id_t objectId, uint8_t susIdx, object_id_t comIF, + CookieIF *comCookie) + : DeviceHandlerBase(objectId, comIF, comCookie), divider(5), dataset(this), susIdx(susIdx) {} + +LegacySusHandler::~LegacySusHandler() {} + +void LegacySusHandler::doStartUp() { + if (comState == ComStates::IDLE) { + comState = ComStates::WRITE_SETUP; + commandExecuted = false; + } + if (comState == ComStates::WRITE_SETUP) { + if (commandExecuted) { + if (goToNormalModeImmediately) { + setMode(MODE_NORMAL); + } else { + setMode(_MODE_TO_ON); + } + commandExecuted = false; + if (clkMode == ClkModes::INT_CLOCKED) { + comState = ComStates::START_INT_CLOCKED_CONVERSIONS; + } else { + comState = ComStates::EXT_CLOCKED_CONVERSIONS; + } + } + } +} + +void LegacySusHandler::doShutDown() { + setMode(_MODE_POWER_DOWN); + comState = ComStates::IDLE; +} + +ReturnValue_t LegacySusHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + switch (comState) { + case (ComStates::IDLE): { + break; + } + case (ComStates::WRITE_SETUP): { + *id = susMax1227::WRITE_SETUP; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (ComStates::EXT_CLOCKED_CONVERSIONS): { + *id = susMax1227::READ_EXT_TIMED_CONVERSIONS; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (ComStates::START_INT_CLOCKED_CONVERSIONS): { + *id = susMax1227::START_INT_TIMED_CONVERSIONS; + comState = ComStates::READ_INT_CLOCKED_CONVERSIONS; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (ComStates::READ_INT_CLOCKED_CONVERSIONS): { + *id = susMax1227::READ_INT_TIMED_CONVERSIONS; + comState = ComStates::START_INT_CLOCKED_CONVERSIONS; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (ComStates::EXT_CLOCKED_TEMP): { + *id = susMax1227::READ_EXT_TIMED_TEMPS; + return buildCommandFromCommand(*id, nullptr, 0); + } + } + return NOTHING_TO_SEND; +} + +ReturnValue_t LegacySusHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + if (comState == ComStates::WRITE_SETUP) { + *id = susMax1227::WRITE_SETUP; + return buildCommandFromCommand(*id, nullptr, 0); + } + return NOTHING_TO_SEND; +} + +ReturnValue_t LegacySusHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + using namespace max1227; + switch (deviceCommand) { + case (susMax1227::WRITE_SETUP): { + if (clkMode == ClkModes::INT_CLOCKED) { + cmdBuffer[0] = susMax1227::SETUP_INT_CLOKED; + } else { + cmdBuffer[0] = susMax1227::SETUP_EXT_CLOCKED; + } + + rawPacket = cmdBuffer; + rawPacketLen = 1; + break; + } + case (susMax1227::START_INT_TIMED_CONVERSIONS): { + std::memset(cmdBuffer, 0, sizeof(cmdBuffer)); + cmdBuffer[0] = max1227::buildResetByte(true); + cmdBuffer[1] = susMax1227::CONVERSION; + rawPacket = cmdBuffer; + rawPacketLen = 2; + break; + } + case (susMax1227::READ_INT_TIMED_CONVERSIONS): { + std::memset(cmdBuffer, 0, sizeof(cmdBuffer)); + rawPacket = cmdBuffer; + rawPacketLen = susMax1227::SIZE_READ_INT_CONVERSIONS; + break; + } + case (susMax1227::READ_EXT_TIMED_CONVERSIONS): { + std::memset(cmdBuffer, 0, sizeof(cmdBuffer)); + rawPacket = cmdBuffer; + for (uint8_t idx = 0; idx < 6; idx++) { + cmdBuffer[idx * 2] = buildConvByte(ScanModes::N_ONCE, idx, false); + cmdBuffer[idx * 2 + 1] = 0; + } + cmdBuffer[12] = 0x00; + rawPacketLen = susMax1227::SIZE_READ_EXT_CONVERSIONS; + break; + } + case (susMax1227::READ_EXT_TIMED_TEMPS): { + cmdBuffer[0] = buildConvByte(ScanModes::N_ONCE, 0, true); + std::memset(cmdBuffer + 1, 0, 24); + rawPacket = cmdBuffer; + rawPacketLen = 25; + break; + } + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::OK; +} + +void LegacySusHandler::fillCommandAndReplyMap() { + insertInCommandAndReplyMap(susMax1227::WRITE_SETUP, 1); + insertInCommandAndReplyMap(susMax1227::START_INT_TIMED_CONVERSIONS, 1); + insertInCommandAndReplyMap(susMax1227::READ_INT_TIMED_CONVERSIONS, 1, &dataset, + susMax1227::SIZE_READ_INT_CONVERSIONS); + insertInCommandAndReplyMap(susMax1227::READ_EXT_TIMED_CONVERSIONS, 1, &dataset, + susMax1227::SIZE_READ_EXT_CONVERSIONS); + insertInCommandAndReplyMap(susMax1227::READ_EXT_TIMED_TEMPS, 1); +} + +ReturnValue_t LegacySusHandler::scanForReply(const uint8_t *start, size_t remainingSize, + DeviceCommandId_t *foundId, size_t *foundLen) { + *foundId = this->getPendingCommand(); + *foundLen = remainingSize; + return returnvalue::OK; +} + +ReturnValue_t LegacySusHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + switch (id) { + case susMax1227::WRITE_SETUP: { + if (getMode() == _MODE_START_UP) { + commandExecuted = true; + } + return returnvalue::OK; + } + case susMax1227::START_INT_TIMED_CONVERSIONS: { + return returnvalue::OK; + } + case susMax1227::READ_INT_TIMED_CONVERSIONS: { + PoolReadGuard readSet(&dataset); + dataset.tempC = max1227::getTemperature(((packet[0] & 0x0f) << 8) | packet[1]); + for (uint8_t idx = 0; idx < 6; idx++) { + dataset.channels[idx] = packet[idx * 2 + 2] << 8 | packet[idx * 2 + 3]; + } + dataset.setValidity(true, true); + printDataset(); + break; + } + case (susMax1227::READ_EXT_TIMED_CONVERSIONS): { + PoolReadGuard readSet(&dataset); + for (uint8_t idx = 0; idx < 6; idx++) { + dataset.channels[idx] = packet[idx * 2 + 1] << 8 | packet[idx * 2 + 2]; + } + dataset.channels.setValid(true); + // Read temperature in next read cycle + if (clkMode == ClkModes::EXT_CLOCKED_WITH_TEMP) { + comState = ComStates::EXT_CLOCKED_TEMP; + } + printDataset(); + break; + } + case (susMax1227::READ_EXT_TIMED_TEMPS): { + PoolReadGuard readSet(&dataset); + dataset.tempC = max1227::getTemperature(((packet[23] & 0x0f) << 8) | packet[24]); + dataset.tempC.setValid(true); + comState = ComStates::EXT_CLOCKED_CONVERSIONS; + break; + } + default: { + sif::debug << "SusHandler::interpretDeviceReply: Unknown reply id" << std::endl; + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + return returnvalue::OK; +} + +uint32_t LegacySusHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 3000; } + +ReturnValue_t LegacySusHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(susMax1227::TEMPERATURE_C, &tempC); + localDataPoolMap.emplace(susMax1227::CHANNEL_VEC, &channelVec); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(dataset.getSid(), false, 5.0)); + return returnvalue::OK; +} + +void LegacySusHandler::setToGoToNormalMode(bool enable) { + this->goToNormalModeImmediately = enable; +} + +void LegacySusHandler::printDataset() { + if (periodicPrintout) { + if (divider.checkAndIncrement()) { + sif::info << "SUS ADC " << static_cast(susIdx) << " hex [" << std::setfill('0') + << std::hex; + for (uint8_t idx = 0; idx < 6; idx++) { + sif::info << std::setw(3) << dataset.channels[idx]; + if (idx < 6 - 1) { + sif::info << ","; + } + } + sif::info << "] | T[C] " << std::dec << dataset.tempC.value << std::endl; + } + } +} + +void LegacySusHandler::enablePeriodicPrintout(bool enable, uint8_t divider) { + this->periodicPrintout = enable; + this->divider.setDivider(divider); +} diff --git a/mission/acs/archive/LegacySusHandler.h b/mission/acs/archive/LegacySusHandler.h new file mode 100644 index 0000000..e9968e3 --- /dev/null +++ b/mission/acs/archive/LegacySusHandler.h @@ -0,0 +1,92 @@ +#ifndef MISSION_DEVICES_LEGACYSUSHANDLER_H_ +#define MISSION_DEVICES_LEGACYSUSHANDLER_H_ + +#include +#include +#include + +#include "events/subsystemIdRanges.h" +#include "fsfw/globalfunctions/PeriodicOperationDivider.h" +#include "returnvalues/classIds.h" + +/** + * @brief This is the device handler class for the SUS sensor based on the MAX1227 ADC. + * + * @details + * Datasheet of MAX1227: https://datasheets.maximintegrated.com/en/ds/MAX1227-MAX1231.pdf + * Details about the SUS electronic can be found at + * https://egit.irs.uni-stuttgart.de/eive/eive_dokumente/src/branch/master/400_Raumsegment/443_SunSensorDocumentation/release + * + * @note When adding a SusHandler to the polling sequence table make sure to add a slot with + * the executionStep FIRST_WRITE. Otherwise the communication sequence will never be + * started. + * + * @author J. Meier + */ +class LegacySusHandler : public DeviceHandlerBase { + public: + enum ClkModes { INT_CLOCKED, EXT_CLOCKED, EXT_CLOCKED_WITH_TEMP }; + + static const uint8_t FIRST_WRITE = 7; + + LegacySusHandler(object_id_t objectId, uint8_t susIdx, object_id_t comIF, CookieIF* comCookie); + virtual ~LegacySusHandler(); + + void enablePeriodicPrintout(bool enable, uint8_t divider); + + void setToGoToNormalMode(bool enable); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::SUS_HANDLER; + + static const ReturnValue_t ERROR_UNLOCK_MUTEX = MAKE_RETURN_CODE(0xA0); + static const ReturnValue_t ERROR_LOCK_MUTEX = MAKE_RETURN_CODE(0xA1); + + enum class ComStates { + IDLE, + WRITE_SETUP, + EXT_CLOCKED_CONVERSIONS, + EXT_CLOCKED_TEMP, + START_INT_CLOCKED_CONVERSIONS, + READ_INT_CLOCKED_CONVERSIONS + }; + + bool periodicPrintout = false; + PeriodicOperationDivider divider; + bool goToNormalModeImmediately = false; + bool commandExecuted = false; + + susMax1227::SusDataset dataset; + // Read temperature in each alternating communication step when using + // externally clocked mode + ClkModes clkMode = ClkModes::INT_CLOCKED; + PoolEntry tempC = PoolEntry({0.0}); + PoolEntry channelVec = PoolEntry({0, 0, 0, 0, 0, 0}); + + uint8_t susIdx = 0; + uint8_t cmdBuffer[susMax1227::MAX_CMD_SIZE]; + ComStates comState = ComStates::IDLE; + + MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING; + uint32_t timeoutMs = 20; + void printDataset(); + + MutexIF* spiMutex = nullptr; +}; + +#endif /* MISSION_DEVICES_LEGACYSUSHANDLER_H_ */ diff --git a/mission/acs/defs.cpp b/mission/acs/defs.cpp new file mode 100644 index 0000000..f90df9a --- /dev/null +++ b/mission/acs/defs.cpp @@ -0,0 +1,36 @@ +#include "defs.h" + +const char* acs::getModeStr(AcsMode mode) { + static const char* modeStr = "UNKNOWN"; + switch (mode) { + case (acs::AcsMode::OFF): { + modeStr = "OFF"; + break; + } + case (acs::AcsMode::SAFE): { + modeStr = "SAFE"; + break; + } + case (acs::AcsMode::PTG_NADIR): { + modeStr = "POITNING NADIR"; + break; + } + case (acs::AcsMode::PTG_IDLE): { + modeStr = "POINTING IDLE"; + break; + } + case (acs::AcsMode::PTG_INERTIAL): { + modeStr = "POINTING INERTIAL"; + break; + } + case (acs::AcsMode::PTG_TARGET): { + modeStr = "POINTING TARGET"; + break; + } + case (acs::AcsMode::PTG_TARGET_GS): { + modeStr = "POINTING TARGET GS"; + break; + } + } + return modeStr; +} diff --git a/mission/acs/defs.h b/mission/acs/defs.h new file mode 100644 index 0000000..47f7f17 --- /dev/null +++ b/mission/acs/defs.h @@ -0,0 +1,102 @@ +#ifndef MISSION_ACSDEFS_H_ +#define MISSION_ACSDEFS_H_ + +#include +#include +#include + +namespace acs { + +enum class SimpleSensorMode { NORMAL = 0, OFF = 1 }; + +// These modes are the modes of the ACS controller and of the ACS subsystem. +enum AcsMode : Mode_t { + OFF = HasModesIF::MODE_OFF, + SAFE = satsystem::Mode::SAFE, + PTG_IDLE = satsystem::Mode::PTG_IDLE, + PTG_NADIR = satsystem::Mode::PTG_NADIR, + PTG_TARGET = satsystem::Mode::PTG_TARGET, + PTG_TARGET_GS = satsystem::Mode::PTG_TARGET_GS, + PTG_INERTIAL = satsystem::Mode::PTG_INERTIAL, +}; + +enum SafeSubmode : Submode_t { DEFAULT = 0, DETUMBLE = 1 }; + +enum ControlModeStrategy : uint8_t { + CTRL_OFF = 0, + CTRL_NO_MAG_FIELD_FOR_CONTROL = 1, + CTRL_NO_SENSORS_FOR_CONTROL = 2, + // OBSW version <= v6.1.0 + LEGACY_SAFECTRL_ACTIVE_MEKF = 10, + LEGACY_SAFECTRL_WITHOUT_MEKF = 11, + LEGACY_SAFECTRL_ECLIPSE_DAMPING = 12, + LEGACY_SAFECTRL_ECLIPSE_IDELING = 13, + // Added in v6.2.0 + SAFECTRL_MEKF = 14, + SAFECTRL_GYR = 15, + SAFECTRL_SUSMGM = 16, + SAFECTRL_ECLIPSE_DAMPING_GYR = 17, + SAFECTRL_ECLIPSE_DAMPING_SUSMGM = 18, + SAFECTRL_ECLIPSE_IDELING = 19, + SAFECTRL_DETUMBLE_FULL = 20, + SAFECTRL_DETUMBLE_DETERIORATED = 21, + // Added in vNext + PTGCTRL_MEKF = 100, + PTGCTRL_STR = 101, + PTGCTRL_QUEST = 102, +}; +namespace gps { +enum Source : uint8_t { + NONE, + GPS, + GPS_EXTRAPOLATED, + SPG4, +}; +} + +namespace rotrate { +enum Source : uint8_t { + NONE, + SUSMGM, + QUEST, + STR, +}; +} + +static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::ACS_SUBSYSTEM; +//! [EXPORT] : [COMMENT] The limits for the rotation in safe mode were violated. +static constexpr Event SAFE_RATE_VIOLATION = MAKE_EVENT(0, severity::MEDIUM); +//! [EXPORT] : [COMMENT] The limits for the rotation in pointing mode were violated. +static constexpr Event PTG_RATE_VIOLATION = MAKE_EVENT(10, severity::MEDIUM); +//! [EXPORT] : [COMMENT] The system has recovered from a rate rotation violation. +static constexpr Event RATE_RECOVERY = MAKE_EVENT(1, severity::MEDIUM); +//! [EXPORT] : [COMMENT] The detumble transition has failed. +//! //! P1: Last detumble state before failure. +static constexpr Event DETUMBLE_TRANSITION_FAILED = MAKE_EVENT(11, severity::HIGH); +//! [EXPORT] : [COMMENT] Multiple RWs are invalid, uncommandable and therefore higher ACS modes +//! cannot be maintained. +static constexpr Event MULTIPLE_RW_INVALID = MAKE_EVENT(2, severity::HIGH); +//! [EXPORT] : [COMMENT] MEKF was not able to compute a solution. +//! P1: MEKF state on exit +static constexpr Event MEKF_INVALID_INFO = MAKE_EVENT(3, severity::INFO); +//! [EXPORT] : [COMMENT] MEKF is able to compute a solution again. +static constexpr Event MEKF_RECOVERY = MAKE_EVENT(4, severity::INFO); +//! [EXPORT] : [COMMENT] MEKF performed an automatic reset after detection of nonfinite values. +static constexpr Event MEKF_AUTOMATIC_RESET = MAKE_EVENT(5, severity::INFO); +//! [EXPORT] : [COMMENT] For a prolonged time, no attitude information was available for the +//! Pointing Controller. Falling back to Safe Mode. +static constexpr Event PTG_CTRL_NO_ATTITUDE_INFORMATION = MAKE_EVENT(6, severity::HIGH); +//! [EXPORT] : [COMMENT] The ACS safe mode controller was not able to compute a solution and has +//! failed. +//! P1: Missing information about magnetic field, P2: Missing information about rotational rate +static constexpr Event SAFE_MODE_CONTROLLER_FAILURE = MAKE_EVENT(7, severity::HIGH); +//! [EXPORT] : [COMMENT] The TLE for the SGP4 Propagator has become too old. +static constexpr Event TLE_TOO_OLD = MAKE_EVENT(8, severity::INFO); +//! [EXPORT] : [COMMENT] The TLE could not be read from the filesystem. +static constexpr Event TLE_FILE_READ_FAILED = MAKE_EVENT(9, severity::LOW); + +extern const char* getModeStr(AcsMode mode); + +} // namespace acs + +#endif /* MISSION_ACSDEFS_H_ */ diff --git a/mission/acs/gyroAdisHelpers.cpp b/mission/acs/gyroAdisHelpers.cpp new file mode 100644 index 0000000..5c1446c --- /dev/null +++ b/mission/acs/gyroAdisHelpers.cpp @@ -0,0 +1,65 @@ +#include "gyroAdisHelpers.h" + +size_t adis1650x::prepareReadCommand(const uint8_t* regList, size_t len, uint8_t* outBuf, + size_t maxLen) { + if (len * 2 + 2 > maxLen) { + return 0; + } + for (size_t idx = 0; idx < len; idx++) { + outBuf[idx * 2] = regList[idx]; + outBuf[idx * 2 + 1] = 0x00; + } + outBuf[len * 2] = 0x00; + outBuf[len * 2 + 1] = 0x00; + return len * 2 + 2; +} + +adis1650x::BurstModes adis1650x::burstModeFromMscCtrl(uint16_t mscCtrl) { + if ((mscCtrl & adis1650x::BURST_32_BIT) == adis1650x::BURST_32_BIT) { + if ((mscCtrl & adis1650x::BURST_SEL_BIT) == adis1650x::BURST_SEL_BIT) { + return BurstModes::BURST_32_BURST_SEL_1; + } else { + return BurstModes::BURST_32_BURST_SEL_0; + } + } else { + if ((mscCtrl & adis1650x::BURST_SEL_BIT) == adis1650x::BURST_SEL_BIT) { + return BurstModes::BURST_16_BURST_SEL_1; + } else { + return BurstModes::BURST_16_BURST_SEL_0; + } + } +} + +double adis1650x::rangMdlToSensitivity(uint16_t rangMdl) { + adis1650x::RangMdlBitfield bitfield = + static_cast((rangMdl >> 2) & 0b11); + switch (bitfield) { + case (adis1650x::RangMdlBitfield::RANGE_125_1BMLZ): { + return SENSITIVITY_1BMLZ; + } + case (adis1650x::RangMdlBitfield::RANGE_500_2BMLZ): { + return SENSITIVITY_2BMLZ; + } + case (adis1650x::RangMdlBitfield::RANGE_2000_3BMLZ): { + return SENSITIVITY_3BMLZ; + } + case (RangMdlBitfield::RESERVED): + default: { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "ADIS1650X: Unexpected value for RANG_MDL register" << std::endl; +#endif + return 0.0; + } + } +} + +void adis1650x::prepareWriteRegCommand(uint8_t regStart, uint16_t val, uint8_t* outBuf, + size_t maxLen) { + if (maxLen < 4) { + return; + } + outBuf[0] = regStart | adis1650x::WRITE_MASK; + outBuf[1] = val & 0xff; + outBuf[2] = (regStart + 1) | adis1650x::WRITE_MASK; + outBuf[3] = (val >> 8) & 0xff; +} diff --git a/mission/acs/gyroAdisHelpers.h b/mission/acs/gyroAdisHelpers.h new file mode 100644 index 0000000..f360db7 --- /dev/null +++ b/mission/acs/gyroAdisHelpers.h @@ -0,0 +1,186 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_GYROADIS16507DEFINITIONS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_GYROADIS16507DEFINITIONS_H_ + +#include + +#include "fsfw/datapoollocal/StaticLocalDataSet.h" +#include "fsfw/devicehandlers/DeviceHandlerIF.h" + +namespace adis1650x { + +enum class BurstModes { + BURST_16_BURST_SEL_0, + BURST_16_BURST_SEL_1, + BURST_32_BURST_SEL_0, + BURST_32_BURST_SEL_1 +}; + +size_t prepareReadCommand(const uint8_t* regList, size_t len, uint8_t* outBuf, size_t maxLen); +void prepareWriteRegCommand(uint8_t regStart, uint16_t val, uint8_t* outBuf, size_t maxLen); + +BurstModes burstModeFromMscCtrl(uint16_t mscCtrl); +double rangMdlToSensitivity(uint16_t rangMdl); + +enum class Type : uint8_t { ADIS16505, ADIS16507 }; + +static constexpr size_t MAXIMUM_REPLY_SIZE = 64; +static constexpr uint8_t WRITE_MASK = 0b1000'0000; + +// Ranges in deg / s +static constexpr double RANGE_UNSET = 0.0; +static constexpr double RANGE_1BMLZ = 125.0; +static constexpr double RANGE_2BMLZ = 500.0; +static constexpr double RANGE_3BMLZ = 2000.0; +// Sensitivities in deg/s/LSB +static constexpr double SENSITIVITY_UNSET = 0.0; +static constexpr double SENSITIVITY_1BMLZ = 0.00625; +static constexpr double SENSITIVITY_2BMLZ = 0.025; +static constexpr double SENSITIVITY_3BMLZ = 0.1; + +enum RangMdlBitfield { + RANGE_125_1BMLZ = 0b00, + RANGE_500_2BMLZ = 0b01, + RESERVED = 0b10, + RANGE_2000_3BMLZ = 0b11 +}; + +static constexpr uint32_t ACCELEROMETER_RANGE_16507 = 392; +static constexpr float ACCELEROMETER_RANGE_16505 = 78.4; + +static constexpr uint32_t STALL_TIME_MICROSECONDS = 16; + +static constexpr uint16_t PROD_ID_16507 = 16507; +static constexpr uint16_t PROD_ID_16505 = 16505; + +static constexpr std::array BURST_READ_ENABLE = {0x68, 0x00}; + +static constexpr dur_millis_t START_UP_TIME = 310; +static constexpr dur_millis_t SW_RESET_BREAK = 255; +static constexpr dur_millis_t FACTORY_CALIBRATION_BREAK = 136; +static constexpr dur_millis_t FLASH_MEMORY_UPDATE_BREAK = 70; +static constexpr dur_millis_t FLASH_MEMORY_TEST_BREAK = 30; +static constexpr dur_millis_t SELF_TEST_BREAK = 24; + +static constexpr uint8_t DIAG_STAT_REG = 0x02; +static constexpr uint8_t FILTER_CTRL_REG = 0x5c; +static constexpr uint8_t RANG_MDL_REG = 0x5e; +static constexpr uint8_t MSC_CTRL_REG = 0x60; +static constexpr uint8_t DEC_RATE_REG = 0x64; +static constexpr uint8_t GLOB_CMD = 0x68; +static constexpr uint8_t PROD_ID_REG = 0x72; + +static constexpr DeviceCommandId_t READ_SENSOR_DATA = 0; +static constexpr DeviceCommandId_t READ_OUT_CONFIG = 1; +static constexpr DeviceCommandId_t SELF_TEST_SENSORS = 2; +static constexpr DeviceCommandId_t SELF_TEST_MEMORY = 3; +static constexpr DeviceCommandId_t UPDATE_NV_CONFIGURATION = 4; +static constexpr DeviceCommandId_t SELECT_BURST_READ_MODE = 5; + +static constexpr DeviceCommandId_t RESET_SENSOR_CONFIGURATION = 30; +static constexpr DeviceCommandId_t SW_RESET = 31; +static constexpr DeviceCommandId_t PRINT_CURRENT_CONFIGURATION = 32; + +static constexpr DeviceCommandId_t REQUEST = 0x70; +static constexpr DeviceCommandId_t REPLY = 0x77; + +static constexpr uint16_t BURST_32_BIT = 1 << 9; +static constexpr uint16_t BURST_SEL_BIT = 1 << 8; +static constexpr uint16_t LIN_ACCEL_COMPENSATION_BIT = 1 << 7; +static constexpr uint16_t POINT_PERCUSSION_COMPENSATION_BIT = 1 << 6; + +static constexpr size_t CONFIG_READOUT_SIZE = 12 + 2; +static constexpr size_t SENSOR_READOUT_SIZE = 20 + 2; + +static constexpr uint32_t ADIS_DATASET_ID = READ_SENSOR_DATA; +static constexpr uint32_t ADIS_CFG_DATASET_ID = READ_OUT_CONFIG; + +static constexpr uint16_t FILT_CTRL = 0x0000; +static constexpr uint16_t DEC_RATE = 0x00C7; + +enum GlobCmds : uint8_t { + FACTORY_CALIBRATION = 0b0000'0010, + SENSOR_SELF_TEST = 0b0000'0100, + FLASH_MEMORY_UPDATE = 0b0000'1000, + FLASH_MEMORY_TEST = 0b0001'0000, + SOFTWARE_RESET = 0b1000'0000 +}; + +enum PrimaryPoolIds : lp_id_t { + ANG_VELOC_X, + ANG_VELOC_Y, + ANG_VELOC_Z, + ACCELERATION_X, + ACCELERATION_Y, + ACCELERATION_Z, + TEMPERATURE, + DIAG_STAT_REGISTER, + FILTER_SETTINGS, + RANG_MDL, + MSC_CTRL_REGISTER, + DEC_RATE_REGISTER, +}; + +enum FilterSettings : uint8_t { + NO_FILTER = 0, + TWO_TAPS = 1, + FOUR_TAPS = 2, + EIGHT_TAPS = 3, + SIXTEEN_TAPS = 4, + THIRTYTWO_TAPS = 5, + SIXTYFOUR_TAPS = 6 +}; + +} // namespace adis1650x + +class AdisGyroPrimaryDataset : public StaticLocalDataSet<8> { + public: + /** Constructor for data users like controllers */ + AdisGyroPrimaryDataset(object_id_t adisId) + : StaticLocalDataSet(sid_t(adisId, adis1650x::ADIS_DATASET_ID)) { + setAllVariablesReadOnly(); + } + + /* Angular velocities in degrees per second (DPS) */ + lp_var_t angVelocX = lp_var_t(sid.objectId, adis1650x::ANG_VELOC_X, this); + lp_var_t angVelocY = lp_var_t(sid.objectId, adis1650x::ANG_VELOC_Y, this); + lp_var_t angVelocZ = lp_var_t(sid.objectId, adis1650x::ANG_VELOC_Z, this); + lp_var_t accelX = lp_var_t(sid.objectId, adis1650x::ACCELERATION_X, this); + lp_var_t accelY = lp_var_t(sid.objectId, adis1650x::ACCELERATION_Y, this); + lp_var_t accelZ = lp_var_t(sid.objectId, adis1650x::ACCELERATION_Z, this); + lp_var_t temperature = lp_var_t(sid.objectId, adis1650x::TEMPERATURE, this); + + private: + friend class GyrAdis1650XHandler; + friend class GyroAdisDummy; + + /** Constructor for the data creator */ + AdisGyroPrimaryDataset(HasLocalDataPoolIF* hkOwner) + : StaticLocalDataSet(hkOwner, adis1650x::ADIS_DATASET_ID) {} +}; + +class AdisGyroConfigDataset : public StaticLocalDataSet<5> { + public: + /** Constructor for data users like controllers */ + AdisGyroConfigDataset(object_id_t adisId) + : StaticLocalDataSet(sid_t(adisId, adis1650x::ADIS_CFG_DATASET_ID)) { + setAllVariablesReadOnly(); + } + + lp_var_t diagStatReg = + lp_var_t(sid.objectId, adis1650x::DIAG_STAT_REGISTER, this); + lp_var_t filterSetting = + lp_var_t(sid.objectId, adis1650x::FILTER_SETTINGS, this); + lp_var_t rangMdl = lp_var_t(sid.objectId, adis1650x::RANG_MDL, this); + lp_var_t mscCtrlReg = + lp_var_t(sid.objectId, adis1650x::MSC_CTRL_REGISTER, this); + lp_var_t decRateReg = + lp_var_t(sid.objectId, adis1650x::DEC_RATE_REGISTER, this); + + private: + friend class GyrAdis1650XHandler; + /** Constructor for the data creator */ + AdisGyroConfigDataset(HasLocalDataPoolIF* hkOwner) + : StaticLocalDataSet(hkOwner, adis1650x::ADIS_CFG_DATASET_ID) {} +}; + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_GYROADIS16507DEFINITIONS_H_ */ diff --git a/mission/acs/imtqHelpers.cpp b/mission/acs/imtqHelpers.cpp new file mode 100644 index 0000000..a222eb7 --- /dev/null +++ b/mission/acs/imtqHelpers.cpp @@ -0,0 +1,70 @@ +#include "imtqHelpers.h" + +uint16_t imtq::integrationTimeFromSelectValue(uint8_t value) { + switch (value) { + case (0): + return 2; + case (1): + return 3; + case (2): + return 6; + case (3): + return 10; + case (4): + return 20; + case (5): + return 40; + case (6): + return 80; + default: + return 10; + } +} + +size_t imtq::getReplySize(CC::CC cc, size_t* optSecondSize) { + switch (cc) { + // Software reset is a bit special and can also cause a I2C NAK because + // the device might be reset at that moment. Otherwise, 2 bytes should be returned + case (CC::CC::SOFTWARE_RESET): { + if (optSecondSize != nullptr) { + *optSecondSize = 0; + } + return 2; + } + case (CC::CC::START_ACTUATION_DIPOLE): + case (CC::CC::SELF_TEST_CMD): + case (CC::CC::START_MTM_MEASUREMENT): { + return 2; + } + case (CC::CC::GET_SYSTEM_STATE): { + return 9; + } + case (CC::CC::GET_RAW_MTM_MEASUREMENT): + case (CC::CC::GET_CAL_MTM_MEASUREMENT): { + return 15; + } + case (CC::CC::GET_COIL_CURRENT): + case (CC::CC::GET_COMMANDED_DIPOLE): + case (CC::CC::GET_COIL_TEMPERATURES): { + return 8; + } + case (CC::CC::GET_SELF_TEST_RESULT): { + // Can also be 360 for the all axes self-test! + if (optSecondSize != nullptr) { + *optSecondSize = 360; + } + return 120; + } + case (CC::CC::GET_RAW_HK_DATA): + case (CC::CC::GET_ENG_HK_DATA): { + return 24; + } + case (CC::CC::GET_PARAM): { + return imtq::replySize::MAX_SET_GET_PARAM_LEN; + } + default: { + return 0; + } + } + return 0; +} diff --git a/mission/acs/imtqHelpers.h b/mission/acs/imtqHelpers.h new file mode 100644 index 0000000..38530d3 --- /dev/null +++ b/mission/acs/imtqHelpers.h @@ -0,0 +1,1239 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_IMTQDEFINITIONS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_IMTQDEFINITIONS_H_ + +#include +#include +#include +#include +#include + +class ImtqHandler; + +// C garbage which can be included from gps.h +#undef STATUS_SET + +namespace imtq { + +uint16_t integrationTimeFromSelectValue(uint8_t value); + +enum class RequestType : uint8_t { MEASURE_NO_ACTUATION, ACTUATE, DO_NOTHING }; + +enum class SpecialRequest : uint8_t { + NONE = 0, + DO_SELF_TEST_POS_X = 1, + DO_SELF_TEST_NEG_X = 2, + DO_SELF_TEST_POS_Y = 3, + DO_SELF_TEST_NEG_Y = 4, + DO_SELF_TEST_POS_Z = 5, + DO_SELF_TEST_NEG_Z = 6, + GET_SELF_TEST_RESULT = 7 +}; + +struct Request { + acs::SimpleSensorMode mode = acs::SimpleSensorMode::OFF; + imtq::RequestType requestType = imtq::RequestType::MEASURE_NO_ACTUATION; + imtq::SpecialRequest specialRequest = imtq::SpecialRequest::NONE; + uint8_t integrationTimeSel = 3; + int16_t dipoles[3]{}; + uint16_t torqueDuration = 0; +}; + +enum ComStep : uint8_t { + DHB_OP = 0, + START_MEASURE_SEND = 1, + START_MEASURE_GET = 2, + READ_MEASURE_SEND = 3, + READ_MEASURE_GET = 4, + START_ACTUATE_SEND = 5, + START_ACTUATE_GET = 6, + READ_ACTUATE_SEND = 7, + READ_ACTUATE_GET = 8, +}; + +static const uint8_t INTERFACE_ID = CLASS_ID::IMTQ_HANDLER; + +static constexpr ReturnValue_t INVALID_COMMAND_CODE = MAKE_RETURN_CODE(0); +static constexpr ReturnValue_t MGM_MEASUREMENT_LOW_LEVEL_ERROR = MAKE_RETURN_CODE(1); +static constexpr ReturnValue_t ACTUATE_CMD_LOW_LEVEL_ERROR = MAKE_RETURN_CODE(2); +static const ReturnValue_t PARAMETER_MISSING = MAKE_RETURN_CODE(3); +static const ReturnValue_t PARAMETER_INVALID = MAKE_RETURN_CODE(4); +static const ReturnValue_t CC_UNAVAILABLE = MAKE_RETURN_CODE(5); +static const ReturnValue_t INTERNAL_PROCESSING_ERROR = MAKE_RETURN_CODE(6); +static const ReturnValue_t REJECTED_WITHOUT_REASON = MAKE_RETURN_CODE(7); +static const ReturnValue_t CMD_ERR_UNKNOWN = MAKE_RETURN_CODE(8); +static constexpr ReturnValue_t STARTUP_CFG_ERROR = MAKE_RETURN_CODE(9); +//! [EXPORT] : [COMMENT] The status reply to a self test command was received but no self test +//! command has been sent. This should normally never happen. +static const ReturnValue_t UNEXPECTED_SELF_TEST_REPLY = MAKE_RETURN_CODE(10); + +namespace cmdIds { + +static constexpr DeviceCommandId_t REQUEST = 0x70; +static constexpr DeviceCommandId_t REPLY_NO_TORQUE = 0x71; +static constexpr DeviceCommandId_t REPLY_WITH_TORQUE = 0x72; +static const DeviceCommandId_t START_ACTUATION_DIPOLE = 0x2; +static const DeviceCommandId_t POS_X_SELF_TEST = 0x7; +static const DeviceCommandId_t NEG_X_SELF_TEST = 0x8; +static const DeviceCommandId_t POS_Y_SELF_TEST = 0x9; +static const DeviceCommandId_t NEG_Y_SELF_TEST = 0xA; +static const DeviceCommandId_t POS_Z_SELF_TEST = 0xB; +static const DeviceCommandId_t NEG_Z_SELF_TEST = 0xC; +static const DeviceCommandId_t GET_SELF_TEST_RESULT = 0xD; + +} // namespace cmdIds + +static const uint8_t POINTER_REG_SIZE = 1; + +enum SetIds : uint32_t { + ENG_HK_NO_TORQUE = 1, + RAW_MTM_NO_TORQUE = 2, + ENG_HK_SET_WITH_TORQUE = 3, + RAW_MTM_WITH_TORQUE = 4, + STATUS_SET = 5, + DIPOLES = 6, + + CAL_MTM_SET = 9, + POSITIVE_X_TEST = 10, + NEGATIVE_X_TEST = 11, + POSITIVE_Y_TEST = 12, + NEGATIVE_Y_TEST = 13, + POSITIVE_Z_TEST = 14, + NEGATIVE_Z_TEST = 15, +}; + +namespace replySize { + +static constexpr uint8_t GET_TEMP_REPLY_SIZE = 2; +static constexpr uint8_t CFGR_CMD_SIZE = 3; +static constexpr size_t DEFAULT_MIN_LEN = 2; +static constexpr size_t STATUS_REPLY = DEFAULT_MIN_LEN; +static constexpr size_t ENG_HK_DATA_REPLY = 24; +static constexpr size_t GET_COMMANDED_DIPOLE_REPLY = 8; +static constexpr size_t MAX_SET_GET_PARAM_LEN = 12; +static constexpr size_t SYSTEM_STATE = 9; +static constexpr size_t CAL_MTM_MEASUREMENT = 15; +static constexpr size_t RAW_MTM_MEASUREMENT = 15; +static constexpr size_t SELF_TEST_RESULTS = 120; +static constexpr size_t SELF_TEST_RESULTS_ALL_AXES = 360; + +} // namespace replySize + +static const uint16_t MAX_REPLY_SIZE = replySize::SELF_TEST_RESULTS_ALL_AXES; +static const uint8_t MAX_COMMAND_SIZE = 16; + +/** Define entries in IMTQ specific dataset */ +static const uint8_t HK_SET_POOL_ENTRIES = 20; +static const uint8_t CAL_MTM_POOL_ENTRIES = 4; +static const uint8_t SELF_TEST_DATASET_ENTRIES = 104; + +/** Error codes for interpreting the self test error byte */ +static const uint8_t I2C_FAILURE_MASK = 0x1; +static const uint8_t SPI_FAILURE_MASK = 0x2; // MTM connectivity +static const uint8_t ADC_FAILURE_MASK = 0x4; // Current/Temp measurement +static const uint8_t PWM_FAILURE_MASK = 0x8; // Coil actuation +static const uint8_t TC_FAILURE_MASK = 0x10; // System failure +static const uint8_t MTM_RANGE_FAILURE_MASK = 0x20; // MTM values outside of expected range +static const uint8_t COIL_CURRENT_FAILURE_MASK = 0x40; // Coil currents outside of expected range +static const uint8_t INVALID_ERROR_BYTE = + 0x80; // This is an invalid error byte and should be never replied by the IMTQ + +static const uint8_t MAIN_STEP_OFFSET = 43; + +// Command Code +namespace CC { + +/** + * Command code definitions. Each command or reply of an IMTQ request will begin with one of + * the following command codes. + */ +enum CC : uint8_t { + START_MTM_MEASUREMENT = 0x4, + START_ACTUATION_DIPOLE = 0x6, + SELF_TEST_CMD = 0x8, + GET_SYSTEM_STATE = 0x41, + GET_RAW_MTM_MEASUREMENT = 0x42, + GET_CAL_MTM_MEASUREMENT = 0x43, + GET_COIL_CURRENT = 0x44, + GET_COIL_TEMPERATURES = 0x45, + GET_COMMANDED_DIPOLE = 0x46, + GET_SELF_TEST_RESULT = 0x47, + GET_RAW_HK_DATA = 0x49, + GET_ENG_HK_DATA = 0x4A, + GET_PARAM = 0x81, + SET_PARAM = 0x82, + SOFTWARE_RESET = 0xAA, + PAST_AVAILABLE_RESPONSE_BYTES = 0xff +}; + +} // namespace CC + +namespace param { + +static constexpr uint16_t SEL_MTM = 0x2002; +static constexpr uint16_t INTEGRATION_TIME_SELECT = 0x2003; + +} // namespace param + +size_t getReplySize(CC::CC cc, size_t* optSecondSize = nullptr); + +namespace mode { +enum Mode : uint8_t { IDLE = 0, SELF_TEST = 1, DETUMBLE = 2 }; +} + +namespace selfTest { + +enum Axis : uint8_t { + + ALL = 0x0, + X_POSITIVE = 0x1, + X_NEGATIVE = 0x2, + Y_POSITIVE = 0x3, + Y_NEGATIVE = 0x4, + Z_POSITIVE = 0x5, + Z_NEGATIVE = 0x6, +}; + +namespace step { + +enum Step : uint8_t { + + INIT = 0x0, + X_POSITIVE = 0x1, + X_NEGATIVE = 0x2, + Y_POSITIVE = 0x3, + Y_NEGATIVE = 0x4, + Z_POSITIVE = 0x5, + Z_NEGATIVE = 0x6, + FINA = 0x7 +}; +} +} // namespace selfTest + +enum PoolIds : lp_id_t { + STATUS_BYTE_MODE, + STATUS_BYTE_ERROR, + STATUS_BYTE_CONF, + STATUS_BYTE_UPTIME, + + DIGITAL_VOLTAGE_MV, + ANALOG_VOLTAGE_MV, + DIGITAL_CURRENT, + ANALOG_CURRENT, + COIL_CURRENTS, + COIL_TEMPERATURES, + MCU_TEMPERATURE, + + DIGITAL_VOLTAGE_MV_WT, + ANALOG_VOLTAGE_MV_WT, + DIGITAL_CURRENT_WT, + ANALOG_CURRENT_WT, + COIL_CURRENTS_WT, + COIL_TEMPERATURES_WT, + MCU_TEMPERATURE_WT, + + MGM_CAL_NT, + ACTUATION_CAL_STATUS, + + MTM_RAW, + ACTUATION_RAW_STATUS, + + MTM_RAW_WT, + ACTUATION_RAW_STATUS_WT, + + DIPOLES_ID, + CURRENT_TORQUE_DURATION, + + INIT_POS_X_ERR, + INIT_POS_X_RAW_MAG_X, + INIT_POS_X_RAW_MAG_Y, + INIT_POS_X_RAW_MAG_Z, + INIT_POS_X_CAL_MAG_X, + INIT_POS_X_CAL_MAG_Y, + INIT_POS_X_CAL_MAG_Z, + INIT_POS_X_COIL_X_CURRENT, + INIT_POS_X_COIL_Y_CURRENT, + INIT_POS_X_COIL_Z_CURRENT, + INIT_POS_X_COIL_X_TEMPERATURE, + INIT_POS_X_COIL_Y_TEMPERATURE, + INIT_POS_X_COIL_Z_TEMPERATURE, + + INIT_NEG_X_ERR, + INIT_NEG_X_RAW_MAG_X, + INIT_NEG_X_RAW_MAG_Y, + INIT_NEG_X_RAW_MAG_Z, + INIT_NEG_X_CAL_MAG_X, + INIT_NEG_X_CAL_MAG_Y, + INIT_NEG_X_CAL_MAG_Z, + INIT_NEG_X_COIL_X_CURRENT, + INIT_NEG_X_COIL_Y_CURRENT, + INIT_NEG_X_COIL_Z_CURRENT, + INIT_NEG_X_COIL_X_TEMPERATURE, + INIT_NEG_X_COIL_Y_TEMPERATURE, + INIT_NEG_X_COIL_Z_TEMPERATURE, + + INIT_POS_Y_ERR, + INIT_POS_Y_RAW_MAG_X, + INIT_POS_Y_RAW_MAG_Y, + INIT_POS_Y_RAW_MAG_Z, + INIT_POS_Y_CAL_MAG_X, + INIT_POS_Y_CAL_MAG_Y, + INIT_POS_Y_CAL_MAG_Z, + INIT_POS_Y_COIL_X_CURRENT, + INIT_POS_Y_COIL_Y_CURRENT, + INIT_POS_Y_COIL_Z_CURRENT, + INIT_POS_Y_COIL_X_TEMPERATURE, + INIT_POS_Y_COIL_Y_TEMPERATURE, + INIT_POS_Y_COIL_Z_TEMPERATURE, + + INIT_NEG_Y_ERR, + INIT_NEG_Y_RAW_MAG_X, + INIT_NEG_Y_RAW_MAG_Y, + INIT_NEG_Y_RAW_MAG_Z, + INIT_NEG_Y_CAL_MAG_X, + INIT_NEG_Y_CAL_MAG_Y, + INIT_NEG_Y_CAL_MAG_Z, + INIT_NEG_Y_COIL_X_CURRENT, + INIT_NEG_Y_COIL_Y_CURRENT, + INIT_NEG_Y_COIL_Z_CURRENT, + INIT_NEG_Y_COIL_X_TEMPERATURE, + INIT_NEG_Y_COIL_Y_TEMPERATURE, + INIT_NEG_Y_COIL_Z_TEMPERATURE, + + INIT_POS_Z_ERR, + INIT_POS_Z_RAW_MAG_X, + INIT_POS_Z_RAW_MAG_Y, + INIT_POS_Z_RAW_MAG_Z, + INIT_POS_Z_CAL_MAG_X, + INIT_POS_Z_CAL_MAG_Y, + INIT_POS_Z_CAL_MAG_Z, + INIT_POS_Z_COIL_X_CURRENT, + INIT_POS_Z_COIL_Y_CURRENT, + INIT_POS_Z_COIL_Z_CURRENT, + INIT_POS_Z_COIL_X_TEMPERATURE, + INIT_POS_Z_COIL_Y_TEMPERATURE, + INIT_POS_Z_COIL_Z_TEMPERATURE, + + INIT_NEG_Z_ERR, + INIT_NEG_Z_RAW_MAG_X, + INIT_NEG_Z_RAW_MAG_Y, + INIT_NEG_Z_RAW_MAG_Z, + INIT_NEG_Z_CAL_MAG_X, + INIT_NEG_Z_CAL_MAG_Y, + INIT_NEG_Z_CAL_MAG_Z, + INIT_NEG_Z_COIL_X_CURRENT, + INIT_NEG_Z_COIL_Y_CURRENT, + INIT_NEG_Z_COIL_Z_CURRENT, + INIT_NEG_Z_COIL_X_TEMPERATURE, + INIT_NEG_Z_COIL_Y_TEMPERATURE, + INIT_NEG_Z_COIL_Z_TEMPERATURE, + + POS_X_ERR, + POS_X_RAW_MAG_X, + POS_X_RAW_MAG_Y, + POS_X_RAW_MAG_Z, + POS_X_CAL_MAG_X, + POS_X_CAL_MAG_Y, + POS_X_CAL_MAG_Z, + POS_X_COIL_X_CURRENT, + POS_X_COIL_Y_CURRENT, + POS_X_COIL_Z_CURRENT, + POS_X_COIL_X_TEMPERATURE, + POS_X_COIL_Y_TEMPERATURE, + POS_X_COIL_Z_TEMPERATURE, + + NEG_X_ERR, + NEG_X_RAW_MAG_X, + NEG_X_RAW_MAG_Y, + NEG_X_RAW_MAG_Z, + NEG_X_CAL_MAG_X, + NEG_X_CAL_MAG_Y, + NEG_X_CAL_MAG_Z, + NEG_X_COIL_X_CURRENT, + NEG_X_COIL_Y_CURRENT, + NEG_X_COIL_Z_CURRENT, + NEG_X_COIL_X_TEMPERATURE, + NEG_X_COIL_Y_TEMPERATURE, + NEG_X_COIL_Z_TEMPERATURE, + + POS_Y_ERR, + POS_Y_RAW_MAG_X, + POS_Y_RAW_MAG_Y, + POS_Y_RAW_MAG_Z, + POS_Y_CAL_MAG_X, + POS_Y_CAL_MAG_Y, + POS_Y_CAL_MAG_Z, + POS_Y_COIL_X_CURRENT, + POS_Y_COIL_Y_CURRENT, + POS_Y_COIL_Z_CURRENT, + POS_Y_COIL_X_TEMPERATURE, + POS_Y_COIL_Y_TEMPERATURE, + POS_Y_COIL_Z_TEMPERATURE, + + NEG_Y_ERR, + NEG_Y_RAW_MAG_X, + NEG_Y_RAW_MAG_Y, + NEG_Y_RAW_MAG_Z, + NEG_Y_CAL_MAG_X, + NEG_Y_CAL_MAG_Y, + NEG_Y_CAL_MAG_Z, + NEG_Y_COIL_X_CURRENT, + NEG_Y_COIL_Y_CURRENT, + NEG_Y_COIL_Z_CURRENT, + NEG_Y_COIL_X_TEMPERATURE, + NEG_Y_COIL_Y_TEMPERATURE, + NEG_Y_COIL_Z_TEMPERATURE, + + POS_Z_ERR, + POS_Z_RAW_MAG_X, + POS_Z_RAW_MAG_Y, + POS_Z_RAW_MAG_Z, + POS_Z_CAL_MAG_X, + POS_Z_CAL_MAG_Y, + POS_Z_CAL_MAG_Z, + POS_Z_COIL_X_CURRENT, + POS_Z_COIL_Y_CURRENT, + POS_Z_COIL_Z_CURRENT, + POS_Z_COIL_X_TEMPERATURE, + POS_Z_COIL_Y_TEMPERATURE, + POS_Z_COIL_Z_TEMPERATURE, + + NEG_Z_ERR, + NEG_Z_RAW_MAG_X, + NEG_Z_RAW_MAG_Y, + NEG_Z_RAW_MAG_Z, + NEG_Z_CAL_MAG_X, + NEG_Z_CAL_MAG_Y, + NEG_Z_CAL_MAG_Z, + NEG_Z_COIL_X_CURRENT, + NEG_Z_COIL_Y_CURRENT, + NEG_Z_COIL_Z_CURRENT, + NEG_Z_COIL_X_TEMPERATURE, + NEG_Z_COIL_Y_TEMPERATURE, + NEG_Z_COIL_Z_TEMPERATURE, + + FINA_POS_X_ERR, + FINA_POS_X_RAW_MAG_X, + FINA_POS_X_RAW_MAG_Y, + FINA_POS_X_RAW_MAG_Z, + FINA_POS_X_CAL_MAG_X, + FINA_POS_X_CAL_MAG_Y, + FINA_POS_X_CAL_MAG_Z, + FINA_POS_X_COIL_X_CURRENT, + FINA_POS_X_COIL_Y_CURRENT, + FINA_POS_X_COIL_Z_CURRENT, + FINA_POS_X_COIL_X_TEMPERATURE, + FINA_POS_X_COIL_Y_TEMPERATURE, + FINA_POS_X_COIL_Z_TEMPERATURE, + + FINA_NEG_X_ERR, + FINA_NEG_X_RAW_MAG_X, + FINA_NEG_X_RAW_MAG_Y, + FINA_NEG_X_RAW_MAG_Z, + FINA_NEG_X_CAL_MAG_X, + FINA_NEG_X_CAL_MAG_Y, + FINA_NEG_X_CAL_MAG_Z, + FINA_NEG_X_COIL_X_CURRENT, + FINA_NEG_X_COIL_Y_CURRENT, + FINA_NEG_X_COIL_Z_CURRENT, + FINA_NEG_X_COIL_X_TEMPERATURE, + FINA_NEG_X_COIL_Y_TEMPERATURE, + FINA_NEG_X_COIL_Z_TEMPERATURE, + + FINA_POS_Y_ERR, + FINA_POS_Y_RAW_MAG_X, + FINA_POS_Y_RAW_MAG_Y, + FINA_POS_Y_RAW_MAG_Z, + FINA_POS_Y_CAL_MAG_X, + FINA_POS_Y_CAL_MAG_Y, + FINA_POS_Y_CAL_MAG_Z, + FINA_POS_Y_COIL_X_CURRENT, + FINA_POS_Y_COIL_Y_CURRENT, + FINA_POS_Y_COIL_Z_CURRENT, + FINA_POS_Y_COIL_X_TEMPERATURE, + FINA_POS_Y_COIL_Y_TEMPERATURE, + FINA_POS_Y_COIL_Z_TEMPERATURE, + + FINA_NEG_Y_ERR, + FINA_NEG_Y_RAW_MAG_X, + FINA_NEG_Y_RAW_MAG_Y, + FINA_NEG_Y_RAW_MAG_Z, + FINA_NEG_Y_CAL_MAG_X, + FINA_NEG_Y_CAL_MAG_Y, + FINA_NEG_Y_CAL_MAG_Z, + FINA_NEG_Y_COIL_X_CURRENT, + FINA_NEG_Y_COIL_Y_CURRENT, + FINA_NEG_Y_COIL_Z_CURRENT, + FINA_NEG_Y_COIL_X_TEMPERATURE, + FINA_NEG_Y_COIL_Y_TEMPERATURE, + FINA_NEG_Y_COIL_Z_TEMPERATURE, + + FINA_POS_Z_ERR, + FINA_POS_Z_RAW_MAG_X, + FINA_POS_Z_RAW_MAG_Y, + FINA_POS_Z_RAW_MAG_Z, + FINA_POS_Z_CAL_MAG_X, + FINA_POS_Z_CAL_MAG_Y, + FINA_POS_Z_CAL_MAG_Z, + FINA_POS_Z_COIL_X_CURRENT, + FINA_POS_Z_COIL_Y_CURRENT, + FINA_POS_Z_COIL_Z_CURRENT, + FINA_POS_Z_COIL_X_TEMPERATURE, + FINA_POS_Z_COIL_Y_TEMPERATURE, + FINA_POS_Z_COIL_Z_TEMPERATURE, + + FINA_NEG_Z_ERR, + FINA_NEG_Z_RAW_MAG_X, + FINA_NEG_Z_RAW_MAG_Y, + FINA_NEG_Z_RAW_MAG_Z, + FINA_NEG_Z_CAL_MAG_X, + FINA_NEG_Z_CAL_MAG_Y, + FINA_NEG_Z_CAL_MAG_Z, + FINA_NEG_Z_COIL_X_CURRENT, + FINA_NEG_Z_COIL_Y_CURRENT, + FINA_NEG_Z_COIL_Z_CURRENT, + FINA_NEG_Z_COIL_X_TEMPERATURE, + FINA_NEG_Z_COIL_Y_TEMPERATURE, + FINA_NEG_Z_COIL_Z_TEMPERATURE, +}; + +class StatusDataset : public StaticLocalDataSet<4> { + public: + StatusDataset(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, imtq::SetIds::STATUS_SET) {} + // Status byte variables + lp_var_t statusByteMode = lp_var_t(sid.objectId, STATUS_BYTE_MODE, this); + lp_var_t statusByteError = lp_var_t(sid.objectId, STATUS_BYTE_ERROR, this); + lp_var_t statusByteConfig = lp_var_t(sid.objectId, STATUS_BYTE_CONF, this); + lp_var_t statusByteUptime = lp_var_t(sid.objectId, STATUS_BYTE_UPTIME, this); +}; + +class HkDataset : public StaticLocalDataSet { + public: + HkDataset(HasLocalDataPoolIF* owner, uint32_t setId, std::array pids) + : StaticLocalDataSet(owner, setId), + digitalVoltageMv(sid.objectId, pids[0], this), + analogVoltageMv(sid.objectId, pids[1], this), + digitalCurrentmA(sid.objectId, pids[2], this), + analogCurrentmA(sid.objectId, pids[3], this), + coilCurrentsMilliamps(sid.objectId, pids[4], this), + /** All temperatures in [C] for X, Y, Z */ + coilTemperatures(sid.objectId, pids[5], this), + mcuTemperature(sid.objectId, pids[6], this) {} + + HkDataset(object_id_t objectId, uint32_t setId, std::array pids) + : StaticLocalDataSet(sid_t(objectId, setId)), + digitalVoltageMv(sid.objectId, pids[0], this), + analogVoltageMv(sid.objectId, pids[1], this), + digitalCurrentmA(sid.objectId, pids[2], this), + analogCurrentmA(sid.objectId, pids[3], this), + coilCurrentsMilliamps(sid.objectId, pids[4], this), + /** All temperatures in [C] for X, Y, Z */ + coilTemperatures(sid.objectId, pids[5], this), + mcuTemperature(sid.objectId, pids[6], this) {} + + // Engineering HK variables + lp_var_t digitalVoltageMv; + lp_var_t analogVoltageMv; + lp_var_t digitalCurrentmA; + lp_var_t analogCurrentmA; + lp_vec_t coilCurrentsMilliamps; + /** All temperatures in [C] for X, Y, Z */ + lp_vec_t coilTemperatures; + lp_var_t mcuTemperature; + + private: +}; + +class HkDatasetNoTorque : public HkDataset { + public: + HkDatasetNoTorque(HasLocalDataPoolIF* owner) + : HkDataset(owner, imtq::SetIds::ENG_HK_NO_TORQUE, + {DIGITAL_VOLTAGE_MV, ANALOG_VOLTAGE_MV, DIGITAL_CURRENT, ANALOG_CURRENT, + COIL_CURRENTS, COIL_TEMPERATURES, MCU_TEMPERATURE}) {} +}; + +class HkDatasetWithTorque : public HkDataset { + public: + HkDatasetWithTorque(HasLocalDataPoolIF* owner) + : HkDataset(owner, imtq::SetIds::ENG_HK_SET_WITH_TORQUE, + {DIGITAL_VOLTAGE_MV_WT, ANALOG_VOLTAGE_MV_WT, DIGITAL_CURRENT_WT, + ANALOG_CURRENT_WT, COIL_CURRENTS_WT, COIL_TEMPERATURES_WT, MCU_TEMPERATURE_WT}) { + } +}; +/** + * + * @brief This dataset holds the last calibrated MTM measurement. + */ +class CalibratedMtmMeasurementSet : public StaticLocalDataSet { + public: + CalibratedMtmMeasurementSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, imtq::SetIds::CAL_MTM_SET) {} + + CalibratedMtmMeasurementSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, imtq::SetIds::CAL_MTM_SET)) {} + + /** The unit of all measurements is nT */ + lp_vec_t mgmXyz = lp_vec_t(sid.objectId, MGM_CAL_NT, this); + /** 1 if coils were actuating during measurement otherwise 0 */ + lp_var_t coilActuationStatus = + lp_var_t(sid.objectId, ACTUATION_CAL_STATUS, this); +}; + +/** + * @brief This dataset holds the raw MTM measurements. + */ +class RawMtmMeasurementSet : public StaticLocalDataSet { + public: + RawMtmMeasurementSet(object_id_t objectId, uint32_t setId, std::array pids) + : StaticLocalDataSet(sid_t(objectId, setId)), + mtmRawNt(sid.objectId, pids.at(0), this), + coilActuationStatus(sid.objectId, pids.at(1), this) {} + + RawMtmMeasurementSet(HasLocalDataPoolIF* owner, uint32_t setId, std::array pids) + : StaticLocalDataSet(owner, setId), + mtmRawNt(sid.objectId, pids.at(0), this), + coilActuationStatus(sid.objectId, pids.at(1), this) {} + + /** The unit of all measurements is nT */ + lp_vec_t mtmRawNt; + /** 1 if coils were actuating during measurement otherwise 0 */ + lp_var_t coilActuationStatus; +}; + +class RawMtmMeasurementNoTorque : public RawMtmMeasurementSet { + public: + RawMtmMeasurementNoTorque(HasLocalDataPoolIF* owner) + : RawMtmMeasurementSet(owner, imtq::SetIds::RAW_MTM_NO_TORQUE, + {PoolIds::MTM_RAW, PoolIds::ACTUATION_RAW_STATUS}) {} + RawMtmMeasurementNoTorque(object_id_t objectId) + : RawMtmMeasurementSet(objectId, imtq::SetIds::RAW_MTM_NO_TORQUE, + {PoolIds::MTM_RAW, PoolIds::ACTUATION_RAW_STATUS}) {} +}; +class RawMtmMeasurementWithTorque : public RawMtmMeasurementSet { + public: + RawMtmMeasurementWithTorque(HasLocalDataPoolIF* owner) + : RawMtmMeasurementSet(owner, imtq::SetIds::RAW_MTM_WITH_TORQUE, + {PoolIds::MTM_RAW_WT, PoolIds::ACTUATION_RAW_STATUS_WT}) {} + RawMtmMeasurementWithTorque(object_id_t objectId) + : RawMtmMeasurementSet(objectId, imtq::SetIds::RAW_MTM_WITH_TORQUE, + {PoolIds::MTM_RAW_WT, PoolIds::ACTUATION_RAW_STATUS_WT}) {} +}; + +/** + * @brief This class can be used to ease the generation of an action message commanding the + * IMTQHandler to configure the magnettorquer with the desired dipoles. + * + * @details Deserialize the packet, write the deserialized data to the ipc store and store the + * the ipc store address in the action message. + */ +class CommandDipolePacket : public SerialLinkedListAdapter { + public: + CommandDipolePacket() { setLinks(); } + + SerializeElement xDipole; + SerializeElement yDipole; + SerializeElement zDipole; + SerializeElement duration; + + private: + /** + * @brief Constructor + * + * @param xDipole The dipole of the x coil in 10 ^ -4 * Am^2 + * @param yDipole The dipole of the y coil in 10 ^ -4 * Am^2 + * @param zDipole The dipole of the z coil in 10 ^ -4 * Am^2 + * @param duration The duration in milliseconds the dipole will be generated by the coils. + * When set to 0, the dipole will be generated until a new dipole actuation + * command is sent. + */ + CommandDipolePacket(uint16_t xDipole, uint16_t yDipole, uint16_t zDipole, uint16_t duration) + : xDipole(xDipole), yDipole(yDipole), zDipole(zDipole), duration(duration) {} + void setLinks() { + setStart(&xDipole); + xDipole.setNext(&yDipole); + yDipole.setNext(&zDipole); + zDipole.setNext(&duration); + } +}; + +class DipoleActuationSet : public StaticLocalDataSet<4> { + friend class ::ImtqHandler; + + public: + DipoleActuationSet(HasLocalDataPoolIF& owner) + : StaticLocalDataSet(&owner, imtq::SetIds::DIPOLES) {} + DipoleActuationSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, imtq::SetIds::DIPOLES)) {} + + // Refresh torque command without changing any of the set dipoles. + void refreshTorqueing(uint16_t durationMs_) { currentTorqueDurationMs = durationMs_; } + + void setDipoles(int16_t xDipole_, int16_t yDipole_, int16_t zDipole_, + uint16_t currentTorqueDurationMs_) { + dipoles[0] = xDipole_; + dipoles[1] = yDipole_; + dipoles[2] = zDipole_; + currentTorqueDurationMs = currentTorqueDurationMs_; + } + + const int16_t* getDipoles() const { return dipoles.value; } + + private: + lp_vec_t dipoles = lp_vec_t(sid.objectId, DIPOLES_ID, this); + lp_var_t currentTorqueDurationMs = + lp_var_t(sid.objectId, CURRENT_TORQUE_DURATION, this); +}; + +/** + * @brief This dataset can be used to store the self test results of the +X self test. + * + * @details Units of measurements: + * Raw magnetic field: [nT] + * Calibrated magnetic field: [nT] + * Coil currents: [mA] + * Temperature: [C] + * The +X self test generates a positive dipole in X direction and measures the magnetic + * field with the built-in MTM. The procedure of the test is as follows: + * 1. All coils off (INIT step) + * 2. +X actuation + * 3. All coils off (FINA step) + */ +class PosXSelfTestSet : public StaticLocalDataSet { + public: + PosXSelfTestSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, imtq::SetIds::POSITIVE_X_TEST) {} + + PosXSelfTestSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, imtq::SetIds::POSITIVE_X_TEST)) {} + + /** INIT block */ + lp_var_t initErr = lp_var_t(sid.objectId, INIT_POS_X_ERR, this); + lp_var_t initRawMagX = lp_var_t(sid.objectId, INIT_POS_X_RAW_MAG_X, this); + lp_var_t initRawMagY = lp_var_t(sid.objectId, INIT_POS_X_RAW_MAG_Y, this); + lp_var_t initRawMagZ = lp_var_t(sid.objectId, INIT_POS_X_RAW_MAG_Z, this); + lp_var_t initCalMagX = lp_var_t(sid.objectId, INIT_POS_X_CAL_MAG_X, this); + lp_var_t initCalMagY = lp_var_t(sid.objectId, INIT_POS_X_CAL_MAG_Y, this); + lp_var_t initCalMagZ = lp_var_t(sid.objectId, INIT_POS_X_CAL_MAG_Z, this); + lp_var_t initCoilXCurrent = lp_var_t(sid.objectId, INIT_POS_X_COIL_X_CURRENT, this); + lp_var_t initCoilYCurrent = lp_var_t(sid.objectId, INIT_POS_X_COIL_Y_CURRENT, this); + lp_var_t initCoilZCurrent = lp_var_t(sid.objectId, INIT_POS_X_COIL_Z_CURRENT, this); + lp_var_t initCoilXTemperature = + lp_var_t(sid.objectId, INIT_POS_X_COIL_X_TEMPERATURE, this); + lp_var_t initCoilYTemperature = + lp_var_t(sid.objectId, INIT_POS_X_COIL_Y_TEMPERATURE, this); + lp_var_t initCoilZTemperature = + lp_var_t(sid.objectId, INIT_POS_X_COIL_Z_TEMPERATURE, this); + + /** +X block */ + lp_var_t err = lp_var_t(sid.objectId, POS_X_ERR, this); + lp_var_t rawMagX = lp_var_t(sid.objectId, POS_X_RAW_MAG_X, this); + lp_var_t rawMagY = lp_var_t(sid.objectId, POS_X_RAW_MAG_Y, this); + lp_var_t rawMagZ = lp_var_t(sid.objectId, POS_X_RAW_MAG_Z, this); + lp_var_t calMagX = lp_var_t(sid.objectId, POS_X_CAL_MAG_X, this); + lp_var_t calMagY = lp_var_t(sid.objectId, POS_X_CAL_MAG_Y, this); + lp_var_t calMagZ = lp_var_t(sid.objectId, POS_X_CAL_MAG_Z, this); + lp_var_t coilXCurrent = lp_var_t(sid.objectId, POS_X_COIL_X_CURRENT, this); + lp_var_t coilYCurrent = lp_var_t(sid.objectId, POS_X_COIL_Y_CURRENT, this); + lp_var_t coilZCurrent = lp_var_t(sid.objectId, POS_X_COIL_Z_CURRENT, this); + lp_var_t coilXTemperature = + lp_var_t(sid.objectId, POS_X_COIL_X_TEMPERATURE, this); + lp_var_t coilYTemperature = + lp_var_t(sid.objectId, POS_X_COIL_Y_TEMPERATURE, this); + lp_var_t coilZTemperature = + lp_var_t(sid.objectId, POS_X_COIL_Z_TEMPERATURE, this); + + /** FINA block */ + lp_var_t finaErr = lp_var_t(sid.objectId, FINA_POS_X_ERR, this); + lp_var_t finaRawMagX = lp_var_t(sid.objectId, FINA_POS_X_RAW_MAG_X, this); + lp_var_t finaRawMagY = lp_var_t(sid.objectId, FINA_POS_X_RAW_MAG_Y, this); + lp_var_t finaRawMagZ = lp_var_t(sid.objectId, FINA_POS_X_RAW_MAG_Z, this); + lp_var_t finaCalMagX = lp_var_t(sid.objectId, FINA_POS_X_CAL_MAG_X, this); + lp_var_t finaCalMagY = lp_var_t(sid.objectId, FINA_POS_X_CAL_MAG_Y, this); + lp_var_t finaCalMagZ = lp_var_t(sid.objectId, FINA_POS_X_CAL_MAG_Z, this); + lp_var_t finaCoilXCurrent = lp_var_t(sid.objectId, FINA_POS_X_COIL_X_CURRENT, this); + lp_var_t finaCoilYCurrent = lp_var_t(sid.objectId, FINA_POS_X_COIL_Y_CURRENT, this); + lp_var_t finaCoilZCurrent = lp_var_t(sid.objectId, FINA_POS_X_COIL_Z_CURRENT, this); + lp_var_t finaCoilXTemperature = + lp_var_t(sid.objectId, FINA_POS_X_COIL_X_TEMPERATURE, this); + lp_var_t finaCoilYTemperature = + lp_var_t(sid.objectId, FINA_POS_X_COIL_Y_TEMPERATURE, this); + lp_var_t finaCoilZTemperature = + lp_var_t(sid.objectId, FINA_POS_X_COIL_Z_TEMPERATURE, this); +}; + +/** + * @brief This dataset can be used to store the self test results of the -X self test. + * + * @details Units of measurements: + * Raw magnetic field: [nT] + * Calibrated magnetic field: [nT] + * Coil currents: [mA] + * Temperature: [C] + * The -X self test generates a negative dipole in X direction and measures the magnetic + * field with the built-in MTM. The procedure of the test is as follows: + * 1. All coils off (INIT step) + * 2. -X actuation + * 3. All coils off (FINA step) + */ +class NegXSelfTestSet : public StaticLocalDataSet { + public: + NegXSelfTestSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, imtq::SetIds::NEGATIVE_X_TEST) {} + + NegXSelfTestSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, imtq::SetIds::NEGATIVE_X_TEST)) {} + + /** INIT block */ + lp_var_t initErr = lp_var_t(sid.objectId, INIT_NEG_X_ERR, this); + lp_var_t initRawMagX = lp_var_t(sid.objectId, INIT_NEG_X_RAW_MAG_X, this); + lp_var_t initRawMagY = lp_var_t(sid.objectId, INIT_NEG_X_RAW_MAG_Y, this); + lp_var_t initRawMagZ = lp_var_t(sid.objectId, INIT_NEG_X_RAW_MAG_Z, this); + lp_var_t initCalMagX = lp_var_t(sid.objectId, INIT_NEG_X_CAL_MAG_X, this); + lp_var_t initCalMagY = lp_var_t(sid.objectId, INIT_NEG_X_CAL_MAG_Y, this); + lp_var_t initCalMagZ = lp_var_t(sid.objectId, INIT_NEG_X_CAL_MAG_Z, this); + lp_var_t initCoilXCurrent = lp_var_t(sid.objectId, INIT_NEG_X_COIL_X_CURRENT, this); + lp_var_t initCoilYCurrent = lp_var_t(sid.objectId, INIT_NEG_X_COIL_Y_CURRENT, this); + lp_var_t initCoilZCurrent = lp_var_t(sid.objectId, INIT_NEG_X_COIL_Z_CURRENT, this); + lp_var_t initCoilXTemperature = + lp_var_t(sid.objectId, INIT_NEG_X_COIL_X_TEMPERATURE, this); + lp_var_t initCoilYTemperature = + lp_var_t(sid.objectId, INIT_NEG_X_COIL_Y_TEMPERATURE, this); + lp_var_t initCoilZTemperature = + lp_var_t(sid.objectId, INIT_NEG_X_COIL_Z_TEMPERATURE, this); + + /** -X block */ + lp_var_t err = lp_var_t(sid.objectId, NEG_X_ERR, this); + lp_var_t rawMagX = lp_var_t(sid.objectId, NEG_X_RAW_MAG_X, this); + lp_var_t rawMagY = lp_var_t(sid.objectId, NEG_X_RAW_MAG_Y, this); + lp_var_t rawMagZ = lp_var_t(sid.objectId, NEG_X_RAW_MAG_Z, this); + lp_var_t calMagX = lp_var_t(sid.objectId, NEG_X_CAL_MAG_X, this); + lp_var_t calMagY = lp_var_t(sid.objectId, NEG_X_CAL_MAG_Y, this); + lp_var_t calMagZ = lp_var_t(sid.objectId, NEG_X_CAL_MAG_Z, this); + lp_var_t coilXCurrent = lp_var_t(sid.objectId, NEG_X_COIL_X_CURRENT, this); + lp_var_t coilYCurrent = lp_var_t(sid.objectId, NEG_X_COIL_Y_CURRENT, this); + lp_var_t coilZCurrent = lp_var_t(sid.objectId, NEG_X_COIL_Z_CURRENT, this); + lp_var_t coilXTemperature = + lp_var_t(sid.objectId, NEG_X_COIL_X_TEMPERATURE, this); + lp_var_t coilYTemperature = + lp_var_t(sid.objectId, NEG_X_COIL_Y_TEMPERATURE, this); + lp_var_t coilZTemperature = + lp_var_t(sid.objectId, NEG_X_COIL_Z_TEMPERATURE, this); + + /** FINA block */ + lp_var_t finaErr = lp_var_t(sid.objectId, FINA_NEG_X_ERR, this); + lp_var_t finaRawMagX = lp_var_t(sid.objectId, FINA_NEG_X_RAW_MAG_X, this); + lp_var_t finaRawMagY = lp_var_t(sid.objectId, FINA_NEG_X_RAW_MAG_Y, this); + lp_var_t finaRawMagZ = lp_var_t(sid.objectId, FINA_NEG_X_RAW_MAG_Z, this); + lp_var_t finaCalMagX = lp_var_t(sid.objectId, FINA_NEG_X_CAL_MAG_X, this); + lp_var_t finaCalMagY = lp_var_t(sid.objectId, FINA_NEG_X_CAL_MAG_Y, this); + lp_var_t finaCalMagZ = lp_var_t(sid.objectId, FINA_NEG_X_CAL_MAG_Z, this); + lp_var_t finaCoilXCurrent = lp_var_t(sid.objectId, FINA_NEG_X_COIL_X_CURRENT, this); + lp_var_t finaCoilYCurrent = lp_var_t(sid.objectId, FINA_NEG_X_COIL_Y_CURRENT, this); + lp_var_t finaCoilZCurrent = lp_var_t(sid.objectId, FINA_NEG_X_COIL_Z_CURRENT, this); + lp_var_t finaCoilXTemperature = + lp_var_t(sid.objectId, FINA_NEG_X_COIL_X_TEMPERATURE, this); + lp_var_t finaCoilYTemperature = + lp_var_t(sid.objectId, FINA_NEG_X_COIL_Y_TEMPERATURE, this); + lp_var_t finaCoilZTemperature = + lp_var_t(sid.objectId, FINA_NEG_X_COIL_Z_TEMPERATURE, this); +}; + +/** + * @brief This dataset can be used to store the self test results of the +Y self test. + * + * @details Units of measurements: + * Raw magnetic field: [nT] + * Calibrated magnetic field: [nT] + * Coil currents: [mA] + * Temperature: [C] + * The +Y self test generates a positive dipole in y direction and measures the magnetic + * field with the built-in MTM. The procedure of the test is as follows: + * 1. All coils off (INIT step) + * 2. +Y actuation + * 3. All coils off (FINA step) + */ +class PosYSelfTestSet : public StaticLocalDataSet { + public: + PosYSelfTestSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, imtq::SetIds::POSITIVE_Y_TEST) {} + + PosYSelfTestSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, imtq::SetIds::POSITIVE_Y_TEST)) {} + + /** INIT block */ + lp_var_t initErr = lp_var_t(sid.objectId, INIT_POS_Y_ERR, this); + lp_var_t initRawMagX = lp_var_t(sid.objectId, INIT_POS_Y_RAW_MAG_X, this); + lp_var_t initRawMagY = lp_var_t(sid.objectId, INIT_POS_Y_RAW_MAG_Y, this); + lp_var_t initRawMagZ = lp_var_t(sid.objectId, INIT_POS_Y_RAW_MAG_Z, this); + lp_var_t initCalMagX = lp_var_t(sid.objectId, INIT_POS_Y_CAL_MAG_X, this); + lp_var_t initCalMagY = lp_var_t(sid.objectId, INIT_POS_Y_CAL_MAG_Y, this); + lp_var_t initCalMagZ = lp_var_t(sid.objectId, INIT_POS_Y_CAL_MAG_Z, this); + lp_var_t initCoilXCurrent = lp_var_t(sid.objectId, INIT_POS_Y_COIL_X_CURRENT, this); + lp_var_t initCoilYCurrent = lp_var_t(sid.objectId, INIT_POS_Y_COIL_Y_CURRENT, this); + lp_var_t initCoilZCurrent = lp_var_t(sid.objectId, INIT_POS_Y_COIL_Z_CURRENT, this); + lp_var_t initCoilXTemperature = + lp_var_t(sid.objectId, INIT_POS_Y_COIL_X_TEMPERATURE, this); + lp_var_t initCoilYTemperature = + lp_var_t(sid.objectId, INIT_POS_Y_COIL_Y_TEMPERATURE, this); + lp_var_t initCoilZTemperature = + lp_var_t(sid.objectId, INIT_POS_Y_COIL_Z_TEMPERATURE, this); + + /** +Y block */ + lp_var_t err = lp_var_t(sid.objectId, POS_Y_ERR, this); + lp_var_t rawMagX = lp_var_t(sid.objectId, POS_Y_RAW_MAG_X, this); + lp_var_t rawMagY = lp_var_t(sid.objectId, POS_Y_RAW_MAG_Y, this); + lp_var_t rawMagZ = lp_var_t(sid.objectId, POS_Y_RAW_MAG_Z, this); + lp_var_t calMagX = lp_var_t(sid.objectId, POS_Y_CAL_MAG_X, this); + lp_var_t calMagY = lp_var_t(sid.objectId, POS_Y_CAL_MAG_Y, this); + lp_var_t calMagZ = lp_var_t(sid.objectId, POS_Y_CAL_MAG_Z, this); + lp_var_t coilXCurrent = lp_var_t(sid.objectId, POS_Y_COIL_X_CURRENT, this); + lp_var_t coilYCurrent = lp_var_t(sid.objectId, POS_Y_COIL_Y_CURRENT, this); + lp_var_t coilZCurrent = lp_var_t(sid.objectId, POS_Y_COIL_Z_CURRENT, this); + lp_var_t coilXTemperature = + lp_var_t(sid.objectId, POS_Y_COIL_X_TEMPERATURE, this); + lp_var_t coilYTemperature = + lp_var_t(sid.objectId, POS_Y_COIL_Y_TEMPERATURE, this); + lp_var_t coilZTemperature = + lp_var_t(sid.objectId, POS_Y_COIL_Z_TEMPERATURE, this); + + /** FINA block */ + lp_var_t finaErr = lp_var_t(sid.objectId, FINA_POS_Y_ERR, this); + lp_var_t finaRawMagX = lp_var_t(sid.objectId, FINA_POS_Y_RAW_MAG_X, this); + lp_var_t finaRawMagY = lp_var_t(sid.objectId, FINA_POS_Y_RAW_MAG_Y, this); + lp_var_t finaRawMagZ = lp_var_t(sid.objectId, FINA_POS_Y_RAW_MAG_Z, this); + lp_var_t finaCalMagX = lp_var_t(sid.objectId, FINA_POS_Y_CAL_MAG_X, this); + lp_var_t finaCalMagY = lp_var_t(sid.objectId, FINA_POS_Y_CAL_MAG_Y, this); + lp_var_t finaCalMagZ = lp_var_t(sid.objectId, FINA_POS_Y_CAL_MAG_Z, this); + lp_var_t finaCoilXCurrent = lp_var_t(sid.objectId, FINA_POS_Y_COIL_X_CURRENT, this); + lp_var_t finaCoilYCurrent = lp_var_t(sid.objectId, FINA_POS_Y_COIL_Y_CURRENT, this); + lp_var_t finaCoilZCurrent = lp_var_t(sid.objectId, FINA_POS_Y_COIL_Z_CURRENT, this); + lp_var_t finaCoilXTemperature = + lp_var_t(sid.objectId, FINA_POS_Y_COIL_X_TEMPERATURE, this); + lp_var_t finaCoilYTemperature = + lp_var_t(sid.objectId, FINA_POS_Y_COIL_Y_TEMPERATURE, this); + lp_var_t finaCoilZTemperature = + lp_var_t(sid.objectId, FINA_POS_Y_COIL_Z_TEMPERATURE, this); +}; + +/** + * @brief This dataset can be used to store the self test results of the -Y self test. + * + * @details Units of measurements: + * Raw magnetic field: [nT] + * Calibrated magnetic field: [nT] + * Coil currents: [mA] + * Temperature: [C] + * The -Y self test generates a negative dipole in y direction and measures the magnetic + * field with the built-in MTM. The procedure of the test is as follows: + * 1. All coils off (INIT step) + * 2. -Y actuation + * 3. All coils off (FINA step) + */ +class NegYSelfTestSet : public StaticLocalDataSet { + public: + NegYSelfTestSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, imtq::SetIds::NEGATIVE_Y_TEST) {} + + NegYSelfTestSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, imtq::SetIds::NEGATIVE_Y_TEST)) {} + + /** INIT block */ + lp_var_t initErr = lp_var_t(sid.objectId, INIT_NEG_Y_ERR, this); + lp_var_t initRawMagX = lp_var_t(sid.objectId, INIT_NEG_Y_RAW_MAG_X, this); + lp_var_t initRawMagY = lp_var_t(sid.objectId, INIT_NEG_Y_RAW_MAG_Y, this); + lp_var_t initRawMagZ = lp_var_t(sid.objectId, INIT_NEG_Y_RAW_MAG_Z, this); + lp_var_t initCalMagX = lp_var_t(sid.objectId, INIT_NEG_Y_CAL_MAG_X, this); + lp_var_t initCalMagY = lp_var_t(sid.objectId, INIT_NEG_Y_CAL_MAG_Y, this); + lp_var_t initCalMagZ = lp_var_t(sid.objectId, INIT_NEG_Y_CAL_MAG_Z, this); + lp_var_t initCoilXCurrent = lp_var_t(sid.objectId, INIT_NEG_Y_COIL_X_CURRENT, this); + lp_var_t initCoilYCurrent = lp_var_t(sid.objectId, INIT_NEG_Y_COIL_Y_CURRENT, this); + lp_var_t initCoilZCurrent = lp_var_t(sid.objectId, INIT_NEG_Y_COIL_Z_CURRENT, this); + lp_var_t initCoilXTemperature = + lp_var_t(sid.objectId, INIT_NEG_Y_COIL_X_TEMPERATURE, this); + lp_var_t initCoilYTemperature = + lp_var_t(sid.objectId, INIT_NEG_Y_COIL_Y_TEMPERATURE, this); + lp_var_t initCoilZTemperature = + lp_var_t(sid.objectId, INIT_NEG_Y_COIL_Z_TEMPERATURE, this); + + /** -Y block */ + lp_var_t err = lp_var_t(sid.objectId, NEG_Y_ERR, this); + lp_var_t rawMagX = lp_var_t(sid.objectId, NEG_Y_RAW_MAG_X, this); + lp_var_t rawMagY = lp_var_t(sid.objectId, NEG_Y_RAW_MAG_Y, this); + lp_var_t rawMagZ = lp_var_t(sid.objectId, NEG_Y_RAW_MAG_Z, this); + lp_var_t calMagX = lp_var_t(sid.objectId, NEG_Y_CAL_MAG_X, this); + lp_var_t calMagY = lp_var_t(sid.objectId, NEG_Y_CAL_MAG_Y, this); + lp_var_t calMagZ = lp_var_t(sid.objectId, NEG_Y_CAL_MAG_Z, this); + lp_var_t coilXCurrent = lp_var_t(sid.objectId, NEG_Y_COIL_X_CURRENT, this); + lp_var_t coilYCurrent = lp_var_t(sid.objectId, NEG_Y_COIL_Y_CURRENT, this); + lp_var_t coilZCurrent = lp_var_t(sid.objectId, NEG_Y_COIL_Z_CURRENT, this); + lp_var_t coilXTemperature = + lp_var_t(sid.objectId, NEG_Y_COIL_X_TEMPERATURE, this); + lp_var_t coilYTemperature = + lp_var_t(sid.objectId, NEG_Y_COIL_Y_TEMPERATURE, this); + lp_var_t coilZTemperature = + lp_var_t(sid.objectId, NEG_Y_COIL_Z_TEMPERATURE, this); + + /** FINA block */ + lp_var_t finaErr = lp_var_t(sid.objectId, FINA_NEG_Y_ERR, this); + lp_var_t finaRawMagX = lp_var_t(sid.objectId, FINA_NEG_Y_RAW_MAG_X, this); + lp_var_t finaRawMagY = lp_var_t(sid.objectId, FINA_NEG_Y_RAW_MAG_Y, this); + lp_var_t finaRawMagZ = lp_var_t(sid.objectId, FINA_NEG_Y_RAW_MAG_Z, this); + lp_var_t finaCalMagX = lp_var_t(sid.objectId, FINA_NEG_Y_CAL_MAG_X, this); + lp_var_t finaCalMagY = lp_var_t(sid.objectId, FINA_NEG_Y_CAL_MAG_Y, this); + lp_var_t finaCalMagZ = lp_var_t(sid.objectId, FINA_NEG_Y_CAL_MAG_Z, this); + lp_var_t finaCoilXCurrent = lp_var_t(sid.objectId, FINA_NEG_Y_COIL_X_CURRENT, this); + lp_var_t finaCoilYCurrent = lp_var_t(sid.objectId, FINA_NEG_Y_COIL_Y_CURRENT, this); + lp_var_t finaCoilZCurrent = lp_var_t(sid.objectId, FINA_NEG_Y_COIL_Z_CURRENT, this); + lp_var_t finaCoilXTemperature = + lp_var_t(sid.objectId, FINA_NEG_Y_COIL_X_TEMPERATURE, this); + lp_var_t finaCoilYTemperature = + lp_var_t(sid.objectId, FINA_NEG_Y_COIL_Y_TEMPERATURE, this); + lp_var_t finaCoilZTemperature = + lp_var_t(sid.objectId, FINA_NEG_Y_COIL_Z_TEMPERATURE, this); +}; + +/** + * @brief This dataset can be used to store the self test results of the +Z self test. + * + * @details Units of measurements: + * Raw magnetic field: [nT] + * Calibrated magnetic field: [nT] + * Coil currents: [mA] + * Temperature: [C] + * The +Z self test generates a positive dipole in z direction and measures the magnetic + * field with the built-in MTM. The procedure of the test is as follows: + * 1. All coils off (INIT step) + * 2. +Z actuation + * 3. All coils off (FINA step) + */ +class PosZSelfTestSet : public StaticLocalDataSet { + public: + PosZSelfTestSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, imtq::SetIds::POSITIVE_Z_TEST) {} + + PosZSelfTestSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, imtq::SetIds::POSITIVE_Y_TEST)) {} + + /** INIT block */ + lp_var_t initErr = lp_var_t(sid.objectId, INIT_POS_Z_ERR, this); + lp_var_t initRawMagX = lp_var_t(sid.objectId, INIT_POS_Z_RAW_MAG_X, this); + lp_var_t initRawMagY = lp_var_t(sid.objectId, INIT_POS_Z_RAW_MAG_Y, this); + lp_var_t initRawMagZ = lp_var_t(sid.objectId, INIT_POS_Z_RAW_MAG_Z, this); + lp_var_t initCalMagX = lp_var_t(sid.objectId, INIT_POS_Z_CAL_MAG_X, this); + lp_var_t initCalMagY = lp_var_t(sid.objectId, INIT_POS_Z_CAL_MAG_Y, this); + lp_var_t initCalMagZ = lp_var_t(sid.objectId, INIT_POS_Z_CAL_MAG_Z, this); + lp_var_t initCoilXCurrent = lp_var_t(sid.objectId, INIT_POS_Z_COIL_X_CURRENT, this); + lp_var_t initCoilYCurrent = lp_var_t(sid.objectId, INIT_POS_Z_COIL_Y_CURRENT, this); + lp_var_t initCoilZCurrent = lp_var_t(sid.objectId, INIT_POS_Z_COIL_Z_CURRENT, this); + lp_var_t initCoilXTemperature = + lp_var_t(sid.objectId, INIT_POS_Z_COIL_X_TEMPERATURE, this); + lp_var_t initCoilYTemperature = + lp_var_t(sid.objectId, INIT_POS_Z_COIL_Y_TEMPERATURE, this); + lp_var_t initCoilZTemperature = + lp_var_t(sid.objectId, INIT_POS_Z_COIL_Z_TEMPERATURE, this); + + /** +Z block */ + lp_var_t err = lp_var_t(sid.objectId, POS_Z_ERR, this); + lp_var_t rawMagX = lp_var_t(sid.objectId, POS_Z_RAW_MAG_X, this); + lp_var_t rawMagY = lp_var_t(sid.objectId, POS_Z_RAW_MAG_Y, this); + lp_var_t rawMagZ = lp_var_t(sid.objectId, POS_Z_RAW_MAG_Z, this); + lp_var_t calMagX = lp_var_t(sid.objectId, POS_Z_CAL_MAG_X, this); + lp_var_t calMagY = lp_var_t(sid.objectId, POS_Z_CAL_MAG_Y, this); + lp_var_t calMagZ = lp_var_t(sid.objectId, POS_Z_CAL_MAG_Z, this); + lp_var_t coilXCurrent = lp_var_t(sid.objectId, POS_Z_COIL_X_CURRENT, this); + lp_var_t coilYCurrent = lp_var_t(sid.objectId, POS_Z_COIL_Y_CURRENT, this); + lp_var_t coilZCurrent = lp_var_t(sid.objectId, POS_Z_COIL_Z_CURRENT, this); + lp_var_t coilXTemperature = + lp_var_t(sid.objectId, POS_Z_COIL_X_TEMPERATURE, this); + lp_var_t coilYTemperature = + lp_var_t(sid.objectId, POS_Z_COIL_Y_TEMPERATURE, this); + lp_var_t coilZTemperature = + lp_var_t(sid.objectId, POS_Z_COIL_Z_TEMPERATURE, this); + + /** FINA block */ + lp_var_t finaErr = lp_var_t(sid.objectId, FINA_POS_Z_ERR, this); + lp_var_t finaRawMagX = lp_var_t(sid.objectId, FINA_POS_Z_RAW_MAG_X, this); + lp_var_t finaRawMagY = lp_var_t(sid.objectId, FINA_POS_Z_RAW_MAG_Y, this); + lp_var_t finaRawMagZ = lp_var_t(sid.objectId, FINA_POS_Z_RAW_MAG_Z, this); + lp_var_t finaCalMagX = lp_var_t(sid.objectId, FINA_POS_Z_CAL_MAG_X, this); + lp_var_t finaCalMagY = lp_var_t(sid.objectId, FINA_POS_Z_CAL_MAG_Y, this); + lp_var_t finaCalMagZ = lp_var_t(sid.objectId, FINA_POS_Z_CAL_MAG_Z, this); + lp_var_t finaCoilXCurrent = lp_var_t(sid.objectId, FINA_POS_Z_COIL_X_CURRENT, this); + lp_var_t finaCoilYCurrent = lp_var_t(sid.objectId, FINA_POS_Z_COIL_Y_CURRENT, this); + lp_var_t finaCoilZCurrent = lp_var_t(sid.objectId, FINA_POS_Z_COIL_Z_CURRENT, this); + lp_var_t finaCoilXTemperature = + lp_var_t(sid.objectId, FINA_POS_Z_COIL_X_TEMPERATURE, this); + lp_var_t finaCoilYTemperature = + lp_var_t(sid.objectId, FINA_POS_Z_COIL_Y_TEMPERATURE, this); + lp_var_t finaCoilZTemperature = + lp_var_t(sid.objectId, FINA_POS_Z_COIL_Z_TEMPERATURE, this); +}; + +/** + * @brief This dataset can be used to store the self test results of the -Z self test. + * + * @details Units of measurements: + * Raw magnetic field: [nT] + * Calibrated magnetic field: [nT] + * Coil currents: [mA] + * Temperature: [C] + * The -Z self test generates a negative dipole in z direction and measures the magnetic + * field with the built-in MTM. The procedure of the test is as follows: + * 1. All coils off (INIT step) + * 2. -Z actuation + * 3. All coils off (FINA step) + */ +class NegZSelfTestSet : public StaticLocalDataSet { + public: + NegZSelfTestSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, imtq::SetIds::NEGATIVE_Z_TEST) {} + + NegZSelfTestSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, imtq::SetIds::NEGATIVE_Z_TEST)) {} + + /** INIT block */ + lp_var_t initErr = lp_var_t(sid.objectId, INIT_NEG_Z_ERR, this); + lp_var_t initRawMagX = lp_var_t(sid.objectId, INIT_NEG_Z_RAW_MAG_X, this); + lp_var_t initRawMagY = lp_var_t(sid.objectId, INIT_NEG_Z_RAW_MAG_Y, this); + lp_var_t initRawMagZ = lp_var_t(sid.objectId, INIT_NEG_Z_RAW_MAG_Z, this); + lp_var_t initCalMagX = lp_var_t(sid.objectId, INIT_NEG_Z_CAL_MAG_X, this); + lp_var_t initCalMagY = lp_var_t(sid.objectId, INIT_NEG_Z_CAL_MAG_Y, this); + lp_var_t initCalMagZ = lp_var_t(sid.objectId, INIT_NEG_Z_CAL_MAG_Z, this); + lp_var_t initCoilXCurrent = lp_var_t(sid.objectId, INIT_NEG_Z_COIL_X_CURRENT, this); + lp_var_t initCoilYCurrent = lp_var_t(sid.objectId, INIT_NEG_Z_COIL_Y_CURRENT, this); + lp_var_t initCoilZCurrent = lp_var_t(sid.objectId, INIT_NEG_Z_COIL_Z_CURRENT, this); + lp_var_t initCoilXTemperature = + lp_var_t(sid.objectId, INIT_NEG_Z_COIL_X_TEMPERATURE, this); + lp_var_t initCoilYTemperature = + lp_var_t(sid.objectId, INIT_NEG_Z_COIL_Y_TEMPERATURE, this); + lp_var_t initCoilZTemperature = + lp_var_t(sid.objectId, INIT_NEG_Z_COIL_Z_TEMPERATURE, this); + + /** +Z block */ + lp_var_t err = lp_var_t(sid.objectId, NEG_Z_ERR, this); + lp_var_t rawMagX = lp_var_t(sid.objectId, NEG_Z_RAW_MAG_X, this); + lp_var_t rawMagY = lp_var_t(sid.objectId, NEG_Z_RAW_MAG_Y, this); + lp_var_t rawMagZ = lp_var_t(sid.objectId, NEG_Z_RAW_MAG_Z, this); + lp_var_t calMagX = lp_var_t(sid.objectId, NEG_Z_CAL_MAG_X, this); + lp_var_t calMagY = lp_var_t(sid.objectId, NEG_Z_CAL_MAG_Y, this); + lp_var_t calMagZ = lp_var_t(sid.objectId, NEG_Z_CAL_MAG_Z, this); + lp_var_t coilXCurrent = lp_var_t(sid.objectId, NEG_Z_COIL_X_CURRENT, this); + lp_var_t coilYCurrent = lp_var_t(sid.objectId, NEG_Z_COIL_Y_CURRENT, this); + lp_var_t coilZCurrent = lp_var_t(sid.objectId, NEG_Z_COIL_Z_CURRENT, this); + lp_var_t coilXTemperature = + lp_var_t(sid.objectId, NEG_Z_COIL_X_TEMPERATURE, this); + lp_var_t coilYTemperature = + lp_var_t(sid.objectId, NEG_Z_COIL_Y_TEMPERATURE, this); + lp_var_t coilZTemperature = + lp_var_t(sid.objectId, NEG_Z_COIL_Z_TEMPERATURE, this); + + /** FINA block */ + lp_var_t finaErr = lp_var_t(sid.objectId, FINA_NEG_Z_ERR, this); + lp_var_t finaRawMagX = lp_var_t(sid.objectId, FINA_NEG_Z_RAW_MAG_X, this); + lp_var_t finaRawMagY = lp_var_t(sid.objectId, FINA_NEG_Z_RAW_MAG_Y, this); + lp_var_t finaRawMagZ = lp_var_t(sid.objectId, FINA_NEG_Z_RAW_MAG_Z, this); + lp_var_t finaCalMagX = lp_var_t(sid.objectId, FINA_NEG_Z_CAL_MAG_X, this); + lp_var_t finaCalMagY = lp_var_t(sid.objectId, FINA_NEG_Z_CAL_MAG_Y, this); + lp_var_t finaCalMagZ = lp_var_t(sid.objectId, FINA_NEG_Z_CAL_MAG_Z, this); + lp_var_t finaCoilXCurrent = lp_var_t(sid.objectId, FINA_NEG_Z_COIL_X_CURRENT, this); + lp_var_t finaCoilYCurrent = lp_var_t(sid.objectId, FINA_NEG_Z_COIL_Y_CURRENT, this); + lp_var_t finaCoilZCurrent = lp_var_t(sid.objectId, FINA_NEG_Z_COIL_Z_CURRENT, this); + lp_var_t finaCoilXTemperature = + lp_var_t(sid.objectId, FINA_NEG_Z_COIL_X_TEMPERATURE, this); + lp_var_t finaCoilYTemperature = + lp_var_t(sid.objectId, FINA_NEG_Z_COIL_Y_TEMPERATURE, this); + lp_var_t finaCoilZTemperature = + lp_var_t(sid.objectId, FINA_NEG_Z_COIL_Z_TEMPERATURE, this); +}; + +} // namespace imtq + +struct ImtqRepliesDefault { + friend class ImtqPollingTask; + + public: + static constexpr size_t BASE_LEN = + 1 + imtq::replySize::DEFAULT_MIN_LEN + 1 + imtq::replySize::SYSTEM_STATE + 1 + + +imtq::replySize::DEFAULT_MIN_LEN + 1 + imtq::replySize::RAW_MTM_MEASUREMENT + 1 + + imtq::replySize::ENG_HK_DATA_REPLY + 1 + imtq::replySize::CAL_MTM_MEASUREMENT + 1; + ImtqRepliesDefault(const uint8_t* rawData) : rawData(const_cast(rawData)) { + initPointers(); + } + + void setConfigured() { rawData[0] = true; } + bool devWasConfigured() const { return rawData[0]; } + + uint8_t* getCalibMgmMeasurement() const { return calibMgmMeasurement + 1; } + bool wasCalibMgmMeasurementRead() const { return calibMgmMeasurement[0]; }; + + uint8_t* getEngHk() const { return engHk + 1; } + bool wasEngHkRead() const { return engHk[0]; }; + + uint8_t* getRawMgmMeasurement() const { return rawMgmMeasurement + 1; } + bool wasGetRawMgmMeasurementRead() const { return rawMgmMeasurement[0]; }; + + uint8_t* getSpecialRequest() const { return specialRequestReply + 1; } + bool wasSpecialRequestRead() const { return specialRequestReply[0]; } + uint8_t* getStartMtmMeasurement() const { return startMtmMeasurement + 1; } + bool wasStartMtmMeasurementRead() const { return startMtmMeasurement[0]; } + + uint8_t* getSwReset() const { return swReset + 1; } + + uint8_t* getSystemState() const { return systemState + 1; } + bool wasGetSystemStateRead() const { return systemState[0]; } + + private: + void initPointers() { + swReset = rawData + 1; + systemState = swReset + imtq::replySize::DEFAULT_MIN_LEN + 1; + startMtmMeasurement = systemState + imtq::replySize::SYSTEM_STATE + 1; + rawMgmMeasurement = startMtmMeasurement + imtq::replySize::DEFAULT_MIN_LEN + 1; + engHk = rawMgmMeasurement + imtq::replySize::RAW_MTM_MEASUREMENT + 1; + calibMgmMeasurement = engHk + imtq::replySize::ENG_HK_DATA_REPLY + 1; + specialRequestReply = calibMgmMeasurement + imtq::replySize::CAL_MTM_MEASUREMENT + 1; + } + + uint8_t* rawData; + uint8_t* swReset; + uint8_t* systemState; + uint8_t* startMtmMeasurement; + uint8_t* rawMgmMeasurement; + uint8_t* engHk; + uint8_t* calibMgmMeasurement; + // Share this to reduce amount of copied code for each transfer. + uint8_t* specialRequestReply; +}; + +struct ImtqRepliesWithTorque { + friend class ImtqPollingTask; + + public: + static constexpr size_t BASE_LEN = + imtq::replySize::DEFAULT_MIN_LEN + 1 + imtq::replySize::ENG_HK_DATA_REPLY + 1 + + imtq::replySize::DEFAULT_MIN_LEN + 1 + imtq::replySize::RAW_MTM_MEASUREMENT + 1; + ImtqRepliesWithTorque(const uint8_t* rawData) : rawData(const_cast(rawData)) { + initPointers(); + } + + uint8_t* getDipoleActuation() const { return dipoleActuation + 1; } + bool wasDipoleActuationRead() const { return dipoleActuation[0]; } + + uint8_t* getEngHk() const { return engHk + 1; } + bool wasEngHkRead() const { return engHk[0]; }; + + uint8_t* getRawMgmMeasurement() const { return rawMgmMeasurement + 1; } + bool wasGetRawMgmMeasurementRead() const { return rawMgmMeasurement[0]; }; + + private: + void initPointers() { + dipoleActuation = rawData; + engHk = dipoleActuation + imtq::replySize::DEFAULT_MIN_LEN + 1; + startMtmMeasurement = engHk + imtq::replySize::ENG_HK_DATA_REPLY + 1; + rawMgmMeasurement = startMtmMeasurement + imtq::replySize::DEFAULT_MIN_LEN + 1; + } + uint8_t* rawData; + uint8_t* dipoleActuation; + uint8_t* engHk; + uint8_t* startMtmMeasurement; + uint8_t* rawMgmMeasurement; +}; +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_IMTQDEFINITIONS_H_ */ diff --git a/mission/acs/rwHelpers.cpp b/mission/acs/rwHelpers.cpp new file mode 100644 index 0000000..d1f6a1b --- /dev/null +++ b/mission/acs/rwHelpers.cpp @@ -0,0 +1,54 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_RWHELPERS_CPP_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_RWHELPERS_CPP_ + +#include "rwHelpers.h" + +void rws::encodeHdlc(const uint8_t* sourceBuf, size_t sourceLen, uint8_t* encodedBuffer, + size_t& encodedLen) { + encodedBuffer[0] = rws::FRAME_DELIMITER; + encodedLen = 1; + for (size_t sourceIdx = 0; sourceIdx < sourceLen; sourceIdx++) { + if (sourceBuf[sourceIdx] == 0x7E) { + encodedBuffer[encodedLen++] = 0x7D; + encodedBuffer[encodedLen++] = 0x5E; + } else if (sourceBuf[sourceIdx] == 0x7D) { + encodedBuffer[encodedLen++] = 0x7D; + encodedBuffer[encodedLen++] = 0x5D; + } else { + encodedBuffer[encodedLen++] = sourceBuf[sourceIdx]; + } + } + encodedBuffer[encodedLen++] = rws::FRAME_DELIMITER; +} + +size_t rws::idToPacketLen(DeviceCommandId_t id) { + switch (id) { + case (rws::GET_RW_STATUS): { + return rws::SIZE_GET_RW_STATUS; + } + case (rws::SET_SPEED): { + return rws::SIZE_SET_SPEED_REPLY; + } + case (rws::CLEAR_LAST_RESET_STATUS): { + return rws::SIZE_CLEAR_RESET_STATUS; + } + case (rws::GET_LAST_RESET_STATUS): { + return rws::SIZE_GET_RESET_STATUS; + } + case (rws::GET_TEMPERATURE): { + return rws::SIZE_GET_TEMPERATURE_REPLY; + } + case (rws::GET_TM): { + return rws::SIZE_GET_TELEMETRY_REPLY; + } + case (rws::INIT_RW_CONTROLLER): { + return rws::SIZE_INIT_RW; + } + default: { + sif::error << "no reply buffer for rw command " << id << std::endl; + return 0; + } + } +} + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_RWHELPERS_CPP_ */ diff --git a/mission/acs/rwHelpers.h b/mission/acs/rwHelpers.h new file mode 100644 index 0000000..7651211 --- /dev/null +++ b/mission/acs/rwHelpers.h @@ -0,0 +1,348 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_RWHELPERS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_RWHELPERS_H_ + +#include +#include +#include +#include + +#include "eive/resultClassIds.h" +#include "events/subsystemIdRanges.h" +#include "objects/systemObjectList.h" + +namespace rws { + +void encodeHdlc(const uint8_t* sourceBuf, size_t sourceLen, uint8_t* encodedBuffer, + size_t& encodedLen); +size_t idToPacketLen(DeviceCommandId_t id); + +static const size_t SIZE_GET_RESET_STATUS = 5; +static const size_t SIZE_CLEAR_RESET_STATUS = 4; +static const size_t SIZE_INIT_RW = 4; +static const size_t SIZE_GET_RW_STATUS = 14; +static const size_t SIZE_SET_SPEED_REPLY = 4; +static const size_t SIZE_GET_TEMPERATURE_REPLY = 8; +/** Max size when requesting telemetry */ +static const size_t SIZE_GET_TELEMETRY_REPLY = 91; + +//! This is the end and start marker of the frame datalinklayer. Also called frame delimiter +//! in the NanoAvionics datasheet. +static constexpr uint8_t FRAME_DELIMITER = 0x7E; + +enum class SpecialRwRequest : uint8_t { + REQUEST_NONE = 0, + RESET_MCU = 1, + INIT_RW_CONTROLLER = 2, + GET_TM = 3, + NUM_REQUESTS +}; + +struct RwRequest { + acs::SimpleSensorMode mode = acs::SimpleSensorMode::OFF; + bool setSpeed = true; + int32_t currentRwSpeed = 0; + uint16_t currentRampTime = 0; + rws::SpecialRwRequest specialRequest = rws::SpecialRwRequest::REQUEST_NONE; + uint8_t rwIdx = 0; +}; + +static const uint8_t INTERFACE_ID = CLASS_ID::RW_HANDLER; + +static const ReturnValue_t SPI_WRITE_FAILURE = MAKE_RETURN_CODE(0xB0); +//! [EXPORT] : [COMMENT] Used by the spi send function to tell a failing read call +static const ReturnValue_t SPI_READ_FAILURE = MAKE_RETURN_CODE(0xB1); +//! [EXPORT] : [COMMENT] Can be used by the HDLC decoding mechanism to inform about a missing +//! start sign 0x7E +static const ReturnValue_t MISSING_START_SIGN = MAKE_RETURN_CODE(0xB2); +//! [EXPORT] : [COMMENT] Can be used by the HDLC decoding mechanism to inform about an invalid +//! substitution combination +static const ReturnValue_t INVALID_SUBSTITUTE = MAKE_RETURN_CODE(0xB3); +//! [EXPORT] : [COMMENT] HDLC decoding mechanism never receives the end sign 0x7E +static const ReturnValue_t MISSING_END_SIGN = MAKE_RETURN_CODE(0xB4); +//! [EXPORT] : [COMMENT] Reaction wheel only responds with empty frames. +static const ReturnValue_t NO_REPLY = MAKE_RETURN_CODE(0xB5); +//! [EXPORT] : [COMMENT] Expected a start marker as first byte +static const ReturnValue_t NO_START_MARKER = MAKE_RETURN_CODE(0xB6); +//! [EXPORT] : [COMMENT] Timeout when reading reply +static const ReturnValue_t SPI_READ_TIMEOUT = MAKE_RETURN_CODE(0xB7); + +static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::RW_HANDLER; + +//! [EXPORT] : [COMMENT] Reaction wheel signals an error state +static constexpr Event ERROR_STATE = MAKE_EVENT(1, severity::HIGH); + +static constexpr Event RESET_OCCURED = event::makeEvent(SUBSYSTEM_ID, 2, severity::LOW); + +//! Minimal delay as specified by the datasheet. +static const uint32_t SPI_REPLY_DELAY = 20000; // us + +enum PoolIds : lp_id_t { + TEMPERATURE_C, + CURR_SPEED, + REFERENCE_SPEED, + STATE, + CLC_MODE, + LAST_RESET_STATUS, + CURRRENT_RESET_STATUS, + TM_LAST_RESET_STATUS, + TM_MCU_TEMPERATURE, + PRESSURE_SENSOR_TEMPERATURE, + PRESSURE, + TM_RW_STATE, + TM_CLC_MODE, + TM_RW_CURR_SPEED, + TM_RW_REF_SPEED, + INVALID_CRC_PACKETS, + INVALID_LEN_PACKETS, + INVALID_CMD_PACKETS, + EXECUTED_REPLIES, + COMMAND_REPLIES, + UART_BYTES_WRITTEN, + UART_BYTES_READ, + UART_PARITY_ERRORS, + UART_NOISE_ERRORS, + UART_FRAME_ERRORS, + UART_REG_OVERRUN_ERRORS, + UART_TOTAL_ERRORS, + TOTAL_ERRORS, + SPI_BYTES_WRITTEN, + SPI_BYTES_READ, + SPI_REG_OVERRUN_ERRORS, + SPI_TOTAL_ERRORS, + + RW_SPEED, + RAMP_TIME, +}; + +enum States : uint8_t { STATE_ERROR, IDLE, COASTING, RUNNING_SPEED_STABLE, RUNNING_SPEED_CHANGING }; + +enum LastResetStatusBitPos : uint8_t { + PIN_RESET = 0, + POR_PDR_BOR_RESET = 1, + SOFTWARE_RESET = 2, + INDEPENDENT_WATCHDOG_RESET = 3, + WINDOW_WATCHDOG_RESET = 4, + LOW_POWER_RESET = 5 +}; + +enum DeviceCommandId : DeviceCommandId_t { + RESET_MCU = 1, + + GET_LAST_RESET_STATUS = 2, + CLEAR_LAST_RESET_STATUS = 3, + GET_RW_STATUS = 4, + INIT_RW_CONTROLLER = 5, + SET_SPEED = 6, + GET_TEMPERATURE = 8, + GET_TM = 9 +}; + +static constexpr DeviceCommandId_t REQUEST_ID = 0x77; +static constexpr DeviceCommandId_t REPLY_ID = 0x78; + +enum SetIds : uint32_t { + TEMPERATURE_SET_ID = DeviceCommandId::GET_TEMPERATURE, + STATUS_SET_ID = DeviceCommandId::GET_RW_STATUS, + LAST_RESET_ID = DeviceCommandId::GET_LAST_RESET_STATUS, + TM_SET_ID = DeviceCommandId::GET_TM, + SPEED_CMD_SET = 10, +}; + +/** Set speed command has maximum size */ +static const size_t MAX_CMD_SIZE = 9; +/** + * Max reply is reached when each byte is replaced by its substitude which should normally never + * happen. + */ +static const size_t MAX_REPLY_SIZE = 2 * SIZE_GET_TELEMETRY_REPLY; + +static const uint8_t LAST_RESET_ENTRIES = 2; +static const uint8_t STATUS_SET_ENTRIES = 5; +static const uint8_t TM_SET_ENTRIES = 24; + +/** + * @brief This dataset can be used to store the data periodically polled from the RW + */ +class StatusSet : public StaticLocalDataSet { + public: + StatusSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, rws::SetIds::STATUS_SET_ID) {} + + StatusSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, rws::SetIds::STATUS_SET_ID)) {} + + lp_var_t temperatureCelcius = + lp_var_t(sid.objectId, PoolIds::TEMPERATURE_C, this); + lp_var_t currSpeed = lp_var_t(sid.objectId, PoolIds::CURR_SPEED, this); + lp_var_t referenceSpeed = + lp_var_t(sid.objectId, PoolIds::REFERENCE_SPEED, this); + lp_var_t state = lp_var_t(sid.objectId, PoolIds::STATE, this); + lp_var_t clcMode = lp_var_t(sid.objectId, PoolIds::CLC_MODE, this); +}; + +/** + * @brief This dataset stores the last reset status. + */ +class LastResetSatus : public StaticLocalDataSet { + public: + LastResetSatus(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, rws::SetIds::LAST_RESET_ID) {} + + LastResetSatus(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, rws::SetIds::LAST_RESET_ID)) {} + + // If a reset occurs, the status code will be cached into this variable + lp_var_t lastNonClearedResetStatus = + lp_var_t(sid.objectId, PoolIds::LAST_RESET_STATUS, this); + // This will always contain the last polled reset status + lp_var_t currentResetStatus = + lp_var_t(sid.objectId, PoolIds::CURRRENT_RESET_STATUS, this); +}; + +/** + * @brief This dataset stores telemetry data as specified in the datasheet of the nano avionics + * reaction wheels. https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/ + * EIVE_IRS/Arbeitsdaten/08_Used%20Components/Nanoavionics_Reactionwheels&fileid=181622 + */ +class TmDataset : public StaticLocalDataSet { + public: + TmDataset(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, rws::SetIds::TM_SET_ID) {} + + TmDataset(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, rws::SetIds::TM_SET_ID)) {} + + lp_var_t lastResetStatus = + lp_var_t(sid.objectId, PoolIds::TM_LAST_RESET_STATUS, this); + lp_var_t mcuTemperature = + lp_var_t(sid.objectId, PoolIds::TM_MCU_TEMPERATURE, this); + lp_var_t pressureSensorTemperature = + lp_var_t(sid.objectId, PoolIds::PRESSURE_SENSOR_TEMPERATURE, this); + lp_var_t pressure = lp_var_t(sid.objectId, PoolIds::PRESSURE, this); + lp_var_t rwState = lp_var_t(sid.objectId, PoolIds::TM_RW_STATE, this); + lp_var_t rwClcMode = lp_var_t(sid.objectId, PoolIds::TM_CLC_MODE, this); + lp_var_t rwCurrSpeed = lp_var_t(sid.objectId, PoolIds::TM_RW_CURR_SPEED, this); + lp_var_t rwRefSpeed = lp_var_t(sid.objectId, PoolIds::TM_RW_REF_SPEED, this); + lp_var_t numOfInvalidCrcPackets = + lp_var_t(sid.objectId, PoolIds::INVALID_CRC_PACKETS, this); + lp_var_t numOfInvalidLenPackets = + lp_var_t(sid.objectId, PoolIds::INVALID_LEN_PACKETS, this); + lp_var_t numOfInvalidCmdPackets = + lp_var_t(sid.objectId, PoolIds::INVALID_CMD_PACKETS, this); + lp_var_t numOfCmdExecutedReplies = + lp_var_t(sid.objectId, PoolIds::EXECUTED_REPLIES, this); + lp_var_t numOfCmdReplies = + lp_var_t(sid.objectId, PoolIds::COMMAND_REPLIES, this); + lp_var_t uartNumOfBytesWritten = + lp_var_t(sid.objectId, PoolIds::UART_BYTES_WRITTEN, this); + lp_var_t uartNumOfBytesRead = + lp_var_t(sid.objectId, PoolIds::UART_BYTES_READ, this); + lp_var_t uartNumOfParityErrors = + lp_var_t(sid.objectId, PoolIds::UART_PARITY_ERRORS, this); + lp_var_t uartNumOfNoiseErrors = + lp_var_t(sid.objectId, PoolIds::UART_NOISE_ERRORS, this); + lp_var_t uartNumOfFrameErrors = + lp_var_t(sid.objectId, PoolIds::UART_FRAME_ERRORS, this); + lp_var_t uartNumOfRegisterOverrunErrors = + lp_var_t(sid.objectId, PoolIds::UART_REG_OVERRUN_ERRORS, this); + lp_var_t uartTotalNumOfErrors = + lp_var_t(sid.objectId, PoolIds::UART_TOTAL_ERRORS, this); + lp_var_t spiNumOfBytesWritten = + lp_var_t(sid.objectId, PoolIds::SPI_BYTES_WRITTEN, this); + lp_var_t spiNumOfBytesRead = + lp_var_t(sid.objectId, PoolIds::SPI_BYTES_READ, this); + lp_var_t spiNumOfRegisterOverrunErrors = + lp_var_t(sid.objectId, PoolIds::SPI_REG_OVERRUN_ERRORS, this); + lp_var_t spiTotalNumOfErrors = + lp_var_t(sid.objectId, PoolIds::SPI_TOTAL_ERRORS, this); +}; + +class RwSpeedActuationSet : public StaticLocalDataSet<2> { + friend class RwHandler; + + public: + RwSpeedActuationSet(HasLocalDataPoolIF& owner) + : StaticLocalDataSet(&owner, rws::SetIds::SPEED_CMD_SET) {} + RwSpeedActuationSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, rws::SetIds::SPEED_CMD_SET)) {} + + void setRwSpeed(int32_t rwSpeed_, uint16_t rampTime_) { + if (rwSpeed.value != rwSpeed_) { + rwSpeed = rwSpeed_; + } + if (rampTime.value != rampTime_) { + rampTime = rampTime_; + } + } + + void getRwSpeed(int32_t& rwSpeed_, uint16_t& rampTime_) { + rwSpeed_ = rwSpeed.value; + rampTime_ = rampTime.value; + } + + private: + lp_var_t rwSpeed = lp_var_t(sid.objectId, rws::PoolIds::RW_SPEED, this); + lp_var_t rampTime = lp_var_t(sid.objectId, rws::PoolIds::RAMP_TIME, this); +}; + +} // namespace rws + +/** + * Raw pointer overlay to hold the different frames received from the reaction + * wheel in a raw buffer and send them to the device handler. + */ +struct RwReplies { + friend class RwPollingTask; + + public: + RwReplies(const uint8_t* rawData) : rawData(const_cast(rawData)) { initPointers(); } + + const uint8_t* getClearLastResetStatusReply() const { return clearLastResetStatusReply + 1; } + bool wasClearLastRsetStatusReplyRead() const { return clearLastResetStatusReply[0]; } + + const uint8_t* getGetLastResetStatusReply() const { return getLastResetStatusReply + 1; } + bool wasGetLastStatusReplyRead() const { return getLastResetStatusReply[0]; } + + const uint8_t* getHkDataReply() const { return hkDataReply + 1; } + bool wasHkDataReplyRead() const { return hkDataReply[0]; } + + const uint8_t* getInitRwControllerReply() const { return initRwControllerReply + 1; } + bool wasInitRwControllerReplyRead() const { return initRwControllerReply[0]; } + + const uint8_t* getRawData() const { return rawData; } + + const uint8_t* getReadTemperatureReply() const { return readTemperatureReply + 1; } + bool wasReadTemperatureReplySet() const { return readTemperatureReply[0]; } + + const uint8_t* getRwStatusReply() const { return rwStatusReply + 1; } + bool wasRwStatusRead() const { return rwStatusReply[0]; } + + const uint8_t* getSetSpeedReply() const { return setSpeedReply + 1; } + bool wasSetSpeedReplyRead() const { return setSpeedReply[0]; } + + private: + RwReplies(uint8_t* rwData) : rawData(rwData) { initPointers(); } + + /** + * The first byte of the reply buffers contains a flag which shows whether that + * frame was read from the reaction wheel at least once. + */ + void initPointers() { + rwStatusReply = rawData; + setSpeedReply = rawData + rws::SIZE_GET_RW_STATUS + 1; + getLastResetStatusReply = setSpeedReply + rws::SIZE_SET_SPEED_REPLY + 1; + clearLastResetStatusReply = getLastResetStatusReply + rws::SIZE_GET_RESET_STATUS + 1; + readTemperatureReply = clearLastResetStatusReply + rws::SIZE_CLEAR_RESET_STATUS + 1; + hkDataReply = readTemperatureReply + rws::SIZE_GET_TEMPERATURE_REPLY + 1; + initRwControllerReply = hkDataReply + rws::SIZE_GET_TELEMETRY_REPLY + 1; + dummyPointer = initRwControllerReply + rws::SIZE_INIT_RW + 1; + } + uint8_t* rawData; + uint8_t* rwStatusReply; + uint8_t* setSpeedReply; + uint8_t* getLastResetStatusReply; + uint8_t* clearLastResetStatusReply; + uint8_t* readTemperatureReply; + uint8_t* hkDataReply; + uint8_t* initRwControllerReply; + uint8_t* dummyPointer; +}; + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_RWHELPERS_H_ */ diff --git a/mission/acs/str/ArcsecDatalinkLayer.cpp b/mission/acs/str/ArcsecDatalinkLayer.cpp new file mode 100644 index 0000000..9d5d26e --- /dev/null +++ b/mission/acs/str/ArcsecDatalinkLayer.cpp @@ -0,0 +1,83 @@ +#include "ArcsecDatalinkLayer.h" + +extern "C" { +#include +#include +} + +ArcsecDatalinkLayer::ArcsecDatalinkLayer() : decodeRingBuf(BUFFER_LENGTHS, true) {} + +ArcsecDatalinkLayer::~ArcsecDatalinkLayer() {} + +ReturnValue_t ArcsecDatalinkLayer::checkRingBufForFrame(const uint8_t** decodedFrame, + size_t& frameLen) { + size_t currentLen = decodeRingBuf.getAvailableReadData(); + if (currentLen == 0) { + return DEC_IN_PROGRESS; + } + decodeRingBuf.readData(rxAnalysisBuffer, currentLen); + + bool startFound = false; + size_t startIdx = 0; + for (size_t idx = 0; idx < currentLen; idx++) { + if (rxAnalysisBuffer[idx] != SLIP_START_AND_END) { + continue; + } + if (not startFound) { + startFound = true; + startIdx = idx; + continue; + } + // Now we can try decoding the whole frame. + size_t encodedDataSize = 0; + slip_error_t slipError = + slip_decode_frame(decodedRxFrame, &rxFrameSize, rxAnalysisBuffer + startIdx, + idx - startIdx + 1, &encodedDataSize, ARC_DEF_SAGITTA_SLIP_ID, 0); + decodeRingBuf.deleteData(idx + 1); + switch (slipError) { + case (SLIP_OK): { + if (decodedFrame != nullptr) { + *decodedFrame = decodedRxFrame; + } + frameLen = rxFrameSize; + return returnvalue::OK; + } + case (SLIP_BAD_CRC): { + return CRC_FAILURE; + } + case (SLIP_OVERFLOW): { + return SLIP_OVERFLOW_RETVAL; + } + // Should not happen, we searched for start and end marker.. + case (SLIP_NO_END): { + return returnvalue::FAILED; + } + case (SLIP_ID_MISMATCH): { + return SLIP_ID_MISSMATCH_RETVAL; + } + default: { + return returnvalue::FAILED; + } + } + } + return DEC_IN_PROGRESS; +} + +ReturnValue_t ArcsecDatalinkLayer::feedData(const uint8_t* rawData, size_t rawDataLen) { + if (rawDataLen > 4096) { + sif::error << "ArcsecDatalinklayer: Can not write more than 4096 bytes to ring buffer" + << std::endl; + return returnvalue::FAILED; + } + return decodeRingBuf.writeData(rawData, rawDataLen); +} + +void ArcsecDatalinkLayer::reset() { decodeRingBuf.clear(); } + +void ArcsecDatalinkLayer::encodeFrame(const uint8_t* data, size_t length, const uint8_t** txFrame, + size_t& size) { + slip_encode_frame(data, length, txEncoded, sizeof(txEncoded), &size, ARC_DEF_SAGITTA_SLIP_ID); + if (txFrame != nullptr) { + *txFrame = txEncoded; + } +} diff --git a/mission/acs/str/ArcsecDatalinkLayer.h b/mission/acs/str/ArcsecDatalinkLayer.h new file mode 100644 index 0000000..06d1485 --- /dev/null +++ b/mission/acs/str/ArcsecDatalinkLayer.h @@ -0,0 +1,95 @@ +#ifndef BSP_Q7S_DEVICES_ARCSECDATALINKLAYER_H_ +#define BSP_Q7S_DEVICES_ARCSECDATALINKLAYER_H_ + +#include +#include +#include + +#include "eive/resultClassIds.h" +#include "fsfw/returnvalues/returnvalue.h" + +extern "C" { +#include +} + +/** + * @brief Helper class to handle the datalinklayer of replies from the star tracker of arcsec. + */ +class ArcsecDatalinkLayer { + public: + static const uint8_t INTERFACE_ID = CLASS_ID::STR_HANDLER; + + //! [EXPORT] : [COMMENT] More data required to complete frame + static const ReturnValue_t DEC_IN_PROGRESS = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] Data too short to represent a valid frame + static const ReturnValue_t REPLY_TOO_SHORT = MAKE_RETURN_CODE(0xA1); + //! [EXPORT] : [COMMENT] Detected CRC failure in received frame + static const ReturnValue_t CRC_FAILURE = MAKE_RETURN_CODE(0xA2); + static const ReturnValue_t SLIP_OVERFLOW_RETVAL = MAKE_RETURN_CODE(0xA3); + static const ReturnValue_t SLIP_ID_MISSMATCH_RETVAL = MAKE_RETURN_CODE(0xA4); + + static const uint8_t STATUS_OK = 0; + + static constexpr size_t BUFFER_LENGTHS = 4096; + + ArcsecDatalinkLayer(); + virtual ~ArcsecDatalinkLayer(); + + /** + * Feed received data to the internal ring buffer. + * @param rawData + * @param rawDataLen + * @return + */ + ReturnValue_t feedData(const uint8_t* rawData, size_t rawDataLen); + + /** + * Runs the arcsec datalink layer decoding algorithm on the data in the ring buffer, decoding + * frames in the process. + * @param decodedFrame + * @param frameLen + * @return + * - returnvalue::OK if a frame was found + * - DEC_IN_PROGRESS if frame decoding is in progress + * - Anything else is a decoding error + */ + ReturnValue_t checkRingBufForFrame(const uint8_t** decodedFrame, size_t& frameLen); + + /** + * @brief SLIP encodes data pointed to by data pointer. + * + * @param data Pointer to data to encode + * @param length Length of buffer to encode + */ + void encodeFrame(const uint8_t* data, size_t length, const uint8_t** txFrame, size_t& frameLen); + + void reset(); + + private: + static const uint8_t ID_OFFSET = 1; + static const uint8_t STATUS_OFFSET = 2; + + // User to buffer and analyse data and allow feeding and checking for frames asychronously. + SimpleRingBuffer decodeRingBuf; + uint8_t rxAnalysisBuffer[BUFFER_LENGTHS]; + + // Used by arcsec slip decoding function to process received data. This should only be written + // to or read from by arcsec functions! + uint8_t rxBufferArc[startracker::MAX_FRAME_SIZE]; + // Decoded frame will be copied to this buffer + uint8_t decodedRxFrame[startracker::MAX_FRAME_SIZE]; + // Size of decoded frame + size_t rxFrameSize = 0; + + // Buffer where encoded frames will be stored. First byte of encoded frame represents type of + // reply + uint8_t txEncoded[startracker::MAX_FRAME_SIZE * 2 + 2]; + // Size of encoded frame + uint32_t txFrameSize = 0; + + // slip_decode_state slipInfo; + + void slipInit(); +}; + +#endif /* BSP_Q7S_DEVICES_ARCSECDATALINKLAYER_H_ */ diff --git a/mission/acs/str/ArcsecJsonParamBase.cpp b/mission/acs/str/ArcsecJsonParamBase.cpp new file mode 100644 index 0000000..704c23a --- /dev/null +++ b/mission/acs/str/ArcsecJsonParamBase.cpp @@ -0,0 +1,106 @@ +#include +#include + +#include "arcsecJsonKeys.h" + +extern "C" { +#include +} + +ArcsecJsonParamBase::ArcsecJsonParamBase(std::string setName) : setName(setName) {} + +ReturnValue_t ArcsecJsonParamBase::create(uint8_t* buffer) { + ReturnValue_t result = createCommand(buffer); + if (result != returnvalue::OK) { + sif::warning << "ArcsecJsonParamBase::create: Failed to create parameter command for set " + << setName << std::endl; + } + return result; +} + +ReturnValue_t ArcsecJsonParamBase::getParam(const std::string name, std::string& value) { + for (json::iterator it = set.begin(); it != set.end(); ++it) { + if ((*it)[arcseckeys::NAME] == name) { + value = (*it)[arcseckeys::VALUE]; + convertEmpty(value); + return returnvalue::OK; + } + } + return PARAM_NOT_EXISTS; +} + +void ArcsecJsonParamBase::convertEmpty(std::string& value) { + if (value == "") { + value = "0"; + } +} + +void ArcsecJsonParamBase::addfloat(const std::string value, uint8_t* buffer) { + float param = std::stof(value); + std::memcpy(buffer, ¶m, sizeof(param)); +} + +void ArcsecJsonParamBase::adduint8(const std::string value, uint8_t* buffer) { + uint8_t param = std::stoi(value); + std::memcpy(buffer, ¶m, sizeof(param)); +} + +void ArcsecJsonParamBase::addint16(const std::string value, uint8_t* buffer) { + int16_t param = std::stoi(value); + std::memcpy(buffer, ¶m, sizeof(param)); +} + +void ArcsecJsonParamBase::adduint16(const std::string value, uint8_t* buffer) { + uint16_t param = std::stoi(value); + std::memcpy(buffer, ¶m, sizeof(param)); +} + +void ArcsecJsonParamBase::adduint32(const std::string value, uint8_t* buffer) { + uint32_t param = std::stoi(value); + std::memcpy(buffer, ¶m, sizeof(param)); +} + +void ArcsecJsonParamBase::addSetParamHeader(uint8_t* buffer, uint8_t setId) { + *buffer = static_cast(TMTC_SETPARAMREQ); + *(buffer + 1) = setId; +} + +ReturnValue_t ArcsecJsonParamBase::init(const std::string& filename) { + ReturnValue_t result = returnvalue::OK; + if (not std::filesystem::exists(filename)) { + sif::warning << "ArcsecJsonParamBase::init: JSON file " << filename << " does not exist" + << std::endl; + return JSON_FILE_NOT_EXISTS; + } + try { + createJsonObject(filename); + result = initSet(); + if (result != returnvalue::OK) { + return result; + } + return returnvalue::OK; + } catch (json::exception& e) { + // TODO: Re-create json file from backup here. + return returnvalue::FAILED; + } +} + +void ArcsecJsonParamBase::createJsonObject(const std::string fullname) { + json j; + std::ifstream file(fullname); + file >> j; + file.close(); + properties = j[arcseckeys::PROPERTIES]; +} + +ReturnValue_t ArcsecJsonParamBase::initSet() { + for (json::iterator it = properties.begin(); it != properties.end(); ++it) { + if ((*it)["name"] == setName) { + set = (*it)["fields"]; + return returnvalue::OK; + } + } + sif::warning << "ArcsecJsonParamBase::initSet: Set " << setName << "not present in json file" + << std::endl; + return SET_NOT_EXISTS; +} diff --git a/mission/acs/str/ArcsecJsonParamBase.h b/mission/acs/str/ArcsecJsonParamBase.h new file mode 100644 index 0000000..b855a32 --- /dev/null +++ b/mission/acs/str/ArcsecJsonParamBase.h @@ -0,0 +1,144 @@ +#ifndef BSP_Q7S_DEVICES_STARTRACKER_ARCSECJSONPARAMBASE_H_ +#define BSP_Q7S_DEVICES_STARTRACKER_ARCSECJSONPARAMBASE_H_ + +#include + +#include +#include +#include + +#include "eive/resultClassIds.h" +#include "fsfw/returnvalues/returnvalue.h" + +using json = nlohmann::json; + +/** + * @brief Base class for creation of parameter configuration commands. Reads parameter set + * from a json file located on the filesystem and generates the appropriate command + * to apply the parameters to the star tracker software. + * + * @author J. Meier + */ +class ArcsecJsonParamBase { + public: + static const uint8_t INTERFACE_ID = CLASS_ID::ARCSEC_JSON_BASE; + //! [EXPORT] : [COMMENT] Specified json file does not exist + static const ReturnValue_t JSON_FILE_NOT_EXISTS = MAKE_RETURN_CODE(1); + //! [EXPORT] : [COMMENT] Requested set does not exist in json file + static const ReturnValue_t SET_NOT_EXISTS = MAKE_RETURN_CODE(2); + //! [EXPORT] : [COMMENT] Requested parameter does not exist in json file + static const ReturnValue_t PARAM_NOT_EXISTS = MAKE_RETURN_CODE(3); + + virtual ~ArcsecJsonParamBase() = default; + /** + * @brief Constructor + * + * @param fullname Name with absolute path of json file containing the parameters to set. + */ + ArcsecJsonParamBase(std::string setName); + + /** + * @brief Initializes the properties json object and the set json object + * + * @param fullname Name including absolute path to json file + * @param setName The name of the set to work on + * + * @param return JSON_FILE_NOT_EXISTS if specified file does not exist, otherwise + * returnvalue::OK + */ + ReturnValue_t init(const std::string& filename); + + /** + * @brief Fills a buffer with a parameter set + * + * @param fullname The name including the absolute path of the json file containing the + * parameter set. + * @param buffer Pointer to the buffer the command will be written to + */ + ReturnValue_t create(uint8_t* buffer); + + /** + * @brief Returns the size of the parameter command. + */ + virtual size_t getSize() = 0; + + protected: + /** + * @brief Reads the value of a parameter from a json set + * + * @param name The name of the parameter + * @param value The string representation of the read value + * + * @return returnvalue::OK if successful, otherwise PARAM_NOT_EXISTS + */ + ReturnValue_t getParam(const std::string name, std::string& value); + + /** + * @brief Converts empty string which is equal to define a value as zero. + */ + void convertEmpty(std::string& value); + + /** + * @brief This function adds a float represented as string to a buffer + * + * @param value The float in string representation to add + * @param buffer Pointer to the buffer the float will be written to + */ + void addfloat(const std::string value, uint8_t* buffer); + + /** + * @brief This function adds a uint8_t represented as string to a buffer + * + * @param value The uint8_t in string representation to add + * @param buffer Pointer to the buffer the uint8_t will be written to + */ + void adduint8(const std::string value, uint8_t* buffer); + + /** + * @brief This function adds a int16_t represented as string to a buffer + * + * @param value The int16_t in string representation to add + * @param buffer Pointer to the buffer the int16_t will be written to + */ + void addint16(const std::string value, uint8_t* buffer); + + /** + * @brief This function adds a uint16_t represented as string to a buffer + * + * @param value The uint16_t in string representation to add + * @param buffer Pointer to the buffer the uint16_t will be written to + */ + void adduint16(const std::string value, uint8_t* buffer); + + /** + * @brief This function adds a uint32_t represented as string to a buffer + * + * @param value The uint32_t in string representation to add + * @param buffer Pointer to the buffer the uint32_t will be written to + */ + void adduint32(const std::string value, uint8_t* buffer); + + void addSetParamHeader(uint8_t* buffer, uint8_t setId); + + private: + json properties; + json set; + std::string setName; + + /** + * @brief This function must be implemented by the derived class to define creation of a + * parameter command. + */ + virtual ReturnValue_t createCommand(uint8_t* buffer) = 0; + + void createJsonObject(const std::string fullname); + + /** + * @brief Extracts the json set object form the json file + * + * @param setName The name of the set to create the json object from + */ + ReturnValue_t initSet(); +}; + +#endif /* BSP_Q7S_DEVICES_STARTRACKER_ARCSECJSONPARAMBASE_H_ */ diff --git a/mission/acs/str/CMakeLists.txt b/mission/acs/str/CMakeLists.txt new file mode 100644 index 0000000..edb3494 --- /dev/null +++ b/mission/acs/str/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources( + ${OBSW_NAME} + PRIVATE StarTrackerHandler.cpp strJsonCommands.cpp ArcsecDatalinkLayer.cpp + ArcsecJsonParamBase.cpp strHelpers.cpp) diff --git a/mission/acs/str/StarTrackerHandler.cpp b/mission/acs/str/StarTrackerHandler.cpp new file mode 100644 index 0000000..7ef1c4b --- /dev/null +++ b/mission/acs/str/StarTrackerHandler.cpp @@ -0,0 +1,2996 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "fsfw/filesystem/HasFileSystemIF.h" +#include "fsfw/ipc/MessageQueueIF.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "mission/memory/SdCardMountedIF.h" + +extern "C" { +#include +#include +#include +#include +#include +} + +#include +#include + +#include "OBSWConfig.h" +#include "eive/definitions.h" +#include "fsfw/thermal/tcsDefinitions.h" + +std::atomic_bool JCFG_DONE(false); + +StarTrackerHandler::StarTrackerHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + StrComHandler* strHelper, power::Switch_t powerSwitch, + startracker::SdCardConfigPathGetter& cfgPathGetter, + SdCardMountedIF& sdCardIF) + : DeviceHandlerBase(objectId, comIF, comCookie), + temperatureSet(this), + versionSet(this), + powerSet(this), + interfaceSet(this), + timeSet(this), + solutionSet(this), + histogramSet(this), + checksumSet(this), + cameraSet(this), + limitsSet(this), + loglevelSet(this), + mountingSet(this), + imageProcessorSet(this), + centroidingSet(this), + lisaSet(this), + matchingSet(this), + trackingSet(this), + validationSet(this), + algoSet(this), + subscriptionSet(this), + logSubscriptionSet(this), + debugCameraSet(this), + autoBlobSet(this), + matchedCentroidsSet(this), + blobSet(this), + blobsSet(this), + centroidSet(this), + centroidsSet(this), + contrastSet(this), + blobStatsSet(this), + strHelper(strHelper), + powerSwitch(powerSwitch), + sdCardIF(sdCardIF), + cfgPathGetter(cfgPathGetter) { + if (comCookie == nullptr) { + sif::error << "StarTrackerHandler: Invalid com cookie" << std::endl; + } + if (strHelper == nullptr) { + sif::error << "StarTrackerHandler: Invalid str image loader" << std::endl; + } + eventQueue = QueueFactory::instance()->createMessageQueue(EventMessage::EVENT_MESSAGE_SIZE * 5); + additionalRequestedTm.emplace(startracker::REQ_TEMPERATURE); + currentSecondaryTmIter = additionalRequestedTm.begin(); +} + +StarTrackerHandler::~StarTrackerHandler() {} + +void StarTrackerHandler::doStartUp() { + switch (startupState) { + case StartupState::IDLE: + startupState = StartupState::CHECK_PROGRAM; + return; + case StartupState::BOOT_BOOTLOADER: + // This step is required in case the star tracker is already in firmware mode to harmonize + // the device handler's submode to the star tracker's mode + return; + case StartupState::DONE: + if (!JCFG_DONE) { + startupState = StartupState::WAIT_JCFG; + return; + } + break; + case StartupState::WAIT_JCFG: { + return; + } + default: + return; + } + startupState = StartupState::DONE; + internalState = InternalState::IDLE; + setMode(_MODE_TO_ON); +} + +void StarTrackerHandler::doShutDown() { + // If the star tracker is shutdown also stop all running processes in the image loader task + strHelper->stopProcess(); + internalState = InternalState::IDLE; + startupState = StartupState::IDLE; + bootState = FwBootState::NONE; + solutionSet.setReportingEnabled(false); + { + PoolReadGuard pg(&solutionSet); + solutionSet.caliQw.value = 0.0; + solutionSet.caliQx.value = 0.0; + solutionSet.caliQy.value = 0.0; + solutionSet.caliQz.value = 0.0; + solutionSet.isTrustWorthy.value = 0; + solutionSet.setValidity(false, true); + } + { + PoolReadGuard pg(&temperatureSet); + temperatureSet.fpgaTemperature = thermal::INVALID_TEMPERATURE; + temperatureSet.cmosTemperature = thermal::INVALID_TEMPERATURE; + temperatureSet.mcuTemperature = thermal::INVALID_TEMPERATURE; + temperatureSet.setValidity(false, true); + } + reinitNextSetParam = false; + setMode(_MODE_POWER_DOWN); +} + +ReturnValue_t StarTrackerHandler::initialize() { + ReturnValue_t result = returnvalue::OK; + result = DeviceHandlerBase::initialize(); + if (result != returnvalue::OK) { + return result; + } + + // Spin up a thread to do the JSON initialization, takes 200-250 ms which would + // delay whole satellite boot process. + reloadJsonCfgFile(); + + // Default firmware target is always initialized from persistent file. + loadTargetFirmwareFromPersistentCfg(); + + EventManagerIF* manager = ObjectManager::instance()->get(objects::EVENT_MANAGER); + if (manager == nullptr) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "StarTrackerHandler::initialize: Invalid event manager" << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + ; + } + result = manager->registerListener(eventQueue->getId()); + if (result != returnvalue::OK) { + return result; + } + result = manager->subscribeToEventRange(eventQueue->getId(), + event::getEventId(StrComHandler::IMAGE_UPLOAD_FAILED), + event::getEventId(StrComHandler::FIRMWARE_UPDATE_FAILED)); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "StarTrackerHandler::initialize: Failed to subscribe to events from " + " str helper" + << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + return returnvalue::OK; +} + +void StarTrackerHandler::loadTargetFirmwareFromPersistentCfg() { + const char* prefix = sdCardIF.getCurrentMountPrefix(); + std::filesystem::path path = std::filesystem::path(prefix) / startracker::FW_TARGET_CFG_PATH; + std::ifstream ifile(path); + if (ifile.is_open() and !ifile.bad()) { + std::string targetStr; + std::getline(ifile, targetStr); + if (targetStr == "backup") { + firmwareTargetRaw = static_cast(startracker::FirmwareTarget::BACKUP); + } + } +} + +bool StarTrackerHandler::reloadJsonCfgFile() { + jcfgCountdown.resetTimer(); + auto strCfgPath = cfgPathGetter.getCfgPath(); + if (strCfgPath.has_value()) { + jcfgPending = true; + JCFG_DONE = false; + jsonCfgTask = std::thread{setUpJsonCfgs, std::ref(jcfgs), strCfgPath.value()}; + return true; + } + // Simplified FDIR: Just continue as usual.. + JCFG_DONE = true; + return false; +} + +ReturnValue_t StarTrackerHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + ReturnValue_t result = returnvalue::OK; + + switch (actionId) { + case (startracker::ADD_SECONDARY_TM_TO_NORMAL_MODE): { + if (size < 4) { + return HasActionsIF::INVALID_PARAMETERS; + } + DeviceCommandId_t idToAdd; + result = + SerializeAdapter::deSerialize(&idToAdd, data, &size, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + addSecondaryTmForNormalMode(idToAdd); + return EXECUTION_FINISHED; + } + case (startracker::RESET_SECONDARY_TM_SET): { + resetSecondaryTmSet(); + return EXECUTION_FINISHED; + } + case (startracker::READ_SECONDARY_TM_SET): { + std::vector dataVec(additionalRequestedTm.size() * 4); + unsigned idx = 0; + size_t serLen = 0; + for (const auto& cmd : additionalRequestedTm) { + SerializeAdapter::serialize(&cmd, dataVec.data() + idx * 4, &serLen, dataVec.size(), + SerializeIF::Endianness::NETWORK); + idx++; + } + actionHelper.reportData(commandedBy, actionId, dataVec.data(), dataVec.size()); + return EXECUTION_FINISHED; + } + case (startracker::STOP_IMAGE_LOADER): { + strHelper->stopProcess(); + return EXECUTION_FINISHED; + } + case (startracker::SET_JSON_FILE_NAME): { + if (size > config::MAX_PATH_SIZE) { + return FILE_PATH_TOO_LONG; + } + paramJsonFile = std::string(reinterpret_cast(data), size); + return EXECUTION_FINISHED; + } + case (startracker::DISABLE_TIMESTAMP_GENERATION): + strHelper->disableTimestamping(); + return EXECUTION_FINISHED; + case (startracker::ENABLE_TIMESTAMP_GENERATION): + strHelper->enableTimestamping(); + return EXECUTION_FINISHED; + default: + break; + } + + if (strHelperHandlingSpecialRequest == true) { + return STR_HELPER_EXECUTING; + } + + result = checkMode(actionId); + if (result != returnvalue::OK) { + return result; + } + + result = checkCommand(actionId); + if (result != returnvalue::OK) { + return result; + } + + // Intercept image loader commands which do not follow the common DHB communication flow + switch (actionId) { + case (startracker::UPLOAD_IMAGE): { + result = DeviceHandlerBase::acceptExternalDeviceCommands(); + if (result != returnvalue::OK) { + return result; + } + if (size > config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE) { + return FILE_PATH_TOO_LONG; + } + result = strHelper->startImageUpload(std::string(reinterpret_cast(data), size)); + if (result != returnvalue::OK) { + return result; + } + strHelperHandlingSpecialRequest = true; + return EXECUTION_FINISHED; + } + case (startracker::DOWNLOAD_IMAGE): { + result = DeviceHandlerBase::acceptExternalDeviceCommands(); + if (result != returnvalue::OK) { + return result; + } + if (size > config::MAX_PATH_SIZE) { + return FILE_PATH_TOO_LONG; + } + result = + strHelper->startImageDownload(std::string(reinterpret_cast(data), size)); + if (result != returnvalue::OK) { + return result; + } + strHelperHandlingSpecialRequest = true; + return EXECUTION_FINISHED; + } + case (startracker::FLASH_READ): { + result = DeviceHandlerBase::acceptExternalDeviceCommands(); + if (result != returnvalue::OK) { + return result; + } + result = executeFlashReadCommand(data, size); + if (result != returnvalue::OK) { + return result; + } + strHelperHandlingSpecialRequest = true; + return EXECUTION_FINISHED; + } + case (startracker::CHANGE_IMAGE_DOWNLOAD_FILE): { + if (size > config::MAX_FILENAME_SIZE) { + return FILENAME_TOO_LONG; + } + strHelper->setDownloadImageName(std::string(reinterpret_cast(data), size)); + return EXECUTION_FINISHED; + } + case (startracker::SET_FLASH_READ_FILENAME): { + if (size > config::MAX_FILENAME_SIZE) { + return FILENAME_TOO_LONG; + } + strHelper->setFlashReadFilename(std::string(reinterpret_cast(data), size)); + return EXECUTION_FINISHED; + } + case (startracker::FIRMWARE_UPDATE_MAIN): { + return handleFirmwareUpdateCommand(data, size, startracker::FirmwareTarget::MAIN); + } + case (startracker::FIRMWARE_UPDATE_BACKUP): { + return handleFirmwareUpdateCommand(data, size, startracker::FirmwareTarget::BACKUP); + } + default: + break; + } + // In case the JSON has changed, reinitiate the next parameter set to update. + reinitNextSetParam = true; + return DeviceHandlerBase::executeAction(actionId, commandedBy, data, size); +} +ReturnValue_t StarTrackerHandler::handleFirmwareUpdateCommand(const uint8_t* data, size_t size, + startracker::FirmwareTarget target) { + ReturnValue_t result = DeviceHandlerBase::acceptExternalDeviceCommands(); + if (result != returnvalue::OK) { + return result; + } + if (size > config::MAX_PATH_SIZE + config::MAX_FILENAME_SIZE) { + return FILE_PATH_TOO_LONG; + } + result = strHelper->startFirmwareUpdate(std::string(reinterpret_cast(data), size), + target); + if (result != returnvalue::OK) { + return result; + } + strHelperHandlingSpecialRequest = true; + return EXECUTION_FINISHED; +} + +void StarTrackerHandler::performOperationHook() { + EventMessage event; + for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::OK; + result = eventQueue->receiveMessage(&event)) { + switch (event.getMessageId()) { + case EventMessage::EVENT_MESSAGE: + handleEvent(&event); + break; + default: + sif::debug << "CCSDSHandler::checkEvents: Did not subscribe to this event message" + << std::endl; + break; + } + } + if (jcfgPending) { + if (JCFG_DONE) { + if (startupState == StartupState::WAIT_JCFG) { + startupState = StartupState::DONE; + } + jsonCfgTask.join(); + jcfgPending = false; + auto iter = deviceCommandMap.find(startracker::RELOAD_JSON_CFG_FILE); + if (iter != deviceCommandMap.end() and iter->second.sendReplyTo != MessageQueueIF::NO_QUEUE) { + actionHelper.finish(true, iter->second.sendReplyTo, startracker::RELOAD_JSON_CFG_FILE); + } + } else if (jcfgCountdown.hasTimedOut()) { + auto iter = deviceCommandMap.find(startracker::RELOAD_JSON_CFG_FILE); + if (iter != deviceCommandMap.end() and iter->second.sendReplyTo != MessageQueueIF::NO_QUEUE) { + actionHelper.finish(false, iter->second.sendReplyTo, startracker::RELOAD_JSON_CFG_FILE); + } + } + } +} + +Submode_t StarTrackerHandler::getInitialSubmode() { return startracker::SUBMODE_BOOTLOADER; } + +ReturnValue_t StarTrackerHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + if (strHelperHandlingSpecialRequest) { + return NOTHING_TO_SEND; + } + switch (normalState) { + case NormalState::SECONDARY_REQUEST: + if (additionalRequestedTm.size() == 0) { + break; + } + *id = *currentSecondaryTmIter; + currentSecondaryTmIter++; + if (currentSecondaryTmIter == additionalRequestedTm.end()) { + currentSecondaryTmIter = additionalRequestedTm.begin(); + } + normalState = NormalState::SOLUTION_REQUEST; + break; + case NormalState::SOLUTION_REQUEST: + *id = startracker::REQ_SOLUTION; + normalState = NormalState::SECONDARY_REQUEST; + break; + default: + sif::debug << "StarTrackerHandler::buildNormalDeviceCommand: Invalid normal step" + << std::endl; + return NOTHING_TO_SEND; + } + return buildCommandFromCommand(*id, NULL, 0); +} + +ReturnValue_t StarTrackerHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + switch (internalState) { + case InternalState::BOOT_FIRMWARE: { + if (bootState == FwBootState::VERIFY_BOOT or isAwaitingReply()) { + return NOTHING_TO_SEND; + } + if (bootState == FwBootState::NONE) { + *id = startracker::BOOT; + bootCountdown.setTimeout(BOOT_TIMEOUT); + bootState = FwBootState::BOOT_DELAY; + return buildCommandFromCommand(*id, nullptr, 0); + } + if (bootState == FwBootState::BOOT_DELAY) { + if (bootCountdown.isBusy()) { + return NOTHING_TO_SEND; + } + // Was already done. + reinitNextSetParam = false; + bootState = FwBootState::REQ_VERSION; + } + switch (bootState) { + case (FwBootState::REQ_VERSION): { + bootState = FwBootState::VERIFY_BOOT; + // Again read program to check if firmware boot was successful + *id = startracker::REQ_VERSION; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (FwBootState::SET_TIME): { + *id = startracker::SET_TIME_FROM_SYS_TIME; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (FwBootState::LOGLEVEL): { + *id = startracker::LOGLEVEL; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + } + case (FwBootState::LIMITS): { + *id = startracker::LIMITS; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + } + case (FwBootState::TRACKING): { + *id = startracker::TRACKING; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + } + case FwBootState::MOUNTING: + *id = startracker::MOUNTING; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::IMAGE_PROCESSOR: + *id = startracker::IMAGE_PROCESSOR; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::CAMERA: + *id = startracker::CAMERA; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::CENTROIDING: + *id = startracker::CENTROIDING; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::LISA: + *id = startracker::LISA; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::MATCHING: + *id = startracker::MATCHING; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::VALIDATION: + *id = startracker::VALIDATION; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::ALGO: + *id = startracker::ALGO; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::LOG_SUBSCRIPTION: + *id = startracker::LOGSUBSCRIPTION; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::DEBUG_CAMERA: + *id = startracker::DEBUG_CAMERA; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + case FwBootState::AUTO_THRESHOLD: + *id = startracker::AUTO_THRESHOLD; + return buildCommandFromCommand( + *id, reinterpret_cast(paramJsonFile.c_str()), paramJsonFile.size()); + default: { + sif::error << "STR: Unexpected boot state" << (int)bootState << std::endl; + return NOTHING_TO_SEND; + } + } + } + case InternalState::BOOT_BOOTLOADER: + internalState = InternalState::BOOTLOADER_CHECK; + *id = startracker::SWITCH_TO_BOOTLOADER_PROGRAM; + return buildCommandFromCommand(*id, nullptr, 0); + case InternalState::BOOTLOADER_CHECK: + *id = startracker::REQ_VERSION; + return buildCommandFromCommand(*id, nullptr, 0); + default: + break; + } + switch (startupState) { + case StartupState::CHECK_PROGRAM: + startupState = StartupState::WAIT_CHECK_PROGRAM; + *id = startracker::REQ_VERSION; + return buildCommandFromCommand(*id, nullptr, 0); + break; + case StartupState::BOOT_BOOTLOADER: + startupState = StartupState::CHECK_PROGRAM; + *id = startracker::SWITCH_TO_BOOTLOADER_PROGRAM; + return buildCommandFromCommand(*id, nullptr, 0); + break; + default: + break; + } + return NOTHING_TO_SEND; +} + +ReturnValue_t StarTrackerHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + switch (deviceCommand) { + case (startracker::PING_REQUEST): { + preparePingRequest(); + return returnvalue::OK; + } + case (startracker::RELOAD_JSON_CFG_FILE): { + if (jcfgPending) { + return HasActionsIF::IS_BUSY; + } + // It should be noted that this just reloads the JSON config structure in memory from the + // JSON file. The configuration still needs to be sent to the STR. The easiest way to achieve + // this is to simply reboot the device after a reload. + reloadJsonCfgFile(); + return returnvalue::OK; + } + case (startracker::SET_TIME_FROM_SYS_TIME): { + SetTimeActionRequest setTimeRequest{}; + timeval tv; + Clock::getClock(&tv); + setTimeRequest.unixTime = + (static_cast(tv.tv_sec) * 1000 * 1000) + (static_cast(tv.tv_usec)); + prv_arc_pack_settime_action_req(&setTimeRequest, commandBuffer, &rawPacketLen); + size_t serLen = 0; + // Time in milliseconds. Manual serialization because arcsec API ignores endianness. + SerializeAdapter::serialize(&setTimeRequest.unixTime, commandBuffer + 2, &serLen, + sizeof(commandBuffer) - 2, SerializeIF::Endianness::LITTLE); + rawPacket = commandBuffer; + return returnvalue::OK; + } + + case (startracker::REQ_TIME): { + prepareTimeRequest(); + return returnvalue::OK; + } + case (startracker::REQ_CENTROID): { + prepareRequestCentroidTm(); + return returnvalue::OK; + } + case (startracker::REQ_CENTROIDS): { + prepareRequestCentroidsTm(); + return returnvalue::OK; + } + case (startracker::REQ_CONTRAST): { + prepareRequestContrastTm(); + return returnvalue::OK; + } + case (startracker::REQ_BLOB_STATS): { + prepareRequestBlobStatsTm(); + return returnvalue::OK; + } + case (startracker::BOOT): { + prepareBootCommand(static_cast(firmwareTargetRaw)); + return returnvalue::OK; + } + case (startracker::REQ_VERSION): { + prepareVersionRequest(); + return returnvalue::OK; + } + case (startracker::REQ_INTERFACE): { + prepareInterfaceRequest(); + return returnvalue::OK; + } + case (startracker::REQ_POWER): { + preparePowerRequest(); + return returnvalue::OK; + } + case (startracker::SWITCH_TO_BOOTLOADER_PROGRAM): { + prepareSwitchToBootloaderCmd(); + return returnvalue::OK; + } + case (startracker::TAKE_IMAGE): { + prepareTakeImageCommand(commandData); + return returnvalue::OK; + } + case (startracker::SUBSCRIPTION): { + result = + prepareParamCommand(commandData, commandDataLen, jcfgs.subscription, reinitNextSetParam); + return returnvalue::OK; + } + case (startracker::REQ_SOLUTION): { + prepareSolutionRequest(); + return returnvalue::OK; + } + case (startracker::REQ_TEMPERATURE): { + prepareTemperatureRequest(); + return returnvalue::OK; + } + case (startracker::REQ_HISTOGRAM): { + prepareHistogramRequest(); + return returnvalue::OK; + } + case (startracker::LIMITS): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.limits, reinitNextSetParam); + return result; + } + case (startracker::MOUNTING): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.mounting, reinitNextSetParam); + return result; + } + case (startracker::IMAGE_PROCESSOR): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.imageProcessor, + reinitNextSetParam); + return result; + } + case (startracker::CAMERA): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.camera, reinitNextSetParam); + return result; + } + case (startracker::CENTROIDING): { + result = + prepareParamCommand(commandData, commandDataLen, jcfgs.centroiding, reinitNextSetParam); + return result; + } + case (startracker::LISA): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.lisa, reinitNextSetParam); + return result; + } + case (startracker::MATCHING): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.matching, reinitNextSetParam); + return result; + } + case (startracker::VALIDATION): { + result = + prepareParamCommand(commandData, commandDataLen, jcfgs.validation, reinitNextSetParam); + return result; + } + case (startracker::ALGO): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.algo, reinitNextSetParam); + return result; + } + case (startracker::TRACKING): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.tracking, reinitNextSetParam); + return result; + } + case (startracker::LOGLEVEL): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.logLevel, reinitNextSetParam); + return result; + } + case (startracker::AUTO_THRESHOLD): { + result = + prepareParamCommand(commandData, commandDataLen, jcfgs.autoThreshold, reinitNextSetParam); + return result; + } + case (startracker::LOGSUBSCRIPTION): { + result = prepareParamCommand(commandData, commandDataLen, jcfgs.logSubscription, + reinitNextSetParam); + return result; + } + case (startracker::DEBUG_CAMERA): { + result = + prepareParamCommand(commandData, commandDataLen, jcfgs.debugCamera, reinitNextSetParam); + return result; + } + case (startracker::CHECKSUM): { + result = prepareChecksumCommand(commandData, commandDataLen); + return result; + } + case (startracker::REQ_CAMERA): { + result = prepareRequestCameraParams(); + return result; + } + case (startracker::REQ_LIMITS): { + result = prepareRequestLimitsParams(); + return result; + } + case (startracker::REQ_LOG_LEVEL): { + result = prepareRequestLogLevelParams(); + return result; + } + case (startracker::REQ_MOUNTING): { + result = prepareRequestMountingParams(); + return result; + } + case (startracker::REQ_IMAGE_PROCESSOR): { + result = prepareRequestImageProcessorParams(); + return result; + } + case (startracker::REQ_CENTROIDING): { + result = prepareRequestCentroidingParams(); + return result; + } + case (startracker::REQ_LISA): { + result = prepareRequestLisaParams(); + return result; + } + case (startracker::REQ_MATCHED_CENTROIDS): { + result = prepareRequestMatchedCentroidsTm(); + return result; + } + case (startracker::REQ_BLOB): { + result = prepareRequestBlobTm(); + return result; + } + case (startracker::REQ_BLOBS): { + result = prepareRequestBlobsTm(); + return result; + } + case (startracker::REQ_AUTO_BLOB): { + result = prepareRequestAutoBlobTm(); + return returnvalue::OK; + } + case (startracker::REQ_MATCHING): { + result = prepareRequestMatchingParams(); + return result; + } + case (startracker::REQ_TRACKING): { + result = prepareRequestTrackingParams(); + return result; + } + case (startracker::REQ_VALIDATION): { + result = prepareRequestValidationParams(); + return result; + } + case (startracker::REQ_ALGO): { + result = prepareRequestAlgoParams(); + return result; + } + case (startracker::REQ_SUBSCRIPTION): { + result = prepareRequestSubscriptionParams(); + return result; + } + case (startracker::REQ_LOG_SUBSCRIPTION): { + result = prepareRequestLogSubscriptionParams(); + return result; + } + case (startracker::REQ_DEBUG_CAMERA): { + result = prepareRequestDebugCameraParams(); + return result; + } + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::FAILED; +} + +void StarTrackerHandler::fillCommandAndReplyMap() { + /** Reply lengths are unknown because of the slip encoding. Thus always maximum reply size + * is specified */ + this->insertInCommandAndReplyMap(startracker::PING_REQUEST, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandMap(startracker::BOOT); + this->insertInCommandAndReplyMap(startracker::REQ_VERSION, 3, &versionSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_TIME, 3, &timeSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandMap(startracker::UPLOAD_IMAGE); + this->insertInCommandMap(startracker::DOWNLOAD_IMAGE); + this->insertInCommandMap(startracker::RELOAD_JSON_CFG_FILE); + this->insertInCommandAndReplyMap(startracker::REQ_POWER, 3, &powerSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_INTERFACE, 3, &interfaceSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + // Reboot has no reply. Star tracker reboots immediately + this->insertInCommandMap(startracker::SWITCH_TO_BOOTLOADER_PROGRAM); + this->insertInCommandAndReplyMap(startracker::SUBSCRIPTION, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_SOLUTION, 3, &solutionSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_TEMPERATURE, 3, &temperatureSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_HISTOGRAM, 3, &histogramSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::LOGLEVEL, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::SET_TIME_FROM_SYS_TIME, 2, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::LOGSUBSCRIPTION, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::DEBUG_CAMERA, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::LIMITS, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::AUTO_THRESHOLD, 2, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::MOUNTING, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::IMAGE_PROCESSOR, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::CAMERA, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::CENTROIDING, 2, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::LISA, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::MATCHING, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::TRACKING, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::VALIDATION, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::ALGO, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::TAKE_IMAGE, 3, nullptr, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::CHECKSUM, 3, &checksumSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_CAMERA, 3, &cameraSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_LIMITS, 3, &limitsSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_LOG_LEVEL, 3, &loglevelSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_MOUNTING, 3, &mountingSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_IMAGE_PROCESSOR, 3, &imageProcessorSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_CENTROIDING, 3, ¢roidingSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_LISA, 3, &lisaSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_MATCHING, 3, &matchingSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_TRACKING, 3, &trackingSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_VALIDATION, 3, &validationSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_ALGO, 3, &algoSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_SUBSCRIPTION, 3, &subscriptionSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_LOG_SUBSCRIPTION, 3, &logSubscriptionSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_DEBUG_CAMERA, 3, &debugCameraSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_AUTO_BLOB, 3, &autoBlobSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_MATCHED_CENTROIDS, 3, &matchedCentroidsSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_BLOB, 3, &blobSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_BLOBS, 3, &blobsSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_CENTROID, 3, ¢roidSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_CENTROIDS, 3, ¢roidsSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_CONTRAST, 3, &contrastSet, + startracker::MAX_FRAME_SIZE * 2 + 2); + this->insertInCommandAndReplyMap(startracker::REQ_BLOB_STATS, 3, &blobStatsSet, + startracker::MAX_FRAME_SIZE * 2 + 2); +} + +ReturnValue_t StarTrackerHandler::isModeCombinationValid(Mode_t mode, Submode_t submode) { + if (getMode() == MODE_NORMAL && mode == MODE_ON) { + return TRANS_NOT_ALLOWED; + } + switch (mode) { + case MODE_OFF: + case MODE_NORMAL: + case MODE_RAW: + if (submode == SUBMODE_NONE) { + return returnvalue::OK; + } else { + return INVALID_SUBMODE; + } + case MODE_ON: + if (submode == startracker::SUBMODE_BOOTLOADER || submode == startracker::SUBMODE_FIRMWARE) { + return returnvalue::OK; + } else { + return INVALID_SUBMODE; + } + default: + return HasModesIF::INVALID_MODE; + } +} + +void StarTrackerHandler::doTransition(Mode_t modeFrom, Submode_t subModeFrom) { + switch (getMode()) { + case _MODE_TO_ON: + doOnTransition(subModeFrom); + break; + case _MODE_TO_RAW: + setMode(MODE_RAW); + break; + case _MODE_TO_NORMAL: + doNormalTransition(modeFrom, subModeFrom); + break; + case _MODE_POWER_DOWN: + internalState = InternalState::IDLE; + startupState = StartupState::IDLE; + break; + default: + break; + } +} + +void StarTrackerHandler::doOnTransition(Submode_t subModeFrom) { + using namespace startracker; + uint8_t dhbSubmode = getSubmode(); + // We hide that the transition to submode firmware actually goes through the submode bootloader. + // This is because the startracker always starts in bootloader mode but we want to allow direct + // transitions to firmware mode. + if (startupState == StartupState::DONE) { + subModeFrom = SUBMODE_BOOTLOADER; + } + if (dhbSubmode == SUBMODE_NONE) { + bootFirmware(MODE_ON); + } + if (dhbSubmode == SUBMODE_BOOTLOADER && subModeFrom == SUBMODE_FIRMWARE) { + bootBootloader(); + } else if (dhbSubmode == SUBMODE_FIRMWARE && subModeFrom == SUBMODE_FIRMWARE) { + setMode(MODE_ON); + } else if (dhbSubmode == SUBMODE_FIRMWARE && subModeFrom == SUBMODE_BOOTLOADER) { + bootFirmware(MODE_ON); + } else if (dhbSubmode == SUBMODE_BOOTLOADER && subModeFrom == SUBMODE_BOOTLOADER) { + setMode(MODE_ON); + } else if (dhbSubmode == SUBMODE_BOOTLOADER && subModeFrom == SUBMODE_NONE) { + setMode(MODE_ON); + } else if (dhbSubmode == SUBMODE_FIRMWARE && subModeFrom == SUBMODE_NONE) { + setMode(MODE_ON); + } +} + +void StarTrackerHandler::doNormalTransition(Mode_t modeFrom, Submode_t subModeFrom) { + using namespace startracker; + if (subModeFrom == SUBMODE_FIRMWARE) { + setMode(MODE_NORMAL); + } else if (subModeFrom == SUBMODE_BOOTLOADER) { + bootFirmware(MODE_NORMAL); + } else if (modeFrom == MODE_NORMAL && subModeFrom == SUBMODE_NONE) { + // Device handler already in mode normal + setMode(MODE_NORMAL); + } +} + +void StarTrackerHandler::bootFirmware(Mode_t toMode) { + switch (internalState) { + case InternalState::IDLE: + sif::info << "STR: Booting to firmware mode" << std::endl; + internalState = InternalState::BOOT_FIRMWARE; + break; + case InternalState::BOOT_FIRMWARE: + break; + case InternalState::FAILED_FIRMWARE_BOOT: + internalState = InternalState::IDLE; + break; + case InternalState::DONE: + if (toMode == MODE_NORMAL) { + setMode(toMode, 0); + } else { + setMode(toMode, startracker::SUBMODE_FIRMWARE); + } + sif::info << "STR: Firmware boot success" << std::endl; + solutionSet.setReportingEnabled(true); + internalState = InternalState::IDLE; + startupState = StartupState::IDLE; + break; + default: + return; + } +} + +void StarTrackerHandler::setUpJsonCfgs(JsonConfigs& cfgs, std::string paramJsonFile) { + cfgs.tracking.init(paramJsonFile); + cfgs.logLevel.init(paramJsonFile); + cfgs.logSubscription.init(paramJsonFile); + cfgs.debugCamera.init(paramJsonFile); + cfgs.algo.init(paramJsonFile); + cfgs.validation.init(paramJsonFile); + cfgs.matching.init(paramJsonFile); + cfgs.lisa.init(paramJsonFile); + cfgs.centroiding.init(paramJsonFile); + cfgs.camera.init(paramJsonFile); + cfgs.imageProcessor.init(paramJsonFile); + cfgs.mounting.init(paramJsonFile); + cfgs.limits.init(paramJsonFile); + cfgs.subscription.init(paramJsonFile); + cfgs.autoThreshold.init(paramJsonFile); + JCFG_DONE = true; +} + +ReturnValue_t StarTrackerHandler::statusFieldCheck(const uint8_t* rawFrame) { + uint8_t status = startracker::getStatusField(rawFrame); + if (status != startracker::STATUS_OK) { + sif::warning << "StarTrackerHandler::handleTm: Reply error: " + << static_cast(status) << std::endl; + return REPLY_ERROR; + } + return returnvalue::OK; +} + +void StarTrackerHandler::addSecondaryTmForNormalMode(DeviceCommandId_t cmd) { + additionalRequestedTm.emplace(cmd); +} + +void StarTrackerHandler::resetSecondaryTmSet() { + additionalRequestedTm.clear(); + additionalRequestedTm.emplace(startracker::REQ_TEMPERATURE); + currentSecondaryTmIter = additionalRequestedTm.begin(); + { + PoolReadGuard pg(&autoBlobSet); + if (pg.getReadResult() == returnvalue::OK) { + autoBlobSet.setValidity(false, true); + } + } + { + PoolReadGuard pg(&matchedCentroidsSet); + if (pg.getReadResult() == returnvalue::OK) { + matchedCentroidsSet.setValidity(false, true); + } + } + { + PoolReadGuard pg(&blobSet); + if (pg.getReadResult() == returnvalue::OK) { + blobSet.setValidity(false, true); + } + } + { + PoolReadGuard pg(&blobsSet); + if (pg.getReadResult() == returnvalue::OK) { + blobsSet.setValidity(false, true); + } + } + { + PoolReadGuard pg(¢roidSet); + if (pg.getReadResult() == returnvalue::OK) { + centroidSet.setValidity(false, true); + } + } + { + PoolReadGuard pg(&contrastSet); + if (pg.getReadResult() == returnvalue::OK) { + contrastSet.setValidity(false, true); + } + } + { + PoolReadGuard pg(¢roidsSet); + if (pg.getReadResult() == returnvalue::OK) { + centroidsSet.setValidity(false, true); + } + } + { + PoolReadGuard pg(&histogramSet); + if (pg.getReadResult() == returnvalue::OK) { + histogramSet.setValidity(false, true); + } + } + { + PoolReadGuard pg(&blobStatsSet); + if (pg.getReadResult() == returnvalue::OK) { + histogramSet.setValidity(false, true); + } + } +} + +void StarTrackerHandler::bootBootloader() { + if (internalState == InternalState::IDLE) { + internalState = InternalState::BOOT_BOOTLOADER; + } else if (internalState == InternalState::FAILED_BOOTLOADER_BOOT) { + internalState = InternalState::IDLE; + } else if (internalState == InternalState::DONE) { + internalState = InternalState::IDLE; + startupState = StartupState::IDLE; + setMode(MODE_ON); + } +} + +ReturnValue_t StarTrackerHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + ReturnValue_t result = returnvalue::OK; + + if (remainingSize == 0) { + *foundLen = remainingSize; + return returnvalue::OK; + } + if (remainingSize < 2) { + sif::error << "StarTrackerHandler: Reply packet with length " << remainingSize + << " less than " + "2 is invalid" + << std::endl; + return returnvalue::FAILED; + } + + switch (startracker::getReplyFrameType(start)) { + case TMTC_COMM_ERROR: { + *foundLen = remainingSize; + triggerEvent(COM_ERROR_REPLY_RECEIVED, start[1]); + break; + } + case TMTC_ACTIONREPLY: { + *foundLen = remainingSize; + fullPacketLen = remainingSize; + return scanForActionReply(startracker::getId(start), foundId); + } + case TMTC_SETPARAMREPLY: { + *foundLen = remainingSize; + fullPacketLen = remainingSize; + return scanForSetParameterReply(startracker::getId(start), foundId); + } + case TMTC_PARAMREPLY: { + *foundLen = remainingSize; + fullPacketLen = remainingSize; + return scanForGetParameterReply(startracker::getId(start), foundId); + } + case TMTC_TELEMETRYREPLYA: + case TMTC_TELEMETRYREPLY: { + *foundLen = remainingSize; + fullPacketLen = remainingSize; + return scanForTmReply(startracker::getId(start), foundId); + } + default: { + sif::debug << "StarTrackerHandler::scanForReply: Reply has invalid type id" << std::endl; + result = returnvalue::FAILED; + } + } + return result; +} + +ReturnValue_t StarTrackerHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t* packet) { + ReturnValue_t result = returnvalue::OK; + + switch (id) { + case (startracker::SET_TIME_FROM_SYS_TIME): { + result = handleActionReply(packet); + break; + } + case (startracker::REQ_TIME): { + result = handleTm(packet, timeSet, "REQ_TIME"); + break; + } + case (startracker::PING_REQUEST): { + result = handlePingReply(packet); + break; + } + case (startracker::BOOT): + case (startracker::TAKE_IMAGE): + break; + case (startracker::CHECKSUM): { + result = handleChecksumReply(packet); + break; + } + case (startracker::REQ_VERSION): { + result = handleTm(packet, versionSet, "REQ_VERSION"); + if (result != returnvalue::OK) { + return result; + } + result = checkProgram(); + if (result != returnvalue::OK) { + return result; + } + break; + } + case (startracker::REQ_INTERFACE): { + result = handleTm(packet, interfaceSet, "REQ_INTERFACE"); + break; + } + case (startracker::REQ_POWER): { + result = handleTm(packet, powerSet, "REQ_POWER"); + break; + } + case (startracker::REQ_SOLUTION): { + result = handleSolution(packet); + break; + } + case (startracker::REQ_CONTRAST): { + result = handleTm(packet, contrastSet, "REQ_CONTRAST"); + break; + } + case (startracker::REQ_AUTO_BLOB): { + result = handleAutoBlobTm(packet); + break; + } + case (startracker::REQ_MATCHED_CENTROIDS): { + result = handleMatchedCentroidTm(packet); + break; + } + case (startracker::REQ_BLOB): { + result = handleBlobTm(packet); + break; + } + case (startracker::REQ_BLOBS): { + result = handleBlobsTm(packet); + break; + } + case (startracker::REQ_CENTROID): { + result = handleCentroidTm(packet); + break; + } + case (startracker::REQ_CENTROIDS): { + result = handleCentroidsTm(packet); + break; + } + case (startracker::REQ_TEMPERATURE): { + result = handleTm(packet, temperatureSet, "REQ_TEMP"); + break; + } + case (startracker::REQ_HISTOGRAM): { + result = handleTm(packet, histogramSet, "REQ_HISTO"); + break; + } + case (startracker::REQ_BLOB_STATS): { + result = handleTm(packet, blobStatsSet, "REQ_BLOB_STATS"); + break; + } + case (startracker::SUBSCRIPTION): + case (startracker::LOGLEVEL): + case (startracker::LOGSUBSCRIPTION): + case (startracker::DEBUG_CAMERA): + case (startracker::LIMITS): + case (startracker::MOUNTING): + case (startracker::CAMERA): + case (startracker::CENTROIDING): + case (startracker::LISA): + case (startracker::MATCHING): + case (startracker::TRACKING): + case (startracker::VALIDATION): + case (startracker::IMAGE_PROCESSOR): + case (startracker::ALGO): + case (startracker::AUTO_THRESHOLD): { + result = handleSetParamReply(packet); + break; + } + case (startracker::REQ_CAMERA): { + handleParamRequest(packet, cameraSet, startracker::CameraSet::SIZE); + break; + } + case (startracker::REQ_LIMITS): { + handleParamRequest(packet, limitsSet, startracker::LimitsSet::SIZE); + break; + } + case (startracker::REQ_LOG_LEVEL): { + handleParamRequest(packet, loglevelSet, startracker::LogLevelSet::SIZE); + break; + } + case (startracker::REQ_MOUNTING): { + handleParamRequest(packet, mountingSet, startracker::MountingSet::SIZE); + break; + } + case (startracker::REQ_IMAGE_PROCESSOR): { + handleParamRequest(packet, imageProcessorSet, startracker::ImageProcessorSet::SIZE); + break; + } + case (startracker::REQ_CENTROIDING): { + handleParamRequest(packet, centroidingSet, startracker::CentroidingSet::SIZE); + break; + } + case (startracker::REQ_LISA): { + handleParamRequest(packet, lisaSet, startracker::LisaSet::SIZE); + break; + } + case (startracker::REQ_MATCHING): { + handleParamRequest(packet, matchingSet, startracker::MatchingSet::SIZE); + break; + } + case (startracker::REQ_TRACKING): { + handleParamRequest(packet, trackingSet, startracker::TrackingSet::SIZE); + break; + } + case (startracker::REQ_VALIDATION): { + handleParamRequest(packet, validationSet, startracker::ValidationSet::SIZE); + break; + } + case (startracker::REQ_ALGO): { + handleParamRequest(packet, algoSet, startracker::AlgoSet::SIZE); + break; + } + case (startracker::REQ_SUBSCRIPTION): { + handleParamRequest(packet, subscriptionSet, startracker::SubscriptionSet::SIZE); + break; + } + case (startracker::REQ_LOG_SUBSCRIPTION): { + handleParamRequest(packet, logSubscriptionSet, startracker::LogSubscriptionSet::SIZE); + break; + } + case (startracker::REQ_DEBUG_CAMERA): { + handleParamRequest(packet, debugCameraSet, startracker::DebugCameraSet::SIZE); + break; + } + default: { + sif::debug << "StarTrackerHandler::interpretDeviceReply: Unknown device reply id:" << id + << std::endl; + result = DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + return result; +} + +void StarTrackerHandler::setNormalDatapoolEntriesInvalid() {} + +uint32_t StarTrackerHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { + return DEFAULT_TRANSITION_DELAY; +} + +ReturnValue_t StarTrackerHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(startracker::TICKS_TIME_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TIME_TIME_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::RUN_TIME, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::UNIX_TIME, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::TICKS_VERSION_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TIME_VERSION_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::PROGRAM, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MAJOR, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MINOR, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::TICKS_INTERFACE_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TIME_INTERFACE_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FRAME_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CHECKSUM_ERROR_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SET_PARAM_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SET_PARAM_REPLY_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::PARAM_REQUEST_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::PARAM_REPLY_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::REQ_TM_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TM_REPLY_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::ACTION_REQ_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::ACTION_REPLY_COUNT, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::TICKS_POWER_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TIME_POWER_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MCU_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MCU_VOLTAGE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FPGA_CORE_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FPGA_CORE_VOLTAGE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FPGA_18_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FPGA_18_VOLTAGE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FPGA_25_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FPGA_25_VOLTAGE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMV_21_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMV_21_VOLTAGE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMV_PIX_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMV_PIX_VOLTAGE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMV_33_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMV_33_VOLTAGE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMV_RES_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMV_RES_VOLTAGE, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::TICKS_TEMPERATURE_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TIME_TEMPERATURE_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MCU_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CMOS_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FPGA_TEMPERATURE, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::TICKS_SOLUTION_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TIME_SOLUTION_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CALI_QW, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CALI_QX, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CALI_QY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CALI_QZ, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACK_CONFIDENCE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACK_QW, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACK_QX, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACK_QY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACK_QZ, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACK_REMOVED, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::STARS_CENTROIDED, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::STARS_MATCHED_DATABASE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_QW, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_QX, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_QY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_QZ, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_PERC_CLOSE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_NR_CLOSE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::STR_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRUST_WORTHY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::STABLE_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SOLUTION_STRATEGY, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::TICKS_HISTOGRAM_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TIME_HISTOGRAM_SET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA0, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA3, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA4, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA5, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA6, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA7, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINA8, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB0, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB3, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB4, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB5, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB6, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB7, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINB8, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC0, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC3, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC4, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC5, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC6, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC7, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BINC8, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND0, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND3, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND4, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND5, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND6, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND7, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::HISTOGRAM_BIND8, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::CAMERA_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::FOCALLENGTH, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::EXPOSURE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::INTERVAL, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAMERA_OFFSET, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::PGAGAIN, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::ADCGAIN, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_REG1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_VAL1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_REG2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_VAL2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_REG3, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_VAL3, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_REG4, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_VAL4, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_REG5, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_VAL5, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_REG6, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_VAL6, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_REG7, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_VAL7, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_REG8, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_VAL8, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CAM_FREQ_1, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::LIMITS_ACTION, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_FPGA18CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_FPGA25CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_FPGA10CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_MCUCURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_CMOS21CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_CMOSPIXCURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_CMOS33CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_CMOSVRESCURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LIMITS_CMOSTEMPERATURE, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::LOGLEVEL1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL3, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL4, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL5, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL6, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL7, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL8, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL9, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL10, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL11, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL12, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL13, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL14, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL15, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOGLEVEL16, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::MOUNTING_QW, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MOUNTING_QX, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MOUNTING_QY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MOUNTING_QZ, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::IMAGE_PROCESSOR_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::IMAGE_PROCESSOR_STORE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::IMAGE_PROCESSOR_SIGNALTHRESHOLD, + new PoolEntry({0})); + localDataPoolMap.emplace(startracker::IMAGE_PROCESSOR_DARKTHRESHOLD, + new PoolEntry({0})); + localDataPoolMap.emplace(startracker::IMAGE_PROCESSOR_BACKGROUNDCOMPENSATION, + new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::CENTROIDING_ENABLE_FILTER, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_MAX_QUALITY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_DARK_THRESHOLD, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_MIN_QUALITY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_MAX_INTENSITY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_MIN_INTENSITY, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_MAX_MAGNITUDE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_GAUSSIAN_CMAX, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_GAUSSIAN_CMIN, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_TRANSMATRIX00, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_TRANSMATRIX01, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_TRANSMATRIX10, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::CENTROIDING_TRANSMATRIX11, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::LISA_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_PREFILTER_DIST_THRESHOLD, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_PREFILTER_ANGLE_THRESHOLD, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_FOV_WIDTH, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_FOV_HEIGHT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_FLOAT_STAR_LIMIT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_CLOSE_STAR_LIMIT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_RATING_WEIGHT_CLOSE_STAR_COUNT, + new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_RATING_WEIGHT_FRACTION_CLOSE, + new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_RATING_WEIGHT_MEAN_SUM, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_RATING_WEIGHT_DB_STAR_COUNT, + new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_MAX_COMBINATIONS, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_NR_STARS_STOP, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LISA_FRACTION_CLOSE_STOP, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::MATCHING_SQUARED_DISTANCE_LIMIT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::MATCHING_SQUARED_SHIFT_LIMIT, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::TRACKING_THIN_LIMIT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACKING_OUTLIER_THRESHOLD, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACKING_OUTLIER_THRESHOLD_QUEST, + new PoolEntry({0})); + localDataPoolMap.emplace(startracker::TRACKING_TRACKER_CHOICE, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::VALIDATION_STABLE_COUNT, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::VALIDATION_MAX_DIFFERENCE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::VALIDATION_MIN_TRACKER_CONFIDENCE, + new PoolEntry({0})); + localDataPoolMap.emplace(startracker::VALIDATION_MIN_MATCHED_STARS, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::ALGO_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::ALGO_I2T_MIN_CONFIDENCE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::ALGO_I2T_MIN_MATCHED, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::ALGO_I2L_MIN_CONFIDENCE, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::ALGO_I2L_MIN_MATCHED, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM3, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM4, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM5, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM6, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM7, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM8, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM9, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM10, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM11, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM12, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM13, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM14, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM15, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::SUBSCRIPTION_TM16, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::LOG_SUBSCRIPTION_LEVEL1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOG_SUBSCRIPTION_MODULE1, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOG_SUBSCRIPTION_LEVEL2, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::LOG_SUBSCRIPTION_MODULE2, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::DEBUG_CAMERA_TIMING, new PoolEntry({0})); + localDataPoolMap.emplace(startracker::DEBUG_CAMERA_TEST, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::CHKSUM, new PoolEntry({0})); + + localDataPoolMap.emplace(startracker::TICKS_AUTO_BLOB, new PoolEntry()); + localDataPoolMap.emplace(startracker::TIME_AUTO_BLOB, new PoolEntry()); + localDataPoolMap.emplace(startracker::AUTO_BLOB_THRESHOLD, new PoolEntry()); + + localDataPoolMap.emplace(startracker::PoolIds::NUM_MATCHED_CENTROIDS, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::MATCHED_CENTROIDS_STAR_IDS, + new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::MATCHED_CENTROIDS_X_COORDS, + new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::MATCHED_CENTROIDS_Y_COORDS, + new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::MATCHED_CENTROIDS_X_ERRORS, + new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::MATCHED_CENTROIDS_Y_ERRORS, + new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::TICKS_MATCHED_CENTROIDS, + new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::TIME_MATCHED_CENTROIDS, new PoolEntry()); + + localDataPoolMap.emplace(startracker::PoolIds::BLOB_TICKS, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::BLOB_TIME, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::BLOB_COUNT, new PoolEntry()); + + localDataPoolMap.emplace(startracker::PoolIds::BLOBS_TICKS, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::BLOBS_TIME, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::BLOBS_COUNT, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::BLOBS_COUNT_USED, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::BLOBS_NR_4LINES_SKIPPED, + new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::BLOBS_X_COORDS, new PoolEntry(8)); + localDataPoolMap.emplace(startracker::PoolIds::BLOBS_Y_COORDS, new PoolEntry(8)); + + localDataPoolMap.emplace(startracker::PoolIds::CENTROID_TICKS, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::CENTROID_TIME, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::CENTROID_COUNT, new PoolEntry()); + + localDataPoolMap.emplace(startracker::PoolIds::CENTROIDS_TICKS, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::CENTROIDS_TIME, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::CENTROIDS_COUNT, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::CENTROIDS_X_COORDS, new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::CENTROIDS_Y_COORDS, new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::CENTROIDS_MAGNITUDES, new PoolEntry(16)); + + localDataPoolMap.emplace(startracker::PoolIds::CONTRAST_TICKS, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::CONTRAST_TIME, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::CONTRAST_A, new PoolEntry(9)); + localDataPoolMap.emplace(startracker::PoolIds::CONTRAST_B, new PoolEntry(9)); + localDataPoolMap.emplace(startracker::PoolIds::CONTRAST_C, new PoolEntry(9)); + localDataPoolMap.emplace(startracker::PoolIds::CONTRAST_D, new PoolEntry(9)); + + localDataPoolMap.emplace(startracker::TICKS_BLOB_STATS, new PoolEntry()); + localDataPoolMap.emplace(startracker::TIME_BLOB_STATS, new PoolEntry()); + localDataPoolMap.emplace(startracker::PoolIds::BLOB_STATS_NOISE, new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::BLOB_STATS_THOLD, new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::BLOB_STATS_LVALID, new PoolEntry(16)); + localDataPoolMap.emplace(startracker::PoolIds::BLOB_STATS_OFLOW, new PoolEntry(16)); + + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(temperatureSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(powerSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(interfaceSet.getSid(), false, 10.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(solutionSet.getSid(), false, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(cameraSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(histogramSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(lisaSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(autoBlobSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(matchedCentroidsSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(blobSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(blobsSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(centroidSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(centroidsSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(contrastSet.getSid(), false, 10.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(blobStatsSet.getSid(), false, 10.0)); + return returnvalue::OK; +} + +size_t StarTrackerHandler::getNextReplyLength(DeviceCommandId_t commandId) { + return startracker::MAX_FRAME_SIZE; +} + +ReturnValue_t StarTrackerHandler::doSendReadHook() { + // Prevent DHB from polling UART during commands executed by the image loader task + if (strHelperHandlingSpecialRequest) { + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches) { + if (powerSwitch == power::NO_SWITCH) { + return DeviceHandlerBase::NO_SWITCH; + } + *numberOfSwitches = 1; + *switches = &powerSwitch; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::checkMode(ActionId_t actionId) { + switch (actionId) { + case startracker::UPLOAD_IMAGE: + case startracker::DOWNLOAD_IMAGE: + case startracker::FLASH_READ: + case startracker::FIRMWARE_UPDATE_BACKUP: + case startracker::FIRMWARE_UPDATE_MAIN: { + return DeviceHandlerBase::acceptExternalDeviceCommands(); + default: + break; + } + } + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::scanForActionReply(uint8_t replyId, DeviceCommandId_t* foundId) { + switch (replyId) { + case (startracker::ID::PING): { + *foundId = startracker::PING_REQUEST; + break; + } + case (startracker::ID::BOOT): { + *foundId = startracker::BOOT; + break; + } + case (startracker::ID::TAKE_IMAGE): { + *foundId = startracker::TAKE_IMAGE; + break; + } + case (startracker::ID::UPLOAD_IMAGE): { + *foundId = startracker::UPLOAD_IMAGE; + break; + } + case (ARC_ACTION_REQ_SETTIME_ID): { + *foundId = startracker::SET_TIME_FROM_SYS_TIME; + break; + } + case (startracker::ID::CHECKSUM): { + *foundId = startracker::CHECKSUM; + break; + } + default: + sif::warning << "StarTrackerHandler::scanForActionReply: Unknown parameter reply id" + << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::scanForSetParameterReply(uint8_t replyId, + DeviceCommandId_t* foundId) { + switch (replyId) { + case (startracker::ID::SUBSCRIPTION): { + *foundId = startracker::SUBSCRIPTION; + break; + } + case (startracker::ID::LIMITS): { + *foundId = startracker::LIMITS; + break; + } + case (startracker::ID::MOUNTING): { + *foundId = startracker::MOUNTING; + break; + } + case (startracker::ID::IMAGE_PROCESSOR): { + *foundId = startracker::IMAGE_PROCESSOR; + break; + } + case (startracker::ID::CAMERA): { + *foundId = startracker::CAMERA; + break; + } + case (startracker::ID::CENTROIDING): { + *foundId = startracker::CENTROIDING; + break; + } + case (startracker::ID::LISA): { + *foundId = startracker::LISA; + break; + } + case (startracker::ID::MATCHING): { + *foundId = startracker::MATCHING; + break; + } + case (startracker::ID::TRACKING): { + *foundId = startracker::TRACKING; + break; + } + case (startracker::ID::VALIDATION): { + *foundId = startracker::VALIDATION; + break; + } + case (startracker::ID::ALGO): { + *foundId = startracker::ALGO; + break; + } + case (startracker::ID::LOG_LEVEL): { + *foundId = startracker::LOGLEVEL; + break; + } + case (startracker::ID::DEBUG_CAMERA): { + *foundId = startracker::DEBUG_CAMERA; + break; + } + case (startracker::ID::AUTO_THRESHOLD): { + *foundId = startracker::AUTO_THRESHOLD; + break; + } + case (startracker::ID::LOG_SUBSCRIPTION): { + *foundId = startracker::LOGSUBSCRIPTION; + break; + } + default: + sif::debug << "StarTrackerHandler::scanForParameterReply: Unknown parameter reply id" + << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::scanForGetParameterReply(uint8_t replyId, + DeviceCommandId_t* foundId) { + switch (replyId) { + case (startracker::ID::CAMERA): { + *foundId = startracker::REQ_CAMERA; + break; + } + case (startracker::ID::LIMITS): { + *foundId = startracker::REQ_LIMITS; + break; + } + case (startracker::ID::LOG_LEVEL): { + *foundId = startracker::REQ_LOG_LEVEL; + break; + } + case (startracker::ID::MOUNTING): { + *foundId = startracker::REQ_MOUNTING; + break; + } + case (startracker::ID::IMAGE_PROCESSOR): { + *foundId = startracker::REQ_IMAGE_PROCESSOR; + break; + } + case (startracker::ID::CENTROIDING): { + *foundId = startracker::REQ_CENTROIDING; + break; + } + case (startracker::ID::LISA): { + *foundId = startracker::REQ_LISA; + break; + } + case (startracker::ID::MATCHING): { + *foundId = startracker::REQ_MATCHING; + break; + } + case (startracker::ID::TRACKING): { + *foundId = startracker::REQ_TRACKING; + break; + } + case (startracker::ID::VALIDATION): { + *foundId = startracker::REQ_VALIDATION; + break; + } + case (startracker::ID::ALGO): { + *foundId = startracker::REQ_ALGO; + break; + } + case (startracker::ID::SUBSCRIPTION): { + *foundId = startracker::REQ_SUBSCRIPTION; + break; + } + case (startracker::ID::LOG_SUBSCRIPTION): { + *foundId = startracker::REQ_LOG_SUBSCRIPTION; + break; + } + case (startracker::ID::DEBUG_CAMERA): { + *foundId = startracker::REQ_DEBUG_CAMERA; + break; + } + default: { + sif::warning << "tarTrackerHandler::scanForGetParameterReply: UnkNown ID" << std::endl; + return returnvalue::FAILED; + break; + } + } + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::scanForTmReply(uint8_t replyId, DeviceCommandId_t* foundId) { + switch (replyId) { + case (startracker::ID::VERSION): { + *foundId = startracker::REQ_VERSION; + break; + } + case (startracker::ID::INTERFACE): { + *foundId = startracker::REQ_INTERFACE; + break; + } + case (startracker::ID::POWER): { + *foundId = startracker::REQ_POWER; + break; + } + case (startracker::ID::TEMPERATURE): { + *foundId = startracker::REQ_TEMPERATURE; + break; + } + case (startracker::ID::HISTOGRAM): { + *foundId = startracker::REQ_HISTOGRAM; + break; + } + case (startracker::ID::TIME): { + *foundId = startracker::REQ_TIME; + break; + } + case (startracker::ID::SOLUTION): { + *foundId = startracker::REQ_SOLUTION; + break; + } + case (startracker::ID::BLOB): { + *foundId = startracker::REQ_BLOB; + break; + } + case (startracker::ID::BLOB_STATS): { + *foundId = startracker::REQ_BLOB_STATS; + break; + } + case (startracker::ID::BLOBS): { + *foundId = startracker::REQ_BLOBS; + break; + } + case (startracker::ID::CENTROID): { + *foundId = startracker::REQ_CENTROID; + break; + } + case (startracker::ID::CENTROIDS): { + *foundId = startracker::REQ_CENTROIDS; + break; + } + case (startracker::ID::AUTO_BLOB): { + *foundId = startracker::REQ_AUTO_BLOB; + break; + } + case (startracker::ID::MATCHED_CENTROIDS): { + *foundId = startracker::REQ_MATCHED_CENTROIDS; + break; + } + case (startracker::ID::CONTRAST): { + *foundId = startracker::REQ_CONTRAST; + break; + } + default: { + sif::debug << "StarTrackerHandler::scanForTmReply: Reply contains invalid reply ID: " + << static_cast(replyId) << std::endl; + return returnvalue::FAILED; + break; + } + } + return returnvalue::OK; +} + +void StarTrackerHandler::handleEvent(EventMessage* eventMessage) { + object_id_t objectId = eventMessage->getReporter(); + switch (objectId) { + case objects::STR_COM_IF: { + // All events from image loader signal either that the operation was successful or that it + // failed + strHelperHandlingSpecialRequest = false; + break; + } + default: + sif::debug << "StarTrackerHandler::handleEvent: Did not subscribe to this event" << std::endl; + break; + } +} + +ReturnValue_t StarTrackerHandler::executeFlashReadCommand(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = returnvalue::OK; + if (commandDataLen < FlashReadCmd::MIN_LENGTH) { + sif::warning << "StarTrackerHandler::executeFlashReadCommand: Command too short" << std::endl; + return COMMAND_TOO_SHORT; + } + uint8_t startRegion = *(commandData); + uint32_t length; + size_t size = sizeof(length); + const uint8_t* lengthPtr = commandData + sizeof(startRegion); + result = SerializeAdapter::deSerialize(&length, lengthPtr, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::debug << "StarTrackerHandler::executeFlashReadCommand: Deserialization of length failed" + << std::endl; + return result; + } + if (commandDataLen - sizeof(startRegion) - sizeof(length) > config::MAX_PATH_SIZE) { + sif::warning << "StarTrackerHandler::executeFlashReadCommand: Received command with invalid" + << " path and filename" << std::endl; + return FILE_PATH_TOO_LONG; + } + const uint8_t* filePtr = commandData + sizeof(startRegion) + sizeof(length); + std::string fullname = std::string(reinterpret_cast(filePtr), + commandDataLen - sizeof(startRegion) - sizeof(length)); + result = strHelper->startFlashRead(fullname, startRegion, length); + if (result != returnvalue::OK) { + return result; + } + return result; +} + +void StarTrackerHandler::prepareBootCommand(startracker::FirmwareTarget target) { + uint32_t length = 0; + struct BootActionRequest bootRequest = {static_cast(target)}; + prv_arc_pack_boot_action_req(&bootRequest, commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +ReturnValue_t StarTrackerHandler::prepareChecksumCommand(const uint8_t* commandData, + size_t commandDataLen) { + struct ChecksumActionRequest req; + ReturnValue_t result = returnvalue::OK; + if (commandDataLen != ChecksumCmd::LENGTH) { + sif::warning << "StarTrackerHandler::prepareChecksumCommand: Invalid length" << std::endl; + return INVALID_LENGTH; + } + req.region = *(commandData); + size_t size = sizeof(req.address); + const uint8_t* addressPtr = commandData + ChecksumCmd::ADDRESS_OFFSET; + result = + SerializeAdapter::deSerialize(&req.address, addressPtr, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::debug << "StarTrackerHandler::prepareChecksumCommand: Deserialization of address " + << "failed" << std::endl; + return result; + } + size = sizeof(req.length); + const uint8_t* lengthPtr = commandData + ChecksumCmd::LENGTH_OFFSET; + result = + SerializeAdapter::deSerialize(&req.length, lengthPtr, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::debug << "StarTrackerHandler::prepareChecksumCommand: Deserialization of length failed" + << std::endl; + return result; + } + uint32_t rawCmdLength = 0; + prv_arc_pack_checksum_action_req(&req, commandBuffer, &rawCmdLength); + rawPacket = commandBuffer; + rawPacketLen = rawCmdLength; + checksumCmd.rememberRegion = req.region; + checksumCmd.rememberAddress = req.address; + checksumCmd.rememberLength = req.length; + return result; +} + +void StarTrackerHandler::prepareTimeRequest() { + uint32_t length = 0; + arc_tm_pack_time_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::preparePingRequest() { + uint32_t length = 0; + struct PingActionRequest pingRequest = {PING_ID}; + prv_arc_pack_ping_action_req(&pingRequest, commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::prepareVersionRequest() { + uint32_t length = 0; + arc_tm_pack_version_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::prepareInterfaceRequest() { + uint32_t length = 0; + arc_tm_pack_interface_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::preparePowerRequest() { + uint32_t length = 0; + arc_tm_pack_power_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::prepareSwitchToBootloaderCmd() { + uint32_t length = 0; + struct RebootActionRequest rebootReq{}; + prv_arc_pack_reboot_action_req(&rebootReq, commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::prepareTakeImageCommand(const uint8_t* commandData) { + uint32_t length = 0; + struct CameraActionRequest camReq; + camReq.actionid = *commandData; + prv_arc_pack_camera_action_req(&camReq, commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::prepareSolutionRequest() { + uint32_t length = 0; + arc_tm_pack_solution_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::prepareTemperatureRequest() { + uint32_t length = 0; + arc_tm_pack_temperature_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +void StarTrackerHandler::prepareHistogramRequest() { + uint32_t length = 0; + arc_tm_pack_histogram_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; +} + +ReturnValue_t StarTrackerHandler::prepareRequestAutoBlobTm() { + uint32_t length = 0; + arc_tm_pack_autoblob_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestMatchedCentroidsTm() { + uint32_t length = 0; + arc_tm_pack_matchedcentroids_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestBlobTm() { + uint32_t length = 0; + arc_tm_pack_blob_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestBlobsTm() { + uint32_t length = 0; + arc_tm_pack_blobs_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestCentroidTm() { + uint32_t length = 0; + arc_tm_pack_centroid_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestBlobStatsTm() { + uint32_t length = 0; + arc_tm_pack_blobstats_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestCentroidsTm() { + uint32_t length = 0; + arc_tm_pack_centroids_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestContrastTm() { + uint32_t length = 0; + arc_tm_pack_contrast_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareParamCommand(const uint8_t* commandData, + size_t commandDataLen, + ArcsecJsonParamBase& paramSet, + bool reinitSet) { + // Stopwatch watch; + ReturnValue_t result = returnvalue::OK; + if (commandDataLen > config::MAX_PATH_SIZE) { + return FILE_PATH_TOO_LONG; + } + if (reinitSet) { + result = paramSet.init(paramJsonFile); + if (result != returnvalue::OK) { + return result; + } + } + + result = paramSet.create(commandBuffer); + if (result != returnvalue::OK) { + return result; + } + rawPacket = commandBuffer; + rawPacketLen = paramSet.getSize(); + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestCameraParams() { + uint32_t length = 0; + arc_pack_camera_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestLimitsParams() { + uint32_t length = 0; + arc_pack_limits_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestLogLevelParams() { + uint32_t length = 0; + arc_pack_loglevel_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestMountingParams() { + uint32_t length = 0; + arc_pack_mounting_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestImageProcessorParams() { + uint32_t length = 0; + arc_pack_imageprocessor_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestCentroidingParams() { + uint32_t length = 0; + arc_pack_centroiding_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestLisaParams() { + uint32_t length = 0; + arc_pack_lisa_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestMatchingParams() { + uint32_t length = 0; + arc_pack_matching_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestTrackingParams() { + uint32_t length = 0; + arc_pack_tracking_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestValidationParams() { + uint32_t length = 0; + arc_pack_validation_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestAlgoParams() { + uint32_t length = 0; + arc_pack_algo_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestSubscriptionParams() { + uint32_t length = 0; + arc_pack_subscription_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestLogSubscriptionParams() { + uint32_t length = 0; + arc_pack_logsubscription_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::prepareRequestDebugCameraParams() { + uint32_t length = 0; + arc_pack_debugcamera_parameter_req(commandBuffer, &length); + rawPacket = commandBuffer; + rawPacketLen = length; + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleSetParamReply(const uint8_t* rawFrame) { + uint8_t status = startracker::getStatusField(rawFrame); + if (status != startracker::STATUS_OK) { + sif::warning << "StarTrackerHandler::handleSetParamReply: Failed to execute parameter set " + "command with parameter ID " + << static_cast(*(rawFrame + PARAMETER_ID_OFFSET)) << std::endl; + if (internalState != InternalState::IDLE) { + internalState = InternalState::IDLE; + } + return SET_PARAM_FAILED; + } + if (internalState != InternalState::IDLE) { + handleStartup(*rawFrame, *(rawFrame + PARAMETER_ID_OFFSET)); + } + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleActionReply(const uint8_t* rawFrame) { + uint8_t status = startracker::getStatusField(rawFrame); + ReturnValue_t result = returnvalue::OK; + if (status != startracker::STATUS_OK) { + sif::warning << "StarTrackerHandler::handleActionReply: Failed to execute action " + << "command with action ID " + << static_cast(*(rawFrame + ACTION_ID_OFFSET)) << " and status " + << static_cast(status) << std::endl; + result = ACTION_FAILED; + } + if (internalState != InternalState::IDLE) { + handleStartup(*rawFrame, *(rawFrame + PARAMETER_ID_OFFSET)); + } + return result; +} + +ReturnValue_t StarTrackerHandler::handleChecksumReply(const uint8_t* rawFrame) { + ReturnValue_t result = returnvalue::OK; + result = handleActionReply(rawFrame); + if (result != returnvalue::OK) { + return result; + } + const uint8_t* replyData = rawFrame + ACTION_DATA_OFFSET; + startracker::ChecksumReply checksumReply(replyData); + if (checksumReply.getRegion() != checksumCmd.rememberRegion) { + sif::warning << "StarTrackerHandler::handleChecksumReply: Region mismatch" << std::endl; + return REGION_MISMATCH; + } + if (checksumReply.getAddress() != checksumCmd.rememberAddress) { + sif::warning << "StarTrackerHandler::handleChecksumReply: Address mismatch" << std::endl; + return ADDRESS_MISMATCH; + } + if (checksumReply.getLength() != checksumCmd.rememberLength) { + sif::warning << "StarTrackerHandler::handleChecksumReply: Length mismatch" << std::endl; + return LENGTH_MISSMATCH; + } + PoolReadGuard rg(&checksumSet); + checksumSet.checksum = checksumReply.getChecksum(); + handleDeviceTm(checksumSet, startracker::CHECKSUM); +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_STARTRACKER == 1 + checksumReply.printChecksum(); +#endif /* OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_STARTRACKER == 1 */ + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleParamRequest(const uint8_t* rawFrame, + LocalPoolDataSetBase& dataset, size_t size) { + ReturnValue_t result = returnvalue::OK; + result = dataset.read(TIMEOUT_TYPE, MUTEX_TIMEOUT); + if (result != returnvalue::OK) { + return result; + } + const uint8_t* reply = rawFrame + PARAMS_OFFSET; + dataset.setValidityBufferGeneration(false); + result = dataset.deSerialize(&reply, &size, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + sif::warning << "StarTrackerHandler::handleParamRequest Deserialization failed" << std::endl; + } + dataset.setValidityBufferGeneration(true); + dataset.setValidity(true, true); + result = dataset.commit(TIMEOUT_TYPE, MUTEX_TIMEOUT); + if (result != returnvalue::OK) { + return result; + } +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_STARTRACKER == 1 + dataset.printSet(); +#endif + return result; +} + +ReturnValue_t StarTrackerHandler::handlePingReply(const uint8_t* rawFrame) { + ReturnValue_t result = returnvalue::OK; + uint32_t pingId = 0; + uint8_t status = startracker::getStatusField(rawFrame); + const uint8_t* buffer = rawFrame + ACTION_DATA_OFFSET; + size_t size = sizeof(pingId); + SerializeAdapter::deSerialize(&pingId, &buffer, &size, SerializeIF::Endianness::LITTLE); + sif::info << "StarTracker: Ping status: " << static_cast(status) << std::endl; + sif::info << "Ping ID: 0x" << std::hex << pingId << std::endl; + if (status != startracker::STATUS_OK || pingId != PING_ID) { + sif::warning << "STR: Ping failed" << std::endl; + result = PING_FAILED; + } else { + sif::info << "STR: Ping OK" << std::endl; + } + return result; +} + +ReturnValue_t StarTrackerHandler::checkProgram() { + PoolReadGuard pg(&versionSet); + switch (versionSet.program.value) { + case startracker::Program::BOOTLOADER: + if (startupState == StartupState::WAIT_CHECK_PROGRAM) { + startupState = StartupState::DONE; + } + if (bootState == FwBootState::VERIFY_BOOT) { + sif::warning << "StarTrackerHandler::checkProgram: Failed to boot firmware" << std::endl; + // Device handler will run into timeout and fall back to transition source mode + triggerEvent(BOOTING_FIRMWARE_FAILED_EVENT); + internalState = InternalState::FAILED_FIRMWARE_BOOT; + } else if (internalState == InternalState::BOOTLOADER_CHECK) { + internalState = InternalState::DONE; + } + break; + case startracker::Program::FIRMWARE_BACKUP: + case startracker::Program::FIRMWARE_MAIN: { + if (startupState == StartupState::WAIT_CHECK_PROGRAM) { + startupState = StartupState::BOOT_BOOTLOADER; + } + if (bootState == FwBootState::VERIFY_BOOT) { + bootState = FwBootState::SET_TIME; + } else if (internalState == InternalState::BOOTLOADER_CHECK) { + triggerEvent(BOOTING_BOOTLOADER_FAILED_EVENT); + internalState = InternalState::FAILED_BOOTLOADER_BOOT; + } + break; + } + default: + sif::warning << "StarTrackerHandler::checkProgram: Version set has invalid program ID " + << static_cast(versionSet.program.value) << std::endl; + return INVALID_PROGRAM; + } + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleTm(const uint8_t* rawFrame, LocalPoolDataSetBase& dataset, + const char* context) { + ReturnValue_t result = statusFieldCheck(rawFrame); + if (result != returnvalue::OK) { + return result; + } + result = dataset.read(TIMEOUT_TYPE, MUTEX_TIMEOUT); + if (result != returnvalue::OK) { + return result; + } + const uint8_t* reply = rawFrame + TICKS_OFFSET; + dataset.setValidityBufferGeneration(false); + size_t sizeLeft = fullPacketLen; + result = dataset.deSerialize(&reply, &sizeLeft, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + sif::warning << "StarTrackerHandler::handleTm: Deserialization failed for " << context << ": 0x" + << std::hex << std::setw(4) << result << std::dec << std::endl; + } + dataset.setValidityBufferGeneration(true); + dataset.setValidity(true, true); + result = dataset.commit(TIMEOUT_TYPE, MUTEX_TIMEOUT); + if (result != returnvalue::OK) { + return result; + } +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_STARTRACKER == 1 + dataset.printSet(); +#endif + return result; +} + +ReturnValue_t StarTrackerHandler::handleSolution(const uint8_t* rawFrame) { + ReturnValue_t result = statusFieldCheck(rawFrame); + if (result != returnvalue::OK) { + return result; + } + PoolReadGuard pg(&solutionSet); + if (pg.getReadResult() != returnvalue::OK) { + return result; + } + const uint8_t* reply = rawFrame + TICKS_OFFSET; + solutionSet.setValidityBufferGeneration(false); + size_t sizeLeft = fullPacketLen; + result = solutionSet.deSerialize(&reply, &sizeLeft, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + sif::warning << "StarTrackerHandler::handleTm: Deserialization failed for solution set: 0x" + << std::hex << std::setw(4) << result << std::dec << std::endl; + } + solutionSet.setValidityBufferGeneration(true); + solutionSet.setValidity(true, true); + solutionSet.caliQw.setValid(solutionSet.isTrustWorthy.value); + solutionSet.caliQx.setValid(solutionSet.isTrustWorthy.value); + solutionSet.caliQy.setValid(solutionSet.isTrustWorthy.value); + solutionSet.caliQz.setValid(solutionSet.isTrustWorthy.value); + solutionSet.trackQw.setValid(solutionSet.isTrustWorthy.value); + solutionSet.trackQx.setValid(solutionSet.isTrustWorthy.value); + solutionSet.trackQy.setValid(solutionSet.isTrustWorthy.value); + solutionSet.trackQz.setValid(solutionSet.isTrustWorthy.value); + return result; +} + +ReturnValue_t StarTrackerHandler::handleAutoBlobTm(const uint8_t* rawFrame) { + ReturnValue_t result = statusFieldCheck(rawFrame); + if (result != returnvalue::OK) { + return result; + } + rawFrame += TICKS_OFFSET; + size_t remainingLen = fullPacketLen; + PoolReadGuard pg(&autoBlobSet); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&autoBlobSet.ticks, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&autoBlobSet.timeUs, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&autoBlobSet.threshold, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + autoBlobSet.setValidity(true, true); + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleMatchedCentroidTm(const uint8_t* rawFrame) { + ReturnValue_t result = statusFieldCheck(rawFrame); + if (result != returnvalue::OK) { + return result; + } + rawFrame += TICKS_OFFSET; + size_t remainingLen = fullPacketLen; + PoolReadGuard pg(&matchedCentroidsSet); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&matchedCentroidsSet.ticks, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&matchedCentroidsSet.timeUs, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&matchedCentroidsSet.numberOfMatchedCentroids, &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + + if (result != returnvalue::OK) { + return result; + } + // Yeah, we serialize it like that because I can't model anything with that local datapool crap. + for (unsigned idx = 0; idx < 16; idx++) { + result = SerializeAdapter::deSerialize(&matchedCentroidsSet.starIds[idx], &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&matchedCentroidsSet.xCoords[idx], &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&matchedCentroidsSet.yCoords[idx], &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&matchedCentroidsSet.xErrors[idx], &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&matchedCentroidsSet.yErrors[idx], &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + } + matchedCentroidsSet.setValidity(true, true); + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleBlobTm(const uint8_t* rawFrame) { + ReturnValue_t result = statusFieldCheck(rawFrame); + if (result != returnvalue::OK) { + return result; + } + rawFrame += TICKS_OFFSET; + size_t remainingLen = fullPacketLen; + PoolReadGuard pg(&blobsSet); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobSet.ticks, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobSet.timeUs, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobSet.blobCount, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + blobSet.setValidity(true, true); + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleBlobsTm(const uint8_t* rawFrame) { + ReturnValue_t result = statusFieldCheck(rawFrame); + if (result != returnvalue::OK) { + return result; + } + rawFrame += TICKS_OFFSET; + size_t remainingLen = fullPacketLen; + PoolReadGuard pg(&blobsSet); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobsSet.ticks, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobsSet.timeUs, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobsSet.blobsCount, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobsSet.blobsCountUsed, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobsSet.nr4LinesSkipped, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + for (unsigned idx = 0; idx < 8; idx++) { + result = SerializeAdapter::deSerialize(&blobsSet.xCoords[idx], &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(&blobsSet.yCoords[idx], &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + } + blobsSet.setValidity(true, true); + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleCentroidTm(const uint8_t* rawFrame) { + ReturnValue_t result = statusFieldCheck(rawFrame); + if (result != returnvalue::OK) { + return result; + } + rawFrame += TICKS_OFFSET; + size_t remainingLen = fullPacketLen; + PoolReadGuard pg(¢roidsSet); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + return result; + } + + result = SerializeAdapter::deSerialize(¢roidSet.ticks, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(¢roidSet.timeUs, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(¢roidSet.centroidCount, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + centroidSet.setValidity(true, true); + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleCentroidsTm(const uint8_t* rawFrame) { + ReturnValue_t result = statusFieldCheck(rawFrame); + if (result != returnvalue::OK) { + return result; + } + rawFrame += TICKS_OFFSET; + size_t remainingLen = fullPacketLen; + PoolReadGuard pg(¢roidsSet); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + return result; + } + + result = SerializeAdapter::deSerialize(¢roidsSet.ticksCentroidsTm, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(¢roidsSet.timeUsCentroidsTm, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(¢roidsSet.centroidsCount, &rawFrame, &remainingLen, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + for (unsigned idx = 0; idx < 16; idx++) { + result = SerializeAdapter::deSerialize(¢roidsSet.centroidsXCoords[idx], &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(¢roidsSet.centroidsYCoords[idx], &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + result = SerializeAdapter::deSerialize(¢roidsSet.centroidsMagnitudes[idx], &rawFrame, + &remainingLen, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + return result; + } + } + centroidsSet.setValidity(true, true); + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::handleActionReplySet(const uint8_t* rawFrame, + LocalPoolDataSetBase& dataset, size_t size) { + ReturnValue_t result = returnvalue::OK; + uint8_t status = startracker::getStatusField(rawFrame); + if (status != startracker::STATUS_OK) { + sif::warning << "StarTrackerHandler::handleActionReplySet: Reply error: " + << static_cast(status) << std::endl; + return REPLY_ERROR; + } + result = dataset.read(TIMEOUT_TYPE, MUTEX_TIMEOUT); + if (result != returnvalue::OK) { + return result; + } + const uint8_t* reply = rawFrame + ACTION_DATA_OFFSET; + dataset.setValidityBufferGeneration(false); + result = dataset.deSerialize(&reply, &size, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + sif::warning << "StarTrackerHandler::handleActionReplySet Deserialization failed" << std::endl; + } + dataset.setValidityBufferGeneration(true); + dataset.setValidity(true, true); + result = dataset.commit(TIMEOUT_TYPE, MUTEX_TIMEOUT); + if (result != returnvalue::OK) { + return result; + } +#if OBSW_VERBOSE_LEVEL >= 1 && OBSW_DEBUG_STARTRACKER == 1 + dataset.printSet(); +#endif + return result; +} + +void StarTrackerHandler::handleStartup(uint8_t tmType, uint8_t parameterId) { + switch (tmType) { + case (TMTC_ACTIONREPLY): { + case (ARC_ACTION_REQ_SETTIME_ID): { + bootState = FwBootState::LOGLEVEL; + return; + } + default: { + break; + } + } + } + switch (parameterId) { + case (startracker::ID::LOG_LEVEL): { + bootState = FwBootState::LIMITS; + break; + } + case (startracker::ID::LIMITS): { + bootState = FwBootState::TRACKING; + break; + } + case (ARC_PARAM_TRACKING_ID): { + bootState = FwBootState::MOUNTING; + break; + } + case (startracker::ID::MOUNTING): { + bootState = FwBootState::IMAGE_PROCESSOR; + break; + } + case (startracker::ID::IMAGE_PROCESSOR): { + bootState = FwBootState::CAMERA; + break; + } + case (startracker::ID::CAMERA): { + bootState = FwBootState::CENTROIDING; + break; + } + case (startracker::ID::CENTROIDING): { + bootState = FwBootState::LISA; + break; + } + case (startracker::ID::LISA): { + bootState = FwBootState::MATCHING; + break; + } + case (startracker::ID::MATCHING): { + bootState = FwBootState::VALIDATION; + break; + } + case (startracker::ID::VALIDATION): { + bootState = FwBootState::ALGO; + break; + } + case (startracker::ID::ALGO): { + bootState = FwBootState::LOG_SUBSCRIPTION; + break; + } + case (startracker::ID::LOG_SUBSCRIPTION): { + bootState = FwBootState::DEBUG_CAMERA; + break; + } + case (startracker::ID::DEBUG_CAMERA): { + bootState = FwBootState::AUTO_THRESHOLD; + break; + } + case (startracker::ID::AUTO_THRESHOLD): { + bootState = FwBootState::NONE; + internalState = InternalState::DONE; + break; + } + default: { + sif::warning << "StarTrackerHandler::handleStartup: Received parameter reply with unexpected" + << " parameter ID " << (int)parameterId << std::endl; + break; + } + } +} + +ReturnValue_t StarTrackerHandler::checkCommand(ActionId_t actionId) { + switch (actionId) { + case startracker::REQ_INTERFACE: + case startracker::REQ_TIME: + case startracker::SWITCH_TO_BOOTLOADER_PROGRAM: + case startracker::DOWNLOAD_IMAGE: + case startracker::UPLOAD_IMAGE: + case startracker::REQ_POWER: + case startracker::TAKE_IMAGE: + case startracker::REQ_SOLUTION: + case startracker::REQ_TEMPERATURE: + case startracker::REQ_HISTOGRAM: + case startracker::REQ_CAMERA: + case startracker::REQ_LIMITS: + case startracker::REQ_LOG_LEVEL: + case startracker::REQ_MOUNTING: + case startracker::REQ_IMAGE_PROCESSOR: + case startracker::REQ_CENTROIDING: + case startracker::REQ_LISA: + case startracker::REQ_MATCHING: + case startracker::REQ_TRACKING: + case startracker::REQ_VALIDATION: + case startracker::REQ_ALGO: + case startracker::REQ_SUBSCRIPTION: + case startracker::REQ_LOG_SUBSCRIPTION: + case startracker::REQ_DEBUG_CAMERA: + case startracker::REQ_MATCHED_CENTROIDS: + case startracker::REQ_BLOB: + case startracker::REQ_BLOBS: + case startracker::REQ_BLOB_STATS: + case startracker::REQ_CENTROID: + case startracker::REQ_CENTROIDS: + case startracker::REQ_CONTRAST: { + if (getMode() == MODE_ON and getSubmode() != startracker::SUBMODE_FIRMWARE) { + return STARTRACKER_NOT_RUNNING_FIRMWARE; + } + break; + } + case startracker::FIRMWARE_UPDATE_MAIN: + case startracker::FIRMWARE_UPDATE_BACKUP: + case startracker::FLASH_READ: + if (getMode() != MODE_ON or getSubmode() != startracker::SUBMODE_BOOTLOADER) { + return STARTRACKER_NOT_RUNNING_BOOTLOADER; + } + break; + default: + break; + } + return returnvalue::OK; +} + +ReturnValue_t StarTrackerHandler::acceptExternalDeviceCommands() { return returnvalue::OK; } + +ReturnValue_t StarTrackerHandler::getParameter(uint8_t domainId, uint8_t uniqueId, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, + uint16_t startAtIndex) { + auto firmwareTargetUpdate = [&](bool persistent) { + uint8_t value = 0; + newValues->getElement(&value); + if (value != static_cast(startracker::FirmwareTarget::MAIN) && + value != static_cast(startracker::FirmwareTarget::BACKUP)) { + return HasParametersIF::INVALID_VALUE; + } + parameterWrapper->set(firmwareTargetRaw); + if (persistent) { + if (sdCardIF.isSdCardUsable(std::nullopt)) { + const char* prefix = sdCardIF.getCurrentMountPrefix(); + std::filesystem::path path = + std::filesystem::path(prefix) / startracker::FW_TARGET_CFG_PATH; + std::ofstream of(path, std::ofstream::out | std::ofstream::trunc); + if (value == static_cast(startracker::FirmwareTarget::MAIN)) { + of << "main\n"; + } else { + of << "backup\n"; + } + } else { + return HasFileSystemIF::FILESYSTEM_INACTIVE; + } + }; + return returnvalue::OK; + }; + if (uniqueId == startracker::ParamId::FIRMWARE_TARGET) { + return firmwareTargetUpdate(false); + } + if (uniqueId == startracker::ParamId::FIRMWARE_TARGET_PERSISTENT) { + return firmwareTargetUpdate(true); + } + return DeviceHandlerBase::getParameter(domainId, uniqueId, parameterWrapper, newValues, + startAtIndex); +} diff --git a/mission/acs/str/StarTrackerHandler.h b/mission/acs/str/StarTrackerHandler.h new file mode 100644 index 0000000..acad969 --- /dev/null +++ b/mission/acs/str/StarTrackerHandler.h @@ -0,0 +1,563 @@ +#ifndef MISSION_DEVICES_STARTRACKERHANDLER_H_ +#define MISSION_DEVICES_STARTRACKERHANDLER_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "fsfw/timemanager/Countdown.h" + +extern "C" { +#include +} + +/** + * @brief This is the device handler for the star tracker from arcsec. + * + * @details Datasheet: https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_IRS/ + * Arbeitsdaten/08_Used%20Components/ArcSec_KULeuven_Startracker/ + * Sagitta%201.0%20Datapack&fileid=659181 + * @note The STR code is a chaotic inconsistent mess and should be re-written with a simpler base + * class. DO NOT USE THIS AS REFERENCE. Stay away from it. + * @author J. Meier, R. Mueller + */ +class StarTrackerHandler : public DeviceHandlerBase { + public: + /** + * @brief Constructor + * + * @param objectId + * @param comIF + * @param comCookie + * @param gpioComIF Pointer to gpio communication interface + * @param enablePin GPIO connected to the enable pin of the reaction wheels. Must be pulled + * to high to enable the device. + */ + StarTrackerHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + StrComHandler* strHelper, power::Switch_t powerSwitch, + startracker::SdCardConfigPathGetter& cfgPathGetter, SdCardMountedIF& sdCardIF); + virtual ~StarTrackerHandler(); + + ReturnValue_t initialize() override; + + /** + * @brief Overwrite this function from DHB to handle commands executed by the str image + * loader task. + */ + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + + void performOperationHook() override; + + Submode_t getInitialSubmode() override; + + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueId, ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, uint16_t startAtIndex) override; + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + void setNormalDatapoolEntriesInvalid() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + /** + * @brief Overwritten here to always read all available data from theSerialComIF. + */ + virtual size_t getNextReplyLength(DeviceCommandId_t deviceCommand) override; + virtual ReturnValue_t doSendReadHook() override; + ReturnValue_t getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches) override; + virtual void doTransition(Mode_t modeFrom, Submode_t subModeFrom) override; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::STR_HANDLER; + + //! [EXPORT] : [COMMENT] Status in temperature reply signals error + static const ReturnValue_t TEMPERATURE_REQ_FAILED = MAKE_RETURN_CODE(0xA0); + //! [EXPORT] : [COMMENT] Ping command failed + static const ReturnValue_t PING_FAILED = MAKE_RETURN_CODE(0xA1); + //! [EXPORT] : [COMMENT] Status in version reply signals error + static const ReturnValue_t VERSION_REQ_FAILED = MAKE_RETURN_CODE(0xA2); + //! [EXPORT] : [COMMENT] Status in interface reply signals error + static const ReturnValue_t INTERFACE_REQ_FAILED = MAKE_RETURN_CODE(0xA3); + //! [EXPORT] : [COMMENT] Status in power reply signals error + static const ReturnValue_t POWER_REQ_FAILED = MAKE_RETURN_CODE(0xA4); + //! [EXPORT] : [COMMENT] Status of reply to parameter set command signals error + static const ReturnValue_t SET_PARAM_FAILED = MAKE_RETURN_CODE(0xA5); + //! [EXPORT] : [COMMENT] Status of reply to action command signals error + static const ReturnValue_t ACTION_FAILED = MAKE_RETURN_CODE(0xA6); + //! [EXPORT] : [COMMENT] Received invalid path string. Exceeds allowed length + static const ReturnValue_t FILE_PATH_TOO_LONG = MAKE_RETURN_CODE(0xA7); + //! [EXPORT] : [COMMENT] Name of file received with command is too long + static const ReturnValue_t FILENAME_TOO_LONG = MAKE_RETURN_CODE(0xA8); + //! [EXPORT] : [COMMENT] Received version reply with invalid program ID + static const ReturnValue_t INVALID_PROGRAM = MAKE_RETURN_CODE(0xA9); + //! [EXPORT] : [COMMENT] Status field reply signals error + static const ReturnValue_t REPLY_ERROR = MAKE_RETURN_CODE(0xAA); + //! [EXPORT] : [COMMENT] Received command which is too short (some data is missing for proper + //! execution) + static const ReturnValue_t COMMAND_TOO_SHORT = MAKE_RETURN_CODE(0xAB); + //! [EXPORT] : [COMMENT] Received command with invalid length (too few or too many parameters) + static const ReturnValue_t INVALID_LENGTH = MAKE_RETURN_CODE(0xAC); + //! [EXPORT] : [COMMENT] Region mismatch between send and received data + static const ReturnValue_t REGION_MISMATCH = MAKE_RETURN_CODE(0xAD); + //! [EXPORT] : [COMMENT] Address mismatch between send and received data + static const ReturnValue_t ADDRESS_MISMATCH = MAKE_RETURN_CODE(0xAE); + //! [EXPORT] : [COMMENT] Length field mismatch between send and received data + static const ReturnValue_t lENGTH_MISMATCH = MAKE_RETURN_CODE(0xAF); + //! [EXPORT] : [COMMENT] Specified file does not exist + static const ReturnValue_t FILE_NOT_EXISTS = MAKE_RETURN_CODE(0xB0); + //! [EXPORT] : [COMMENT] Download blob pixel command has invalid type field + static const ReturnValue_t INVALID_TYPE = MAKE_RETURN_CODE(0xB1); + //! [EXPORT] : [COMMENT] Received FPGA action command with invalid ID + static const ReturnValue_t INVALID_ID = MAKE_RETURN_CODE(0xB2); + //! [EXPORT] : [COMMENT] Received reply is too short + static const ReturnValue_t REPLY_TOO_SHORT = MAKE_RETURN_CODE(0xB3); + //! [EXPORT] : [COMMENT] Received reply with invalid CRC + static const ReturnValue_t CRC_FAILURE = MAKE_RETURN_CODE(0xB4); + //! [EXPORT] : [COMMENT] Star tracker handler currently executing a command and using the + //! communication interface + static const ReturnValue_t STR_HELPER_EXECUTING = MAKE_RETURN_CODE(0xB5); + //! [EXPORT] : [COMMENT] Star tracker is already in firmware mode + static const ReturnValue_t STARTRACKER_ALREADY_BOOTED = MAKE_RETURN_CODE(0xB6); + //! [EXPORT] : [COMMENT] Star tracker must be in firmware mode to run this command + static const ReturnValue_t STARTRACKER_NOT_RUNNING_FIRMWARE = MAKE_RETURN_CODE(0xB7); + //! [EXPORT] : [COMMENT] Star tracker must be in bootloader mode to run this command + static const ReturnValue_t STARTRACKER_NOT_RUNNING_BOOTLOADER = MAKE_RETURN_CODE(0xB8); + + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::STR_HANDLER; + + //! [EXPORT] : [COMMENT] Failed to boot firmware + static const Event BOOTING_FIRMWARE_FAILED_EVENT = MAKE_EVENT(1, severity::LOW); + //! [EXPORT] : [COMMENT] Failed to boot star tracker into bootloader mode + static const Event BOOTING_BOOTLOADER_FAILED_EVENT = MAKE_EVENT(2, severity::LOW); + //! [EXPORT] : [COMMENT] Received COM error. P1: Communication Error ID (datasheet p32) + static constexpr Event COM_ERROR_REPLY_RECEIVED = MAKE_EVENT(3, severity::LOW); + + static const uint8_t STATUS_OFFSET = 2; + static const uint8_t PARAMS_OFFSET = 2; + static const uint8_t TICKS_OFFSET = 3; + static const uint8_t TIME_OFFSET = 7; + static const uint8_t TM_PARAM_OFFSET = 15; + static const uint8_t PARAMETER_ID_OFFSET = 1; + static const uint8_t ACTION_ID_OFFSET = 1; + static const uint8_t ACTION_DATA_OFFSET = 3; + + // Ping request will reply ping with this ID (data field) + static const uint32_t PING_ID = 0x55; + uint8_t firmwareTargetRaw = static_cast(startracker::FirmwareTarget::MAIN); + + static const MutexIF::TimeoutType TIMEOUT_TYPE = MutexIF::TimeoutType::WAITING; + static const uint32_t MUTEX_TIMEOUT = 20; + static const uint32_t BOOT_TIMEOUT = 1000; + static const uint32_t DEFAULT_TRANSITION_DELAY = 15000; + + struct FlashReadCmd { + // Minimum length of a read command (region, length and filename) + static const size_t MIN_LENGTH = 7; + }; + + struct ChecksumCmd { + static const uint8_t ADDRESS_OFFSET = 1; + static const uint8_t LENGTH_OFFSET = 5; + // Length of checksum command + static const size_t LENGTH = 9; + uint8_t rememberRegion = 0; + uint32_t rememberAddress = 0; + uint32_t rememberLength = 0; + }; + + ChecksumCmd checksumCmd; + + MessageQueueIF* eventQueue = nullptr; + + startracker::TemperatureSet temperatureSet; + startracker::VersionSet versionSet; + startracker::PowerSet powerSet; + startracker::InterfaceSet interfaceSet; + startracker::TimeSet timeSet; + startracker::SolutionSet solutionSet; + startracker::HistogramSet histogramSet; + startracker::ChecksumSet checksumSet; + startracker::CameraSet cameraSet; + startracker::LimitsSet limitsSet; + startracker::LogLevelSet loglevelSet; + startracker::MountingSet mountingSet; + startracker::ImageProcessorSet imageProcessorSet; + startracker::CentroidingSet centroidingSet; + startracker::LisaSet lisaSet; + startracker::MatchingSet matchingSet; + startracker::TrackingSet trackingSet; + startracker::ValidationSet validationSet; + startracker::AlgoSet algoSet; + startracker::SubscriptionSet subscriptionSet; + startracker::LogSubscriptionSet logSubscriptionSet; + startracker::DebugCameraSet debugCameraSet; + startracker::AutoBlobSet autoBlobSet; + startracker::MatchedCentroidsSet matchedCentroidsSet; + startracker::BlobSet blobSet; + startracker::BlobsSet blobsSet; + startracker::CentroidSet centroidSet; + startracker::CentroidsSet centroidsSet; + startracker::ContrastSet contrastSet; + startracker::BlobStatsSet blobStatsSet; + + // Pointer to object responsible for uploading and downloading images to/from the star tracker + StrComHandler* strHelper = nullptr; + + uint8_t commandBuffer[startracker::MAX_FRAME_SIZE]; + + // Countdown to insert delay for star tracker to switch from bootloader to firmware program + // Loading firmware requires some time and the command will not trigger a reply when executed + Countdown bootCountdown; + + struct JsonConfigs { + Tracking tracking; + LogLevel logLevel; + LogSubscription logSubscription; + DebugCamera debugCamera; + Algo algo; + Validation validation; + Matching matching; + Lisa lisa; + Centroiding centroiding; + Camera camera; + ImageProcessor imageProcessor; + Mounting mounting; + Limits limits; + Subscription subscription; + AutoThreshold autoThreshold; + }; + bool jcfgPending = false; + JsonConfigs jcfgs; + Countdown jcfgCountdown = Countdown(1000); + bool commandExecuted = false; + std::thread jsonCfgTask; + static void setUpJsonCfgs(JsonConfigs& cfgs, std::string paramJsonFile); + + std::string paramJsonFile; + + enum class NormalState { SECONDARY_REQUEST, SOLUTION_REQUEST }; + + NormalState normalState = NormalState::SECONDARY_REQUEST; + + enum class StartupState { + IDLE, + CHECK_PROGRAM, + WAIT_CHECK_PROGRAM, + BOOT_BOOTLOADER, + WAIT_JCFG, + DONE + }; + StartupState startupState = StartupState::IDLE; + + enum class InternalState { + IDLE, + BOOT_FIRMWARE, + DONE, + FAILED_FIRMWARE_BOOT, + BOOT_BOOTLOADER, + BOOTLOADER_CHECK, + FAILED_BOOTLOADER_BOOT + }; + + enum class FwBootState { + NONE, + BOOT_DELAY, + REQ_VERSION, + VERIFY_BOOT, + SET_TIME, + LOGLEVEL, + LIMITS, + TRACKING, + MOUNTING, + IMAGE_PROCESSOR, + CAMERA, + BLOB, + CENTROIDING, + LISA, + MATCHING, + VALIDATION, + ALGO, + LOG_SUBSCRIPTION, + DEBUG_CAMERA, + AUTO_THRESHOLD, + WAIT_FOR_EXECUTION, + RETRY_CFG_CMD + }; + FwBootState bootState = FwBootState::NONE; + + InternalState internalState = InternalState::IDLE; + + bool reinitNextSetParam = false; + + bool strHelperHandlingSpecialRequest = false; + + const power::Switch_t powerSwitch = power::NO_SWITCH; + + size_t fullPacketLen = 0; + + std::set additionalRequestedTm{}; + std::set::iterator currentSecondaryTmIter; + + SdCardMountedIF& sdCardIF; + startracker::SdCardConfigPathGetter& cfgPathGetter; + + /** + * @brief Handles internal state + */ + void handleInternalState(); + void loadTargetFirmwareFromPersistentCfg(); + + /** + * @brief Checks mode for commands requiring MODE_ON of MODE_NORMAL for execution. + * + * @param actionId Action id of command to execute + */ + ReturnValue_t checkMode(ActionId_t actionId); + + /** + * @brief This function initializes the serial link ip protocol struct slipInfo. + */ + void slipInit(); + + ReturnValue_t scanForActionReply(uint8_t replyId, DeviceCommandId_t* foundId); + ReturnValue_t scanForSetParameterReply(uint8_t replyId, DeviceCommandId_t* foundId); + ReturnValue_t scanForGetParameterReply(uint8_t replyId, DeviceCommandId_t* foundId); + ReturnValue_t scanForTmReply(uint8_t replyId, DeviceCommandId_t* foundId); + + /** + * @brief Fills command buffer with data to ping the star tracker + */ + void preparePingRequest(); + + /** + * @brief Fills command buffer with data to request the time telemetry. + */ + void prepareTimeRequest(); + + /** + * @brief Handles all received event messages + */ + void handleEvent(EventMessage* eventMessage); + + /** + * @brief Extracts information for flash-read-command from TC data and starts execution of + * flash-read-procedure + * + * @param commandData Pointer to received command data + * @param commandDataLen Size of received command data + * + * @return returnvalue::OK if start of execution was successful, otherwise error return value + */ + ReturnValue_t executeFlashReadCommand(const uint8_t* commandData, size_t commandDataLen); + + /** + * Add a TM request to the list of telemetry which will be polled in the secondary step of + * the device communication. + * @param cmd + */ + void addSecondaryTmForNormalMode(DeviceCommandId_t cmd); + + /** + * Reset the secondary set, which will only contain a TEMPERATURE set request after the reset. + */ + void resetSecondaryTmSet(); + + /** + * @brief Fills command buffer with data to boot image (works only when star tracker is + * in bootloader mode). + */ + void prepareBootCommand(startracker::FirmwareTarget target); + + /** + * @brief Fills command buffer with command to get the checksum of a flash part + */ + ReturnValue_t prepareChecksumCommand(const uint8_t* commandData, size_t commandDataLen); + + /** + * @brief Fills the command buffer with the command to take an image. + */ + void prepareTakeImageCommand(const uint8_t* commandData); + + /** + * @brief Fills command buffer with data to request the version telemetry packet + */ + void prepareVersionRequest(); + + /** + * @brief Fills the command buffer with data to request the interface telemetry packet. + */ + void prepareInterfaceRequest(); + + /** + * @brief Fills the command buffer with data to request the power telemetry packet. + */ + void preparePowerRequest(); + + /** + * @brief Fills command buffer with data to reboot star tracker. + */ + void prepareSwitchToBootloaderCmd(); + + /** + * @brief Fills command buffer with data to subscribe to a telemetry packet. + * + * @param tmId The ID of the telemetry packet to subscribe to + */ + void prepareSubscriptionCommand(const uint8_t* tmId); + + /** + * @brief Fills command buffer with data to request solution telemtry packet (contains + * attitude information) + */ + void prepareSolutionRequest(); + + /** + * @brief Fills command buffer with data to request temperature from star tracker + */ + void prepareTemperatureRequest(); + + /** + * @brief Fills command buffer with data to request histogram + */ + void prepareHistogramRequest(); + + /** + * @brief Reads parameters from json file specified by string in commandData and + * prepares the command to apply the parameter set to the star tracker + * + * @param commandData Contains string with file name + * @param commandDataLen Length of command + * @param paramSet The object defining the command generation + * + * @return returnvalue::OK if successful, otherwise error return Value + */ + ReturnValue_t prepareParamCommand(const uint8_t* commandData, size_t commandDataLen, + ArcsecJsonParamBase& paramSet, bool reinitSet); + + /** + * @brief The following function will fill the command buffer with the command to request + * a parameter set. + */ + ReturnValue_t prepareRequestCameraParams(); + ReturnValue_t prepareRequestLimitsParams(); + ReturnValue_t prepareRequestLogLevelParams(); + ReturnValue_t prepareRequestMountingParams(); + ReturnValue_t prepareRequestImageProcessorParams(); + ReturnValue_t prepareRequestCentroidingParams(); + ReturnValue_t prepareRequestLisaParams(); + ReturnValue_t prepareRequestMatchingParams(); + ReturnValue_t prepareRequestAutoBlobTm(); + ReturnValue_t prepareRequestMatchedCentroidsTm(); + ReturnValue_t prepareRequestBlobTm(); + ReturnValue_t prepareRequestBlobsTm(); + ReturnValue_t prepareRequestCentroidTm(); + ReturnValue_t prepareRequestCentroidsTm(); + ReturnValue_t prepareRequestContrastTm(); + ReturnValue_t prepareRequestBlobStatsTm(); + ReturnValue_t prepareRequestTrackingParams(); + ReturnValue_t prepareRequestValidationParams(); + ReturnValue_t prepareRequestAlgoParams(); + ReturnValue_t prepareRequestSubscriptionParams(); + ReturnValue_t prepareRequestLogSubscriptionParams(); + ReturnValue_t prepareRequestDebugCameraParams(); + + /** + * @brief Handles action replies with datasets. + */ + ReturnValue_t handleActionReplySet(const uint8_t* rawFrame, LocalPoolDataSetBase& dataset, + size_t size); + + /** + * @brief Default function to handle action replies + */ + ReturnValue_t handleActionReply(const uint8_t* rawFrame); + + /** + * @brief Handles reply to upload centroid command + */ + ReturnValue_t handleUploadCentroidReply(); + + /** + * @brief Handles reply to checksum command + */ + ReturnValue_t handleChecksumReply(const uint8_t* rawFrame); + + /** + * @brief Handles all set parameter replies + */ + ReturnValue_t handleSetParamReply(const uint8_t* rawFrame); + + ReturnValue_t handlePingReply(const uint8_t* rawFrame); + + ReturnValue_t handleParamRequest(const uint8_t* rawFrame, LocalPoolDataSetBase& dataset, + size_t size); + + /** + * @brief Checks the loaded program by means of the version set + */ + ReturnValue_t checkProgram(); + + /** + * @brief Handles the startup state machine + */ + void handleStartup(uint8_t tmType, uint8_t parameterId); + + ReturnValue_t statusFieldCheck(const uint8_t* rawFrame); + /** + * @brief Handles telemtry replies and fills the appropriate dataset + * + * @param dataset Dataset where reply data will be written to + * @param size Size of the dataset + * + * @return returnvalue::OK if successful, otherwise error return value + */ + ReturnValue_t handleTm(const uint8_t* rawFrame, LocalPoolDataSetBase& dataset, + const char* context); + + ReturnValue_t handleSolution(const uint8_t* rawFrame); + ReturnValue_t handleAutoBlobTm(const uint8_t* rawFrame); + ReturnValue_t handleMatchedCentroidTm(const uint8_t* rawFrame); + ReturnValue_t handleBlobTm(const uint8_t* rawFrame); + ReturnValue_t handleBlobsTm(const uint8_t* rawFrame); + ReturnValue_t handleCentroidTm(const uint8_t* rawFrame); + ReturnValue_t handleCentroidsTm(const uint8_t* rawFrame); + + /** + * @brief Checks if star tracker is in valid mode for executing the received command. + * + * @param actioId Id of received command + * + * @return returnvalue::OK if star tracker is in valid mode, otherwise error return value + */ + ReturnValue_t checkCommand(ActionId_t actionId); + + void doOnTransition(Submode_t subModeFrom); + void doNormalTransition(Mode_t modeFrom, Submode_t subModeFrom); + void bootFirmware(Mode_t toMode); + void bootBootloader(); + bool reloadJsonCfgFile(); + ReturnValue_t acceptExternalDeviceCommands() override; + + ReturnValue_t handleFirmwareUpdateCommand(const uint8_t* data, size_t size, + startracker::FirmwareTarget target); +}; + +#endif /* MISSION_DEVICES_STARTRACKERHANDLER_H_ */ diff --git a/mission/acs/str/arcsecJsonKeys.h b/mission/acs/str/arcsecJsonKeys.h new file mode 100644 index 0000000..c44ece9 --- /dev/null +++ b/mission/acs/str/arcsecJsonKeys.h @@ -0,0 +1,188 @@ +#ifndef BSP_Q7S_DEVICES_DEVICEDEFINITIONS_ARCSECJSONKEYS_H_ +#define BSP_Q7S_DEVICES_DEVICEDEFINITIONS_ARCSECJSONKEYS_H_ + +/** + * @brief Keys used in JSON file of ARCSEC. + */ +namespace arcseckeys { +static const char PROPERTIES[] = "properties"; +static const char NAME[] = "name"; +static const char VALUE[] = "value"; + +static const char LIMITS[] = "Limits"; +static const char ACTION[] = "action"; +static const char FPGA18CURRENT[] = "FPGA18Current"; +static const char FPGA25CURRENT[] = "FPGA25Current"; +static const char FPGA10CURRENT[] = "FPGA10Current"; +static const char MCUCURRENT[] = "MCUCurrent"; +static const char CMOS21CURRENT[] = "CMOS21Current"; +static const char CMOSPIXCURRENT[] = "CMOSPixCurrent"; +static const char CMOS33CURRENT[] = "CMOS33Current"; +static const char CMOSVRESCURRENT[] = "CMOSVResCurrent"; +static const char CMOS_TEMPERATURE[] = "CMOSTemperature"; +static const char MCU_TEMPERATURE[] = "MCUTemperature"; + +static const char MOUNTING[] = "Mounting"; +static const char qw[] = "qw"; +static const char qx[] = "qx"; +static const char qy[] = "qy"; +static const char qz[] = "qz"; + +static const char IMAGE_PROCESSOR[] = "ImageProcessor"; +static const char IMAGE_PROCESSOR_MODE[] = "mode"; +static const char STORE[] = "store"; +static const char SIGNAL_THRESHOLD[] = "signalThreshold"; +static const char IMAGE_PROCESSOR_DARK_THRESHOLD[] = "darkThreshold"; +static const char BACKGROUND_COMPENSATION[] = "backgroundcompensation"; + +static const char CAMERA[] = "Camera"; +static const char MODE[] = "mode"; +static const char FOCALLENGTH[] = "focallength"; +static const char EXPOSURE[] = "exposure"; +static const char INTERVAL[] = "interval"; +static const char OFFSET[] = "offset"; +static const char PGAGAIN[] = "PGAGain"; +static const char ADCGAIN[] = "ADCGain"; +static const char REG_1[] = "reg1"; +static const char VAL_1[] = "val1"; +static const char REG_2[] = "reg2"; +static const char VAL_2[] = "val2"; +static const char REG_3[] = "reg3"; +static const char VAL_3[] = "val3"; +static const char REG_4[] = "reg4"; +static const char VAL_4[] = "val4"; +static const char REG_5[] = "reg5"; +static const char VAL_5[] = "val5"; +static const char REG_6[] = "reg6"; +static const char VAL_6[] = "val6"; +static const char REG_7[] = "reg7"; +static const char VAL_7[] = "val7"; +static const char REG_8[] = "reg8"; +static const char VAL_8[] = "val8"; +static const char FREQ_1[] = "freq1"; + +static const char BLOB[] = "blob"; +static const char MIN_VALUE[] = "minValue"; +static const char MIN_DISTANCE[] = "minDistance"; +static const char NEIGHBOUR_DISTANCE[] = "neighbourDistance"; +static const char NEIGHBOUR_BRIGHT_PIXELS[] = "neighbourBrightPixels"; +static const char MIN_TOTAL_VALUE[] = "minTotalValue"; +static const char MAX_TOTAL_VALUE[] = "maxTotalValue"; +static const char MIN_BRIGHT_NEIGHBOURS[] = "minBrightNeighbours"; +static const char MAX_BRIGHT_NEIGHBOURS[] = "maxBrightNeighbours"; +static const char MAX_PIXEL_TO_CONSIDER[] = "maxPixelsToConsider"; +// static const char SIGNAL_THRESHOLD[] = "signalThreshold"; +static const char BLOB_DARK_THRESHOLD[] = "darkThreshold"; +static const char ENABLE_HISTOGRAM[] = "enableHistogram"; +static const char ENABLE_CONTRAST[] = "enableContrast"; +static const char BIN_MODE[] = "binMode"; + +static const char CENTROIDING[] = "Centroiding"; +static const char ENABLE_FILTER[] = "enableFilter"; +static const char MAX_QUALITY[] = "maxquality"; +static const char DARK_THRESHOLD[] = "darkthreshold"; +static const char MIN_QUALITY[] = "minquality"; +static const char MAX_INTENSITY[] = "maxintensity"; +static const char MIN_INTENSITY[] = "minintensity"; +static const char MAX_MAGNITUDE[] = "maxmagnitude"; +static const char GAUSSIAN_CMAX[] = "gaussianCmax"; +static const char GAUSSIAN_CMIN[] = "gaussianCmin"; +static const char TRANSMATRIX_00[] = "transmatrix00"; +static const char TRANSMATRIX_01[] = "transmatrix01"; +static const char TRANSMATRIX_10[] = "transmatrix10"; +static const char TRANSMATRIX_11[] = "transmatrix11"; + +static const char LISA[] = "LISA"; +static const char LISA_MODE[] = "mode"; +static const char PREFILTER_DIST_THRESHOLD[] = "prefilterDistThreshold"; +static const char PREFILTER_ANGLE_THRESHOLD[] = "prefilterAngleThreshold"; +static const char FOV_WIDTH[] = "fov_width"; +static const char FOV_HEIGHT[] = "fov_height"; +static const char FLOAT_STAR_LIMIT[] = "float_star_limit"; +static const char CLOSE_STAR_LIMIT[] = "close_star_limit"; +static const char RATING_WEIGHT_CLOSE_STAR_COUNT[] = "rating_weight_close_star_count"; +static const char RATING_WEIGHT_FRACTION_CLOSE[] = "rating_weight_fraction_close"; +static const char RATING_WEIGHT_MEAN_SUM[] = "rating_weight_mean_sum"; +static const char RATING_WEIGHT_DB_STAR_COUNT[] = "rating_weight_db_star_count"; +static const char MAX_COMBINATIONS[] = "max_combinations"; +static const char NR_STARS_STOP[] = "nr_stars_stop"; +static const char FRACTION_CLOSE_STOP[] = "fraction_close_stop"; + +static const char MATCHING[] = "Matching"; +static const char SQUARED_DISTANCE_LIMIT[] = "squaredDistanceLimit"; +static const char SQUARED_SHIFT_LIMIT[] = "squaredShiftLimit"; + +static const char VALIDATION[] = "Validation"; +static const char STABLE_COUNT[] = "stable_count"; +static const char MAX_DIFFERENCE[] = "max_difference"; +static const char MIN_TRACKER_CONFIDENCE[] = "min_trackerConfidence"; +static const char MIN_MATCHED_STARS[] = "min_matchedStars"; + +static const char TRACKING[] = "Tracking"; +static const char THIN_LIMIT[] = "thinLimit"; +static const char OUTLIER_THRESHOLD[] = "outlierThreshold"; +static const char OUTLIER_THRESHOLD_QUEST[] = "outlierThresholdQUEST"; +static const char TRACKER_CHOICE[] = "trackerChoice"; + +static const char ALGO[] = "Algo"; +static const char L2T_MIN_CONFIDENCE[] = "l2t_minConfidence"; +static const char L2T_MIN_MATCHED[] = "l2t_minMatched"; +static const char T2L_MIN_CONFIDENCE[] = "t2l_minConfidence"; +static const char T2L_MIN_MATCHED[] = "t2l_minMatched"; + +static const char LOGLEVEL[] = "LogLevel"; +static const char LOGLEVEL1[] = "loglevel1"; +static const char LOGLEVEL2[] = "loglevel2"; +static const char LOGLEVEL3[] = "loglevel3"; +static const char LOGLEVEL4[] = "loglevel4"; +static const char LOGLEVEL5[] = "loglevel5"; +static const char LOGLEVEL6[] = "loglevel6"; +static const char LOGLEVEL7[] = "loglevel7"; +static const char LOGLEVEL8[] = "loglevel8"; +static const char LOGLEVEL9[] = "loglevel9"; +static const char LOGLEVEL10[] = "loglevel10"; +static const char LOGLEVEL11[] = "loglevel11"; +static const char LOGLEVEL12[] = "loglevel12"; +static const char LOGLEVEL13[] = "loglevel13"; +static const char LOGLEVEL14[] = "loglevel14"; +static const char LOGLEVEL15[] = "loglevel15"; +static const char LOGLEVEL16[] = "loglevel16"; + +static const char SUBSCRIPTION[] = "Subscription"; +static const char TELEMETRY_1[] = "telemetry1"; +static const char TELEMETRY_2[] = "telemetry2"; +static const char TELEMETRY_3[] = "telemetry3"; +static const char TELEMETRY_4[] = "telemetry4"; +static const char TELEMETRY_5[] = "telemetry5"; +static const char TELEMETRY_6[] = "telemetry6"; +static const char TELEMETRY_7[] = "telemetry7"; +static const char TELEMETRY_8[] = "telemetry8"; +static const char TELEMETRY_9[] = "telemetry9"; +static const char TELEMETRY_10[] = "telemetry10"; +static const char TELEMETRY_11[] = "telemetry11"; +static const char TELEMETRY_12[] = "telemetry12"; +static const char TELEMETRY_13[] = "telemetry13"; +static const char TELEMETRY_14[] = "telemetry14"; +static const char TELEMETRY_15[] = "telemetry15"; +static const char TELEMETRY_16[] = "telemetry16"; + +static const char LOG_SUBSCRIPTION[] = "LogSubscription"; +static const char LEVEL1[] = "level1"; +static const char MODULE1[] = "module1"; +static const char LEVEL2[] = "level2"; +static const char MODULE2[] = "module2"; + +static const char DEBUG_CAMERA[] = "DebugCamera"; +static const char TIMING[] = "timing"; +static const char TEST[] = "test"; + +static constexpr char AUTO_THRESHOLD[] = "AutoThreshold"; +static constexpr char AT_MODE[] = "mode"; +static constexpr char AT_DESIRED_BLOB_COUNTS[] = "desiredBlobsCount"; +static constexpr char AT_MIN_THRESHOLD[] = "minThreshold"; +static constexpr char AT_MAX_THRESHOLD[] = "maxThreshold"; +static constexpr char AT_THRESHOLD_KP[] = "thresholdKp"; + +} // namespace arcseckeys + +#endif /* BSP_Q7S_DEVICES_DEVICEDEFINITIONS_ARCSECJSONKEYS_H_ */ diff --git a/mission/acs/str/strHelpers.cpp b/mission/acs/str/strHelpers.cpp new file mode 100644 index 0000000..c6d59c7 --- /dev/null +++ b/mission/acs/str/strHelpers.cpp @@ -0,0 +1,7 @@ +#include + +uint8_t startracker::getReplyFrameType(const uint8_t* rawFrame) { return rawFrame[0]; } + +uint8_t startracker::getId(const uint8_t* rawFrame) { return rawFrame[1]; } + +uint8_t startracker::getStatusField(const uint8_t* rawFrame) { return rawFrame[2]; } diff --git a/mission/acs/str/strHelpers.h b/mission/acs/str/strHelpers.h new file mode 100644 index 0000000..ebbc80c --- /dev/null +++ b/mission/acs/str/strHelpers.h @@ -0,0 +1,1759 @@ +#ifndef LINUX_DEVICES_DEVICEDEFINITIONS_STARTRACKER_DEFINITIONS_H_ +#define LINUX_DEVICES_DEVICEDEFINITIONS_STARTRACKER_DEFINITIONS_H_ + +#include +#include +#include +#include +#include + +#include + +namespace startracker { + +static const Submode_t SUBMODE_BOOTLOADER = 1; +static const Submode_t SUBMODE_FIRMWARE = 2; + +enum class FirmwareTarget : uint8_t { MAIN = 1, BACKUP = 10 }; + +static constexpr char FW_TARGET_CFG_PATH[] = "startracker/fw-target.txt"; + +enum ParamId : uint32_t { FIRMWARE_TARGET = 1, FIRMWARE_TARGET_PERSISTENT = 2 }; + +class SdCardConfigPathGetter { + public: + virtual ~SdCardConfigPathGetter() = default; + virtual std::optional getCfgPath() = 0; +}; + +/** + * @brief Returns the frame type field of a decoded frame. + */ +uint8_t getReplyFrameType(const uint8_t* rawFrame); +uint8_t getId(const uint8_t* rawFrame); +uint8_t getStatusField(const uint8_t* rawFrame); + +/** This is the address of the star tracker */ +static const uint8_t ADDRESS = 33; + +static const uint8_t STATUS_OK = 0; + +enum PoolIds : lp_id_t { + TICKS_TIME_SET, + TIME_TIME_SET, + RUN_TIME, + UNIX_TIME, + TICKS_VERSION_SET, + TIME_VERSION_SET, + PROGRAM, + MAJOR, + MINOR, + TICKS_INTERFACE_SET, + TIME_INTERFACE_SET, + FRAME_COUNT, + CHECKSUM_ERROR_COUNT, + SET_PARAM_COUNT, + SET_PARAM_REPLY_COUNT, + PARAM_REQUEST_COUNT, + PARAM_REPLY_COUNT, + REQ_TM_COUNT, + TM_REPLY_COUNT, + ACTION_REQ_COUNT, + ACTION_REPLY_COUNT, + TICKS_POWER_SET, + TIME_POWER_SET, + MCU_CURRENT, + MCU_VOLTAGE, + FPGA_CORE_CURRENT, + FPGA_CORE_VOLTAGE, + FPGA_18_CURRENT, + FPGA_18_VOLTAGE, + FPGA_25_CURRENT, + FPGA_25_VOLTAGE, + CMV_21_CURRENT, + CMV_21_VOLTAGE, + CMV_PIX_CURRENT, + CMV_PIX_VOLTAGE, + CMV_33_CURRENT, + CMV_33_VOLTAGE, + CMV_RES_CURRENT, + CMV_RES_VOLTAGE, + TICKS_TEMPERATURE_SET, + TIME_TEMPERATURE_SET, + MCU_TEMPERATURE, + CMOS_TEMPERATURE, + FPGA_TEMPERATURE, + TICKS_SOLUTION_SET, + TIME_SOLUTION_SET, + CALI_QW, + CALI_QX, + CALI_QY, + CALI_QZ, + TRACK_CONFIDENCE, + TRACK_QW, + TRACK_QX, + TRACK_QY, + TRACK_QZ, + TRACK_REMOVED, + STARS_CENTROIDED, + STARS_MATCHED_DATABASE, + LISA_QW, + LISA_QX, + LISA_QY, + LISA_QZ, + LISA_PERC_CLOSE, + LISA_NR_CLOSE, + STR_MODE, + TRUST_WORTHY, + STABLE_COUNT, + SOLUTION_STRATEGY, + TICKS_HISTOGRAM_SET, + TIME_HISTOGRAM_SET, + HISTOGRAM_BINA0, + HISTOGRAM_BINA1, + HISTOGRAM_BINA2, + HISTOGRAM_BINA3, + HISTOGRAM_BINA4, + HISTOGRAM_BINA5, + HISTOGRAM_BINA6, + HISTOGRAM_BINA7, + HISTOGRAM_BINA8, + HISTOGRAM_BINB0, + HISTOGRAM_BINB1, + HISTOGRAM_BINB2, + HISTOGRAM_BINB3, + HISTOGRAM_BINB4, + HISTOGRAM_BINB5, + HISTOGRAM_BINB6, + HISTOGRAM_BINB7, + HISTOGRAM_BINB8, + HISTOGRAM_BINC0, + HISTOGRAM_BINC1, + HISTOGRAM_BINC2, + HISTOGRAM_BINC3, + HISTOGRAM_BINC4, + HISTOGRAM_BINC5, + HISTOGRAM_BINC6, + HISTOGRAM_BINC7, + HISTOGRAM_BINC8, + HISTOGRAM_BIND0, + HISTOGRAM_BIND1, + HISTOGRAM_BIND2, + HISTOGRAM_BIND3, + HISTOGRAM_BIND4, + HISTOGRAM_BIND5, + HISTOGRAM_BIND6, + HISTOGRAM_BIND7, + HISTOGRAM_BIND8, + CHKSUM, + CAMERA_MODE, + FOCALLENGTH, + EXPOSURE, + INTERVAL, + CAMERA_OFFSET, + PGAGAIN, + ADCGAIN, + CAM_REG1, + CAM_VAL1, + CAM_REG2, + CAM_VAL2, + CAM_REG3, + CAM_VAL3, + CAM_REG4, + CAM_VAL4, + CAM_REG5, + CAM_VAL5, + CAM_REG6, + CAM_VAL6, + CAM_REG7, + CAM_VAL7, + CAM_REG8, + CAM_VAL8, + CAM_FREQ_1, + LIMITS_ACTION, + LIMITS_FPGA18CURRENT, + LIMITS_FPGA25CURRENT, + LIMITS_FPGA10CURRENT, + LIMITS_MCUCURRENT, + LIMITS_CMOS21CURRENT, + LIMITS_CMOSPIXCURRENT, + LIMITS_CMOS33CURRENT, + LIMITS_CMOSVRESCURRENT, + LIMITS_CMOSTEMPERATURE, + LIMITS_MCUTEMPERATURE, + BLOB_MODE, + BLOB_MIN_VALUE, + BLOB_MIN_DISTANCE, + BLOB_NEIGHBOUR_DISTANCE, + BLOB_NEIGHBOUR_BRIGHTPIXELS, + BLOB_MIN_TOTAL_VALUE, + BLOB_MAX_TOTAL_VALUE, + BLOB_MIN_BRIGHT_NEIGHBOURS, + BLOB_MAX_BRIGHT_NEIGHBOURS, + BLOB_MAX_PIXELSTOCONSIDER, + BLOB_SIGNAL_THRESHOLD, + BLOB_DARK_THRESHOLD, + BLOB_ENABLE_HISTOGRAM, + BLOB_ENABLE_CONTRAST, + BLOB_BIN_MODE, + LOGLEVEL1, + LOGLEVEL2, + LOGLEVEL3, + LOGLEVEL4, + LOGLEVEL5, + LOGLEVEL6, + LOGLEVEL7, + LOGLEVEL8, + LOGLEVEL9, + LOGLEVEL10, + LOGLEVEL11, + LOGLEVEL12, + LOGLEVEL13, + LOGLEVEL14, + LOGLEVEL15, + LOGLEVEL16, + MOUNTING_QW, + MOUNTING_QX, + MOUNTING_QY, + MOUNTING_QZ, + IMAGE_PROCESSOR_MODE, + IMAGE_PROCESSOR_STORE, + IMAGE_PROCESSOR_SIGNALTHRESHOLD, + IMAGE_PROCESSOR_DARKTHRESHOLD, + IMAGE_PROCESSOR_BACKGROUNDCOMPENSATION, + CENTROIDING_ENABLE_FILTER, + CENTROIDING_MAX_QUALITY, + CENTROIDING_DARK_THRESHOLD, + CENTROIDING_MIN_QUALITY, + CENTROIDING_MAX_INTENSITY, + CENTROIDING_MIN_INTENSITY, + CENTROIDING_MAX_MAGNITUDE, + CENTROIDING_GAUSSIAN_CMAX, + CENTROIDING_GAUSSIAN_CMIN, + CENTROIDING_TRANSMATRIX00, + CENTROIDING_TRANSMATRIX01, + CENTROIDING_TRANSMATRIX10, + CENTROIDING_TRANSMATRIX11, + LISA_MODE, + LISA_PREFILTER_DIST_THRESHOLD, + LISA_PREFILTER_ANGLE_THRESHOLD, + LISA_FOV_WIDTH, + LISA_FOV_HEIGHT, + LISA_FLOAT_STAR_LIMIT, + LISA_CLOSE_STAR_LIMIT, + LISA_RATING_WEIGHT_CLOSE_STAR_COUNT, + LISA_RATING_WEIGHT_FRACTION_CLOSE, + LISA_RATING_WEIGHT_MEAN_SUM, + LISA_RATING_WEIGHT_DB_STAR_COUNT, + LISA_MAX_COMBINATIONS, + LISA_NR_STARS_STOP, + LISA_FRACTION_CLOSE_STOP, + MATCHING_SQUARED_DISTANCE_LIMIT, + MATCHING_SQUARED_SHIFT_LIMIT, + TRACKING_THIN_LIMIT, + TRACKING_OUTLIER_THRESHOLD, + TRACKING_OUTLIER_THRESHOLD_QUEST, + TRACKING_TRACKER_CHOICE, + VALIDATION_STABLE_COUNT, + VALIDATION_MAX_DIFFERENCE, + VALIDATION_MIN_TRACKER_CONFIDENCE, + VALIDATION_MIN_MATCHED_STARS, + ALGO_MODE, + ALGO_I2T_MIN_CONFIDENCE, + ALGO_I2T_MIN_MATCHED, + ALGO_I2L_MIN_CONFIDENCE, + ALGO_I2L_MIN_MATCHED, + SUBSCRIPTION_TM1, + SUBSCRIPTION_TM2, + SUBSCRIPTION_TM3, + SUBSCRIPTION_TM4, + SUBSCRIPTION_TM5, + SUBSCRIPTION_TM6, + SUBSCRIPTION_TM7, + SUBSCRIPTION_TM8, + SUBSCRIPTION_TM9, + SUBSCRIPTION_TM10, + SUBSCRIPTION_TM11, + SUBSCRIPTION_TM12, + SUBSCRIPTION_TM13, + SUBSCRIPTION_TM14, + SUBSCRIPTION_TM15, + SUBSCRIPTION_TM16, + LOG_SUBSCRIPTION_LEVEL1, + LOG_SUBSCRIPTION_MODULE1, + LOG_SUBSCRIPTION_LEVEL2, + LOG_SUBSCRIPTION_MODULE2, + DEBUG_CAMERA_TIMING, + DEBUG_CAMERA_TEST, + + TICKS_AUTO_BLOB, + TIME_AUTO_BLOB, + AUTO_BLOB_THRESHOLD, + + TICKS_MATCHED_CENTROIDS, + TIME_MATCHED_CENTROIDS, + NUM_MATCHED_CENTROIDS, + MATCHED_CENTROIDS_STAR_IDS, + MATCHED_CENTROIDS_X_COORDS, + MATCHED_CENTROIDS_Y_COORDS, + MATCHED_CENTROIDS_X_ERRORS, + MATCHED_CENTROIDS_Y_ERRORS, + + BLOB_TICKS, + BLOB_TIME, + BLOB_COUNT, + + BLOBS_TICKS, + BLOBS_TIME, + BLOBS_COUNT, + BLOBS_COUNT_USED, + BLOBS_NR_4LINES_SKIPPED, + BLOBS_X_COORDS, + BLOBS_Y_COORDS, + + CENTROID_TICKS, + CENTROID_TIME, + CENTROID_COUNT, + + CENTROIDS_TICKS, + CENTROIDS_TIME, + CENTROIDS_COUNT, + CENTROIDS_X_COORDS, + CENTROIDS_Y_COORDS, + CENTROIDS_MAGNITUDES, + + CONTRAST_TICKS, + CONTRAST_TIME, + CONTRAST_A, + CONTRAST_B, + CONTRAST_C, + CONTRAST_D, + + TICKS_BLOB_STATS, + TIME_BLOB_STATS, + BLOB_STATS_NOISE, + BLOB_STATS_THOLD, + BLOB_STATS_LVALID, + BLOB_STATS_OFLOW, +}; + +static const DeviceCommandId_t PING_REQUEST = 0; +// Boots image (works only in bootloader mode) +static const DeviceCommandId_t BOOT = 1; +static const DeviceCommandId_t REQ_VERSION = 2; +static const DeviceCommandId_t REQ_INTERFACE = 3; +static const DeviceCommandId_t REQ_TIME = 4; +static const DeviceCommandId_t SWITCH_TO_BOOTLOADER_PROGRAM = 7; +static const DeviceCommandId_t DOWNLOAD_IMAGE = 9; +static const DeviceCommandId_t UPLOAD_IMAGE = 10; +static const DeviceCommandId_t REQ_POWER = 11; +static const DeviceCommandId_t TAKE_IMAGE = 15; +static const DeviceCommandId_t SUBSCRIPTION = 18; +static const DeviceCommandId_t IMAGE_PROCESSOR = 19; +static const DeviceCommandId_t REQ_SOLUTION = 24; +static const DeviceCommandId_t REQ_TEMPERATURE = 25; +static const DeviceCommandId_t REQ_HISTOGRAM = 28; +static constexpr DeviceCommandId_t REQ_CONTRAST = 29; +static const DeviceCommandId_t LIMITS = 40; +static const DeviceCommandId_t MOUNTING = 41; +static const DeviceCommandId_t CAMERA = 42; +static const DeviceCommandId_t CENTROIDING = 44; +static const DeviceCommandId_t LISA = 45; +static const DeviceCommandId_t MATCHING = 46; +static const DeviceCommandId_t TRACKING = 47; +static const DeviceCommandId_t VALIDATION = 48; +static const DeviceCommandId_t ALGO = 49; +static const DeviceCommandId_t CHECKSUM = 50; +static const DeviceCommandId_t FLASH_READ = 51; +static const DeviceCommandId_t STOP_IMAGE_LOADER = 55; +static const DeviceCommandId_t CHANGE_IMAGE_DOWNLOAD_FILE = 57; +static const DeviceCommandId_t SET_JSON_FILE_NAME = 58; +static const DeviceCommandId_t SET_FLASH_READ_FILENAME = 59; +static const DeviceCommandId_t REQ_CAMERA = 67; +static const DeviceCommandId_t REQ_LIMITS = 68; +static const DeviceCommandId_t REQ_LOG_LEVEL = 69; +static const DeviceCommandId_t REQ_MOUNTING = 70; +static const DeviceCommandId_t REQ_IMAGE_PROCESSOR = 71; +static const DeviceCommandId_t REQ_CENTROIDING = 72; +static const DeviceCommandId_t REQ_LISA = 73; +static const DeviceCommandId_t REQ_MATCHING = 74; +static const DeviceCommandId_t REQ_TRACKING = 75; +static const DeviceCommandId_t REQ_VALIDATION = 76; +static const DeviceCommandId_t REQ_ALGO = 77; +static const DeviceCommandId_t REQ_SUBSCRIPTION = 78; +static const DeviceCommandId_t REQ_LOG_SUBSCRIPTION = 79; +static const DeviceCommandId_t REQ_DEBUG_CAMERA = 80; +static const DeviceCommandId_t LOGLEVEL = 81; +static const DeviceCommandId_t LOGSUBSCRIPTION = 82; +static const DeviceCommandId_t DEBUG_CAMERA = 83; +static const DeviceCommandId_t FIRMWARE_UPDATE_MAIN = 84; +static const DeviceCommandId_t DISABLE_TIMESTAMP_GENERATION = 85; +static const DeviceCommandId_t ENABLE_TIMESTAMP_GENERATION = 86; +static constexpr DeviceCommandId_t SET_TIME_FROM_SYS_TIME = 87; +static constexpr DeviceCommandId_t AUTO_THRESHOLD = 88; +static constexpr DeviceCommandId_t REQ_AUTO_BLOB = 89; +static constexpr DeviceCommandId_t REQ_MATCHED_CENTROIDS = 90; +static constexpr DeviceCommandId_t REQ_BLOB = 91; +static constexpr DeviceCommandId_t REQ_BLOBS = 92; +static constexpr DeviceCommandId_t REQ_CENTROID = 93; +static constexpr DeviceCommandId_t REQ_CENTROIDS = 94; +static constexpr DeviceCommandId_t ADD_SECONDARY_TM_TO_NORMAL_MODE = 95; +static constexpr DeviceCommandId_t RESET_SECONDARY_TM_SET = 96; +static constexpr DeviceCommandId_t READ_SECONDARY_TM_SET = 97; +static constexpr DeviceCommandId_t RELOAD_JSON_CFG_FILE = 100; +static constexpr DeviceCommandId_t FIRMWARE_UPDATE_BACKUP = 101; +static constexpr DeviceCommandId_t REQ_BLOB_STATS = 102; + +static const DeviceCommandId_t NONE = 0xFFFFFFFF; + +static const uint32_t VERSION_SET_ID = REQ_VERSION; +static const uint32_t INTERFACE_SET_ID = REQ_INTERFACE; +static const uint32_t POWER_SET_ID = REQ_POWER; +static const uint32_t TEMPERATURE_SET_ID = REQ_TEMPERATURE; +static const uint32_t TIME_SET_ID = REQ_TIME; +static const uint32_t SOLUTION_SET_ID = REQ_SOLUTION; +static const uint32_t HISTOGRAM_SET_ID = REQ_HISTOGRAM; +static const uint32_t CHECKSUM_SET_ID = CHECKSUM; +static const uint32_t CAMERA_SET_ID = REQ_CAMERA; +static const uint32_t LIMITS_SET_ID = REQ_LIMITS; +static const uint32_t LOG_LEVEL_SET_ID = REQ_LOG_LEVEL; +static const uint32_t MOUNTING_SET_ID = REQ_MOUNTING; +static const uint32_t IMAGE_PROCESSOR_SET_ID = REQ_IMAGE_PROCESSOR; +static const uint32_t CENTROIDING_SET_ID = REQ_CENTROIDING; +static const uint32_t LISA_SET_ID = REQ_LISA; +static const uint32_t MATCHING_SET_ID = REQ_MATCHING; +static const uint32_t TRACKING_SET_ID = REQ_TRACKING; +static const uint32_t VALIDATION_SET_ID = REQ_VALIDATION; +static const uint32_t ALGO_SET_ID = REQ_ALGO; +static const uint32_t SUBSCRIPTION_SET_ID = REQ_SUBSCRIPTION; +static const uint32_t LOG_SUBSCRIPTION_SET_ID = REQ_LOG_SUBSCRIPTION; +static const uint32_t DEBUG_CAMERA_SET_ID = REQ_DEBUG_CAMERA; +static const uint32_t AUTO_BLOB_SET_ID = REQ_AUTO_BLOB; +static const uint32_t MATCHED_CENTROIDS_SET_ID = REQ_MATCHED_CENTROIDS; +static const uint32_t BLOB_SET_ID = REQ_BLOB; +static const uint32_t BLOBS_SET_ID = REQ_BLOBS; +static const uint32_t CENTROID_SET_ID = REQ_CENTROID; +static const uint32_t CENTROIDS_SET_ID = REQ_CENTROIDS; +static const uint32_t CONTRAST_SET_ID = REQ_CONTRAST; +static const uint32_t BLOB_STATS_SET_ID = REQ_BLOB_STATS; + +/** Max size of unencoded frame */ +static const size_t MAX_FRAME_SIZE = 1200; + +static const uint8_t TEMPERATURE_SET_ENTRIES = 5; +static const uint8_t VERSION_SET_ENTRIES = 5; +static const uint8_t INTERFACE_SET_ENTRIES = 4; +static const uint8_t POWER_SET_ENTRIES = 18; +static const uint8_t TIME_SET_ENTRIES = 4; +static const uint8_t SOLUTION_SET_ENTRIES = 25; +static const uint8_t HISTOGRAM_SET_ENTRIES = 38; +static const uint8_t CHECKSUM_SET_ENTRIES = 1; +static const uint8_t CAMERA_SET_ENTRIES = 24; +static const uint8_t LIMITS_SET_ENTRIES = 11; +static const uint8_t LOG_LEVEL_SET_ENTRIES = 16; +static const uint8_t MOUNTING_SET_ENTRIES = 4; +static const uint8_t IMAGE_PROCESSOR_SET_ENTRIES = 5; +static const uint8_t CENTROIDING_PARAMS_SET_ENTRIES = 13; +static const uint8_t LISA_SET_ENTRIES = 14; +static const uint8_t MATCHING_SET_ENTRIES = 2; +static const uint8_t TRACKING_SET_ENTRIES = 4; +static const uint8_t VALIDATION_SET_ENTRIES = 4; +static const uint8_t ALGO_SET_ENTRIES = 5; +static const uint8_t SUBSCRIPTION_SET_ENTRIES = 16; +static const uint8_t LOG_SUBSCRIPTION_SET_ENTRIES = 4; +static const uint8_t DEBUG_CAMERA_SET_ENTRIES = 2; + +// Action, parameter and telemetry IDs +namespace ID { +static const uint8_t PING = 0; +static const uint8_t BOOT = 1; +static const uint8_t VERSION = 2; +static const uint8_t INTERFACE = 3; +static const uint8_t LIMITS = 5; +static const uint8_t MOUNTING = 6; +static const uint8_t IMAGE_PROCESSOR = 10; +static const uint8_t CAMERA = 9; +static const uint8_t CENTROIDING = 11; +static const uint8_t LISA = 12; +static const uint8_t MATCHING = 13; +static const uint8_t TRACKING = 14; +static const uint8_t VALIDATION = 15; +static const uint8_t ALGO = 16; +static const uint8_t REBOOT = 7; +static const uint8_t UPLOAD_IMAGE = 10; +static const uint8_t POWER = 11; +static const uint8_t SUBSCRIPTION = 18; +static const uint8_t SOLUTION = 24; +static const uint8_t TEMPERATURE = 27; +static const uint8_t HISTOGRAM = 28; +static const uint8_t CONTRAST = 29; +static const uint8_t TIME = 1; +static const uint8_t WRITE = 2; +static const uint8_t READ = 3; +static const uint8_t CHECKSUM = 4; +static const uint8_t TAKE_IMAGE = 15; +static const uint8_t LOG_LEVEL = 3; +static const uint8_t LOG_SUBSCRIPTION = 19; +static const uint8_t DEBUG_CAMERA = 20; +static const uint8_t AUTO_THRESHOLD = 23; +static constexpr uint8_t BLOB = 25; +static constexpr uint8_t BLOBS = 36; +static constexpr uint8_t CENTROID = 26; +static constexpr uint8_t CENTROIDS = 37; +static constexpr uint8_t AUTO_BLOB = 39; +static constexpr uint8_t MATCHED_CENTROIDS = 40; +static constexpr uint8_t BLOB_STATS = 49; +} // namespace ID + +namespace Program { +static const uint8_t BOOTLOADER = 1; +static const uint8_t FIRMWARE_MAIN = 2; +static const uint8_t FIRMWARE_BACKUP = 3; +} // namespace Program + +namespace region_secrets { +static const uint32_t REGION_0_SECRET = 0xd1a220d3; +static const uint32_t REGION_1_SECRET = 0xdc770fa8; +static const uint32_t REGION_2_SECRET = 0xdf9066b0; +static const uint32_t REGION_3_SECRET = 0x5f6a0423; +static const uint32_t REGION_4_SECRET = 0xbbaad5d8; +static const uint32_t REGION_5_SECRET = 0xa81c3678; +static const uint32_t REGION_6_SECRET = 0xe10f76f8; +static const uint32_t REGION_7_SECRET = 0x83220919; +static const uint32_t REGION_8_SECRET = 0xec37289d; +static const uint32_t REGION_9_SECRET = 0x27ac0ef8; +static const uint32_t REGION_10_SECRET = 0xf017e43d; +static const uint32_t REGION_11_SECRET = 0xbc7f7f49; +static const uint32_t REGION_12_SECRET = 0x42fedef6; +static const uint32_t REGION_13_SECRET = 0xe53cf10d; +static const uint32_t REGION_14_SECRET = 0xe862b70b; +static const uint32_t REGION_15_SECRET = 0x79b537ca; +static const uint32_t SECRETS[16]{ + REGION_0_SECRET, REGION_1_SECRET, REGION_2_SECRET, REGION_3_SECRET, + REGION_4_SECRET, REGION_5_SECRET, REGION_6_SECRET, REGION_7_SECRET, + REGION_8_SECRET, REGION_9_SECRET, REGION_10_SECRET, REGION_11_SECRET, + REGION_12_SECRET, REGION_13_SECRET, REGION_14_SECRET, REGION_15_SECRET}; +} // namespace region_secrets + +namespace comError { +enum Id { + BAD_CRC = 1, + UNKNOWN_TM_ID = 2, + UNKNOWN_PARAM_ID = 3, + UNKNOWN_ACTION_REQ = 4, + INVALID_TM_SIZE = 5, + INVALID_PARAM_SIZE = 6, + INVALID_ACTION_REQ_SIZE = 7, + FRAME_TOO_SHORT = 8, + INVALID_FRAME_TYPE = 9, + UNKNOWN_ERROR = 10 +}; +} + +enum class FlashSections : uint8_t { + BOOTLOADER_SECTION = 0, + MAIN_FIRMWARE_SECTION = 1, + ARC_CONFIG_SECTION = 2 +}; + +// Flash region IDs of firmware partition +enum class FirmwareRegions : uint32_t { + FIRST_MAIN = 1, + LAST_MAIN = 8, + FIRST_BACKUP = 10, + LAST_BACKUP = 16 +}; + +static const uint32_t FLASH_REGION_SIZE = 0x20000; + +/** + * @brief This dataset can be used to store the temperature of a reaction wheel. + */ +class TemperatureSet : public StaticLocalDataSet { + public: + TemperatureSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, TEMPERATURE_SET_ID) {} + + TemperatureSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, TEMPERATURE_SET_ID)) {} + + // Ticks is time reference generated by internal counter of the star tracker + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_TEMPERATURE_SET, this); + /** Unix time in microseconds */ + lp_var_t time = lp_var_t(sid.objectId, PoolIds::TIME_TEMPERATURE_SET, this); + lp_var_t mcuTemperature = lp_var_t(sid.objectId, PoolIds::MCU_TEMPERATURE, this); + lp_var_t cmosTemperature = lp_var_t(sid.objectId, PoolIds::CMOS_TEMPERATURE, this); + lp_var_t fpgaTemperature = lp_var_t(sid.objectId, PoolIds::FPGA_TEMPERATURE, this); + + void printSet() { + sif::info << "TemperatureSet::printSet: Ticks: " << this->ticks << std::endl; + sif::info << "TemperatureSet::printSet: Time: " << this->time << " us" << std::endl; + sif::info << "TemperatureSet::printSet: MCU Temperature: " << this->mcuTemperature << " °C" + << std::endl; + sif::info << "TemperatureSet::printSet: CMOS Temperature (raw): " << this->cmosTemperature + << std::endl; + sif::info << "TemperatureSet::printSet: FPGA Temperature (random value): " + << this->fpgaTemperature << " °C" << std::endl; + } +}; + +/** + * @brief Package to store version parameters + */ +class VersionSet : public StaticLocalDataSet { + public: + VersionSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, VERSION_SET_ID) {} + + VersionSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, VERSION_SET_ID)) {} + + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_VERSION_SET, this); + /** Unix time in microseconds */ + lp_var_t time = lp_var_t(sid.objectId, PoolIds::TIME_VERSION_SET, this); + lp_var_t program = lp_var_t(sid.objectId, PoolIds::PROGRAM, this); + lp_var_t major = lp_var_t(sid.objectId, PoolIds::MAJOR, this); + lp_var_t minor = lp_var_t(sid.objectId, PoolIds::MINOR, this); + + void printSet() { + PoolReadGuard rg(this); + sif::info << "VersionSet::printSet: Ticks: " << std::dec << this->ticks << std::endl; + sif::info << "VersionSet::printSet: Unix Time: " << this->time << " us" << std::endl; + sif::info << "VersionSet::printSet: Program: " << static_cast(this->program.value) + << std::endl; + sif::info << "VersionSet::printSet: Major: " << static_cast(this->major.value) + << std::endl; + sif::info << "VersionSet::printSet: Minor: " << static_cast(this->minor.value) + << std::endl; + } +}; + +/** + * @brief Dataset to store the interface telemtry data. + */ +class InterfaceSet : public StaticLocalDataSet { + public: + InterfaceSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, REQ_INTERFACE) {} + + InterfaceSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, REQ_INTERFACE)) {} + + // Ticks is time reference generated by interanl counter of the star tracker + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_INTERFACE_SET, this); + /** Unix time in microseconds */ + lp_var_t time = lp_var_t(sid.objectId, PoolIds::TIME_INTERFACE_SET, this); + lp_var_t frameCount = lp_var_t(sid.objectId, PoolIds::FRAME_COUNT, this); + lp_var_t checksumerrorCount = + lp_var_t(sid.objectId, PoolIds::CHECKSUM_ERROR_COUNT, this); + + void printSet() { + PoolReadGuard rg(this); + sif::info << "InterfaceSet::printSet: Ticks: " << this->ticks << std::endl; + sif::info << "InterfaceSet::printSet: Time: " << this->time << " us" << std::endl; + sif::info << "InterfaceSet::printSet: Frame Count: " << this->frameCount << std::endl; + sif::info << "InterfaceSet::printSet: Checksum Error Count: " << this->checksumerrorCount + << std::endl; + } +}; + +/** + * @brief Dataset to store the data of the power telemetry reply. + */ +class PowerSet : public StaticLocalDataSet { + public: + PowerSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, REQ_INTERFACE) {} + + PowerSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, REQ_INTERFACE)) {} + + // Ticks is time reference generated by interanl counter of the star tracker + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_POWER_SET, this); + /** Unix time in microseconds */ + lp_var_t time = lp_var_t(sid.objectId, PoolIds::TIME_POWER_SET, this); + lp_var_t mcuCurrent = lp_var_t(sid.objectId, PoolIds::MCU_CURRENT, this); + lp_var_t mcuVoltage = lp_var_t(sid.objectId, PoolIds::MCU_VOLTAGE, this); + lp_var_t fpgaCoreCurrent = lp_var_t(sid.objectId, PoolIds::FPGA_CORE_CURRENT, this); + lp_var_t fpgaCoreVoltage = lp_var_t(sid.objectId, PoolIds::FPGA_CORE_VOLTAGE, this); + lp_var_t fpga18Current = lp_var_t(sid.objectId, PoolIds::FPGA_18_CURRENT, this); + lp_var_t fpga18Voltage = lp_var_t(sid.objectId, PoolIds::FPGA_18_VOLTAGE, this); + lp_var_t fpga25Current = lp_var_t(sid.objectId, PoolIds::FPGA_25_CURRENT, this); + lp_var_t fpga25Voltage = lp_var_t(sid.objectId, PoolIds::FPGA_25_VOLTAGE, this); + lp_var_t cmv21Current = lp_var_t(sid.objectId, PoolIds::CMV_21_CURRENT, this); + lp_var_t cmv21Voltage = lp_var_t(sid.objectId, PoolIds::CMV_21_VOLTAGE, this); + lp_var_t cmvPixCurrent = lp_var_t(sid.objectId, PoolIds::CMV_PIX_CURRENT, this); + lp_var_t cmvPixVoltage = lp_var_t(sid.objectId, PoolIds::CMV_PIX_VOLTAGE, this); + lp_var_t cmv33Current = lp_var_t(sid.objectId, PoolIds::CMV_33_CURRENT, this); + lp_var_t cmv33Voltage = lp_var_t(sid.objectId, PoolIds::CMV_33_VOLTAGE, this); + lp_var_t cmvResCurrent = lp_var_t(sid.objectId, PoolIds::CMV_RES_CURRENT, this); + lp_var_t cmvResVoltage = lp_var_t(sid.objectId, PoolIds::CMV_RES_VOLTAGE, this); + + void printSet() { + PoolReadGuard rg(this); + sif::info << "PowerSet::printSet: Ticks: " << this->ticks << std::endl; + sif::info << "PowerSet::printSet: Time: " << this->time << " us" << std::endl; + sif::info << "PowerSet::printSet: MCU Current: " << this->mcuCurrent << " A" << std::endl; + sif::info << "PowerSet::printSet: MCU Voltage: " << this->mcuVoltage << " V" << std::endl; + sif::info << "PowerSet::printSet: FPGA Core current: " << this->fpgaCoreCurrent << " A" + << std::endl; + sif::info << "PowerSet::printSet: FPGA Core voltage: " << this->fpgaCoreVoltage << " V" + << std::endl; + sif::info << "PowerSet::printSet: FPGA 18 current: " << this->fpga18Current << " A" + << std::endl; + sif::info << "PowerSet::printSet: FPGA 18 voltage: " << this->fpga18Voltage << " V" + << std::endl; + sif::info << "PowerSet::printSet: FPGA 25 current: " << this->fpga25Current << " A" + << std::endl; + sif::info << "PowerSet::printSet: FPGA 25 voltage: " << this->fpga25Voltage << " V" + << std::endl; + sif::info << "PowerSet::printSet: CMV 21 current: " << this->cmv21Current << " A" << std::endl; + sif::info << "PowerSet::printSet: CMV 21 voltage: " << this->cmv21Voltage << " V" << std::endl; + sif::info << "PowerSet::printSet: CMV Pix current: " << this->cmvPixCurrent << " A" + << std::endl; + sif::info << "PowerSet::printSet: CMV Pix voltage: " << this->cmvPixVoltage << " V" + << std::endl; + sif::info << "PowerSet::printSet: CMV 33 current: " << this->cmv33Current << " A" << std::endl; + sif::info << "PowerSet::printSet: CMV 33 voltage: " << this->cmv33Voltage << " V" << std::endl; + sif::info << "PowerSet::printSet: CMV Res current: " << this->cmvResCurrent << " A" + << std::endl; + sif::info << "PowerSet::printSet: CMV Res voltage: " << this->cmvResVoltage << " V" + << std::endl; + } +}; + +/** + * @brief Data set to store the time telemetry packet. + */ +class TimeSet : public StaticLocalDataSet { + public: + TimeSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, TIME_SET_ID) {} + + TimeSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, TIME_SET_ID)) {} + + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_TIME_SET, this); + /** Unix time in microseconds */ + lp_var_t time = lp_var_t(sid.objectId, PoolIds::TIME_TIME_SET, this); + // Number of milliseconds since processor start-up + lp_var_t runTime = lp_var_t(sid.objectId, PoolIds::RUN_TIME, this); + // Unix time in seconds?? --> maybe typo in datasheet. Seems to be microseconds + lp_var_t unixTime = lp_var_t(sid.objectId, PoolIds::UNIX_TIME, this); + void printSet() { + PoolReadGuard rg(this); + sif::info << "TimeSet::printSet: Ticks: " << this->ticks << std::endl; + sif::info << "TimeSet::printSet: Time (time stamp): " << this->time << " us" << std::endl; + sif::info << "TimeSet::printSet: Run Time: " << this->runTime << " ms" << std::endl; + sif::info << "TimeSet::printSet: Unix Time: " << this->unixTime << " s" << std::endl; + } +}; + +/** + * @brief The solution dataset is the main dataset of the star tracker and contains the + * attitude information. + */ +class SolutionSet : public StaticLocalDataSet { + public: + SolutionSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, SOLUTION_SET_ID) {} + + SolutionSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, SOLUTION_SET_ID)) {} + + // Ticks timestamp + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_SOLUTION_SET, this); + // Unix time stamp + lp_var_t timeUs = lp_var_t(sid.objectId, PoolIds::TIME_SOLUTION_SET, this); + // Calibrated quaternion (takes into account the mounting quaternion), typically same as + // track q values + lp_var_t caliQw = lp_var_t(sid.objectId, PoolIds::CALI_QW, this); + lp_var_t caliQx = lp_var_t(sid.objectId, PoolIds::CALI_QX, this); + lp_var_t caliQy = lp_var_t(sid.objectId, PoolIds::CALI_QY, this); + lp_var_t caliQz = lp_var_t(sid.objectId, PoolIds::CALI_QZ, this); + // The lower this value the more confidence that the star tracker solution is correct + lp_var_t trackConfidence = lp_var_t(sid.objectId, PoolIds::TRACK_CONFIDENCE, this); + // Estimated attitude of spacecraft + lp_var_t trackQw = lp_var_t(sid.objectId, PoolIds::TRACK_QW, this); + lp_var_t trackQx = lp_var_t(sid.objectId, PoolIds::TRACK_QX, this); + lp_var_t trackQy = lp_var_t(sid.objectId, PoolIds::TRACK_QY, this); + lp_var_t trackQz = lp_var_t(sid.objectId, PoolIds::TRACK_QZ, this); + // Number of stars removed from tracking solution + lp_var_t trackRemoved = lp_var_t(sid.objectId, PoolIds::TRACK_REMOVED, this); + // Number of stars for which a valid centroid was found + lp_var_t starsCentroided = + lp_var_t(sid.objectId, PoolIds::STARS_CENTROIDED, this); + // Number of stars that matched to a database star + lp_var_t starsMatchedDatabase = + lp_var_t(sid.objectId, PoolIds::STARS_MATCHED_DATABASE, this); + // Result of LISA (lost in space algorithm), searches for stars without prior knowledge of + // attitude + lp_var_t lisaQw = lp_var_t(sid.objectId, PoolIds::LISA_QW, this); + lp_var_t lisaQx = lp_var_t(sid.objectId, PoolIds::LISA_QX, this); + lp_var_t lisaQy = lp_var_t(sid.objectId, PoolIds::LISA_QY, this); + lp_var_t lisaQz = lp_var_t(sid.objectId, PoolIds::LISA_QZ, this); + // Percentage of close stars in LISA solution + lp_var_t lisaPercentageClose = + lp_var_t(sid.objectId, PoolIds::LISA_PERC_CLOSE, this); + // Number of close stars in LISA solution + lp_var_t lisaNrClose = lp_var_t(sid.objectId, PoolIds::LISA_NR_CLOSE, this); + lp_var_t strMode = lp_var_t(sid.objectId, PoolIds::STR_MODE, this); + // Gives a combined overview of the validation parameters (1 for valid solution, otherwise 0) + lp_var_t isTrustWorthy = lp_var_t(sid.objectId, PoolIds::TRUST_WORTHY, this); + // Number of times the validation criteria was met + lp_var_t stableCount = lp_var_t(sid.objectId, PoolIds::STABLE_COUNT, this); + // Shows the autonomous mode used to obtain the star tracker attitude + lp_var_t solutionStrategy = + lp_var_t(sid.objectId, PoolIds::SOLUTION_STRATEGY, this); + + void printSet() { + PoolReadGuard rg(this); + sif::info << "SolutionSet::printSet: Ticks: " << this->ticks << std::endl; + sif::info << "SolutionSet::printSet: Time: " << this->timeUs << std::endl; + sif::info << "SolutionSet::printSet: Calibrated quaternion Qw: " << this->caliQw << std::endl; + sif::info << "SolutionSet::printSet: Calibrated quaternion Qx: " << this->caliQx << std::endl; + sif::info << "SolutionSet::printSet: Calibrated quaternion Qy: " << this->caliQy << std::endl; + sif::info << "SolutionSet::printSet: Calibrated quaternion Qz: " << this->caliQz << std::endl; + sif::info << "SolutionSet::printSet: Track confidence: " << this->trackConfidence << std::endl; + sif::info << "SolutionSet::printSet: Track Qw: " << this->trackQw << std::endl; + sif::info << "SolutionSet::printSet: Track Qx: " << this->trackQx << std::endl; + sif::info << "SolutionSet::printSet: Track Qy: " << this->trackQy << std::endl; + sif::info << "SolutionSet::printSet: Track Qz: " << this->trackQz << std::endl; + sif::info << "SolutionSet::printSet: Track removed: " + << static_cast(this->trackRemoved.value) << std::endl; + sif::info << "SolutionSet::printSet: Number of stars centroided: " + << static_cast(this->starsCentroided.value) << std::endl; + sif::info << "SolutionSet::printSet: Number of stars matched database: " + << static_cast(this->starsMatchedDatabase.value) << std::endl; + sif::info << "SolutionSet::printSet: LISA Qw: " << this->lisaQw << std::endl; + sif::info << "SolutionSet::printSet: LISA Qx: " << this->lisaQx << std::endl; + sif::info << "SolutionSet::printSet: LISA Qy: " << this->lisaQy << std::endl; + sif::info << "SolutionSet::printSet: LISA Qz: " << this->lisaQz << std::endl; + sif::info << "SolutionSet::printSet: LISA Percentage close: " << this->lisaPercentageClose + << std::endl; + sif::info << "SolutionSet::printSet: LISA number of close stars: " + << static_cast(this->lisaNrClose.value) << std::endl; + sif::info << "SolutionSet::printSet: Is trust worthy: " + << static_cast(this->isTrustWorthy.value) << std::endl; + sif::info << "SolutionSet::printSet: Stable count: " << this->stableCount << std::endl; + sif::info << "SolutionSet::printSet: Solution strategy: " + << static_cast(this->solutionStrategy.value) << std::endl; + } +}; + +/** + * @brief Dataset to store the histogram + */ +class HistogramSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 156; + + HistogramSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, HISTOGRAM_SET_ID) {} + + HistogramSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, HISTOGRAM_SET_ID)) {} + + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_HISTOGRAM_SET, this); + lp_var_t time = lp_var_t(sid.objectId, PoolIds::TIME_HISTOGRAM_SET, this); + lp_var_t binA0 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA0, this); + lp_var_t binA1 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA1, this); + lp_var_t binA2 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA2, this); + lp_var_t binA3 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA3, this); + lp_var_t binA4 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA4, this); + lp_var_t binA5 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA5, this); + lp_var_t binA6 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA6, this); + lp_var_t binA7 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA7, this); + lp_var_t binA8 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINA8, this); + lp_var_t binB0 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB0, this); + lp_var_t binB1 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB1, this); + lp_var_t binB2 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB2, this); + lp_var_t binB3 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB3, this); + lp_var_t binB4 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB4, this); + lp_var_t binB5 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB5, this); + lp_var_t binB6 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB6, this); + lp_var_t binB7 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB7, this); + lp_var_t binB8 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINB8, this); + lp_var_t binC0 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC0, this); + lp_var_t binC1 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC1, this); + lp_var_t binC2 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC2, this); + lp_var_t binC3 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC3, this); + lp_var_t binC4 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC4, this); + lp_var_t binC5 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC5, this); + lp_var_t binC6 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC6, this); + lp_var_t binC7 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC7, this); + lp_var_t binC8 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BINC8, this); + lp_var_t binD0 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND0, this); + lp_var_t binD1 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND1, this); + lp_var_t binD2 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND2, this); + lp_var_t binD3 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND3, this); + lp_var_t binD4 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND4, this); + lp_var_t binD5 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND5, this); + lp_var_t binD6 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND6, this); + lp_var_t binD7 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND7, this); + lp_var_t binD8 = lp_var_t(sid.objectId, PoolIds::HISTOGRAM_BIND8, this); + + void printSet() { + PoolReadGuard rg(this); + sif::info << "HistogramSet::printSet: Ticks: " << this->ticks << std::endl; + sif::info << "HistogramSet::printSet: Time (time stamp): " << this->time << " us" << std::endl; + sif::info << "HistogramSet::printSet: BinA0: " << this->binA0 << std::endl; + sif::info << "HistogramSet::printSet: BinA1: " << this->binA1 << std::endl; + sif::info << "HistogramSet::printSet: BinA2: " << this->binA2 << std::endl; + sif::info << "HistogramSet::printSet: BinA3: " << this->binA3 << std::endl; + sif::info << "HistogramSet::printSet: BinA4: " << this->binA4 << std::endl; + sif::info << "HistogramSet::printSet: BinA5: " << this->binA5 << std::endl; + sif::info << "HistogramSet::printSet: BinA6: " << this->binA6 << std::endl; + sif::info << "HistogramSet::printSet: BinA7: " << this->binA7 << std::endl; + sif::info << "HistogramSet::printSet: BinA8: " << this->binA8 << std::endl; + sif::info << "HistogramSet::printSet: BinB0: " << this->binB0 << std::endl; + sif::info << "HistogramSet::printSet: BinB1: " << this->binB1 << std::endl; + sif::info << "HistogramSet::printSet: BinB2: " << this->binB2 << std::endl; + sif::info << "HistogramSet::printSet: BinB3: " << this->binB3 << std::endl; + sif::info << "HistogramSet::printSet: BinB4: " << this->binB4 << std::endl; + sif::info << "HistogramSet::printSet: BinB5: " << this->binB5 << std::endl; + sif::info << "HistogramSet::printSet: BinB6: " << this->binB6 << std::endl; + sif::info << "HistogramSet::printSet: BinB7: " << this->binB7 << std::endl; + sif::info << "HistogramSet::printSet: BinB8: " << this->binB8 << std::endl; + sif::info << "HistogramSet::printSet: BinC0: " << this->binC0 << std::endl; + sif::info << "HistogramSet::printSet: BinC1: " << this->binC1 << std::endl; + sif::info << "HistogramSet::printSet: BinC2: " << this->binC2 << std::endl; + sif::info << "HistogramSet::printSet: BinC3: " << this->binC3 << std::endl; + sif::info << "HistogramSet::printSet: BinC4: " << this->binC4 << std::endl; + sif::info << "HistogramSet::printSet: BinC5: " << this->binC5 << std::endl; + sif::info << "HistogramSet::printSet: BinC6: " << this->binC6 << std::endl; + sif::info << "HistogramSet::printSet: BinC7: " << this->binC7 << std::endl; + sif::info << "HistogramSet::printSet: BinC8: " << this->binC8 << std::endl; + sif::info << "HistogramSet::printSet: BinD0: " << this->binD0 << std::endl; + sif::info << "HistogramSet::printSet: BinD1: " << this->binD1 << std::endl; + sif::info << "HistogramSet::printSet: BinD2: " << this->binD2 << std::endl; + sif::info << "HistogramSet::printSet: BinD3: " << this->binD3 << std::endl; + sif::info << "HistogramSet::printSet: BinD4: " << this->binD4 << std::endl; + sif::info << "HistogramSet::printSet: BinD5: " << this->binD5 << std::endl; + sif::info << "HistogramSet::printSet: BinD6: " << this->binD6 << std::endl; + sif::info << "HistogramSet::printSet: BinD7: " << this->binD7 << std::endl; + sif::info << "HistogramSet::printSet: BinD8: " << this->binD8 << std::endl; + } +}; + +/** + * @brief Helper Class to extract information from bytestream. + */ +class ChecksumReply { + public: + /** + * @brief Constructor + * + * @param datafield Pointer to datafield in reply buffer + * + */ + ChecksumReply(const uint8_t* datafield) { + ReturnValue_t result = returnvalue::OK; + region = *(datafield); + const uint8_t* addressData = datafield + ADDRESS_OFFSET; + size_t size = sizeof(address); + result = SerializeAdapter::deSerialize(&address, &addressData, &size, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + sif::debug << "ChecksumReply::ChecksumReply: Failed to deserialize address" << std::endl; + } + const uint8_t* lengthData = datafield + LENGTH_OFFSET; + size = sizeof(length); + result = + SerializeAdapter::deSerialize(&length, &lengthData, &size, SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + sif::debug << "ChecksumReply::ChecksumReply: Failed to deserialize length" << std::endl; + } + const uint8_t* checksumData = datafield + CHECKSUM_OFFSET; + size = sizeof(checksum); + result = SerializeAdapter::deSerialize(&checksum, &checksumData, &size, + SerializeIF::Endianness::LITTLE); + if (result != returnvalue::OK) { + sif::debug << "ChecksumReply::ChecksumReply: Failed to deserialize checksum" << std::endl; + } + } + + uint8_t getRegion() { return region; } + + uint32_t getAddress() { return address; } + + uint32_t getLength() { return length; } + + uint32_t getChecksum() { return checksum; } + + void printChecksum() { + sif::info << "ChecksumReply::printChecksum: 0x" << std::hex << checksum << std::endl; + } + + private: + static const uint8_t ADDRESS_OFFSET = 1; + static const uint8_t LENGTH_OFFSET = 5; + static const uint8_t CHECKSUM_OFFSET = 9; + + uint8_t region = 0; + uint32_t address = 0; + uint32_t length = 0; + uint32_t checksum = 0; +}; + +class ChecksumSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 156; + + ChecksumSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, CHECKSUM_SET_ID) {} + + ChecksumSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, CHECKSUM_SET_ID)) {} + + lp_var_t checksum = lp_var_t(sid.objectId, PoolIds::CHKSUM, this); +}; + +/** + * @brief Will store the camera parameters set in the star tracker which are retrieved with + * a get parameter request. + */ +class CameraSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 34; + + CameraSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, CAMERA_SET_ID) {} + + CameraSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, CAMERA_SET_ID)) {} + + lp_var_t mode = lp_var_t(sid.objectId, PoolIds::CAMERA_MODE, this); + lp_var_t focallength = lp_var_t(sid.objectId, PoolIds::FOCALLENGTH, this); + lp_var_t exposure = lp_var_t(sid.objectId, PoolIds::EXPOSURE, this); + lp_var_t interval = lp_var_t(sid.objectId, PoolIds::INTERVAL, this); + lp_var_t offset = lp_var_t(sid.objectId, PoolIds::CAMERA_OFFSET, this); + lp_var_t pgagain = lp_var_t(sid.objectId, PoolIds::PGAGAIN, this); + lp_var_t adcgain = lp_var_t(sid.objectId, PoolIds::ADCGAIN, this); + lp_var_t reg1 = lp_var_t(sid.objectId, PoolIds::CAM_REG1, this); + lp_var_t val1 = lp_var_t(sid.objectId, PoolIds::CAM_VAL1, this); + lp_var_t reg2 = lp_var_t(sid.objectId, PoolIds::CAM_REG2, this); + lp_var_t val2 = lp_var_t(sid.objectId, PoolIds::CAM_VAL2, this); + lp_var_t reg3 = lp_var_t(sid.objectId, PoolIds::CAM_REG3, this); + lp_var_t val3 = lp_var_t(sid.objectId, PoolIds::CAM_VAL3, this); + lp_var_t reg4 = lp_var_t(sid.objectId, PoolIds::CAM_REG4, this); + lp_var_t val4 = lp_var_t(sid.objectId, PoolIds::CAM_VAL4, this); + lp_var_t reg5 = lp_var_t(sid.objectId, PoolIds::CAM_REG5, this); + lp_var_t val5 = lp_var_t(sid.objectId, PoolIds::CAM_VAL5, this); + lp_var_t reg6 = lp_var_t(sid.objectId, PoolIds::CAM_REG6, this); + lp_var_t val6 = lp_var_t(sid.objectId, PoolIds::CAM_VAL6, this); + lp_var_t reg7 = lp_var_t(sid.objectId, PoolIds::CAM_REG7, this); + lp_var_t val7 = lp_var_t(sid.objectId, PoolIds::CAM_VAL7, this); + lp_var_t reg8 = lp_var_t(sid.objectId, PoolIds::CAM_REG8, this); + lp_var_t val8 = lp_var_t(sid.objectId, PoolIds::CAM_VAL8, this); + lp_var_t freq1 = lp_var_t(sid.objectId, PoolIds::CAM_FREQ_1, this); + + void printSet() { + PoolReadGuard rg(this); + sif::info << "CameraSet::printSet: mode: " << static_cast(this->mode.value) + << std::endl; + sif::info << "CameraSet::printSet: focallength: " << this->focallength << std::endl; + sif::info << "CameraSet::printSet: exposure: " << this->exposure << std::endl; + sif::info << "CameraSet::printSet: interval: " << this->interval << std::endl; + sif::info << "CameraSet::printSet: offset: " << this->offset << std::endl; + sif::info << "CameraSet::printSet: PGA gain: " << static_cast(this->pgagain.value) + << std::endl; + sif::info << "CameraSet::printSet: ADC gain: " << static_cast(this->adcgain.value) + << std::endl; + sif::info << "CameraSet::printSet: Reg 1: " << static_cast(this->reg1.value) + << std::endl; + sif::info << "CameraSet::printSet: Val 1: " << static_cast(this->val1.value) + << std::endl; + sif::info << "CameraSet::printSet: Reg 2: " << static_cast(this->reg2.value) + << std::endl; + sif::info << "CameraSet::printSet: Val 2: " << static_cast(this->val2.value) + << std::endl; + sif::info << "CameraSet::printSet: Reg 3: " << static_cast(this->reg3.value) + << std::endl; + sif::info << "CameraSet::printSet: Val 3: " << static_cast(this->val3.value) + << std::endl; + sif::info << "CameraSet::printSet: Reg 4: " << static_cast(this->reg4.value) + << std::endl; + sif::info << "CameraSet::printSet: Val 4: " << static_cast(this->val4.value) + << std::endl; + sif::info << "CameraSet::printSet: Reg 5: " << static_cast(this->reg5.value) + << std::endl; + sif::info << "CameraSet::printSet: Val 5: " << static_cast(this->val5.value) + << std::endl; + sif::info << "CameraSet::printSet: Reg 6: " << static_cast(this->reg6.value) + << std::endl; + sif::info << "CameraSet::printSet: Val 6: " << static_cast(this->val6.value) + << std::endl; + sif::info << "CameraSet::printSet: Reg 7: " << static_cast(this->reg7.value) + << std::endl; + sif::info << "CameraSet::printSet: Val 7: " << static_cast(this->val7.value) + << std::endl; + sif::info << "CameraSet::printSet: Reg 8: " << static_cast(this->reg8.value) + << std::endl; + sif::info << "CameraSet::printSet: Val 8: " << static_cast(this->val8.value) + << std::endl; + sif::info << "CameraSet::printSet: Freq 1: " << static_cast(this->freq1.value) + << std::endl; + } +}; + +/** + * @brief Will store the requested limits + */ +class LimitsSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 41; + + LimitsSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, LIMITS_SET_ID) {} + + LimitsSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, LIMITS_SET_ID)) {} + + lp_var_t action = lp_var_t(sid.objectId, PoolIds::LIMITS_ACTION, this); + lp_var_t fpga18current = + lp_var_t(sid.objectId, PoolIds::LIMITS_FPGA18CURRENT, this); + lp_var_t fpga25current = + lp_var_t(sid.objectId, PoolIds::LIMITS_FPGA25CURRENT, this); + lp_var_t fpga10current = + lp_var_t(sid.objectId, PoolIds::LIMITS_FPGA10CURRENT, this); + lp_var_t mcuCurrent = lp_var_t(sid.objectId, PoolIds::LIMITS_MCUCURRENT, this); + lp_var_t cmos21current = + lp_var_t(sid.objectId, PoolIds::LIMITS_CMOS21CURRENT, this); + lp_var_t cmosPixCurrent = + lp_var_t(sid.objectId, PoolIds::LIMITS_CMOSPIXCURRENT, this); + lp_var_t cmos33current = + lp_var_t(sid.objectId, PoolIds::LIMITS_CMOS33CURRENT, this); + lp_var_t cmosVresCurrent = + lp_var_t(sid.objectId, PoolIds::LIMITS_CMOSVRESCURRENT, this); + lp_var_t cmosTemperature = + lp_var_t(sid.objectId, PoolIds::LIMITS_CMOSTEMPERATURE, this); + + void printSet() { + PoolReadGuard rg(this); + sif::info << "LimitsSet::printSet: action: " << static_cast(this->action.value) + << std::endl; + sif::info << "LimitsSet::printSet: FPGA18Current: " << this->fpga18current << std::endl; + sif::info << "LimitsSet::printSet: FPGA25Current: " << this->fpga25current << std::endl; + sif::info << "LimitsSet::printSet: FPGA10Current: " << this->fpga10current << std::endl; + sif::info << "LimitsSet::printSet: MCUCurrent: " << this->mcuCurrent << std::endl; + sif::info << "LimitsSet::printSet: CMOS21Current: " << this->cmos21current << std::endl; + sif::info << "LimitsSet::printSet: CMOSPixCurrent: " << this->cmosPixCurrent << std::endl; + sif::info << "LimitsSet::printSet: CMOS33Current: " << this->cmos33current << std::endl; + sif::info << "LimitsSet::printSet: CMOSVResCurrent: " << this->cmosVresCurrent << std::endl; + sif::info << "LimitsSet::printSet: CMOSTemperature: " << this->cmosTemperature << std::endl; + } +}; + +/** + * @brief Will store the requested log level parameters + */ +class LogLevelSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 16; + + LogLevelSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, LOG_LEVEL_SET_ID) {} + + LogLevelSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, LOG_LEVEL_SET_ID)) {} + + lp_var_t loglevel1 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL1, this); + lp_var_t loglevel2 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL2, this); + lp_var_t loglevel3 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL3, this); + lp_var_t loglevel4 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL4, this); + lp_var_t loglevel5 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL5, this); + lp_var_t loglevel6 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL6, this); + lp_var_t loglevel7 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL7, this); + lp_var_t loglevel8 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL8, this); + lp_var_t loglevel9 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL9, this); + lp_var_t loglevel10 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL10, this); + lp_var_t loglevel11 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL11, this); + lp_var_t loglevel12 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL12, this); + lp_var_t loglevel13 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL13, this); + lp_var_t loglevel14 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL14, this); + lp_var_t loglevel15 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL15, this); + lp_var_t loglevel16 = lp_var_t(sid.objectId, PoolIds::LOGLEVEL16, this); + + void printSet() { + PoolReadGuard rg(this); + sif::info << "LogLevelSet::printSet: loglevel1: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel2: " + << static_cast(this->loglevel2.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel3: " + << static_cast(this->loglevel3.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel4: " + << static_cast(this->loglevel4.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel5: " + << static_cast(this->loglevel5.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel6: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel7: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel8: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel9: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel10: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel11: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel12: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel13: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel14: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel15: " + << static_cast(this->loglevel1.value) << std::endl; + sif::info << "LogLevelSet::printSet: loglevel16: " + << static_cast(this->loglevel1.value) << std::endl; + } +}; + +/** + * @brief Will store the requested mounting parameters + */ +class MountingSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 16; + + MountingSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, MOUNTING_SET_ID) {} + + MountingSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, MOUNTING_SET_ID)) {} + + lp_var_t qw = lp_var_t(sid.objectId, PoolIds::MOUNTING_QW, this); + lp_var_t qx = lp_var_t(sid.objectId, PoolIds::MOUNTING_QX, this); + lp_var_t qy = lp_var_t(sid.objectId, PoolIds::MOUNTING_QY, this); + lp_var_t qz = lp_var_t(sid.objectId, PoolIds::MOUNTING_QZ, this); + + void printSet() { + sif::info << "MountingSet::printSet: qw: " << this->qw << std::endl; + sif::info << "MountingSet::printSet: qx: " << this->qx << std::endl; + sif::info << "MountingSet::printSet: qy: " << this->qy << std::endl; + sif::info << "MountingSet::printSet: qz: " << this->qz << std::endl; + } +}; + +/** + * @brief Will store the requested image processor parameters + */ +class ImageProcessorSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 7; + + ImageProcessorSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, IMAGE_PROCESSOR_SET_ID) {} + + ImageProcessorSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, IMAGE_PROCESSOR_SET_ID)) {} + + lp_var_t mode = lp_var_t(sid.objectId, PoolIds::IMAGE_PROCESSOR_MODE, this); + lp_var_t store = lp_var_t(sid.objectId, PoolIds::IMAGE_PROCESSOR_STORE, this); + lp_var_t signalThreshold = + lp_var_t(sid.objectId, PoolIds::IMAGE_PROCESSOR_SIGNALTHRESHOLD, this); + lp_var_t darkThreshold = + lp_var_t(sid.objectId, PoolIds::IMAGE_PROCESSOR_DARKTHRESHOLD, this); + lp_var_t backgroundCompensation = + lp_var_t(sid.objectId, PoolIds::IMAGE_PROCESSOR_BACKGROUNDCOMPENSATION, this); + + void printSet() { + sif::info << "ImageProcessorSet::printSet: mode: " + << static_cast(this->mode.value) << std::endl; + sif::info << "ImageProcessorSet::printSet: store: " + << static_cast(this->store.value) << std::endl; + sif::info << "ImageProcessorSet::printSet: signal threshold: " << this->signalThreshold + << std::endl; + sif::info << "ImageProcessorSet::printSet: dark threshold: " << this->darkThreshold + << std::endl; + sif::info << "ImageProcessorSet::printSet: background compensation: " + << static_cast(this->backgroundCompensation.value) << std::endl; + } +}; + +/** + * @brief Will store the requested centroiding parameters + */ +class CentroidingSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 49; + + CentroidingSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, CENTROIDING_SET_ID) {} + + CentroidingSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, CENTROIDING_SET_ID)) {} + + lp_var_t enableFilter = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_ENABLE_FILTER, this); + lp_var_t maxQuality = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_MAX_QUALITY, this); + lp_var_t darkThreshold = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_DARK_THRESHOLD, this); + lp_var_t minQuality = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_MIN_QUALITY, this); + lp_var_t maxIntensity = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_MAX_INTENSITY, this); + lp_var_t minIntensity = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_MIN_INTENSITY, this); + lp_var_t maxMagnitude = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_MAX_MAGNITUDE, this); + lp_var_t gaussianCmax = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_GAUSSIAN_CMAX, this); + lp_var_t gaussianCmin = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_GAUSSIAN_CMIN, this); + lp_var_t transmatrix00 = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_TRANSMATRIX00, this); + lp_var_t transmatrix01 = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_TRANSMATRIX01, this); + lp_var_t transmatrix10 = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_TRANSMATRIX10, this); + lp_var_t transmatrix11 = + lp_var_t(sid.objectId, PoolIds::CENTROIDING_TRANSMATRIX11, this); + + void printSet() { + sif::info << "CentroidingSet::printSet: enable filter: " + << static_cast(this->enableFilter.value) << std::endl; + sif::info << "CentroidingSet::printSet: max quality: " << this->maxQuality << std::endl; + sif::info << "CentroidingSet::printSet: dark threshold: " << this->darkThreshold << std::endl; + sif::info << "CentroidingSet::printSet: min quality: " << this->minQuality << std::endl; + sif::info << "CentroidingSet::printSet: max intensity: " << this->maxIntensity << std::endl; + sif::info << "CentroidingSet::printSet: min intensity: " << this->minIntensity << std::endl; + sif::info << "CentroidingSet::printSet: max magnitude: " << this->maxMagnitude << std::endl; + sif::info << "CentroidingSet::printSet: gaussian cmax: " << this->gaussianCmax << std::endl; + sif::info << "CentroidingSet::printSet: gaussian cmin: " << this->gaussianCmin << std::endl; + sif::info << "CentroidingSet::printSet: transmatrix 00 : " << this->transmatrix00 << std::endl; + sif::info << "CentroidingSet::printSet: transmatrix 01 : " << this->transmatrix01 << std::endl; + sif::info << "CentroidingSet::printSet: transmatrix 10 : " << this->transmatrix10 << std::endl; + sif::info << "CentroidingSet::printSet: transmatrix 11 : " << this->transmatrix11 << std::endl; + } +}; + +/** + * @brief Will store the requested centroiding parameters + */ +class LisaSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 50; + + LisaSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, LISA_SET_ID) {} + + LisaSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, LISA_SET_ID)) {} + + lp_var_t mode = lp_var_t(sid.objectId, PoolIds::LISA_MODE, this); + lp_var_t prefilterDistThreshold = + lp_var_t(sid.objectId, PoolIds::LISA_PREFILTER_DIST_THRESHOLD, this); + lp_var_t prefilterAngleThreshold = + lp_var_t(sid.objectId, PoolIds::LISA_PREFILTER_ANGLE_THRESHOLD, this); + lp_var_t fovWidth = lp_var_t(sid.objectId, PoolIds::LISA_FOV_WIDTH, this); + lp_var_t fovHeight = lp_var_t(sid.objectId, PoolIds::LISA_FOV_HEIGHT, this); + lp_var_t floatStarLimit = + lp_var_t(sid.objectId, PoolIds::LISA_FLOAT_STAR_LIMIT, this); + lp_var_t closeStarLimit = + lp_var_t(sid.objectId, PoolIds::LISA_CLOSE_STAR_LIMIT, this); + lp_var_t ratingWeightCloseStarCount = + lp_var_t(sid.objectId, PoolIds::LISA_RATING_WEIGHT_CLOSE_STAR_COUNT, this); + lp_var_t ratingWeightFractionClose = + lp_var_t(sid.objectId, PoolIds::LISA_RATING_WEIGHT_FRACTION_CLOSE, this); + lp_var_t ratingWeightMeanSum = + lp_var_t(sid.objectId, PoolIds::LISA_RATING_WEIGHT_MEAN_SUM, this); + lp_var_t ratingWeightDbStarCount = + lp_var_t(sid.objectId, PoolIds::LISA_RATING_WEIGHT_DB_STAR_COUNT, this); + lp_var_t maxCombinations = + lp_var_t(sid.objectId, PoolIds::LISA_MAX_COMBINATIONS, this); + lp_var_t nrStarsStop = + lp_var_t(sid.objectId, PoolIds::LISA_NR_STARS_STOP, this); + lp_var_t fractionCloseStop = + lp_var_t(sid.objectId, PoolIds::LISA_FRACTION_CLOSE_STOP, this); + + void printSet() { + sif::info << "LisaSet::printSet: mode: " << this->mode << std::endl; + sif::info << "LisaSet::printSet: prefilter dist threshold: " << this->prefilterDistThreshold + << std::endl; + sif::info << "LisaSet::printSet: prefilter angle threshold: " << this->prefilterAngleThreshold + << std::endl; + sif::info << "LisaSet::printSet: fov width: " << this->fovWidth << std::endl; + sif::info << "LisaSet::printSet: fov height: " << this->fovHeight << std::endl; + sif::info << "LisaSet::printSet: float star limit: " << this->floatStarLimit << std::endl; + sif::info << "LisaSet::printSet: close star limit: " << this->closeStarLimit << std::endl; + sif::info << "LisaSet::printSet: rating weight close star count: " + << this->ratingWeightCloseStarCount << std::endl; + sif::info << "LisaSet::printSet: rating weight fraction close: " + << this->ratingWeightFractionClose << std::endl; + sif::info << "LisaSet::printSet: rating weight mean sum: " << this->ratingWeightMeanSum + << std::endl; + sif::info << "LisaSet::printSet: rating weight db star count: " << this->ratingWeightDbStarCount + << std::endl; + sif::info << "LisaSet::printSet: max combinations: " + << static_cast(this->maxCombinations.value) << std::endl; + sif::info << "LisaSet::printSet: nr stars stop: " + << static_cast(this->nrStarsStop.value) << std::endl; + sif::info << "LisaSet::printSet: fraction close stop: " << this->fractionCloseStop << std::endl; + } +}; + +/** + * @brief Will store the requested matching parameters + */ +class MatchingSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 8; + + MatchingSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, MATCHING_SET_ID) {} + + MatchingSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, MATCHING_SET_ID)) {} + + lp_var_t squaredDistanceLimit = + lp_var_t(sid.objectId, PoolIds::MATCHING_SQUARED_DISTANCE_LIMIT, this); + lp_var_t squaredShiftLimit = + lp_var_t(sid.objectId, PoolIds::MATCHING_SQUARED_SHIFT_LIMIT, this); + + void printSet() { + sif::info << "MatchingSet::printSet: squared distance limit: " << this->squaredDistanceLimit + << std::endl; + sif::info << "MatchingSet::printSet: squared distance limit: " << this->squaredShiftLimit + << std::endl; + } +}; + +/** + * @brief Will store the requested tracking parameters + */ +class TrackingSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 13; + + TrackingSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, TRACKING_SET_ID) {} + + TrackingSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, TRACKING_SET_ID)) {} + + lp_var_t thinLimit = lp_var_t(sid.objectId, PoolIds::TRACKING_THIN_LIMIT, this); + lp_var_t outlierThreshold = + lp_var_t(sid.objectId, PoolIds::TRACKING_OUTLIER_THRESHOLD, this); + lp_var_t outlierThresholdQuest = + lp_var_t(sid.objectId, PoolIds::TRACKING_OUTLIER_THRESHOLD_QUEST, this); + lp_var_t trackerChoice = + lp_var_t(sid.objectId, PoolIds::TRACKING_TRACKER_CHOICE, this); + + void printSet() { + sif::info << "TrackingSet::printSet: thin limit: " << this->thinLimit << std::endl; + sif::info << "TrackingSet::printSet: outlier threshold: " << this->outlierThreshold + << std::endl; + sif::info << "TrackingSet::printSet: outlier threshold quest: " << this->outlierThresholdQuest + << std::endl; + sif::info << "TrackingSet::printSet: tracker choice: " + << static_cast(this->trackerChoice.value) << std::endl; + } +}; + +/** + * @brief Will store the requested validation parameters + */ +class ValidationSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 10; + + ValidationSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, VALIDATION_SET_ID) {} + + ValidationSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, VALIDATION_SET_ID)) {} + + lp_var_t stableCount = + lp_var_t(sid.objectId, PoolIds::VALIDATION_STABLE_COUNT, this); + lp_var_t maxDifference = + lp_var_t(sid.objectId, PoolIds::VALIDATION_MAX_DIFFERENCE, this); + lp_var_t minTrackerConfidence = + lp_var_t(sid.objectId, PoolIds::VALIDATION_MIN_TRACKER_CONFIDENCE, this); + lp_var_t minMatchedStars = + lp_var_t(sid.objectId, PoolIds::VALIDATION_MIN_MATCHED_STARS, this); + + void printSet() { + sif::info << "ValidationSet::printSet: stable count: " + << static_cast(this->stableCount.value) << std::endl; + sif::info << "ValidationSet::printSet: max difference: " << this->maxDifference << std::endl; + sif::info << "ValidationSet::printSet: min tracker confidence: " << this->minTrackerConfidence + << std::endl; + sif::info << "ValidationSet::printSet: min matched stars: " + << static_cast(this->minMatchedStars.value) << std::endl; + } +}; + +class AutoBlobSet : public StaticLocalDataSet<3> { + public: + AutoBlobSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, AUTO_BLOB_SET_ID) {} + // Ticks timestamp + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_AUTO_BLOB, this); + // Unix time stamp + lp_var_t timeUs = lp_var_t(sid.objectId, PoolIds::TIME_AUTO_BLOB, this); + lp_var_t threshold = lp_var_t(sid.objectId, PoolIds::AUTO_BLOB_THRESHOLD, this); + + private: +}; + +class MatchedCentroidsSet : public StaticLocalDataSet<20> { + public: + MatchedCentroidsSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, MATCHED_CENTROIDS_SET_ID) {} + MatchedCentroidsSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, MATCHED_CENTROIDS_SET_ID)) {} + // Ticks timestamp + lp_var_t ticks = + lp_var_t(sid.objectId, PoolIds::TICKS_MATCHED_CENTROIDS, this); + // Unix time stamp + lp_var_t timeUs = + lp_var_t(sid.objectId, PoolIds::TIME_MATCHED_CENTROIDS, this); + lp_var_t numberOfMatchedCentroids = + lp_var_t(sid.objectId, PoolIds::NUM_MATCHED_CENTROIDS, this); + lp_vec_t starIds = + lp_vec_t(sid.objectId, PoolIds::MATCHED_CENTROIDS_STAR_IDS, this); + lp_vec_t xCoords = + lp_vec_t(sid.objectId, PoolIds::MATCHED_CENTROIDS_X_COORDS, this); + lp_vec_t yCoords = + lp_vec_t(sid.objectId, PoolIds::MATCHED_CENTROIDS_Y_COORDS, this); + lp_vec_t xErrors = + lp_vec_t(sid.objectId, PoolIds::MATCHED_CENTROIDS_X_ERRORS, this); + lp_vec_t yErrors = + lp_vec_t(sid.objectId, PoolIds::MATCHED_CENTROIDS_Y_ERRORS, this); + + private: +}; + +class BlobSet : public StaticLocalDataSet<5> { + public: + BlobSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, BLOB_SET_ID) {} + // The blob count received from the Blob Telemetry Set (ID 25) + // Ticks timestamp + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::BLOB_TICKS, this); + // Unix time stamp + lp_var_t timeUs = lp_var_t(sid.objectId, PoolIds::BLOB_TIME, this); + lp_var_t blobCount = lp_var_t(sid.objectId, PoolIds::BLOB_COUNT, this); +}; + +class BlobsSet : public StaticLocalDataSet<10> { + public: + BlobsSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, BLOBS_SET_ID) {} + // Ticks timestamp + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::BLOBS_TICKS, this); + // Unix time stamp + lp_var_t timeUs = lp_var_t(sid.objectId, PoolIds::BLOBS_TIME, this); + lp_var_t blobsCount = lp_var_t(sid.objectId, PoolIds::BLOBS_COUNT, this); + lp_var_t blobsCountUsed = + lp_var_t(sid.objectId, PoolIds::BLOBS_COUNT_USED, this); + lp_var_t nr4LinesSkipped = + lp_var_t(sid.objectId, PoolIds::BLOBS_NR_4LINES_SKIPPED, this); + lp_vec_t xCoords = + lp_vec_t(sid.objectId, PoolIds::BLOBS_X_COORDS, this); + lp_vec_t yCoords = + lp_vec_t(sid.objectId, PoolIds::BLOBS_Y_COORDS, this); +}; + +class CentroidSet : public StaticLocalDataSet<5> { + public: + CentroidSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, CENTROID_SET_ID) {} + + // Data received from the Centroid Telemetry Set (ID 26) + // Ticks timestamp + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::CENTROID_TICKS, this); + // Unix time stamp + lp_var_t timeUs = lp_var_t(sid.objectId, PoolIds::CENTROID_TIME, this); + // The centroid count received from the Centroid Telemetry Set (ID 26) + lp_var_t centroidCount = + lp_var_t(sid.objectId, PoolIds::CENTROID_COUNT, this); +}; + +class CentroidsSet : public StaticLocalDataSet<10> { + public: + CentroidsSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, CENTROIDS_SET_ID) {} + + // Data received from the Centroids Telemetry Set (ID 37) + lp_var_t ticksCentroidsTm = + lp_var_t(sid.objectId, PoolIds::CENTROIDS_TICKS, this); + // Unix time stamp + lp_var_t timeUsCentroidsTm = + lp_var_t(sid.objectId, PoolIds::CENTROIDS_TIME, this); + lp_var_t centroidsCount = + lp_var_t(sid.objectId, PoolIds::CENTROIDS_COUNT, this); + lp_vec_t centroidsXCoords = + lp_vec_t(sid.objectId, PoolIds::CENTROIDS_X_COORDS, this); + lp_vec_t centroidsYCoords = + lp_vec_t(sid.objectId, PoolIds::CENTROIDS_Y_COORDS, this); + lp_vec_t centroidsMagnitudes = + lp_vec_t(sid.objectId, PoolIds::CENTROIDS_MAGNITUDES, this); +}; + +class BlobStatsSet : public StaticLocalDataSet<6> { + public: + BlobStatsSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, BLOB_STATS_SET_ID) {} + + // Data received from the Centroids Telemetry Set (ID 49) + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::TICKS_BLOB_STATS, this); + lp_var_t time = lp_var_t(sid.objectId, PoolIds::TIME_BLOB_STATS, this); + lp_vec_t noise = + lp_vec_t(sid.objectId, PoolIds::BLOB_STATS_NOISE, this); + lp_vec_t thold = + lp_vec_t(sid.objectId, PoolIds::BLOB_STATS_THOLD, this); + lp_vec_t lvalid = + lp_vec_t(sid.objectId, PoolIds::BLOB_STATS_LVALID, this); + lp_vec_t oflow = + lp_vec_t(sid.objectId, PoolIds::BLOB_STATS_OFLOW, this); +}; + +class ContrastSet : public StaticLocalDataSet<8> { + public: + ContrastSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, CONTRAST_SET_ID) {} + + // Data received from the Centroids Telemetry Set (ID 29) + lp_var_t ticks = lp_var_t(sid.objectId, PoolIds::CONTRAST_TICKS, this); + // Unix time stamp + lp_var_t timeUs = lp_var_t(sid.objectId, PoolIds::CONTRAST_TIME, this); + lp_vec_t contrastA = lp_vec_t(sid.objectId, PoolIds::CONTRAST_A, this); + lp_vec_t contrastB = lp_vec_t(sid.objectId, PoolIds::CONTRAST_B, this); + lp_vec_t contrastC = lp_vec_t(sid.objectId, PoolIds::CONTRAST_C, this); + lp_vec_t contrastD = lp_vec_t(sid.objectId, PoolIds::CONTRAST_D, this); +}; + +/** + * @brief Will store the requested algo parameters + */ +class AlgoSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 11; + + AlgoSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, ALGO_SET_ID) {} + + AlgoSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, ALGO_SET_ID)) {} + + lp_var_t mode = lp_var_t(sid.objectId, PoolIds::ALGO_MODE, this); + lp_var_t i2tMinConfidence = + lp_var_t(sid.objectId, PoolIds::ALGO_I2T_MIN_CONFIDENCE, this); + lp_var_t i2tMinMatched = + lp_var_t(sid.objectId, PoolIds::ALGO_I2T_MIN_MATCHED, this); + lp_var_t i2lMinConfidence = + lp_var_t(sid.objectId, PoolIds::ALGO_I2L_MIN_CONFIDENCE, this); + lp_var_t i2lMinMatched = + lp_var_t(sid.objectId, PoolIds::ALGO_I2L_MIN_MATCHED, this); + + void printSet() { + sif::info << "AlgoSet::printSet: mode: " << static_cast(this->mode.value) + << std::endl; + sif::info << "AlgoSet::printSet: i2t min confidence: " << this->i2tMinConfidence << std::endl; + sif::info << "AlgoSet::printSet: i2t min matched: " + << static_cast(this->i2tMinMatched.value) << std::endl; + sif::info << "AlgoSet::printSet: i2l min confidence: " << this->i2lMinConfidence << std::endl; + sif::info << "AlgoSet::printSet: i2l min matched: " + << static_cast(this->i2lMinMatched.value) << std::endl; + } +}; + +/** + * @brief Will store the requested subscription parameters + */ +class SubscriptionSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 16; + + SubscriptionSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, SUBSCRIPTION_SET_ID) {} + + SubscriptionSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, SUBSCRIPTION_SET_ID)) {} + + lp_var_t tm1 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM1, this); + lp_var_t tm2 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM2, this); + lp_var_t tm3 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM3, this); + lp_var_t tm4 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM4, this); + lp_var_t tm5 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM5, this); + lp_var_t tm6 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM6, this); + lp_var_t tm7 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM7, this); + lp_var_t tm8 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM8, this); + lp_var_t tm9 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM9, this); + lp_var_t tm10 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM10, this); + lp_var_t tm11 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM11, this); + lp_var_t tm12 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM12, this); + lp_var_t tm13 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM13, this); + lp_var_t tm14 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM14, this); + lp_var_t tm15 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM15, this); + lp_var_t tm16 = lp_var_t(sid.objectId, PoolIds::SUBSCRIPTION_TM16, this); + + void printSet() { + sif::info << "SubscriptionSet::printSet: telemetry 1: " + << static_cast(this->tm1.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 2: " + << static_cast(this->tm2.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 3: " + << static_cast(this->tm3.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 4: " + << static_cast(this->tm4.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 5: " + << static_cast(this->tm5.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 6: " + << static_cast(this->tm6.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 7: " + << static_cast(this->tm7.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 8: " + << static_cast(this->tm8.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 9: " + << static_cast(this->tm9.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 10: " + << static_cast(this->tm10.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 11: " + << static_cast(this->tm11.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 12: " + << static_cast(this->tm12.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 13: " + << static_cast(this->tm13.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 14: " + << static_cast(this->tm14.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 15: " + << static_cast(this->tm15.value) << std::endl; + sif::info << "SubscriptionSet::printSet: telemetry 16: " + << static_cast(this->tm16.value) << std::endl; + } +}; + +/** + * @brief Will store the requested log subscription parameters + */ +class LogSubscriptionSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 4; + + LogSubscriptionSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, LOG_SUBSCRIPTION_SET_ID) {} + + LogSubscriptionSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, LOG_SUBSCRIPTION_SET_ID)) {} + + lp_var_t level1 = + lp_var_t(sid.objectId, PoolIds::LOG_SUBSCRIPTION_LEVEL1, this); + lp_var_t module1 = + lp_var_t(sid.objectId, PoolIds::LOG_SUBSCRIPTION_MODULE1, this); + lp_var_t level2 = + lp_var_t(sid.objectId, PoolIds::LOG_SUBSCRIPTION_LEVEL2, this); + lp_var_t module2 = + lp_var_t(sid.objectId, PoolIds::LOG_SUBSCRIPTION_MODULE2, this); + + void printSet() { + sif::info << "LogSubscriptionSet::printSet: level 1: " + << static_cast(this->level1.value) << std::endl; + sif::info << "LogSubscriptionSet::printSet: module 1: " + << static_cast(this->module1.value) << std::endl; + sif::info << "LogSubscriptionSet::printSet: level 2: " + << static_cast(this->level2.value) << std::endl; + sif::info << "LogSubscriptionSet::printSet: module 2: " + << static_cast(this->module2.value) << std::endl; + } +}; + +/** + * @brief Will store the requested debug camera parameters + */ +class DebugCameraSet : public StaticLocalDataSet { + public: + // Size of dataset + static const size_t SIZE = 8; + + DebugCameraSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, DEBUG_CAMERA_SET_ID) {} + + lp_var_t timing = lp_var_t(sid.objectId, PoolIds::DEBUG_CAMERA_TIMING, this); + lp_var_t test = lp_var_t(sid.objectId, PoolIds::DEBUG_CAMERA_TEST, this); + + DebugCameraSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, DEBUG_CAMERA_SET_ID)) {} + + void printSet() { + sif::info << "DebugCameraSet::printSet: timing: " << this->timing << std::endl; + sif::info << "DebugCameraSet::printSet: test: " << this->test << std::endl; + } +}; +} // namespace startracker +#endif /* LINUX_DEVICES_DEVICEDEFINITIONS_STARTRACKER_DEFINITIONS_H_ */ diff --git a/mission/acs/str/strJsonCommands.cpp b/mission/acs/str/strJsonCommands.cpp new file mode 100644 index 0000000..5a910f8 --- /dev/null +++ b/mission/acs/str/strJsonCommands.cpp @@ -0,0 +1,960 @@ +#include +#include + +#include "arcsecJsonKeys.h" + +Limits::Limits() : ArcsecJsonParamBase(arcseckeys::LIMITS) {} + +size_t Limits::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Limits::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::LIMITS); + offset = 2; + result = getParam(arcseckeys::ACTION, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::FPGA18CURRENT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::FPGA25CURRENT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::FPGA10CURRENT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MCUCURRENT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::CMOS21CURRENT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::CMOSPIXCURRENT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::CMOS33CURRENT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::CMOSVRESCURRENT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::CMOS_TEMPERATURE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MCU_TEMPERATURE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + return returnvalue::OK; +} + +Tracking::Tracking() : ArcsecJsonParamBase(arcseckeys::TRACKING) {} + +size_t Tracking::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Tracking::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::TRACKING); + offset = 2; + result = getParam(arcseckeys::THIN_LIMIT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::OUTLIER_THRESHOLD, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::OUTLIER_THRESHOLD_QUEST, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::TRACKER_CHOICE, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + return returnvalue::OK; +} + +Mounting::Mounting() : ArcsecJsonParamBase(arcseckeys::MOUNTING) {} + +size_t Mounting::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Mounting::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::MOUNTING); + offset = 2; + result = getParam(arcseckeys::qw, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::qx, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::qy, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::qz, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + return returnvalue::OK; +} + +ImageProcessor::ImageProcessor() : ArcsecJsonParamBase(arcseckeys::IMAGE_PROCESSOR) {} + +size_t ImageProcessor::getSize() { return COMMAND_SIZE; } + +ReturnValue_t ImageProcessor::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::IMAGE_PROCESSOR); + offset = 2; + result = getParam(arcseckeys::IMAGE_PROCESSOR_MODE, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::STORE, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::SIGNAL_THRESHOLD, param); + if (result != returnvalue::OK) { + return result; + } + adduint16(param, buffer + offset); + offset += sizeof(uint16_t); + result = getParam(arcseckeys::IMAGE_PROCESSOR_DARK_THRESHOLD, param); + if (result != returnvalue::OK) { + return result; + } + adduint16(param, buffer + offset); + offset += sizeof(uint16_t); + result = getParam(arcseckeys::BACKGROUND_COMPENSATION, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + return returnvalue::OK; +} + +Camera::Camera() : ArcsecJsonParamBase(arcseckeys::CAMERA) {} + +size_t Camera::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Camera::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::CAMERA); + offset = 2; + result = getParam(arcseckeys::MODE, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::FOCALLENGTH, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::EXPOSURE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::INTERVAL, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::OFFSET, param); + if (result != returnvalue::OK) { + return result; + } + addint16(param, buffer + offset); + offset += sizeof(int16_t); + result = getParam(arcseckeys::PGAGAIN, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::ADCGAIN, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::REG_1, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::VAL_1, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::REG_2, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::VAL_2, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::REG_3, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::VAL_3, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::REG_4, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::VAL_4, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::REG_5, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::VAL_5, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::REG_6, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::VAL_6, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::REG_7, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::VAL_7, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::REG_8, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::VAL_8, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::FREQ_1, param); + if (result != returnvalue::OK) { + return result; + } + adduint32(param, buffer + offset); + return returnvalue::OK; +} + +Centroiding::Centroiding() : ArcsecJsonParamBase(arcseckeys::CENTROIDING) {} + +size_t Centroiding::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Centroiding::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::CENTROIDING); + offset = 2; + result = getParam(arcseckeys::ENABLE_FILTER, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::MAX_QUALITY, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::DARK_THRESHOLD, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MIN_QUALITY, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MAX_INTENSITY, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MIN_INTENSITY, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MAX_MAGNITUDE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::GAUSSIAN_CMAX, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::GAUSSIAN_CMIN, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::TRANSMATRIX_00, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::TRANSMATRIX_01, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::TRANSMATRIX_10, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::TRANSMATRIX_11, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + return returnvalue::OK; +} + +Lisa::Lisa() : ArcsecJsonParamBase(arcseckeys::LISA) {} + +size_t Lisa::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Lisa::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::LISA); + offset = 2; + result = getParam(arcseckeys::LISA_MODE, param); + if (result != returnvalue::OK) { + return result; + } + adduint32(param, buffer + offset); + offset += sizeof(uint32_t); + result = getParam(arcseckeys::PREFILTER_DIST_THRESHOLD, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::PREFILTER_ANGLE_THRESHOLD, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::FOV_WIDTH, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::FOV_HEIGHT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::FLOAT_STAR_LIMIT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::CLOSE_STAR_LIMIT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::RATING_WEIGHT_CLOSE_STAR_COUNT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::RATING_WEIGHT_FRACTION_CLOSE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::RATING_WEIGHT_MEAN_SUM, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::RATING_WEIGHT_DB_STAR_COUNT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MAX_COMBINATIONS, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::NR_STARS_STOP, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::FRACTION_CLOSE_STOP, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + return returnvalue::OK; +} + +Matching::Matching() : ArcsecJsonParamBase(arcseckeys::MATCHING) {} + +size_t Matching::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Matching::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::MATCHING); + offset = 2; + result = getParam(arcseckeys::SQUARED_DISTANCE_LIMIT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::SQUARED_SHIFT_LIMIT, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + return returnvalue::OK; +} + +Validation::Validation() : ArcsecJsonParamBase(arcseckeys::VALIDATION) {} + +size_t Validation::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Validation::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::VALIDATION); + offset = 2; + result = getParam(arcseckeys::STABLE_COUNT, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::MAX_DIFFERENCE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MIN_TRACKER_CONFIDENCE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::MIN_MATCHED_STARS, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + return returnvalue::OK; +} + +Algo::Algo() : ArcsecJsonParamBase(arcseckeys::ALGO) {} + +size_t Algo::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Algo::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::ALGO); + offset = 2; + result = getParam(arcseckeys::MODE, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::L2T_MIN_CONFIDENCE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::L2T_MIN_MATCHED, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::T2L_MIN_CONFIDENCE, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + offset += sizeof(float); + result = getParam(arcseckeys::T2L_MIN_MATCHED, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + return returnvalue::OK; +} + +LogLevel::LogLevel() : ArcsecJsonParamBase(arcseckeys::LOGLEVEL) {} + +size_t LogLevel::getSize() { return COMMAND_SIZE; } + +ReturnValue_t LogLevel::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::LOG_LEVEL); + offset = 2; + result = getParam(arcseckeys::LOGLEVEL1, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL2, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL3, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL4, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL5, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL6, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL7, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL8, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL9, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL10, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL11, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL12, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL13, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL14, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL15, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LOGLEVEL16, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + + return returnvalue::OK; +} + +Subscription::Subscription() : ArcsecJsonParamBase(arcseckeys::SUBSCRIPTION) {} + +size_t Subscription::getSize() { return COMMAND_SIZE; } + +ReturnValue_t Subscription::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::SUBSCRIPTION); + offset = 2; + result = getParam(arcseckeys::TELEMETRY_1, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_2, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_3, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_4, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_5, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_6, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_7, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_8, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_9, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_10, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_11, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_12, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_13, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_14, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_15, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::TELEMETRY_16, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + return returnvalue::OK; +} + +LogSubscription::LogSubscription() : ArcsecJsonParamBase(arcseckeys::LOG_SUBSCRIPTION) {} + +size_t LogSubscription::getSize() { return COMMAND_SIZE; } + +ReturnValue_t LogSubscription::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::LOG_SUBSCRIPTION); + offset = 2; + result = getParam(arcseckeys::LEVEL1, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::MODULE1, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::LEVEL2, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += sizeof(uint8_t); + result = getParam(arcseckeys::MODULE2, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + return returnvalue::OK; +} + +DebugCamera::DebugCamera() : ArcsecJsonParamBase(arcseckeys::DEBUG_CAMERA) {} + +size_t DebugCamera::getSize() { return COMMAND_SIZE; } + +ReturnValue_t DebugCamera::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::DEBUG_CAMERA); + offset = 2; + result = getParam(arcseckeys::TIMING, param); + if (result != returnvalue::OK) { + return result; + } + adduint32(param, buffer + offset); + offset += sizeof(uint32_t); + result = getParam(arcseckeys::TEST, param); + if (result != returnvalue::OK) { + return result; + } + adduint32(param, buffer + offset); + return returnvalue::OK; +} + +AutoThreshold::AutoThreshold() : ArcsecJsonParamBase(arcseckeys::AUTO_THRESHOLD) {} + +size_t AutoThreshold::getSize() { return COMMAND_SIZE; } + +ReturnValue_t AutoThreshold::createCommand(uint8_t* buffer) { + ReturnValue_t result = returnvalue::OK; + uint8_t offset = 0; + std::string param; + addSetParamHeader(buffer, startracker::ID::AUTO_THRESHOLD); + offset = 2; + result = getParam(arcseckeys::AT_MODE, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += 1; + result = getParam(arcseckeys::AT_DESIRED_BLOB_COUNTS, param); + if (result != returnvalue::OK) { + return result; + } + adduint8(param, buffer + offset); + offset += 1; + result = getParam(arcseckeys::AT_MIN_THRESHOLD, param); + if (result != returnvalue::OK) { + return result; + } + adduint16(param, buffer + offset); + offset += 2; + result = getParam(arcseckeys::AT_MAX_THRESHOLD, param); + if (result != returnvalue::OK) { + return result; + } + adduint16(param, buffer + offset); + offset += 2; + result = getParam(arcseckeys::AT_THRESHOLD_KP, param); + if (result != returnvalue::OK) { + return result; + } + addfloat(param, buffer + offset); + return returnvalue::OK; +} diff --git a/mission/acs/str/strJsonCommands.h b/mission/acs/str/strJsonCommands.h new file mode 100644 index 0000000..7e75c0b --- /dev/null +++ b/mission/acs/str/strJsonCommands.h @@ -0,0 +1,257 @@ +#ifndef BSP_Q7S_DEVICES_DEVICEDEFINITIONS_STARTRACKERJSONCOMMANDS_H_ +#define BSP_Q7S_DEVICES_DEVICEDEFINITIONS_STARTRACKERJSONCOMMANDS_H_ + +/** + * @brief This file defines a few helper classes to generate commands by means of the parameters + * defined in the arcsec json files. + * @author J. Meier + */ + +#include + +#include + +#include "fsfw/serviceinterface/ServiceInterface.h" + +/** + * @brief Generates command to set the limit parameters + * + */ +class Limits : public ArcsecJsonParamBase { + public: + Limits(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 43; + + virtual ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates the command to configure the tracking algorithm. + * + */ +class Tracking : public ArcsecJsonParamBase { + public: + Tracking(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 15; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates the command to set the mounting quaternion + * + */ +class Mounting : public ArcsecJsonParamBase { + public: + Mounting(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 18; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates the command to configure the image processor + * + */ +class ImageProcessor : public ArcsecJsonParamBase { + public: + ImageProcessor(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 9; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates the command to set the mounting quaternion + * + */ +class Camera : public ArcsecJsonParamBase { + public: + Camera(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 39; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates the command to configure the centroiding algorithm + * + */ +class Centroiding : public ArcsecJsonParamBase { + public: + Centroiding(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 51; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates the command to configure the LISA (lost in space algorithm) + * + */ +class Lisa : public ArcsecJsonParamBase { + public: + Lisa(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 52; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates the command to configure the matching algorithm + * + */ +class Matching : public ArcsecJsonParamBase { + public: + Matching(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 10; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates the command to configure the validation parameters + * + */ +class Validation : public ArcsecJsonParamBase { + public: + Validation(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 12; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates command to configure the mechanism of automatically switching between the + * LISA and other algorithms. + * + */ +class Algo : public ArcsecJsonParamBase { + public: + Algo(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 13; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates command to configure the log level parameters. + * + */ +class LogLevel : public ArcsecJsonParamBase { + public: + LogLevel(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 18; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates command to set subscription parameters. + * + */ +class Subscription : public ArcsecJsonParamBase { + public: + Subscription(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 18; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates command to set log subscription parameters. + * + */ +class LogSubscription : public ArcsecJsonParamBase { + public: + LogSubscription(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 6; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates command to set log subscription parameters. + * + */ +class AutoThreshold : public ArcsecJsonParamBase { + public: + AutoThreshold(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 12; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +/** + * @brief Generates command to set debug camera parameters + * + */ +class DebugCamera : public ArcsecJsonParamBase { + public: + DebugCamera(); + + size_t getSize(); + + private: + static const size_t COMMAND_SIZE = 10; + + ReturnValue_t createCommand(uint8_t* buffer) override; +}; + +#endif /* BSP_Q7S_DEVICES_DEVICEDEFINITIONS_STARTRACKERJSONCOMMANDS_H_ */ diff --git a/mission/acs/susMax1227Helpers.h b/mission/acs/susMax1227Helpers.h new file mode 100644 index 0000000..0314b83 --- /dev/null +++ b/mission/acs/susMax1227Helpers.h @@ -0,0 +1,85 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_SUS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_SUS_H_ + +#include +#include + +#include + +namespace susMax1227 { + +// This is 16 seconds for a polling frequency of 0.4 seconds. +static constexpr uint32_t MAX_INVALID_MSG_COUNT = 40; +// Using a decrement time of 32 seconds should cause faulty device incrementation to best faster +// the decrementation, so that FDIR reactions will eventuall be triggered. +// NOTE: Not used currently, we perform the strange reply check logic in the handler and trigger +// a reboot directly using the appropriate event. +static constexpr uint32_t FAULTY_COM_DECREMENT_TIME_MS = 32000; + +static const DeviceCommandId_t NONE = 0x0; // Set when no command is pending + +static const DeviceCommandId_t WRITE_SETUP = 1; +/** + * This command initiates the ADC conversion for all channels including the internal + * temperature sensor. + */ +static const DeviceCommandId_t START_INT_TIMED_CONVERSIONS = 2; +/** + * This command reads the internal fifo which holds the temperature and the channel + * conversions. + */ +static constexpr DeviceCommandId_t READ_INT_TIMED_CONVERSIONS = 3; + +static constexpr DeviceCommandId_t READ_EXT_TIMED_CONVERSIONS = 4; + +static constexpr DeviceCommandId_t READ_EXT_TIMED_TEMPS = 5; + +/** + * @brief This is the configuration byte which will be written to the setup register after + * power on. + * + * @note Bit1 (DIFFSEL1) - Bit0 (DIFFSEL0): 0b00, No byte is following the setup byte + * Bit3 (REFSEL1) - Bit2 (REFSEL0): 0b10, Internal reference, no wake-up delay + * Bit5 (CLKSEL1) - Bit4 (CLKSEL0): 0b10, Internally clocked + * Bit7 - Bit6: 0b01, Tells MAX1227 that this byte should be + * written to the setup register + * + */ +static constexpr uint8_t SETUP_INT_CLOKED = 0b01101000; +static constexpr uint8_t SETUP_EXT_CLOCKED = 0b01111000; + +/** + * @brief This values will always be written to the ADC conversion register to specify the + * conversions to perform. + * @details Bit0: 1 - Enables temperature conversion + * Bit2 (SCAN1) and Bit1 (SCAN0): 0b00, Scans channels 0 through N + * Bit6 - Bit3 defines N: 0b0101 (N = 5) + * Bit7: Always 1. Tells the ADC that this is the conversion register. + */ +static const uint8_t CONVERSION = 0b10101001; + +static const uint8_t SUS_DATA_SET_ID = READ_INT_TIMED_CONVERSIONS; + +/** Size of data replies. Temperature and 6 channel convesions (AIN0 - AIN5) */ +static const uint8_t SIZE_READ_INT_CONVERSIONS = 14; +// 6 * conv byte, 6 * 0 and one trailing zero +static constexpr uint8_t SIZE_READ_EXT_CONVERSIONS = 13; + +static const uint8_t MAX_CMD_SIZE = 32; + +static const uint8_t POOL_ENTRIES = 7; + +enum SusPoolIds : lp_id_t { TEMPERATURE_C, CHANNEL_VEC }; + +class SusDataset : public StaticLocalDataSet { + public: + SusDataset(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, SUS_DATA_SET_ID) {} + + SusDataset(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, SUS_DATA_SET_ID)) {} + + lp_var_t tempC = lp_var_t(sid.objectId, TEMPERATURE_C, this); + lp_vec_t channels = lp_vec_t(sid.objectId, CHANNEL_VEC, this); +}; +} // namespace susMax1227 + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_SUS_H_ */ diff --git a/mission/cfdp/CMakeLists.txt b/mission/cfdp/CMakeLists.txt new file mode 100644 index 0000000..418b66e --- /dev/null +++ b/mission/cfdp/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${LIB_EIVE_MISSION} PRIVATE CfdpHandler.cpp CfdpUser.cpp) diff --git a/mission/cfdp/CfdpFaultHandler.h b/mission/cfdp/CfdpFaultHandler.h new file mode 100644 index 0000000..09ba084 --- /dev/null +++ b/mission/cfdp/CfdpFaultHandler.h @@ -0,0 +1,38 @@ +#ifndef MISSION_CFDP_CFDPFAULTHANDLER_H_ +#define MISSION_CFDP_CFDPFAULTHANDLER_H_ + +#include "defs.h" +#include "fsfw/cfdp.h" + +namespace cfdp { + +class EiveFaultHandler : public cfdp::FaultHandlerBase, public SystemObject { + public: + EiveFaultHandler(object_id_t objectId) : SystemObject(objectId) {} + + void noticeOfSuspensionCb(cfdp::TransactionId& id, cfdp::ConditionCode code) override { + sif::warning << "Notice of suspension detected for transaction " << id + << " with condition code: " << cfdp::getConditionCodeString(code) << std::endl; + triggerEvent(cfdp::FAULT_HANDLER_TRIGGERED, FaultHandlerCode::NOTICE_OF_SUSPENSION, code); + } + void noticeOfCancellationCb(cfdp::TransactionId& id, cfdp::ConditionCode code) override { + sif::warning << "Notice of suspension detected for transaction " << id + << " with condition code: " << cfdp::getConditionCodeString(code) << std::endl; + triggerEvent(cfdp::FAULT_HANDLER_TRIGGERED, FaultHandlerCode::NOTICE_OF_CANCELLATION, code); + } + void abandonCb(cfdp::TransactionId& id, cfdp::ConditionCode code) override { + sif::warning << "Transaction " << id + << " was abandoned, condition code : " << cfdp::getConditionCodeString(code) + << std::endl; + triggerEvent(cfdp::FAULT_HANDLER_TRIGGERED, FaultHandlerCode::ABANDON_TRANSACTION, code); + } + void ignoreCb(cfdp::TransactionId& id, cfdp::ConditionCode code) override { + sif::warning << "Fault ignored for transaction " << id + << ", condition code: " << cfdp::getConditionCodeString(code) << std::endl; + triggerEvent(cfdp::FAULT_HANDLER_TRIGGERED, FaultHandlerCode::IGNORE_ERROR, code); + } +}; + +} // namespace cfdp + +#endif /* MISSION_CFDP_CFDPFAULTHANDLER_H_ */ diff --git a/mission/cfdp/CfdpHandler.cpp b/mission/cfdp/CfdpHandler.cpp new file mode 100644 index 0000000..bb98b94 --- /dev/null +++ b/mission/cfdp/CfdpHandler.cpp @@ -0,0 +1,253 @@ +#include "CfdpHandler.h" + +#include +#include + +#include "eive/definitions.h" +#include "fsfw/cfdp/pdu/AckPduReader.h" +#include "fsfw/cfdp/pdu/PduHeaderReader.h" +#include "fsfw/globalfunctions/arrayprinter.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/tasks/TaskFactory.h" +#include "fsfw/tmtcservices/TmTcMessage.h" +#include "mission/sysDefs.h" + +using namespace returnvalue; +using namespace cfdp; + +CfdpHandler::CfdpHandler(const FsfwHandlerParams& fsfwHandlerParams, const CfdpHandlerCfg& cfdpCfg, + const std::atomic_bool& throttleSignal) + : SystemObject(fsfwHandlerParams.objectId), + pduQueue(fsfwHandlerParams.tmtcQueue), + cfdpRequestQueue(fsfwHandlerParams.cfdpQueue), + localCfg(cfdpCfg.id, cfdpCfg.indicCfg, cfdpCfg.faultHandler), + remoteCfgProvider(cfdpCfg.remoteCfgProvider), + fsfwParams(fsfwHandlerParams.packetDest, &fsfwHandlerParams.tmtcQueue, this, + fsfwHandlerParams.tcStore, fsfwHandlerParams.tmStore), + destHandler(DestHandlerParams(localCfg, cfdpCfg.userHandler, cfdpCfg.remoteCfgProvider, + cfdpCfg.packetInfoList, cfdpCfg.lostSegmentsList), + this->fsfwParams), + srcHandler(SourceHandlerParams(localCfg, cfdpCfg.userHandler, seqCntProvider), + this->fsfwParams), + ipcStore(fsfwHandlerParams.ipcStore), + throttleSignal(throttleSignal) {} + +[[nodiscard]] const char* CfdpHandler::getName() const { return "CFDP Handler"; } + +[[nodiscard]] uint32_t CfdpHandler::getIdentifier() const { + return destHandler.getDestHandlerParams().cfg.localId.getValue(); +} + +[[nodiscard]] MessageQueueId_t CfdpHandler::getRequestQueue() const { return pduQueue.getId(); } + +ReturnValue_t CfdpHandler::initialize() { + ReturnValue_t result = destHandler.initialize(); + if (result != OK) { + return result; + } + tcStore = destHandler.getTcStore(); + tmStore = destHandler.getTmStore(); + + return SystemObject::initialize(); +} + +[[noreturn]] ReturnValue_t CfdpHandler::performOperation(uint8_t operationCode) { + while (true) { + bool shortDelay = false; + ReturnValue_t result = handlePduPacketMessages(); + if (result != OK) { + } + result = handleCfdpMessages(); + if (result != OK) { + } + uint32_t fsmCount = 1; + const DestHandler::FsmResult& destResult = destHandler.stateMachine(); + while (destResult.callStatus == CallStatus::CALL_AGAIN) { + if (fsmCount == config::CFDP_MAX_FSM_CALL_COUNT_DEST_HANDLER) { + shortDelay = true; + break; + } + destHandler.stateMachine(); + fsmCount++; + } + fsmCount = 1; + + throttlePeriodOngoing = throttleSignal; + + // CFDP can be throttled by the slowest live TM handler to handle back pressure in a sensible + // way without requiring huge amounts of memory for large files. + if (!throttlePeriodOngoing) { + const SourceHandler::FsmResult& srcResult = srcHandler.stateMachine(); + if (srcResult.packetsSent > 0) { + signals::CFDP_MSG_COUNTER.fetch_add(srcResult.packetsSent, std::memory_order_relaxed); + } + while (srcResult.callStatus == CallStatus::CALL_AGAIN) { + // Limit number of messages. + if (fsmCount == config::CFDP_MAX_FSM_CALL_COUNT_SRC_HANDLER) { + shortDelay = true; + break; + } + srcHandler.stateMachine(); + if (srcResult.packetsSent > 0) { + signals::CFDP_MSG_COUNTER.fetch_add(srcResult.packetsSent, std::memory_order_relaxed); + } + if (srcResult.result == cfdp::TM_STORE_FULL) { + sif::warning << "CFDP Source Handler: TM store is full" << std::endl; + } else if (srcResult.result == cfdp::TARGET_MSG_QUEUE_FULL) { + sif::warning << "CFDP Source Handler: TM queue is full" << std::endl; + } + fsmCount++; + } + } + if (shortDelay) { + TaskFactory::delayTask(config::CFDP_SHORT_DELAY_MS); + continue; + } + TaskFactory::delayTask(config::CFDP_REGULAR_DELAY_MS); + } +} + +ReturnValue_t CfdpHandler::handlePduPacket(TmTcMessage& msg) { + auto accessorPair = tcStore->getData(msg.getStorageId()); + if (accessorPair.first != OK) { + return accessorPair.first; + } + PduHeaderReader reader(accessorPair.second.data(), accessorPair.second.size()); + ReturnValue_t result = reader.parseData(); + if (result != returnvalue::OK) { + return INVALID_PDU_FORMAT; + } + // The CFDP distributor should have taken care of ensuring the destination ID is correct + PduType type = reader.getPduType(); + // Only the destination handler can process these PDUs + if (type == PduType::FILE_DATA) { + // Disable auto-deletion of packet + accessorPair.second.release(); + PacketInfo info(type, msg.getStorageId()); + result = destHandler.passPacket(info); + } else { + // Route depending on PDU type and directive type if applicable. It retrieves directive type + // from the raw stream for better performance (with sanity and directive code check). + // The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding + // procedure. + + // PDU header only. Invalid supplied data. A directive packet should have a valid data field + // with at least one byte being the directive code + const uint8_t* pduDataField = reader.getPduDataField(); + if (pduDataField == nullptr) { + return INVALID_PDU_FORMAT; + } + if (not FileDirectiveReader::checkFileDirective(pduDataField[0])) { + sif::error << "CfdpHandler: Invalid PDU directive field " << static_cast(pduDataField[0]) + << std::endl; + return INVALID_DIRECTIVE_FIELD; + } + auto directive = static_cast(pduDataField[0]); + + auto passToDestHandler = [&]() { + accessorPair.second.release(); + PacketInfo info(type, msg.getStorageId(), directive); + return destHandler.passPacket(info); + }; + auto passToSourceHandler = [&]() { + accessorPair.second.release(); + PacketInfo info(type, msg.getStorageId(), directive); + // Implement this function. + // result = srcHandler.passPacket(info); + }; + if (directive == FileDirective::METADATA or directive == FileDirective::EOF_DIRECTIVE or + directive == FileDirective::PROMPT) { + // Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a. + // the destination handler + return passToDestHandler(); + } else if (directive == FileDirective::FINISH or directive == FileDirective::NAK or + directive == FileDirective::KEEP_ALIVE) { + // Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a. + // the source handler + passToSourceHandler(); + } else if (directive == FileDirective::ACK) { + // Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply + // extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to + // the source handler, for a Finished PDU, it is passed to the destination handler. + FileDirective ackedDirective; + if (not AckPduReader::checkAckedDirectiveField(pduDataField[1], ackedDirective)) { + return INVALID_ACK_DIRECTIVE_FIELDS; + } + if (ackedDirective == FileDirective::EOF_DIRECTIVE) { + passToSourceHandler(); + } else if (ackedDirective == FileDirective::FINISH) { + return passToDestHandler(); + } + } + } + return result; +} + +ReturnValue_t CfdpHandler::handleCfdpRequest(CommandMessage& msg) { + if (msg.getCommand() == CfdpMessage::PUT_REQUEST) { + sif::info << "Received CFDP put request" << std::endl; + if (srcHandler.getState() != CfdpState::IDLE) { + if (putRequestQueue.full()) { + // TODO: Trigger event and discard request. Queue is full, too many requests. + return FAILED; + } + putRequestQueue.push(CfdpMessage::getStoreId(&msg)); + } else { + PutRequest putRequest; + auto accessorPair = ipcStore.getData(CfdpMessage::getStoreId(&msg)); + const uint8_t* dataPtr = accessorPair.second.data(); + size_t dataSize = accessorPair.second.size(); + ReturnValue_t result = + putRequest.deSerialize(&dataPtr, &dataSize, SerializeIF::Endianness::MACHINE); + if (result != OK) { + return result; + } + RemoteEntityCfg* remoteCfg; + remoteCfgProvider.getRemoteCfg(putRequest.getDestId(), &remoteCfg); + if (remoteCfg == nullptr) { + sif::error << "CfdpHandler: No remote configuration found for destination ID " + << putRequest.getDestId() << std::endl; + // TODO: Trigger event + return FAILED; + } + sif::info << "Starting file copy operation for source file " + << putRequest.getSourceName().getString() << " and dest file " + << putRequest.getDestName().getString() << std::endl; + return srcHandler.transactionStart(putRequest, *remoteCfg); + } + } + return OK; +} + +ReturnValue_t CfdpHandler::handlePduPacketMessages() { + ReturnValue_t status; + ReturnValue_t result = OK; + TmTcMessage pduMsg; + for (status = pduQueue.receiveMessage(&pduMsg); status == returnvalue::OK; + status = pduQueue.receiveMessage(&pduMsg)) { + result = handlePduPacket(pduMsg); + if (result != OK) { + // TODO: Maybe add printout with context specific information? + status = result; + } + } + return status; +} + +ReturnValue_t CfdpHandler::handleCfdpMessages() { + ReturnValue_t status; + ReturnValue_t result; + CommandMessage cfdpMsg; + for (status = cfdpRequestQueue.receiveMessage(&cfdpMsg); status == returnvalue::OK; + status = cfdpRequestQueue.receiveMessage(&cfdpMsg)) { + result = handleCfdpRequest(cfdpMsg); + if (result != OK) { + sif::warning << "Handling CFDP request failed with code 0x" << std::setw(4) << std::hex + << result << std::dec << std::endl; + triggerEvent(cfdp::events::HANDLING_CFDP_REQUEST_FAILED, 0, result); + // TODO: Maybe add printout with context specific information? + status = result; + } + } + return status; +} diff --git a/mission/cfdp/CfdpHandler.h b/mission/cfdp/CfdpHandler.h new file mode 100644 index 0000000..b2f4967 --- /dev/null +++ b/mission/cfdp/CfdpHandler.h @@ -0,0 +1,100 @@ +#ifndef FSFW_EXAMPLE_HOSTED_CFDPHANDLER_H +#define FSFW_EXAMPLE_HOSTED_CFDPHANDLER_H + +#include +#include +#include +#include + +#include + +#include "fsfw/cfdp/handler/DestHandler.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw/tmtcservices/AcceptsTelecommandsIF.h" +#include "fsfw/tmtcservices/TmTcMessage.h" +#include "fsfw/util/SeqCountProvider.h" + +struct FsfwHandlerParams { + FsfwHandlerParams(object_id_t objectId, HasFileSystemIF& vfs, AcceptsTelemetryIF& packetDest, + StorageManagerIF& tcStore, StorageManagerIF& tmStore, + StorageManagerIF& ipcStore, MessageQueueIF& tmtcQueue, + MessageQueueIF& cfdpQueue) + : objectId(objectId), + vfs(vfs), + packetDest(packetDest), + tcStore(tcStore), + tmStore(tmStore), + ipcStore(ipcStore), + tmtcQueue(tmtcQueue), + cfdpQueue(cfdpQueue) {} + object_id_t objectId{}; + HasFileSystemIF& vfs; + AcceptsTelemetryIF& packetDest; + StorageManagerIF& tcStore; + StorageManagerIF& tmStore; + StorageManagerIF& ipcStore; + MessageQueueIF& tmtcQueue; + MessageQueueIF& cfdpQueue; +}; + +struct CfdpHandlerCfg { + CfdpHandlerCfg(cfdp::EntityId localId, cfdp::IndicationCfg indicationCfg, + cfdp::UserBase& userHandler, cfdp::FaultHandlerBase& userFaultHandler, + cfdp::PacketInfoListBase& packetInfo, cfdp::LostSegmentsListBase& lostSegmentsList, + cfdp::RemoteConfigTableIF& remoteCfgProvider) + : id(std::move(localId)), + indicCfg(indicationCfg), + packetInfoList(packetInfo), + lostSegmentsList(lostSegmentsList), + remoteCfgProvider(remoteCfgProvider), + userHandler(userHandler), + faultHandler(userFaultHandler) {} + + cfdp::EntityId id; + cfdp::IndicationCfg indicCfg; + cfdp::PacketInfoListBase& packetInfoList; + cfdp::LostSegmentsListBase& lostSegmentsList; + cfdp::RemoteConfigTableIF& remoteCfgProvider; + cfdp::UserBase& userHandler; + cfdp::FaultHandlerBase& faultHandler; +}; + +class CfdpHandler : public SystemObject, public ExecutableObjectIF, public AcceptsTelecommandsIF { + public: + explicit CfdpHandler(const FsfwHandlerParams& fsfwParams, const CfdpHandlerCfg& cfdpCfg, + const std::atomic_bool& throttleSignal); + + [[nodiscard]] const char* getName() const override; + [[nodiscard]] uint32_t getIdentifier() const override; + [[nodiscard]] MessageQueueId_t getRequestQueue() const override; + + ReturnValue_t initialize() override; + [[noreturn]] ReturnValue_t performOperation(uint8_t operationCode) override; + + private: + MessageQueueIF& pduQueue; + MessageQueueIF& cfdpRequestQueue; + bool throttlePeriodOngoing = false; + + cfdp::LocalEntityCfg localCfg; + cfdp::RemoteConfigTableIF& remoteCfgProvider; + cfdp::FsfwParams fsfwParams; + SeqCountProviderU16 seqCntProvider; + cfdp::DestHandler destHandler; + cfdp::SourceHandler srcHandler; + etl::queue putRequestQueue; + + StorageManagerIF& ipcStore; + StorageManagerIF* tcStore = nullptr; + StorageManagerIF* tmStore = nullptr; + + const std::atomic_bool& throttleSignal; + + ReturnValue_t handlePduPacketMessages(); + ReturnValue_t handlePduPacket(TmTcMessage& msg); + ReturnValue_t handleCfdpRequest(CommandMessage& msg); + ReturnValue_t handleCfdpMessages(); +}; + +#endif // FSFW_EXAMPLE_HOSTED_CFDPHANDLER_H diff --git a/mission/cfdp/CfdpUser.cpp b/mission/cfdp/CfdpUser.cpp new file mode 100644 index 0000000..c2e6024 --- /dev/null +++ b/mission/cfdp/CfdpUser.cpp @@ -0,0 +1,51 @@ +#include "CfdpUser.h" + +#include + +using namespace returnvalue; + +namespace cfdp { + +EiveUserHandler::EiveUserHandler(HasFileSystemIF& vfs, StorageManagerIF& ipcStore, + MessageQueueId_t cfdpRequestId) + : cfdp::UserBase(vfs), userQueue(QueueFactory::instance()->createMessageQueue(10)) { + if (userQueue == nullptr) { + sif::error << "EiveUserHandler: Queue creation failed" << std::endl; + return; + } + userQueue->setDefaultDestination(cfdpRequestId); + reservedMsgParser = new ReservedMessageParser(ipcStore, *userQueue, cfdpRequestId); +} + +EiveUserHandler::~EiveUserHandler() { QueueFactory::instance()->deleteMessageQueue(userQueue); } + +void EiveUserHandler::transactionIndication(const cfdp::TransactionId& id) {} +void EiveUserHandler::eofSentIndication(const cfdp::TransactionId& id) {} +void EiveUserHandler::transactionFinishedIndication(const cfdp::TransactionFinishedParams& params) { + sif::info << "File transaction finished for transaction with " << params.id << std::endl; +} +void EiveUserHandler::metadataRecvdIndication(const cfdp::MetadataRecvdParams& params) { + sif::info << "Metadata received for transaction with " << params.id << std::endl; + if (params.numberOfMsgsToUser > 0 and params.msgsToUserArray != nullptr) { + ReturnValue_t result = + reservedMsgParser->parse(params.msgsToUserArray, params.numberOfMsgsToUser); + if (result != OK) { + sif::warning << "EiveUserHandler: Parsing reserved CFDP messages failed" << std::endl; + } + } +} +void EiveUserHandler::fileSegmentRecvdIndication(const cfdp::FileSegmentRecvdParams& params) {} +void EiveUserHandler::reportIndication(const cfdp::TransactionId& id, + cfdp::StatusReportIF& report) {} +void EiveUserHandler::suspendedIndication(const cfdp::TransactionId& id, cfdp::ConditionCode code) { +} +void EiveUserHandler::resumedIndication(const cfdp::TransactionId& id, size_t progress) {} +void EiveUserHandler::faultIndication(const cfdp::TransactionId& id, cfdp::ConditionCode code, + size_t progress) {} +void EiveUserHandler::abandonedIndication(const cfdp::TransactionId& id, cfdp::ConditionCode code, + size_t progress) {} +void EiveUserHandler::eofRecvIndication(const cfdp::TransactionId& id) { + sif::info << "EOF PDU received for transaction with " << id << std::endl; +} + +} // namespace cfdp diff --git a/mission/cfdp/CfdpUser.h b/mission/cfdp/CfdpUser.h new file mode 100644 index 0000000..ba981cf --- /dev/null +++ b/mission/cfdp/CfdpUser.h @@ -0,0 +1,37 @@ +#ifndef MISSION_CFDP_CFDPUSER_H_ +#define MISSION_CFDP_CFDPUSER_H_ + +#include +#include + +namespace cfdp { + +class EiveUserHandler : public cfdp::UserBase { + public: + explicit EiveUserHandler(HasFileSystemIF& vfs, StorageManagerIF& ipcStore, + MessageQueueId_t cfdpRequestId); + + virtual ~EiveUserHandler(); + + void transactionIndication(const cfdp::TransactionId& id) override; + void eofSentIndication(const cfdp::TransactionId& id) override; + void transactionFinishedIndication(const cfdp::TransactionFinishedParams& params) override; + void metadataRecvdIndication(const cfdp::MetadataRecvdParams& params) override; + void fileSegmentRecvdIndication(const cfdp::FileSegmentRecvdParams& params) override; + void reportIndication(const cfdp::TransactionId& id, cfdp::StatusReportIF& report) override; + void suspendedIndication(const cfdp::TransactionId& id, cfdp::ConditionCode code); + void resumedIndication(const cfdp::TransactionId& id, size_t progress) override; + void faultIndication(const cfdp::TransactionId& id, cfdp::ConditionCode code, + size_t progress) override; + void abandonedIndication(const cfdp::TransactionId& id, cfdp::ConditionCode code, + size_t progress) override; + void eofRecvIndication(const cfdp::TransactionId& id) override; + + private: + MessageQueueIF* userQueue; + ReservedMessageParser* reservedMsgParser; +}; + +} // namespace cfdp + +#endif /* MISSION_CFDP_CFDPUSER_H_ */ diff --git a/mission/cfdp/defs.h b/mission/cfdp/defs.h new file mode 100644 index 0000000..249bec0 --- /dev/null +++ b/mission/cfdp/defs.h @@ -0,0 +1,16 @@ +#ifndef MISSION_CFDP_DEFS_H_ +#define MISSION_CFDP_DEFS_H_ + +#include "eive/eventSubsystemIds.h" +#include "fsfw/events/Event.h" + +namespace cfdp { + +static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::CFDP_APP; + +//! [EXPORT] : [COMMENT] P1: CFDP fault handler code. P2: CFDP condition code. +static constexpr Event FAULT_HANDLER_TRIGGERED = event::makeEvent(SUBSYSTEM_ID, 0, severity::LOW); + +} // namespace cfdp + +#endif /* MISSION_CFDP_DEFS_H_ */ diff --git a/mission/com/CMakeLists.txt b/mission/com/CMakeLists.txt new file mode 100644 index 0000000..90e90c8 --- /dev/null +++ b/mission/com/CMakeLists.txt @@ -0,0 +1,11 @@ +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE SyrlinksHandler.cpp + CcsdsIpCoreHandler.cpp + LiveTmTask.cpp + PersistentLogTmStoreTask.cpp + TmStoreTaskBase.cpp + VirtualChannel.cpp + VirtualChannelWithQueue.cpp + PersistentSingleTmStoreTask.cpp + LiveTmTask.cpp) diff --git a/mission/com/CcsdsIpCoreHandler.cpp b/mission/com/CcsdsIpCoreHandler.cpp new file mode 100644 index 0000000..19dd4f5 --- /dev/null +++ b/mission/com/CcsdsIpCoreHandler.cpp @@ -0,0 +1,391 @@ +#include "CcsdsIpCoreHandler.h" + +#include +#include +#include +#include + +#include "eive/definitions.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/objectmanager/ObjectManager.h" +#include "fsfw/serialize/SerializeAdapter.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "fsfw/serviceinterface/serviceInterfaceDefintions.h" +#include "mission/com/syrlinksDefs.h" + +CcsdsIpCoreHandler::CcsdsIpCoreHandler(object_id_t objectId, object_id_t tcDestination, + PtmeConfig& ptmeConfig, std::atomic_bool& linkState, + GpioIF* gpioIF, PtmeGpios gpioIds, + std::atomic_bool& ptmeLocked) + : SystemObject(objectId), + linkState(linkState), + ptmeLocked(ptmeLocked), + tcDestination(tcDestination), + parameterHelper(this), + actionHelper(this, nullptr), + modeHelper(this), + ptmeConfig(ptmeConfig), + ptmeGpios(gpioIds), + gpioIF(gpioIF) { + commandQueue = QueueFactory::instance()->createMessageQueue(QUEUE_SIZE); + auto mqArgs = MqArgs(objectId, static_cast(this)); + eventQueue = + QueueFactory::instance()->createMessageQueue(10, EventMessage::EVENT_MESSAGE_SIZE, &mqArgs); + ptmeLocked = true; +} + +CcsdsIpCoreHandler::~CcsdsIpCoreHandler() = default; + +ReturnValue_t CcsdsIpCoreHandler::performOperation(uint8_t operationCode) { + readCommandQueue(); + performPtmeUpdateWhenApplicable(); + return returnvalue::OK; +} + +ReturnValue_t CcsdsIpCoreHandler::initialize() { + AcceptsTelecommandsIF* tcDistributor = + ObjectManager::instance()->get(tcDestination); + if (tcDistributor == nullptr) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "CcsdsHandler::initialize: Invalid TC Distributor object" << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + tcDistributorQueueId = tcDistributor->getRequestQueue(); + + ReturnValue_t result = parameterHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + + result = actionHelper.initialize(commandQueue); + if (result != returnvalue::OK) { + return result; + } + + result = modeHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + + result = ptmeConfig.initialize(); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + // This also pulls the PTME out of reset state. + updateBatPriorityFromParam(); + ptmeConfig.setPollThreshold( + static_cast(params.pollThresholdParam)); + resetPtme(); + ptmeLocked = false; + +#if OBSW_SYRLINKS_SIMULATED == 1 + // Update data on rising edge + ptmeConfig.invertTxClock(false); + linkState = LINK_UP; +#endif /* OBSW_SYRLINKS_SIMULATED == 1*/ + + return result; +} + +void CcsdsIpCoreHandler::readCommandQueue(void) { + CommandMessage commandMessage; + ReturnValue_t result = returnvalue::FAILED; + + result = commandQueue->receiveMessage(&commandMessage); + if (result == returnvalue::OK) { + result = parameterHelper.handleParameterMessage(&commandMessage); + if (result == returnvalue::OK) { + return; + } + result = actionHelper.handleActionMessage(&commandMessage); + if (result == returnvalue::OK) { + return; + } + result = modeHelper.handleModeCommand(&commandMessage); + if (result == returnvalue::OK) { + return; + } + CommandMessage reply; + reply.setReplyRejected(CommandMessage::UNKNOWN_COMMAND, commandMessage.getCommand()); + commandQueue->reply(&reply); + return; + } +} + +MessageQueueId_t CcsdsIpCoreHandler::getCommandQueue() const { return commandQueue->getId(); } + +ReturnValue_t CcsdsIpCoreHandler::getParameter(uint8_t domainId, uint8_t uniqueIdentifier, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, + uint16_t startAtIndex) { + if (domainId != 0) { + return HasParametersIF::INVALID_DOMAIN_ID; + } + if (uniqueIdentifier == ParamId::BAT_PRIORITY) { + uint8_t newVal = 0; + ReturnValue_t result = newValues->getElement(&newVal); + if (result != returnvalue::OK) { + return result; + } + if (newVal > 1) { + return HasParametersIF::INVALID_VALUE; + } + parameterWrapper->set(params.batPriorityParam); + if (newVal != params.batPriorityParam) { + // This ensures that the BAT priority is updated at some point when an update of the PTME is + // allowed + updateContext.updateBatPrio = true; + // If we are off, we can do the update after X cycles. Otherwise, wait until the transmitter + // goes off. + if (mode == MODE_OFF) { + initPtmeUpdateAfterXCycles(); + } + } + return returnvalue::OK; + } else if (uniqueIdentifier == ParamId::POLL_THRESHOLD) { + uint8_t newVal = 0; + ReturnValue_t result = newValues->getElement(&newVal); + if (result != returnvalue::OK) { + return result; + } + if (newVal > static_cast(AxiPtmeConfig::NEVER)) { + return HasParametersIF::INVALID_VALUE; + } + parameterWrapper->set(newVal); + if (newVal != params.pollThresholdParam) { + updateContext.updatePollThreshold = true; + if (mode == MODE_OFF) { + initPtmeUpdateAfterXCycles(); + } + } + return returnvalue::OK; + } + return HasParametersIF::INVALID_IDENTIFIER_ID; +} + +uint32_t CcsdsIpCoreHandler::getIdentifier() const { return 0; } + +MessageQueueId_t CcsdsIpCoreHandler::getRequestQueue() const { + // Forward packets directly to the CCSDS TC distributor + return tcDistributorQueueId; +} + +ReturnValue_t CcsdsIpCoreHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + ReturnValue_t result = returnvalue::OK; + switch (actionId) { + case ARBITRARY_RATE: { + uint32_t bitrate = 0; + result = SerializeAdapter::deSerialize(&bitrate, &data, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + return result; + } + ptmeConfig.setRate(bitrate); + updateContext.updateClockRate = true; + if (mode == MODE_OFF) { + initPtmeUpdateAfterXCycles(); + } + break; + } + case ENABLE_TX_CLK_MANIPULATOR: { + ptmeConfig.configTxManipulator(true); + break; + } + case DISABLE_TX_CLK_MANIPULATOR: { + ptmeConfig.configTxManipulator(false); + break; + } + case UPDATE_ON_RISING_EDGE: { + ptmeConfig.invertTxClock(false); + break; + } + case UPDATE_ON_FALLING_EDGE: { + ptmeConfig.invertTxClock(true); + break; + } + default: + return COMMAND_NOT_IMPLEMENTED; + } + if (result != returnvalue::OK) { + return result; + } + return EXECUTION_FINISHED; +} + +void CcsdsIpCoreHandler::updateLinkState() { linkState = LINK_UP; } + +void CcsdsIpCoreHandler::enableTransmit() { + gpioIF->pullHigh(ptmeGpios.enableTxClock); + gpioIF->pullHigh(ptmeGpios.enableTxData); + linkState = LINK_UP; +} + +void CcsdsIpCoreHandler::getMode(Mode_t* mode, Submode_t* submode) { + *mode = this->mode; + *submode = this->submode; +} + +ReturnValue_t CcsdsIpCoreHandler::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) { + if (mode == HasModesIF::MODE_ON) { + if (submode != static_cast(com::CcsdsSubmode::DATARATE_HIGH) and + submode != static_cast(com::CcsdsSubmode::DATARATE_LOW) and + submode != static_cast(com::CcsdsSubmode::DATARATE_DEFAULT)) { + return HasModesIF::INVALID_SUBMODE; + } + } else if (mode != HasModesIF::MODE_OFF) { + return returnvalue::FAILED; + } + *msToReachTheMode = 2000; + return returnvalue::OK; +} + +void CcsdsIpCoreHandler::startTransition(Mode_t mode, Submode_t submode) { + triggerEvent(CHANGING_MODE, mode, submode); + if (mode == HasModesIF::MODE_ON) { + uint32_t currentRate = ptmeConfig.getRate(); + // Check whether the rate actually changes. + if ((this->submode != submode) and + (((submode == static_cast(com::CcsdsSubmode::DATARATE_LOW) and + (currentRate != RATE_100KBPS))) or + ((submode == static_cast(com::CcsdsSubmode::DATARATE_HIGH) and + (currentRate != RATE_500KBPS))))) { + initPtmeUpdateAfterXCycles(); + updateContext.enableTransmitAfterPtmeUpdate = true; + updateContext.updateClockRate = true; + this->submode = submode; + this->mode = mode; + updateContext.setModeAfterUpdate = true; + return; + } + // No rate change, so enable transmitter right away. + enableTransmit(); + } else if (mode == HasModesIF::MODE_OFF) { + disableTransmit(); + } + setMode(mode, submode); +} + +void CcsdsIpCoreHandler::announceMode(bool recursive) { triggerEvent(MODE_INFO, mode, submode); } + +void CcsdsIpCoreHandler::disableTransmit() { +#ifndef TE0720_1CFA + gpioIF->pullLow(ptmeGpios.enableTxClock); + gpioIF->pullLow(ptmeGpios.enableTxData); +#endif + linkState = LINK_DOWN; + // Some parameters need update and transmitter is off now. + if (updateContext.updateBatPrio or updateContext.updateClockRate) { + initPtmeUpdateAfterXCycles(); + } +} + +const char* CcsdsIpCoreHandler::getName() const { return "CCSDS Handler"; } + +const HasHealthIF* CcsdsIpCoreHandler::getOptHealthIF() const { return nullptr; } + +const HasModesIF& CcsdsIpCoreHandler::getModeIF() const { return *this; } + +ReturnValue_t CcsdsIpCoreHandler::connectModeTreeParent(HasModeTreeChildrenIF& parent) { + return modetree::connectModeTreeParent(parent, *this, nullptr, modeHelper); +} + +ModeTreeChildIF& CcsdsIpCoreHandler::getModeTreeChildIF() { return *this; } + +object_id_t CcsdsIpCoreHandler::getObjectId() const { return SystemObject::getObjectId(); } + +void CcsdsIpCoreHandler::enablePrioritySelectMode() { ptmeConfig.enableBatPriorityBit(true); } + +void CcsdsIpCoreHandler::disablePrioritySelectMode() { ptmeConfig.enableBatPriorityBit(false); } + +void CcsdsIpCoreHandler::updateBatPriorityFromParam() { + if (params.batPriorityParam == 0) { + disablePrioritySelectMode(); + } else { + enablePrioritySelectMode(); + } +} + +void CcsdsIpCoreHandler::setMode(Mode_t mode, Submode_t submode) { + this->submode = submode; + this->mode = mode; + modeHelper.modeChanged(mode, submode); + announceMode(false); +} + +void CcsdsIpCoreHandler::performPtmeUpdateWhenApplicable() { + if (not updateContext.performPtmeUpdateAfterXCycles) { + return; + } + if (updateContext.ptmeUpdateCycleCount >= 2) { + bool doResetPtme = false; + if (updateContext.updateBatPrio) { + updateBatPriorityFromParam(); + updateContext.updateBatPrio = false; + doResetPtme = true; + } + if (updateContext.updatePollThreshold) { + ptmeConfig.setPollThreshold( + static_cast(params.pollThresholdParam)); + updateContext.updatePollThreshold = false; + doResetPtme = true; + } + ReturnValue_t result = returnvalue::OK; + if (updateContext.updateClockRate) { + if (submode == static_cast(com::CcsdsSubmode::DATARATE_DEFAULT)) { + com::Datarate currentDatarate = com::getCurrentDatarate(); + if (currentDatarate == com::Datarate::LOW_RATE_MODULATION_BPSK) { + result = ptmeConfig.setRate(RATE_100KBPS); + } else if (currentDatarate == com::Datarate::HIGH_RATE_MODULATION_0QPSK) { + result = ptmeConfig.setRate(RATE_500KBPS); + } + } else if (submode == static_cast(com::CcsdsSubmode::DATARATE_HIGH)) { + result = ptmeConfig.setRate(RATE_500KBPS); + } else if (submode == static_cast(com::CcsdsSubmode::DATARATE_LOW)) { + result = ptmeConfig.setRate(RATE_100KBPS); + } + if (result != returnvalue::OK) { + sif::error << "CcsdsIpCoreHandler: Setting datarate failed" << std::endl; + } + updateContext.updateClockRate = false; + doResetPtme = true; + } + finishPtmeUpdateAfterXCycles(doResetPtme); + return; + } + updateContext.ptmeUpdateCycleCount++; +} + +void CcsdsIpCoreHandler::resetPtme() { + gpioIF->pullLow(ptmeGpios.ptmeResetn); + usleep(10); + gpioIF->pullHigh(ptmeGpios.ptmeResetn); +} + +void CcsdsIpCoreHandler::initPtmeUpdateAfterXCycles() { + if (not updateContext.performPtmeUpdateAfterXCycles) { + updateContext.performPtmeUpdateAfterXCycles = true; + updateContext.ptmeUpdateCycleCount = 0; + ptmeLocked = true; + } +} + +void CcsdsIpCoreHandler::finishPtmeUpdateAfterXCycles(bool doResetPtme) { + if (doResetPtme) { + resetPtme(); + } + ptmeLocked = false; + updateContext.performPtmeUpdateAfterXCycles = false; + updateContext.ptmeUpdateCycleCount = 0; + if (updateContext.enableTransmitAfterPtmeUpdate) { + enableTransmit(); + updateContext.enableTransmitAfterPtmeUpdate = false; + } + if (updateContext.setModeAfterUpdate) { + setMode(mode, submode); + updateContext.setModeAfterUpdate = false; + } +} diff --git a/mission/com/CcsdsIpCoreHandler.h b/mission/com/CcsdsIpCoreHandler.h new file mode 100644 index 0000000..a5d02ad --- /dev/null +++ b/mission/com/CcsdsIpCoreHandler.h @@ -0,0 +1,214 @@ +#ifndef CCSDSHANDLER_H_ +#define CCSDSHANDLER_H_ + +#include +#include + +#include +#include + +#include "OBSWConfig.h" +#include "eive/definitions.h" +#include "fsfw/action/ActionHelper.h" +#include "fsfw/action/HasActionsIF.h" +#include "fsfw/events/EventMessage.h" +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/parameters/ParameterHelper.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/subsystem/ModeTreeConnectionIF.h" +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw/timemanager/Countdown.h" +#include "fsfw/tmtcservices/AcceptsTelecommandsIF.h" +#include "fsfw/tmtcservices/AcceptsTelemetryIF.h" +#include "fsfw_hal/common/gpio/GpioIF.h" +#include "fsfw_hal/common/gpio/gpioDefinitions.h" +#include "linux/ipcore/PtmeConfig.h" +#include "mission/com/defs.h" + +struct PtmeGpios { + gpioId_t enableTxClock = gpio::NO_GPIO; + gpioId_t enableTxData = gpio::NO_GPIO; + gpioId_t ptmeResetn = gpio::NO_GPIO; +}; + +/** + * @brief This class handles the data exchange with the CCSDS IP cores implemented in the + * programmable logic of the Q7S. + * + * @details + * From a system view, OFF means that the transmitter is off, on means that the transmitter is on. + * The handler will only take care of the IP configuration, the actual swithing and configuration + * of the COM hardware (Syrlinks for the EIVE project) will be done in a separate device handler. + * + * After reboot default CADU bitrate is always set to 100 kbps (results in downlink rate + * of 200 kbps due to convolutional code added by syrlinks transceiver). The IP core handler exposes + * a parameter to enable the priority selection mode for the PTME core. + * + * If the transmitter is on, the selection mode will be enabled when the transmitter goes off. + * If the transmitter is off, the update of the PTME will be done immediately on a parameter update. + * This is done because changing this parameter requires a reset of the PTME core to avoid bugs + * while the transmitter is enabled. + * + * @author J. Meier + */ +class CcsdsIpCoreHandler : public SystemObject, + public ExecutableObjectIF, + public ModeTreeChildIF, + public ModeTreeConnectionIF, + public HasModesIF, + public AcceptsTelecommandsIF, + public ReceivesParameterMessagesIF, + public HasActionsIF { + public: + enum ParamId : uint8_t { BAT_PRIORITY = 0, POLL_THRESHOLD = 1 }; + + static const bool LINK_UP = true; + static const bool LINK_DOWN = false; + using VcId_t = uint8_t; + + /** + * @brief Constructor + * + * @param objectId Object ID of the CCSDS handler + * @param ptmeId Object ID of the PTME object providing access to the PTME IP Core. + * @param tcDestination Object ID of object handling received TC space packets + * @param txRateSetter Object providing the functionality to switch the input bitrate of + * the S-Band transceiver. + * @param gpioIF Required to enable TX data and TX clock RS485 transceiver chips. + * @param enTxClock GPIO ID of RS485 tx clock enable + * @param enTxData GPIO ID of RS485 tx data enable + */ + CcsdsIpCoreHandler(object_id_t objectId, object_id_t tcDestination, PtmeConfig& ptmeConfig, + std::atomic_bool& linkState, GpioIF* gpioIF, PtmeGpios gpioIds, + std::atomic_bool& ptmeLocked); + + ~CcsdsIpCoreHandler(); + + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + ReturnValue_t initialize(); + MessageQueueId_t getCommandQueue() const override; + + // ModesIF + void getMode(Mode_t* mode, Submode_t* submode) override; + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + void startTransition(Mode_t mode, Submode_t submode) override; + void announceMode(bool recursive) override; + + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueIdentifier, + ParameterWrapper* parameterWrapper, const ParameterWrapper* newValues, + uint16_t startAtIndex); + + uint32_t getIdentifier() const override; + MessageQueueId_t getRequestQueue() const override; + const char* getName() const override; + + virtual ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size); + const HasHealthIF* getOptHealthIF() const override; + const HasModesIF& getModeIF() const override; + object_id_t getObjectId() const override; + ReturnValue_t connectModeTreeParent(HasModeTreeChildrenIF& parent) override; + ModeTreeChildIF& getModeTreeChildIF() override; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::CCSDS_HANDLER; + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PLOC_MPSOC_HANDLER; + + static const uint32_t QUEUE_SIZE = config::CCSDS_HANDLER_QUEUE_SIZE; + + static const ActionId_t SET_LOW_RATE = 0; + static const ActionId_t SET_HIGH_RATE = 1; + static const ActionId_t EN_TRANSMITTER = 2; + static const ActionId_t DISABLE_TRANSMITTER = 3; + static const ActionId_t ARBITRARY_RATE = 4; + static const ActionId_t ENABLE_TX_CLK_MANIPULATOR = 5; + static const ActionId_t DISABLE_TX_CLK_MANIPULATOR = 6; + // Will update data with respect to tx clock signal of cadu bitstream on rising edge + static const ActionId_t UPDATE_ON_RISING_EDGE = 7; + // Will update data with respect to tx clock signal of cadu bitstream on falling edge + static const ActionId_t UPDATE_ON_FALLING_EDGE = 8; + + // Syrlinks supports two bitrates (200 kbps and 1000 kbps) + // Due to convolutional code added by the syrlinks the input frequency must be half the + // target frequency + static const uint32_t RATE_100KBPS = 100000; + static const uint32_t RATE_500KBPS = 500000; + + //! [EXPORT] : [COMMENT] Received action message with unknown action id + static const ReturnValue_t COMMAND_NOT_IMPLEMENTED = MAKE_RETURN_CODE(0xA0); + + std::atomic_bool& linkState; + std::atomic_bool& ptmeLocked; + + object_id_t tcDestination; + + MessageQueueIF* commandQueue = nullptr; + MessageQueueIF* eventQueue = nullptr; + ParameterHelper parameterHelper; + + ActionHelper actionHelper; + Mode_t mode = HasModesIF::MODE_OFF; + Submode_t submode = static_cast(com::CcsdsSubmode::UNSET); + ModeHelper modeHelper; + + MessageQueueId_t tcDistributorQueueId = MessageQueueIF::NO_QUEUE; + + PtmeConfig& ptmeConfig; + PtmeGpios ptmeGpios; + struct Parameters { + // BAT priority bit on by default to enable priority selection mode for the PTME. + uint8_t batPriorityParam = 0; + uint8_t pollThresholdParam = static_cast(AxiPtmeConfig::IdlePollThreshold::POLL_4); + + } params; + + struct UpdateContext { + bool updateBatPrio = false; + bool updateClockRate = false; + bool updatePollThreshold = false; + bool enableTransmitAfterPtmeUpdate = false; + uint8_t ptmeUpdateCycleCount = 0; + bool performPtmeUpdateAfterXCycles = false; + bool setModeAfterUpdate = false; + } updateContext{}; + + GpioIF* gpioIF = nullptr; + + void readCommandQueue(void); + + /** + * @brief Forward link state to virtual channels. + */ + void updateLinkState(); + + /** + * @brief Starts transmit timer and enables transmitter. + */ + void enableTransmit(); + + /** + * @brief Disables the transmitter by pulling the enable tx clock and tx data pin of the + * RS485 transceiver chips to high. + */ + void disableTransmit(); + + void performPtmeUpdateWhenApplicable(); + + /** + * The following set of functions configure the mode of the PTME bandwith allocation table (BAT) + * module. This consists of the following 2 steps: + * 1. Update the BAT priority bit in the PTME wrapper + * 2. Reset the PTME as specified in the datasheet. + */ + void enablePrioritySelectMode(); + void disablePrioritySelectMode(); + void updateBatPriorityFromParam(); + + void setMode(Mode_t mode, Submode_t submode); + void resetPtme(); + void initPtmeUpdateAfterXCycles(); + void finishPtmeUpdateAfterXCycles(bool doResetPtme); +}; + +#endif /* CCSDSHANDLER_H_ */ diff --git a/mission/com/LiveTmTask.cpp b/mission/com/LiveTmTask.cpp new file mode 100644 index 0000000..33b9322 --- /dev/null +++ b/mission/com/LiveTmTask.cpp @@ -0,0 +1,248 @@ +#include "LiveTmTask.h" + +#include +#include +#include +#include + +#include "mission/sysDefs.h" + +static constexpr bool DEBUG_TM_QUEUE_SPEED = false; +std::atomic_bool signals::CFDP_CHANNEL_THROTTLE_SIGNAL = false; +std::atomic_uint32_t signals::CFDP_MSG_COUNTER = 0; + +LiveTmTask::LiveTmTask(object_id_t objectId, PusTmFunnel& pusFunnel, CfdpTmFunnel& cfdpFunnel, + VirtualChannel& channel, const std::atomic_bool& ptmeLocked, + uint32_t regularTmQueueDepth, uint32_t cfdpQueueDepth) + : SystemObject(objectId), + modeHelper(this), + pusFunnel(pusFunnel), + cfdpFunnel(cfdpFunnel), + channel(channel), + ptmeLocked(ptmeLocked) { + requestQueue = QueueFactory::instance()->createMessageQueue(); + cfdpTmQueue = QueueFactory::instance()->createMessageQueue(cfdpQueueDepth); + regularTmQueue = QueueFactory::instance()->createMessageQueue(regularTmQueueDepth); +} + +ReturnValue_t LiveTmTask::performOperation(uint8_t opCode) { + readCommandQueue(); + bool handledTm; + ReturnValue_t result; + uint32_t consecutiveRegularCounter = 0; + uint32_t consecutiveCfdpCounter = 0; + bool isCfdp = false; + while (true) { + isCfdp = false; + // TODO: Must read CFDP TM queue and regular TM queue and forward them. Handle regular queue + // first. + handledTm = false; + updateBusyFlag(); + if (!channelIsBusy) { + result = handleRegularTmQueue(); + if (result == MessageQueueIF::EMPTY) { + result = handleCfdpTmQueue(); + isCfdp = true; + } + if (result == returnvalue::OK) { + handledTm = true; + if (DEBUG_TM_QUEUE_SPEED) { + if (isCfdp) { + consecutiveCfdpCounter++; + } else { + consecutiveRegularCounter++; + } + } + } else if (result != MessageQueueIF::EMPTY) { + const char* contextStr = "Regular TM queue"; + if (isCfdp) { + contextStr = "CFDP TM queue"; + } + sif::warning << "LiveTmTask: " << contextStr << " handling failure, returncode 0x" + << std::setfill('0') << std::hex << std::setw(4) << result << std::dec + << std::endl; + } + } + + cfdpBackpressureHandling(); + + if (!handledTm) { + if (tmFunnelCd.hasTimedOut()) { + pusFunnel.performOperation(0); + cfdpFunnel.performOperation(0); + tmFunnelCd.resetTimer(); + } + // Read command queue during idle times. + readCommandQueue(); + if (DEBUG_TM_QUEUE_SPEED) { + if (consecutiveCfdpCounter > 0) { + sif::debug << "Consecutive CFDP TM handled: " << consecutiveCfdpCounter << std::endl; + } + if (consecutiveRegularCounter > 0) { + sif::debug << "Consecutive regular TM handled: " << consecutiveRegularCounter + << std::endl; + } + consecutiveRegularCounter = 0; + consecutiveCfdpCounter = 0; + } + // 40 ms IDLE delay. Might tweak this in the future. + TaskFactory::delayTask(40); + } + } +} + +MessageQueueId_t LiveTmTask::getCommandQueue() const { return requestQueue->getId(); } + +void LiveTmTask::getMode(Mode_t* mode, Submode_t* submode) { + if (mode != nullptr) { + *mode = this->mode; + } + if (submode != nullptr) { + *submode = SUBMODE_NONE; + } +} + +ReturnValue_t LiveTmTask::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) { + if (mode == MODE_ON or mode == MODE_OFF) { + return returnvalue::OK; + } + return returnvalue::FAILED; +} + +void LiveTmTask::startTransition(Mode_t mode, Submode_t submode) { + this->mode = mode; + modeHelper.modeChanged(mode, submode); + announceMode(false); +} + +void LiveTmTask::announceMode(bool recursive) { triggerEvent(MODE_INFO, mode, SUBMODE_NONE); } + +object_id_t LiveTmTask::getObjectId() const { return SystemObject::getObjectId(); } + +const HasHealthIF* LiveTmTask::getOptHealthIF() const { return nullptr; } + +const HasModesIF& LiveTmTask::getModeIF() const { return *this; } + +ReturnValue_t LiveTmTask::connectModeTreeParent(HasModeTreeChildrenIF& parent) { + return modetree::connectModeTreeParent(parent, *this, nullptr, modeHelper); +} + +void LiveTmTask::readCommandQueue(void) { + CommandMessage commandMessage; + ReturnValue_t result = returnvalue::FAILED; + + result = requestQueue->receiveMessage(&commandMessage); + if (result == returnvalue::OK) { + result = modeHelper.handleModeCommand(&commandMessage); + if (result == returnvalue::OK) { + return; + } + CommandMessage reply; + reply.setReplyRejected(CommandMessage::UNKNOWN_COMMAND, commandMessage.getCommand()); + requestQueue->reply(&reply); + return; + } +} + +ReturnValue_t LiveTmTask::handleRegularTmQueue() { + return handleGenericTmQueue(*regularTmQueue, false); +} + +ReturnValue_t LiveTmTask::handleCfdpTmQueue() { return handleGenericTmQueue(*cfdpTmQueue, true); } + +ReturnValue_t LiveTmTask::handleGenericTmQueue(MessageQueueIF& queue, bool isCfdp) { + TmTcMessage message; + ReturnValue_t result = queue.receiveMessage(&message); + if (result == MessageQueueIF::EMPTY) { + return result; + } + if (isCfdp and signals::CFDP_MSG_COUNTER > 0) { + signals::CFDP_MSG_COUNTER--; + } + if (DEBUG_CFDP_TO_LIVE_TM_TASK and signals::CFDP_MSG_COUNTER > 0) { + sif::debug << "LiveTmTask: CFDP message counter: " << signals::CFDP_MSG_COUNTER << std::endl; + } + store_address_t storeId = message.getStorageId(); + const uint8_t* data = nullptr; + size_t size = 0; + result = tmStore->getData(storeId, &data, &size); + if (result != returnvalue::OK) { + sif::warning << "VirtualChannel::performOperation: Failed to read data from TM store" + << std::endl; + tmStore->deleteData(storeId); + return result; + } + + if (!ptmeLocked) { + size_t writtenSize = 0; + result = channel.write(data, size, writtenSize); + if (result == DirectTmSinkIF::PARTIALLY_WRITTEN) { + result = channel.handleWriteCompletionSynchronously(writtenSize, 400); + if (result != returnvalue::OK) { + // TODO: Event? Might lead to dangerous spam though.. + sif::warning << "LiveTmTask: Synchronous write of last segment failed with code 0x" + << std::setfill('0') << std::setw(4) << std::hex << result << std::dec + << std::endl; + } + } else if (result != returnvalue::OK) { + sif::error << "LiveTmTask: Channel write failed with code 0x" << std::setfill('0') << std::hex + << std::setw(4) << result << std::dec << std::endl; + } + } + // Try delete in any case, ignore failures (which should not happen), it is more important to + // propagate write errors. + tmStore->deleteData(storeId); + return result; +} + +void LiveTmTask::throttleCfdp() { + throttlePeriodOngoing = true; + signals::CFDP_CHANNEL_THROTTLE_SIGNAL = true; + if (DEBUG_CFDP_TO_LIVE_TM_TASK) { + sif::debug << "Throttling CFDP" << std::endl; + } +} + +void LiveTmTask::releaseCfdp() { + throttlePeriodOngoing = false; + signals::CFDP_CHANNEL_THROTTLE_SIGNAL = false; + if (DEBUG_CFDP_TO_LIVE_TM_TASK) { + sif::debug << "Releasing CFDP" << std::endl; + } +} + +void LiveTmTask::updateBusyFlag() { + // We cache this as a member, because the busy bit can toggle very quickly.. + channelIsBusy = channel.isBusy(); +} + +void LiveTmTask::cfdpBackpressureHandling() { + if (channelIsBusy and !throttlePeriodOngoing) { + // Throttle CFDP packet creator. It is by far the most relevant data creator, so throttling + // it is the easiest way to handle back pressure for now in a sensible way. + if (signals::CFDP_MSG_COUNTER >= (config::LIVE_CHANNEL_CFDP_QUEUE_SIZE / 2)) { + throttleCfdp(); + } + } else if (!channelIsBusy and throttlePeriodOngoing) { + // Half full/empty flow control: Release the CFDP is the queue is empty enough. + if (signals::CFDP_MSG_COUNTER <= (config::LIVE_CHANNEL_CFDP_QUEUE_SIZE / 4)) { + releaseCfdp(); + } + } +} + +ModeTreeChildIF& LiveTmTask::getModeTreeChildIF() { return *this; } + +ReturnValue_t LiveTmTask::initialize() { + modeHelper.initialize(); + tmStore = ObjectManager::instance()->get(objects::TM_STORE); + if (tmStore == nullptr) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + return returnvalue::OK; +} + +MessageQueueId_t LiveTmTask::getNormalLiveQueueId() const { return regularTmQueue->getId(); } + +MessageQueueId_t LiveTmTask::getCfdpLiveQueueId() const { return cfdpTmQueue->getId(); } diff --git a/mission/com/LiveTmTask.h b/mission/com/LiveTmTask.h new file mode 100644 index 0000000..48bbb9f --- /dev/null +++ b/mission/com/LiveTmTask.h @@ -0,0 +1,79 @@ +#ifndef MISSION_TMTC_LIVETMTASK_H_ +#define MISSION_TMTC_LIVETMTASK_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "eive/definitions.h" + +static constexpr bool DEBUG_CFDP_TO_LIVE_TM_TASK = false; + +class LiveTmTask : public SystemObject, + public HasModesIF, + public ExecutableObjectIF, + public ModeTreeChildIF, + public ModeTreeConnectionIF { + public: + LiveTmTask(object_id_t objectId, PusTmFunnel& pusFunnel, CfdpTmFunnel& cfdpFunnel, + VirtualChannel& channel, const std::atomic_bool& ptmeLocked, + uint32_t regularTmQueueDepth, uint32_t cfdpQueueDepth); + + MessageQueueId_t getNormalLiveQueueId() const; + MessageQueueId_t getCfdpLiveQueueId() const; + ReturnValue_t performOperation(uint8_t opCode) override; + ReturnValue_t initialize() override; + ReturnValue_t connectModeTreeParent(HasModeTreeChildrenIF& parent) override; + + private: + MessageQueueIF* requestQueue; + MessageQueueIF* cfdpTmQueue; + MessageQueueIF* regularTmQueue; + StorageManagerIF* tmStore = nullptr; + ModeHelper modeHelper; + Mode_t mode = HasModesIF::MODE_OFF; + Countdown tmFunnelCd = Countdown(100); + PusTmFunnel& pusFunnel; + CfdpTmFunnel& cfdpFunnel; + VirtualChannel& channel; + const std::atomic_bool& ptmeLocked; + bool throttlePeriodOngoing = false; + bool channelIsBusy = false; + + void readCommandQueue(void); + + ReturnValue_t handleRegularTmQueue(); + ReturnValue_t handleCfdpTmQueue(); + ReturnValue_t handleGenericTmQueue(MessageQueueIF& queue, bool isCfdp); + + MessageQueueId_t getCommandQueue() const override; + + void getMode(Mode_t* mode, Submode_t* submode) override; + + void cfdpBackpressureHandling(); + + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + + void startTransition(Mode_t mode, Submode_t submode) override; + + void announceMode(bool recursive) override; + void throttleCfdp(); + void releaseCfdp(); + void updateBusyFlag(); + + object_id_t getObjectId() const override; + const HasHealthIF* getOptHealthIF() const override; + const HasModesIF& getModeIF() const override; + ModeTreeChildIF& getModeTreeChildIF() override; +}; + +#endif /* MISSION_TMTC_LIVETMTASK_H_ */ diff --git a/mission/com/PersistentLogTmStoreTask.cpp b/mission/com/PersistentLogTmStoreTask.cpp new file mode 100644 index 0000000..2854545 --- /dev/null +++ b/mission/com/PersistentLogTmStoreTask.cpp @@ -0,0 +1,72 @@ +#include "PersistentLogTmStoreTask.h" + +#include +#include + +PersistentLogTmStoreTask::PersistentLogTmStoreTask(object_id_t objectId, StorageManagerIF& ipcStore, + LogStores stores, VirtualChannel& channel, + SdCardMountedIF& sdcMan, + const std::atomic_bool& ptmeLocked) + : TmStoreTaskBase(objectId, ipcStore, channel, sdcMan, ptmeLocked), + stores(stores), + okStoreContext(persTmStore::DUMP_OK_STORE_DONE, persTmStore::DUMP_OK_CANCELLED), + notOkStoreContext(persTmStore::DUMP_NOK_STORE_DONE, persTmStore::DUMP_NOK_CANCELLED), + miscStoreContext(persTmStore::DUMP_MISC_STORE_DONE, persTmStore::DUMP_MISC_CANCELLED) {} + +ReturnValue_t PersistentLogTmStoreTask::performOperation(uint8_t opCode) { + bool someonesBusy = false; + bool vcBusyDuringDump = false; + auto stateHandlingForStore = [&](bool storeIsBusy, DumpContext& ctx) { + if (storeIsBusy) { + someonesBusy = true; + } + if (fileHasSwapped) { + someFileWasSwapped = fileHasSwapped; + } + if (ctx.vcBusyDuringDump) { + vcBusyDuringDump = true; + } + }; + while (true) { + readCommandQueue(); + + if (not cyclicStoreCheck()) { + continue; + } + someonesBusy = false; + someFileWasSwapped = false; + vcBusyDuringDump = false; + stateHandlingForStore(handleOneStore(stores.okStore, okStoreContext), okStoreContext); + stateHandlingForStore(handleOneStore(stores.notOkStore, notOkStoreContext), notOkStoreContext); + stateHandlingForStore(handleOneStore(stores.miscStore, miscStoreContext), miscStoreContext); + if (not someonesBusy) { + TaskFactory::delayTask(100); + } else if (vcBusyDuringDump) { + TaskFactory::delayTask(10); + } + } +} + +bool PersistentLogTmStoreTask::initStoresIfPossible() { + if (sdcMan.isSdCardUsable(std::nullopt)) { + stores.okStore.initializeTmStore(); + stores.miscStore.initializeTmStore(); + stores.notOkStore.initializeTmStore(); + return true; + } + return false; +} + +void PersistentLogTmStoreTask::startTransition(Mode_t mode, Submode_t submode) { + if (mode == MODE_OFF) { + bool channelIsOn = channel.isTxOn(); + cancelDump(okStoreContext, stores.okStore, channelIsOn); + cancelDump(notOkStoreContext, stores.notOkStore, channelIsOn); + cancelDump(miscStoreContext, stores.miscStore, channelIsOn); + this->mode = MODE_OFF; + } else if (mode == MODE_ON) { + this->mode = MODE_ON; + } + modeHelper.modeChanged(mode, submode); + announceMode(false); +} diff --git a/mission/com/PersistentLogTmStoreTask.h b/mission/com/PersistentLogTmStoreTask.h new file mode 100644 index 0000000..18da93f --- /dev/null +++ b/mission/com/PersistentLogTmStoreTask.h @@ -0,0 +1,43 @@ +#ifndef MISSION_TMTC_PERSISTENTLOGTMSTORETASK_H_ +#define MISSION_TMTC_PERSISTENTLOGTMSTORETASK_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct LogStores { + LogStores(PersistentTmStores& stores) + : okStore(*stores.okStore), notOkStore(*stores.notOkStore), miscStore(*stores.miscStore) {} + PersistentTmStoreWithTmQueue& okStore; + PersistentTmStoreWithTmQueue& notOkStore; + PersistentTmStoreWithTmQueue& miscStore; +}; + +class PersistentLogTmStoreTask : public TmStoreTaskBase, public ExecutableObjectIF { + public: + PersistentLogTmStoreTask(object_id_t objectId, StorageManagerIF& ipcStore, LogStores tmStore, + VirtualChannel& channel, SdCardMountedIF& sdcMan, + const std::atomic_bool& ptmeLocked); + + ReturnValue_t performOperation(uint8_t opCode) override; + + private: + LogStores stores; + DumpContext okStoreContext; + DumpContext notOkStoreContext; + DumpContext miscStoreContext; + Countdown tcHandlingCd = Countdown(400); + Countdown graceDelayDuringDumping = Countdown(200); + bool someFileWasSwapped = false; + + bool initStoresIfPossible() override; + void startTransition(Mode_t mode, Submode_t submode) override; +}; + +#endif /* MISSION_TMTC_PERSISTENTLOGTMSTORETASK_H_ */ diff --git a/mission/com/PersistentSingleTmStoreTask.cpp b/mission/com/PersistentSingleTmStoreTask.cpp new file mode 100644 index 0000000..d6f4328 --- /dev/null +++ b/mission/com/PersistentSingleTmStoreTask.cpp @@ -0,0 +1,49 @@ +#include "PersistentSingleTmStoreTask.h" + +#include +#include +#include + +PersistentSingleTmStoreTask::PersistentSingleTmStoreTask( + object_id_t objectId, StorageManagerIF& ipcStore, PersistentTmStoreWithTmQueue& tmStore, + VirtualChannel& channel, Event eventIfDumpDone, Event eventIfCancelled, SdCardMountedIF& sdcMan, + const std::atomic_bool& ptmeLocked) + : TmStoreTaskBase(objectId, ipcStore, channel, sdcMan, ptmeLocked), + storeWithQueue(tmStore), + dumpContext(eventIfDumpDone, eventIfCancelled) {} + +ReturnValue_t PersistentSingleTmStoreTask::performOperation(uint8_t opCode) { + while (true) { + readCommandQueue(); + + // Delay done by the check + if (not cyclicStoreCheck()) { + continue; + } + bool busy = handleOneStore(storeWithQueue, dumpContext); + if (not busy) { + TaskFactory::delayTask(100); + } else if (dumpContext.vcBusyDuringDump) { + TaskFactory::delayTask(10); + } + } +} + +bool PersistentSingleTmStoreTask::initStoresIfPossible() { + if (sdcMan.isSdCardUsable(std::nullopt)) { + storeWithQueue.initializeTmStore(); + return true; + } + return false; +} + +void PersistentSingleTmStoreTask::startTransition(Mode_t mode, Submode_t submode) { + if (mode == MODE_OFF) { + cancelDump(dumpContext, storeWithQueue, channel.isTxOn()); + this->mode = MODE_OFF; + } else if (mode == MODE_ON) { + this->mode = MODE_ON; + } + modeHelper.modeChanged(mode, submode); + announceMode(false); +} diff --git a/mission/com/PersistentSingleTmStoreTask.h b/mission/com/PersistentSingleTmStoreTask.h new file mode 100644 index 0000000..1a4c868 --- /dev/null +++ b/mission/com/PersistentSingleTmStoreTask.h @@ -0,0 +1,30 @@ +#ifndef MISSION_TMTC_PERSISTENTSINGLETMSTORETASK_H_ +#define MISSION_TMTC_PERSISTENTSINGLETMSTORETASK_H_ + +#include +#include +#include +#include +#include + +class PersistentSingleTmStoreTask : public TmStoreTaskBase, public ExecutableObjectIF { + public: + PersistentSingleTmStoreTask(object_id_t objectId, StorageManagerIF& ipcStore, + PersistentTmStoreWithTmQueue& storeWithQueue, VirtualChannel& channel, + Event eventIfDumpDone, Event eventIfCancelled, + SdCardMountedIF& sdcMan, const std::atomic_bool& ptmeLocked); + + ReturnValue_t performOperation(uint8_t opCode) override; + + private: + PersistentTmStoreWithTmQueue& storeWithQueue; + DumpContext dumpContext; + Countdown tcHandlingCd = Countdown(400); + Countdown graceDelayDuringDumping = Countdown(100); + + bool initStoresIfPossible() override; + + void startTransition(Mode_t mode, Submode_t submode) override; +}; + +#endif /* MISSION_TMTC_PERSISTENTSINGLETMSTORETASK_H_ */ diff --git a/mission/com/SyrlinksHandler.cpp b/mission/com/SyrlinksHandler.cpp new file mode 100644 index 0000000..50e6a56 --- /dev/null +++ b/mission/com/SyrlinksHandler.cpp @@ -0,0 +1,835 @@ +#include +#include +#include + +#include "OBSWConfig.h" +#include "mission/config/comCfg.h" + +SyrlinksHandler::SyrlinksHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + power::Switch_t powerSwitch, FailureIsolationBase* customFdir) + : DeviceHandlerBase(objectId, comIF, comCookie, customFdir), + rxDataset(this), + txDataset(this), + temperatureSet(this), + powerSwitch(powerSwitch) { + if (comCookie == nullptr) { + sif::warning << "SyrlinksHkHandler: Invalid com cookie" << std::endl; + } +} + +SyrlinksHandler::~SyrlinksHandler() = default; + +void SyrlinksHandler::doStartUp() { + if (internalState == InternalState::OFF) { + internalState = InternalState::ENABLE_TEMPERATURE_PROTECTION; + commandExecuted = false; + } + if (internalState == InternalState::ENABLE_TEMPERATURE_PROTECTION) { + if (commandExecuted) { + // Go to normal mode immediately and disable transmitter on startup. + setMode(_MODE_TO_ON); + internalState = InternalState::IDLE; + transState = TransitionState::IDLE; + commandExecuted = false; + } + } +} + +void SyrlinksHandler::doShutDown() { + // In any case, always disable TX first. + if (internalState != InternalState::TX_TRANSITION) { + internalState = InternalState::TX_TRANSITION; + transState = TransitionState::SET_TX_STANDBY; + commandExecuted = false; + } + if (internalState == InternalState::TX_TRANSITION) { + if (commandExecuted) { + temperatureSet.setValidity(false, true); + internalState = InternalState::OFF; + transState = TransitionState::IDLE; + commandExecuted = false; + setMode(_MODE_POWER_DOWN); + } + } +} + +ReturnValue_t SyrlinksHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + switch (nextCommand) { + case (syrlinks::READ_RX_STATUS_REGISTERS): + *id = syrlinks::READ_RX_STATUS_REGISTERS; + nextCommand = syrlinks::READ_TX_STATUS; + break; + case (syrlinks::READ_TX_STATUS): + *id = syrlinks::READ_TX_STATUS; + nextCommand = syrlinks::READ_TX_WAVEFORM; + break; + case (syrlinks::READ_TX_WAVEFORM): + *id = syrlinks::READ_TX_WAVEFORM; + nextCommand = syrlinks::READ_TX_AGC_VALUE_HIGH_BYTE; + break; + case (syrlinks::READ_TX_AGC_VALUE_HIGH_BYTE): + *id = syrlinks::READ_TX_AGC_VALUE_HIGH_BYTE; + nextCommand = syrlinks::READ_TX_AGC_VALUE_LOW_BYTE; + break; + case (syrlinks::READ_TX_AGC_VALUE_LOW_BYTE): + *id = syrlinks::READ_TX_AGC_VALUE_LOW_BYTE; + nextCommand = syrlinks::TEMP_POWER_AMPLIFIER_HIGH_BYTE; + break; + case (syrlinks::TEMP_POWER_AMPLIFIER_HIGH_BYTE): + *id = syrlinks::TEMP_POWER_AMPLIFIER_HIGH_BYTE; + nextCommand = syrlinks::TEMP_POWER_AMPLIFIER_LOW_BYTE; + break; + case (syrlinks::TEMP_POWER_AMPLIFIER_LOW_BYTE): + *id = syrlinks::TEMP_POWER_AMPLIFIER_LOW_BYTE; + nextCommand = syrlinks::TEMP_BASEBAND_BOARD_HIGH_BYTE; + break; + case (syrlinks::TEMP_BASEBAND_BOARD_HIGH_BYTE): + *id = syrlinks::TEMP_BASEBAND_BOARD_HIGH_BYTE; + nextCommand = syrlinks::TEMP_BASEBAND_BOARD_LOW_BYTE; + break; + case (syrlinks::TEMP_BASEBAND_BOARD_LOW_BYTE): + *id = syrlinks::TEMP_BASEBAND_BOARD_LOW_BYTE; + nextCommand = syrlinks::READ_RX_STATUS_REGISTERS; + break; + default: + sif::debug << "SyrlinksHkHandler::buildNormalDeviceCommand: rememberCommandId has invalid" + << "command id" << std::endl; + break; + } + return buildCommandFromCommand(*id, nullptr, 0); +} + +ReturnValue_t SyrlinksHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + if (transState == TransitionState::CMD_PENDING or transState == TransitionState::DONE) { + return NOTHING_TO_SEND; + } + switch (internalState) { + case InternalState::ENABLE_TEMPERATURE_PROTECTION: { + *id = syrlinks::WRITE_LCL_CONFIG; + transState = TransitionState::CMD_PENDING; + return buildCommandFromCommand(*id, nullptr, 0); + } + case InternalState::TX_TRANSITION: { + switch (transState) { + case TransitionState::SET_TX_MODULATION: { + *id = syrlinks::SET_TX_MODE_MODULATION; + return buildCommandFromCommand(*id, nullptr, 0); + } + case TransitionState::SELECT_MODULATION_BPSK: { + *id = syrlinks::SET_WAVEFORM_BPSK; + return buildCommandFromCommand(*id, nullptr, 0); + } + case TransitionState::SELECT_MODULATION_0QPSK: { + *id = syrlinks::SET_WAVEFORM_0QPSK; + return buildCommandFromCommand(*id, nullptr, 0); + } + case TransitionState::SET_TX_CW: { + *id = syrlinks::SET_TX_MODE_CW; + return buildCommandFromCommand(*id, nullptr, 0); + } + case TransitionState::SET_TX_STANDBY: { + *id = syrlinks::SET_TX_MODE_STANDBY; + return buildCommandFromCommand(*id, nullptr, 0); + } + default: { + return NOTHING_TO_SEND; + } + } + transState = TransitionState::CMD_PENDING; + break; + } + default: { + break; + } + } + return NOTHING_TO_SEND; +} + +ReturnValue_t SyrlinksHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + switch (deviceCommand) { + case (syrlinks::RESET_UNIT): { + prepareCommand(resetCommand, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::SET_TX_MODE_STANDBY): { + prepareCommand(setTxModeStandby, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::SET_TX_MODE_MODULATION): { + prepareCommand(setTxModeModulation, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::SET_TX_MODE_CW): { + prepareCommand(setTxModeCw, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::WRITE_LCL_CONFIG): { + prepareCommand(writeLclConfig, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::READ_RX_STATUS_REGISTERS): { + prepareCommand(readRxStatusRegCommand, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::READ_LCL_CONFIG): { + prepareCommand(readLclConfig, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::READ_TX_STATUS): { + prepareCommand(readTxStatus, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::READ_TX_WAVEFORM): { + prepareCommand(readTxWaveform, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::READ_TX_AGC_VALUE_HIGH_BYTE): { + prepareCommand(readTxAgcValueHighByte, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::READ_TX_AGC_VALUE_LOW_BYTE): { + prepareCommand(readTxAgcValueLowByte, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::TEMP_POWER_AMPLIFIER_HIGH_BYTE): { + prepareCommand(tempPowerAmpBoardHighByte, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::TEMP_POWER_AMPLIFIER_LOW_BYTE): { + prepareCommand(tempPowerAmpBoardLowByte, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::TEMP_BASEBAND_BOARD_HIGH_BYTE): { + prepareCommand(tempBasebandBoardHighByte, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::TEMP_BASEBAND_BOARD_LOW_BYTE): { + prepareCommand(tempBasebandBoardLowByte, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::SET_WAVEFORM_BPSK): { + prepareCommand(configBPSK, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::SET_WAVEFORM_0QPSK): { + prepareCommand(configOQPSK, deviceCommand); + return returnvalue::OK; + } + case (syrlinks::ENABLE_DEBUG): { + debugMode = true; + rawPacketLen = 0; + return returnvalue::OK; + } + case (syrlinks::DISABLE_DEBUG): { + debugMode = false; + rawPacketLen = 0; + return returnvalue::OK; + } + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::FAILED; +} + +void SyrlinksHandler::fillCommandAndReplyMap() { + this->insertInCommandAndReplyMap(syrlinks::RESET_UNIT, 1, nullptr, syrlinks::ACK_SIZE, false, + true, syrlinks::ACK_REPLY); + this->insertInCommandAndReplyMap(syrlinks::SET_TX_MODE_STANDBY, 1, nullptr, syrlinks::ACK_SIZE, + false, true, syrlinks::ACK_REPLY); + this->insertInCommandAndReplyMap(syrlinks::SET_TX_MODE_MODULATION, 1, nullptr, syrlinks::ACK_SIZE, + false, true, syrlinks::ACK_REPLY); + this->insertInCommandAndReplyMap(syrlinks::SET_TX_MODE_CW, 1, nullptr, syrlinks::ACK_SIZE, false, + true, syrlinks::ACK_REPLY); + this->insertInCommandAndReplyMap(syrlinks::WRITE_LCL_CONFIG, 1, nullptr, syrlinks::ACK_SIZE, + false, true, syrlinks::ACK_REPLY); + this->insertInCommandAndReplyMap(syrlinks::SET_WAVEFORM_BPSK, 1, nullptr, syrlinks::ACK_SIZE, + false, true, syrlinks::ACK_REPLY); + this->insertInCommandAndReplyMap(syrlinks::SET_WAVEFORM_0QPSK, 1, nullptr, syrlinks::ACK_SIZE, + false, true, syrlinks::ACK_REPLY); + this->insertInCommandMap(syrlinks::ENABLE_DEBUG); + this->insertInCommandMap(syrlinks::DISABLE_DEBUG); + this->insertInCommandAndReplyMap(syrlinks::READ_LCL_CONFIG, 1, nullptr, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::READ_TX_STATUS, 1, &txDataset, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::READ_TX_WAVEFORM, 1, &txDataset, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::READ_TX_AGC_VALUE_HIGH_BYTE, 1, &txDataset, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::READ_TX_AGC_VALUE_LOW_BYTE, 1, &txDataset, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::TEMP_POWER_AMPLIFIER_HIGH_BYTE, 1, nullptr, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::TEMP_POWER_AMPLIFIER_LOW_BYTE, 1, nullptr, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::TEMP_BASEBAND_BOARD_HIGH_BYTE, 1, nullptr, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::TEMP_BASEBAND_BOARD_LOW_BYTE, 1, nullptr, + syrlinks::READ_ONE_REGISTER_REPLY_SIE); + this->insertInCommandAndReplyMap(syrlinks::READ_RX_STATUS_REGISTERS, 1, &rxDataset, + syrlinks::RX_STATUS_REGISTERS_REPLY_SIZE); +} + +ReturnValue_t SyrlinksHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + ReturnValue_t result = returnvalue::OK; + + if (*start != '<') { + sif::warning << "SyrlinksHkHandler::scanForReply: Missing start frame character" << std::endl; + return MISSING_START_FRAME_CHARACTER; + } + + switch (*(start + 1)) { + case ('A'): + *foundLen = syrlinks::ACK_SIZE; + *foundId = syrlinks::ACK_REPLY; + break; + case ('E'): + *foundLen = syrlinks::RX_STATUS_REGISTERS_REPLY_SIZE; + *foundId = syrlinks::READ_RX_STATUS_REGISTERS; + break; + case ('R'): + *foundId = rememberCommandId; + *foundLen = syrlinks::READ_ONE_REGISTER_REPLY_SIE; + break; + default: + sif::warning << "SyrlinksHkHandler::scanForReply: Unknown reply identifier" << std::endl; + result = IGNORE_FULL_PACKET; + break; + } + + return result; +} + +ReturnValue_t SyrlinksHandler::getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches) { + if (powerSwitch == power::NO_SWITCH) { + return DeviceHandlerBase::NO_SWITCH; + } + *numberOfSwitches = 1; + *switches = &powerSwitch; + return returnvalue::OK; +} + +ReturnValue_t SyrlinksHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) { + ReturnValue_t result; + + switch (id) { + case (syrlinks::ACK_REPLY): { + result = verifyReply(packet, syrlinks::ACK_SIZE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Acknowledgment reply has " + "invalid crc" + << std::endl; + return CRC_FAILURE; + } + result = handleAckReply(packet); + if (result != returnvalue::OK) { + return result; + } + break; + } + case (syrlinks::READ_RX_STATUS_REGISTERS): { + result = verifyReply(packet, syrlinks::RX_STATUS_REGISTERS_REPLY_SIZE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read rx status registers reply " + << "has invalid crc" << std::endl; + return CRC_FAILURE; + } + parseRxStatusRegistersReply(packet); + break; + } + case (syrlinks::READ_LCL_CONFIG): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read config lcl reply " + << "has invalid crc" << std::endl; + return CRC_FAILURE; + } + parseLclConfigReply(packet); + break; + } + case (syrlinks::READ_TX_STATUS): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read tx status reply " + << "has invalid crc" << std::endl; + return CRC_FAILURE; + } + parseTxStatusReply(packet); + break; + } + case (syrlinks::READ_TX_WAVEFORM): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read tx waveform reply " + << "has invalid crc" << std::endl; + return CRC_FAILURE; + } + parseTxWaveformReply(packet); + break; + } + case (syrlinks::READ_TX_AGC_VALUE_HIGH_BYTE): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read tx AGC high byte reply " + << "has invalid crc" << std::endl; + return CRC_FAILURE; + } + parseAgcHighByte(packet); + break; + } + case (syrlinks::READ_TX_AGC_VALUE_LOW_BYTE): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read tx AGC low byte reply " + << "has invalid crc" << std::endl; + return CRC_FAILURE; + } + parseAgcLowByte(packet); + break; + } + case (syrlinks::TEMP_BASEBAND_BOARD_HIGH_BYTE): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read temperature baseband board " + << "high byte reply has invalid crc" << std::endl; + return CRC_FAILURE; + } + rawTempBasebandBoard = convertHexStringToUint8(reinterpret_cast( + packet + syrlinks::MESSAGE_HEADER_SIZE)) + << 8; + break; + } + case (syrlinks::TEMP_BASEBAND_BOARD_LOW_BYTE): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read temperature baseband board" + " low byte reply has invalid crc" + << std::endl; + return CRC_FAILURE; + } + rawTempBasebandBoard |= convertHexStringToUint8( + reinterpret_cast(packet + syrlinks::MESSAGE_HEADER_SIZE)); + tempBasebandBoard = calcTempVal(rawTempBasebandBoard); + PoolReadGuard rg(&temperatureSet); + temperatureSet.temperatureBasebandBoard = tempBasebandBoard; + temperatureSet.temperatureBasebandBoard.setValid(true); + if (debugMode) { + sif::info << "Syrlinks temperature baseband board: " << tempBasebandBoard << " °C" + << std::endl; + } + break; + } + case (syrlinks::TEMP_POWER_AMPLIFIER_HIGH_BYTE): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read temperature power amplifier " + << "board high byte reply has invalid crc" << std::endl; + return CRC_FAILURE; + } + rawTempPowerAmplifier = 0; + rawTempPowerAmplifier = convertHexStringToUint8(reinterpret_cast( + packet + syrlinks::MESSAGE_HEADER_SIZE)) + << 8; + break; + } + case (syrlinks::TEMP_POWER_AMPLIFIER_LOW_BYTE): { + result = verifyReply(packet, syrlinks::READ_ONE_REGISTER_REPLY_SIE); + if (result != returnvalue::OK) { + sif::warning << "SyrlinksHkHandler::interpretDeviceReply: Read temperature power amplifier" + << " board low byte reply has invalid crc" << std::endl; + return CRC_FAILURE; + } + rawTempPowerAmplifier |= convertHexStringToUint8( + reinterpret_cast(packet + syrlinks::MESSAGE_HEADER_SIZE)); + tempPowerAmplifier = calcTempVal(rawTempPowerAmplifier); + PoolReadGuard rg(&temperatureSet); + temperatureSet.temperaturePowerAmplifier = tempPowerAmplifier; + temperatureSet.temperaturePowerAmplifier.setValid(true); + if (debugMode) { + sif::info << "Syrlinks temperature power amplifier board: " << tempPowerAmplifier << " °C" + << std::endl; + } + break; + } + default: { + sif::debug << "SyrlinksHkHandler::interpretDeviceReply: Unknown device reply id" << std::endl; + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + return returnvalue::OK; +} + +LocalPoolDataSetBase* SyrlinksHandler::getDataSetHandle(sid_t sid) { + if (sid == rxDataset.getSid()) { + return &rxDataset; + } else if (sid == txDataset.getSid()) { + return &txDataset; + } else if (sid == temperatureSet.getSid()) { + return &temperatureSet; + } else { + sif::warning << "SyrlinksHkHandler::getDataSetHandle: Invalid sid" << std::endl; + return nullptr; + } +} + +std::string SyrlinksHandler::convertUint16ToHexString(uint16_t intValue) { + std::stringstream stream; + stream << std::setfill('0') << std::setw(4) << std::hex << std::uppercase << intValue; + return stream.str(); +} + +uint8_t SyrlinksHandler::convertHexStringToUint8(const char* twoChars) { + uint32_t value; + std::string hexString(twoChars, 2); + std::stringstream stream; + stream << std::hex << hexString; + stream >> value; + return static_cast(value); +} + +uint16_t SyrlinksHandler::convertHexStringToUint16(const char* fourChars) { + uint16_t value = 0; + value = convertHexStringToUint8(fourChars) << 8 | convertHexStringToUint8(fourChars + 2); + return value; +} + +ReturnValue_t SyrlinksHandler::parseReplyStatus(const char* status) { + switch (*status) { + case '0': + return returnvalue::OK; + case '1': + sif::debug << "SyrlinksHkHandler::parseReplyStatus: Uart framing or parity error" + << std::endl; + return UART_FRAMIN_OR_PARITY_ERROR_ACK; + case '2': + sif::debug << "SyrlinksHkHandler::parseReplyStatus: Bad character detected" << std::endl; + return BAD_CHARACTER_ACK; + case '3': + sif::debug << "SyrlinksHkHandler::parseReplyStatus: Bad parameter value (unexpected value " + << "detected" << std::endl; + return BAD_PARAMETER_VALUE_ACK; + case '4': + sif::debug << "SyrlinksHkHandler::parseReplyStatus: Bad end of frame" << std::endl; + return BAD_END_OF_FRAME_ACK; + case '5': + sif::debug << "SyrlinksHkHandler::parseReplyStatus: Unknown command id or attempt to access" + << " a protected register" << std::endl; + return UNKNOWN_COMMAND_ID_ACK; + case '6': + sif::debug << "SyrlinksHkHandler::parseReplyStatus: Bad CRC" << std::endl; + return BAD_CRC_ACK; + default: + sif::debug << "SyrlinksHkHandler::parseReplyStatus: Status reply contains an invalid " + << "status id" << std::endl; + return returnvalue::FAILED; + } +} + +ReturnValue_t SyrlinksHandler::verifyReply(const uint8_t* packet, uint8_t size) { + int result = 0; + /* Calculate crc from received packet */ + uint16_t crc = + CRC::crc16ccitt(packet, size - syrlinks::SIZE_CRC_AND_TERMINATION, CRC_INITIAL_VALUE); + std::string recalculatedCrc = convertUint16ToHexString(crc); + + const char* startOfCrc = + reinterpret_cast(packet + size - syrlinks::SIZE_CRC_AND_TERMINATION); + const char* endOfCrc = reinterpret_cast(packet + size - 1); + + std::string replyCrc(startOfCrc, endOfCrc); + + result = recalculatedCrc.compare(replyCrc); + if (result != 0) { + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void SyrlinksHandler::parseRxStatusRegistersReply(const uint8_t* packet) { + PoolReadGuard readHelper(&rxDataset); + uint16_t offset = syrlinks::MESSAGE_HEADER_SIZE; + rxDataset.rxStatus = convertHexStringToUint8(reinterpret_cast(packet + offset)); + offset += 2; + rxDataset.rxSensitivity = + convertHexStringTo32bit(reinterpret_cast(packet + offset), 6); + offset += 6; + rxDataset.rxFrequencyShift = + convertHexStringTo32bit(reinterpret_cast(packet + offset), 6); + offset += 6; + rxDataset.rxIqPower = convertHexStringToUint16(reinterpret_cast(packet + offset)); + offset += 4; + rxDataset.rxAgcValue = convertHexStringToUint16(reinterpret_cast(packet + offset)); + offset += 4; + offset += 2; // reserved register + rxDataset.rxDemodEb = + convertHexStringTo32bit(reinterpret_cast(packet + offset), 6); + offset += 6; + rxDataset.rxDemodN0 = + convertHexStringTo32bit(reinterpret_cast(packet + offset), 6); + offset += 6; + rxDataset.rxDataRate = convertHexStringToUint8(reinterpret_cast(packet + offset)); + + rxDataset.setValidity(true, true); + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Syrlinks RX Status: 0x" << std::hex << (unsigned int)rxDataset.rxStatus.value + << std::endl; + sif::info << "Syrlinks RX Sensitivity: " << std::dec << rxDataset.rxSensitivity << std::endl; + sif::info << "Syrlinks RX Frequency Shift: " << rxDataset.rxFrequencyShift << std::endl; + sif::info << "Syrlinks RX IQ Power: " << rxDataset.rxIqPower << std::endl; + sif::info << "Syrlinks RX AGC Value: " << rxDataset.rxAgcValue << std::endl; + sif::info << "Syrlinks RX Demod Eb: " << rxDataset.rxDemodEb << std::endl; + sif::info << "Syrlinks RX Demod N0: " << rxDataset.rxDemodN0 << std::endl; + sif::info << "Syrlinks RX Datarate: " << (unsigned int)rxDataset.rxDataRate.value << std::endl; +#endif + } +} + +void SyrlinksHandler::parseLclConfigReply(const uint8_t* packet) { + uint16_t offset = syrlinks::MESSAGE_HEADER_SIZE; + uint8_t lclConfig = convertHexStringToUint8(reinterpret_cast(packet + offset)); + if (debugMode) { + sif::info << "SyrlinksHkHandler::parseRxStatusRegistersReply: Lcl config: " + << static_cast(lclConfig) << std::endl; + } +} + +void SyrlinksHandler::parseTxStatusReply(const uint8_t* packet) { + PoolReadGuard readHelper(&txDataset); + uint16_t offset = syrlinks::MESSAGE_HEADER_SIZE; + txDataset.txStatus = convertHexStringToUint8(reinterpret_cast(packet + offset)); + txDataset.txStatus.setValid(true); + if (debugMode) { + sif::info << "Syrlinks TX Status: 0x" << std::hex << (unsigned int)txDataset.txStatus.value + << std::endl; + } +} + +void SyrlinksHandler::parseTxWaveformReply(const uint8_t* packet) { + PoolReadGuard readHelper(&txDataset); + uint16_t offset = syrlinks::MESSAGE_HEADER_SIZE; + txDataset.txWaveform = convertHexStringToUint8(reinterpret_cast(packet + offset)); + txDataset.txWaveform.setValid(true); + if (debugMode) { + sif::info << "Syrlinks TX Waveform: 0x" << std::hex << (unsigned int)txDataset.txWaveform.value + << std::endl; + } +} + +void SyrlinksHandler::parseAgcLowByte(const uint8_t* packet) { + PoolReadGuard readHelper(&txDataset); + uint16_t offset = syrlinks::MESSAGE_HEADER_SIZE; + txDataset.txAgcValue = agcValueHighByte << 8 | + convertHexStringToUint8(reinterpret_cast(packet + offset)); + txDataset.txAgcValue.setValid(true); + if (debugMode) { + sif::info << "Syrlinks TX AGC Value: " << txDataset.txAgcValue << std::endl; + } +} + +void SyrlinksHandler::parseAgcHighByte(const uint8_t* packet) { + PoolReadGuard readHelper(&txDataset); + uint16_t offset = syrlinks::MESSAGE_HEADER_SIZE; + agcValueHighByte = convertHexStringToUint8(reinterpret_cast(packet + offset)); +} + +uint32_t SyrlinksHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 2500; } + +ReturnValue_t SyrlinksHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(syrlinks::RX_STATUS, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::RX_SENSITIVITY, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::RX_FREQUENCY_SHIFT, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::RX_IQ_POWER, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::RX_AGC_VALUE, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::RX_DEMOD_EB, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::RX_DEMOD_N0, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::RX_DATA_RATE, new PoolEntry({0})); + + localDataPoolMap.emplace(syrlinks::TX_STATUS, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::TX_WAVEFORM, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::TX_AGC_VALUE, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::TEMP_BASEBAND_BOARD, new PoolEntry({0})); + localDataPoolMap.emplace(syrlinks::TEMP_POWER_AMPLIFIER, new PoolEntry({0})); + + bool enableHkSets = false; +#if OBSW_ENABLE_PERIODIC_HK == 1 + enableHkSets = true; +#endif + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(txDataset.getSid(), enableHkSets, 60.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(rxDataset.getSid(), enableHkSets, 60.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(temperatureSet.getSid(), enableHkSets, 120.0)); + return returnvalue::OK; +} + +void SyrlinksHandler::setModeNormal() { setMode(_MODE_TO_NORMAL); } + +float SyrlinksHandler::calcTempVal(uint16_t raw) { return 0.126984 * raw - 67.87; } + +ReturnValue_t SyrlinksHandler::handleAckReply(const uint8_t* packet) { + ReturnValue_t result = + parseReplyStatus(reinterpret_cast(packet + syrlinks::MESSAGE_HEADER_SIZE)); + switch (rememberCommandId) { + case (syrlinks::WRITE_LCL_CONFIG): { + if (isTransitionalMode()) { + if (result != returnvalue::OK) { + internalState = InternalState::OFF; + } else if (result == returnvalue::OK) { + commandExecuted = true; + } + } + break; + } + case (syrlinks::SET_WAVEFORM_BPSK): + case (syrlinks::SET_WAVEFORM_0QPSK): { + if (result == returnvalue::OK and isTransitionalMode()) { + transState = TransitionState::SET_TX_MODULATION; + commandExecuted = true; + } + break; + } + case (syrlinks::SET_TX_MODE_STANDBY): { + if (result == returnvalue::OK and isTransitionalMode()) { + transState = TransitionState::DONE; + commandExecuted = true; + } + break; + } + case (syrlinks::SET_TX_MODE_MODULATION): { + if (result == returnvalue::OK and isTransitionalMode()) { + transState = TransitionState::DONE; + commandExecuted = true; + } + break; + } + case (syrlinks::SET_TX_MODE_CW): { + if (result == returnvalue::OK and isTransitionalMode()) { + commandExecuted = true; + transState = TransitionState::DONE; + } + break; + } + default: { + sif::error << "Syrlinks: Unexpected ACK reply" << std::endl; + } + } + switch (rememberCommandId) { + case (syrlinks::SET_TX_MODE_STANDBY): { + triggerEvent(syrlinks::TX_OFF, 0, 0); + break; + } + case (syrlinks::SET_TX_MODE_MODULATION): + case (syrlinks::SET_TX_MODE_CW): { + triggerEvent(syrlinks::TX_ON, getSubmode(), static_cast(com::getCurrentDatarate())); + break; + } + } + return result; +} + +ReturnValue_t SyrlinksHandler::isModeCombinationValid(Mode_t mode, Submode_t submode) { + if (mode == HasModesIF::MODE_ON or mode == DeviceHandlerIF::MODE_NORMAL) { + if (submode >= com::Submode::NUM_SUBMODES or submode < com::Submode::RX_ONLY) { + return HasModesIF::INVALID_SUBMODE; + } + return returnvalue::OK; + } + return DeviceHandlerBase::isModeCombinationValid(mode, submode); +} + +ReturnValue_t SyrlinksHandler::getParameter(uint8_t domainId, uint8_t uniqueId, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, + uint16_t startAtIndex) { + return DeviceHandlerBase::getParameter(domainId, uniqueId, parameterWrapper, newValues, + startAtIndex); +} + +void SyrlinksHandler::prepareCommand(std::string command, DeviceCommandId_t commandId) { + command.copy(reinterpret_cast(commandBuffer), command.size(), 0); + rawPacketLen = command.size(); + rememberCommandId = commandId; + rawPacket = commandBuffer; +} + +void SyrlinksHandler::setDebugMode(bool enable) { this->debugMode = enable; } + +void SyrlinksHandler::doTransition(Mode_t modeFrom, Submode_t subModeFrom) { + Mode_t tgtMode = getBaseMode(getMode()); + auto doneHandler = [&]() { + internalState = InternalState::IDLE; + transState = TransitionState::IDLE; + DeviceHandlerBase::doTransition(modeFrom, subModeFrom); + }; + if (transState == TransitionState::DONE) { + return doneHandler(); + } + auto txStandbyHandler = [&]() { + txDataset.setReportingEnabled(false); + poolManager.changeCollectionInterval(temperatureSet.getSid(), 60.0); + poolManager.changeCollectionInterval(rxDataset.getSid(), 60.0); + transState = TransitionState::SET_TX_STANDBY; + internalState = InternalState::TX_TRANSITION; + }; + auto txOnHandler = [&](TransitionState tgtTransitionState) { + txDataset.setReportingEnabled(true); + poolManager.changeCollectionInterval(rxDataset.getSid(), 5.0); + poolManager.changeCollectionInterval(txDataset.getSid(), 10.0); + poolManager.changeCollectionInterval(temperatureSet.getSid(), 5.0); + transState = tgtTransitionState; + internalState = InternalState::TX_TRANSITION; + }; + + if (tgtMode == HasModesIF::MODE_ON or tgtMode == DeviceHandlerIF::MODE_NORMAL) { + // If submode has not changed, no special transition handling necessary. + if (getSubmode() == subModeFrom) { + return doneHandler(); + } + // Transition is on-going, wait for it to finish. + if (transState != TransitionState::IDLE) { + return; + } + // Transition start logic. + switch (getSubmode()) { + case (com::Submode::RX_AND_TX_DEFAULT_DATARATE): { + auto currentDatarate = com::getCurrentDatarate(); + if (currentDatarate == com::Datarate::LOW_RATE_MODULATION_BPSK) { + txOnHandler(TransitionState::SELECT_MODULATION_BPSK); + } else if (currentDatarate == com::Datarate::HIGH_RATE_MODULATION_0QPSK) { + txOnHandler(TransitionState::SELECT_MODULATION_0QPSK); + } + break; + } + case (com::Submode::RX_AND_TX_LOW_DATARATE): { + txOnHandler(TransitionState::SELECT_MODULATION_BPSK); + break; + } + case (com::Submode::RX_AND_TX_HIGH_DATARATE): { + txOnHandler(TransitionState::SELECT_MODULATION_0QPSK); + break; + } + case (com::Submode::RX_ONLY): { + txStandbyHandler(); + return; + } + case (com::Submode::RX_AND_TX_CW): { + txOnHandler(TransitionState::SET_TX_CW); + break; + } + default: { + sif::error << "SyrlinksHandler: Unexpected submode " << getSubmode() << std::endl; + DeviceHandlerBase::doTransition(modeFrom, subModeFrom); + } + } + } else if (tgtMode == HasModesIF::MODE_OFF) { + txStandbyHandler(); + } else { + return doneHandler(); + } +} diff --git a/mission/com/SyrlinksHandler.h b/mission/com/SyrlinksHandler.h new file mode 100644 index 0000000..2c9d620 --- /dev/null +++ b/mission/com/SyrlinksHandler.h @@ -0,0 +1,268 @@ +#ifndef MISSION_DEVICES_SYRLINKSHANDLER_H_ +#define MISSION_DEVICES_SYRLINKSHANDLER_H_ + +#include + +#include "devices/powerSwitcherList.h" +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "fsfw/timemanager/Countdown.h" +#include "fsfw_hal/linux/gpio/Gpio.h" +#include "mission/com/defs.h" +#include "mission/com/syrlinksDefs.h" +#include "returnvalues/classIds.h" + +/** + * @brief This is the device handler for the syrlinks transceiver. It handles the command + * transmission and reading of housekeeping data via the housekeeping interface. The + * transmission of telemetry and the reception of telecommands is handled by an additional + * class. + * + * @author J. Meier + */ +class SyrlinksHandler : public DeviceHandlerBase { + public: + SyrlinksHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + power::Switch_t powerSwitch, FailureIsolationBase* customFdir); + virtual ~SyrlinksHandler(); + + /** + * @brief Sets mode to MODE_NORMAL. Can be used for debugging. + */ + void setModeNormal(); + + void setDebugMode(bool enable); + + protected: + void doStartUp() override; + void doShutDown() override; + void doTransition(Mode_t modeFrom, Submode_t subModeFrom) override; + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + void fillCommandAndReplyMap() override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + ReturnValue_t getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + // Parameter IF + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueId, ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, uint16_t startAtIndex) override; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::SYRLINKS_HANDLER; + + static const ReturnValue_t CRC_FAILURE = MAKE_RETURN_CODE(0xA0); + static const ReturnValue_t UART_FRAMIN_OR_PARITY_ERROR_ACK = MAKE_RETURN_CODE(0xA1); + static const ReturnValue_t BAD_CHARACTER_ACK = MAKE_RETURN_CODE(0xA2); + static const ReturnValue_t BAD_PARAMETER_VALUE_ACK = MAKE_RETURN_CODE(0xA3); + static const ReturnValue_t BAD_END_OF_FRAME_ACK = MAKE_RETURN_CODE(0xA4); + static const ReturnValue_t UNKNOWN_COMMAND_ID_ACK = MAKE_RETURN_CODE(0xA5); + static const ReturnValue_t BAD_CRC_ACK = MAKE_RETURN_CODE(0xA6); + static const ReturnValue_t REPLY_WRONG_SIZE = MAKE_RETURN_CODE(0xA7); + static const ReturnValue_t MISSING_START_FRAME_CHARACTER = MAKE_RETURN_CODE(0xA8); + + static const uint8_t CRC_INITIAL_VALUE = 0x0; + + // Uses CRC-16/XMODEM + std::string resetCommand = ""; + std::string readRxStatusRegCommand = ""; + std::string setTxModeStandby = ""; + /** W - write, 04 - 4 bytes in data field, 01 - value, 40 register to write value */ + std::string setTxModeModulation = ""; + std::string configBPSK = ""; + std::string configOQPSK = ""; + std::string setTxModeCw = ""; + std::string writeLclConfig = ""; + std::string setWaveformOQPSK = ""; + std::string setWaveformBPSK = ""; + std::string readTxStatus = ""; + std::string readTxWaveform = ""; + std::string readTxAgcValueHighByte = ""; + std::string readTxAgcValueLowByte = ""; + std::string readLclConfig = ""; + std::string tempPowerAmpBoardHighByte = ""; + std::string tempPowerAmpBoardLowByte = ""; + std::string tempBasebandBoardHighByte = ""; + std::string tempBasebandBoardLowByte = ""; + + /** + * In some cases it is not possible to extract from the received reply the information about + * the associated command. This variable is thus used to remember the command id. + */ + DeviceCommandId_t rememberCommandId = syrlinks::NONE; + + syrlinks::RxDataset rxDataset; + syrlinks::TxDataset txDataset; + syrlinks::TemperatureSet temperatureSet; + + const power::Switch_t powerSwitch = power::NO_SWITCH; + + bool debugMode = false; + uint8_t agcValueHighByte = 0; + uint16_t rawTempPowerAmplifier = 0; + uint16_t rawTempBasebandBoard = 0; + float tempPowerAmplifier = 0; + float tempBasebandBoard = 0; + bool commandExecuted = false; + + uint8_t commandBuffer[syrlinks::MAX_COMMAND_SIZE]; + + enum class InternalState { + OFF, + ENABLE_TEMPERATURE_PROTECTION, + TX_TRANSITION, + IDLE + } internalState = InternalState::OFF; + + enum class TransitionState { + IDLE, + SELECT_MODULATION_BPSK, + SELECT_MODULATION_0QPSK, + SET_TX_MODULATION, + SET_TX_CW, + SET_TX_STANDBY, + CMD_PENDING, + DONE + } transState = TransitionState::IDLE; + + /** + * This object is used to store the id of the next command to execute. This controls the + * read out of multiple registers which can not be fetched with one single command. + */ + DeviceCommandId_t nextCommand = syrlinks::READ_RX_STATUS_REGISTERS; + + /** + * @brief This function converts an uint16_t into its hexadecimal string representation. + * + * @param intValue The value to convert. + * + * @return An std::string object containing the hex representation of intValue. + */ + std::string convertUint16ToHexString(uint16_t intValue); + + /** + * @brief This function converts a hex number represented by to chars to an 8-bit integer. + * + * @param twoChars Pointer to the two characters representing the hex value. + * + * @details E.g. when twoChars points to an array with the two characters "A5" then the function + * will return 0xA5. + * @return The converted integer. + */ + uint8_t convertHexStringToUint8(const char* twoChars); + + /** + * @brief This function converts a hex number represented by 4 chars to an uint16_t. + * + * @param Pointer to the fourCharacters representing the 16-bit integer. + * + * @return The uint16_t result. + */ + uint16_t convertHexStringToUint16(const char* fourChars); + + /** + * @brief Function converts a hex number represented by 6 or 8 characters to an uint32_t or + * int32_t, depending on the template parameter. + * + * @param characters Pointer to the hex characters array. + * @param numberOfChars Number of characters representing the hex value. Must be 6 or 8. + * + * @return The value. + */ + template + T convertHexStringTo32bit(const char* characters, uint8_t numberOfChars); + /** + * @brief This function parses the status reply + * @param status Pointer to the status information. + * + * @details Some commands reply with a status message giving information about the preceding + * command transmission and/or execution was successful. + */ + ReturnValue_t parseReplyStatus(const char* status); + + /** + * @brief Function verifies the received reply from the syrlinks by recalculating and + * comparing the crc. + * + * @param packet Pointer to the received reply. + * @param size Size of the whole packet including the crc and the packet termination + * character '>'. + * + * @return returnvalue::OK if successful, otherwise returnvalue::FAILED. + */ + ReturnValue_t verifyReply(const uint8_t* packet, uint8_t size); + + /** + * @brief This function extracts the data from a rx status registers reply and writes the + * information to the status registers dataset. + * @param packet Pointer to the reply packet. + */ + void parseRxStatusRegistersReply(const uint8_t* packet); + + void parseLclConfigReply(const uint8_t* packet); + + /** + * @brief This function writes the read tx status register to the txStatusDataset. + * @param packet Pointer to the received packet. + */ + void parseTxStatusReply(const uint8_t* packet); + + /** + * @brief This function writes the received waveform configuration to the txDataset. + */ + void parseTxWaveformReply(const uint8_t* packet); + + /** + * @brief The agc value is split over two registers. The parse agc functions are used to get + * the values from the received reply and write them into the txDataset. + */ + void parseAgcLowByte(const uint8_t* packet); + void parseAgcHighByte(const uint8_t* packet); + + /** + * @brief Calculates temperature in degree celcius form raw value + */ + float calcTempVal(uint16_t); + + ReturnValue_t handleAckReply(const uint8_t* packet); + + void prepareCommand(std::string command, DeviceCommandId_t commandId); +}; + +template +T SyrlinksHandler::convertHexStringTo32bit(const char* characters, uint8_t numberOfChars) { + if (sizeof(T) < 4) { + sif::error << "SyrlinksHkHandler::convertHexStringToRaw: Only works for 32-bit conversion" + << std::endl; + } + T value = 0; + + switch (numberOfChars) { + case 6: + // The bitshift trickery required is necessary when creating an int32_t from a + // 24 bit signed value. + value = ((convertHexStringToUint8(characters) << 24) | + (convertHexStringToUint8(characters + 2) << 16) | + (convertHexStringToUint8(characters + 4) << 8)) >> + 8; + return value; + case 8: + value = convertHexStringToUint8(characters) << 24 | + convertHexStringToUint8(characters + 2) << 16 | + convertHexStringToUint8(characters + 4) << 8 | + convertHexStringToUint8(characters + 4); + return value; + default: + sif::debug << "SyrlinksHkHandler::convertHexStringToUint32: Invalid number of characters. " + << "Must be either 6 or 8" << std::endl; + return 0; + } +} +#endif /* MISSION_DEVICES_SYRLINKSHANDLER_H_ */ diff --git a/mission/com/TmStoreTaskBase.cpp b/mission/com/TmStoreTaskBase.cpp new file mode 100644 index 0000000..9e86ba0 --- /dev/null +++ b/mission/com/TmStoreTaskBase.cpp @@ -0,0 +1,233 @@ +#include "TmStoreTaskBase.h" + +#include +#include +#include +#include +#include +#include + +#include "mission/persistentTmStoreDefs.h" + +TmStoreTaskBase::TmStoreTaskBase(object_id_t objectId, StorageManagerIF& ipcStore, + VirtualChannel& channel, SdCardMountedIF& sdcMan, + const std::atomic_bool& ptmeLocked) + : SystemObject(objectId), + modeHelper(this), + ipcStore(ipcStore), + tmReader(&timeReader), + channel(channel), + sdcMan(sdcMan), + ptmeLocked(ptmeLocked) { + requestQueue = QueueFactory::instance()->createMessageQueue(10); +} + +bool TmStoreTaskBase::handleOneStore(PersistentTmStoreWithTmQueue& store, + DumpContext& dumpContext) { + ReturnValue_t result; + bool tmToStoreReceived = false; + bool tcRequestReceived = false; + bool dumpPerformed = false; + fileHasSwapped = false; + dumpContext.packetWasDumped = false; + dumpContext.vcBusyDuringDump = false; + + // Store TM persistently + result = store.handleNextTm(); + if (result == returnvalue::OK) { + tmToStoreReceived = true; + } + // Dump TMs + if (store.getState() == PersistentTmStore::State::DUMPING) { + if (handleOneDump(store, dumpContext, dumpPerformed) != returnvalue::OK) { + return result; + } + } else { + Command_t execCmd; + // Handle TC requests, for example deletion or retrieval requests. + // TODO: Not really clean here.. would be better if the executed command is returns as an + // enumeration. + result = store.handleCommandQueue(ipcStore, execCmd); + if (execCmd == TmStoreMessage::DOWNLINK_STORE_CONTENT_TIME) { + if (result == PersistentTmStore::DUMP_DONE) { + dumpDoneHandler(store, dumpContext); + } else if (result == returnvalue::OK) { + cancelDumpCd.resetTimer(); + tmSinkBusyCd.resetTimer(); + dumpContext.reset(); + } + } + if (execCmd != CommandMessageIF::CMD_NONE) { + tcRequestReceived = true; + } + } + if (tcRequestReceived or tmToStoreReceived or dumpPerformed) { + return true; + } + return false; +} + +bool TmStoreTaskBase::cyclicStoreCheck() { + if (not storesInitialized) { + storesInitialized = initStoresIfPossible(); + if (not storesInitialized) { + TaskFactory::delayTask(400); + return false; + } + } else if (sdCardCheckCd.hasTimedOut()) { + if (not sdcMan.isSdCardUsable(std::nullopt)) { + // Might be due to imminent shutdown or SD card switch. + storesInitialized = false; + TaskFactory::delayTask(100); + return false; + } + sdCardCheckCd.resetTimer(); + } + return true; +} + +void TmStoreTaskBase::cancelDump(DumpContext& ctx, PersistentTmStore& store, bool isTxOn) { + ctx.reset(); + if (store.getState() == PersistentTmStore::State::DUMPING) { + triggerEvent(ctx.eventIfCancelled, ctx.numberOfDumpedPackets, ctx.dumpedBytes); + } + store.cancelDump(); + if (isTxOn) { + channel.cancelTransfer(); + } +} + +ReturnValue_t TmStoreTaskBase::handleOneDump(PersistentTmStoreWithTmQueue& store, + DumpContext& dumpContext, bool& dumpPerformed) { + ReturnValue_t result = returnvalue::OK; + // It is assumed that the PTME will only be locked for a short period (e.g. to change datarate). + if (not channel.isBusy() and not ptmeLocked) { + performDump(store, dumpContext, dumpPerformed); + } else { + // The PTME might be at full load, so it might sense to delay for a bit to let it do + // its work until some more bandwidth is available. Set a flag here so the upper layer can + // do ths. + dumpContext.vcBusyDuringDump = true; + dumpContext.ptmeBusyCounter++; + if (dumpContext.ptmeBusyCounter == 100) { + // If this happens, something is probably wrong. + sif::warning << "PTME busy for longer period. Cancelling dump" << std::endl; + cancelDump(dumpContext, store, channel.isTxOn()); + } + } + if (cancelDumpCd.hasTimedOut() or tmSinkBusyCd.hasTimedOut()) { + cancelDump(dumpContext, store, channel.isTxOn()); + } + return result; +} + +ReturnValue_t TmStoreTaskBase::performDump(PersistentTmStoreWithTmQueue& store, + DumpContext& dumpContext, bool& dumpPerformed) { + size_t dumpedLen = 0; + + // Dump the next packet into the PTME. + dumpContext.ptmeBusyCounter = 0; + tmSinkBusyCd.resetTimer(); + ReturnValue_t result = store.getNextDumpPacket(tmReader, fileHasSwapped); + if (fileHasSwapped and result == PersistentTmStore::DUMP_DONE) { + // This can happen if a file is corrupted and the next file swap completes the dump. + dumpDoneHandler(store, dumpContext); + return returnvalue::OK; + } else if (result != returnvalue::OK) { + sif::error << "PersistentTmStore: Getting next dump packet failed" << std::endl; + return result; + } + dumpedLen = tmReader.getFullPacketLen(); + size_t writtenSize = 0; + result = channel.write(tmReader.getFullData(), dumpedLen, writtenSize); + if (result == VirtualChannelIF::PARTIALLY_WRITTEN) { + result = channel.handleWriteCompletionSynchronously(writtenSize, 400); + if (result != returnvalue::OK) { + // TODO: Event? Might lead to dangerous spam though.. + sif::warning << "LiveTmTask: Synchronous write of last segment failed with code 0x" + << std::setfill('0') << std::setw(4) << std::hex << result << std::dec + << std::endl; + } + } else if (result == DirectTmSinkIF::IS_BUSY) { + sif::warning << "PersistentTmStore: Unexpected VC channel busy" << std::endl; + } else if (result != returnvalue::OK) { + sif::warning << "PersistentTmStore: Unexpected VC channel write failure" << std::endl; + } + + result = store.confirmDump(tmReader, fileHasSwapped); + if ((result == PersistentTmStore::DUMP_DONE or result == returnvalue::OK)) { + dumpPerformed = true; + if (dumpedLen > 0) { + dumpContext.dumpedBytes += dumpedLen; + dumpContext.numberOfDumpedPackets += 1; + dumpContext.packetWasDumped = true; + } + } + if (result == PersistentTmStore::DUMP_DONE) { + dumpDoneHandler(store, dumpContext); + } + return returnvalue::OK; +} + +ReturnValue_t TmStoreTaskBase::initialize() { + modeHelper.initialize(); + return returnvalue::OK; +} + +void TmStoreTaskBase::getMode(Mode_t* mode, Submode_t* submode) { + if (mode != nullptr) { + *mode = this->mode; + } + if (submode != nullptr) { + *submode = SUBMODE_NONE; + } +} + +ReturnValue_t TmStoreTaskBase::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) { + if (mode == MODE_ON or mode == MODE_OFF) { + return returnvalue::OK; + } + return returnvalue::FAILED; +} + +MessageQueueId_t TmStoreTaskBase::getCommandQueue() const { return requestQueue->getId(); } + +void TmStoreTaskBase::announceMode(bool recursive) { triggerEvent(MODE_INFO, mode, SUBMODE_NONE); } + +object_id_t TmStoreTaskBase::getObjectId() const { return SystemObject::getObjectId(); } + +const HasHealthIF* TmStoreTaskBase::getOptHealthIF() const { return nullptr; } + +const HasModesIF& TmStoreTaskBase::getModeIF() const { return *this; } + +ReturnValue_t TmStoreTaskBase::connectModeTreeParent(HasModeTreeChildrenIF& parent) { + return modetree::connectModeTreeParent(parent, *this, nullptr, modeHelper); +} + +void TmStoreTaskBase::dumpDoneHandler(PersistentTmStore& store, DumpContext& dumpContext) { + uint32_t startTime; + uint32_t endTime; + store.getStartAndEndTimeCurrentOrLastDump(startTime, endTime); + triggerEvent(dumpContext.eventIfDone, dumpContext.numberOfDumpedPackets, dumpContext.dumpedBytes); + dumpContext.reset(); +} + +ModeTreeChildIF& TmStoreTaskBase::getModeTreeChildIF() { return *this; } + +void TmStoreTaskBase::readCommandQueue(void) { + CommandMessage commandMessage; + ReturnValue_t result = returnvalue::FAILED; + + result = requestQueue->receiveMessage(&commandMessage); + if (result == returnvalue::OK) { + result = modeHelper.handleModeCommand(&commandMessage); + if (result == returnvalue::OK) { + return; + } + CommandMessage reply; + reply.setReplyRejected(CommandMessage::UNKNOWN_COMMAND, commandMessage.getCommand()); + requestQueue->reply(&reply); + return; + } +} diff --git a/mission/com/TmStoreTaskBase.h b/mission/com/TmStoreTaskBase.h new file mode 100644 index 0000000..2bcd3b1 --- /dev/null +++ b/mission/com/TmStoreTaskBase.h @@ -0,0 +1,108 @@ +#ifndef MISSION_TMTC_TMSTORETASKBASE_H_ +#define MISSION_TMTC_TMSTORETASKBASE_H_ + +#include +#include +#include +#include +#include +#include +#include + +/** + * Generic class which composes a Virtual Channel and a persistent TM stores. This allows dumping + * the TM store into the virtual channel directly. + */ +class TmStoreTaskBase : public SystemObject, + public HasModesIF, + public ModeTreeChildIF, + public ModeTreeConnectionIF { + public: + struct DumpContext { + DumpContext(Event eventIfDone, Event eventIfCancelled) + : eventIfDone(eventIfDone), eventIfCancelled(eventIfCancelled) {} + void reset() { + numberOfDumpedPackets = 0; + dumpedBytes = 0; + vcBusyDuringDump = false; + packetWasDumped = false; + bytesDumpedAtLastDelay = 0; + ptmeBusyCounter = 0; + } + const Event eventIfDone; + const Event eventIfCancelled; + size_t numberOfDumpedPackets = 0; + size_t bytesDumpedAtLastDelay = 0; + size_t dumpedBytes = 0; + uint32_t ptmeBusyCounter = 0; + bool packetWasDumped = false; + bool vcBusyDuringDump = false; + }; + + TmStoreTaskBase(object_id_t objectId, StorageManagerIF& ipcStore, VirtualChannel& channel, + SdCardMountedIF& sdcMan, const std::atomic_bool& ptmeLocked); + + ReturnValue_t initialize() override; + ReturnValue_t connectModeTreeParent(HasModeTreeChildrenIF& parent) override; + + protected: + ModeHelper modeHelper; + MessageQueueIF* requestQueue; + StorageManagerIF& ipcStore; + PusTmReader tmReader; + CdsShortTimeStamper timeReader; + VirtualChannel& channel; + SdCardMountedIF& sdcMan; + const std::atomic_bool& ptmeLocked; + + Mode_t mode = HasModesIF::MODE_OFF; + Countdown sdCardCheckCd = Countdown(800); + // 20 minutes are allowed as maximum dump time. + Countdown cancelDumpCd = Countdown(60 * 20 * 1000); + // If the TM sink is busy for 1 minute for whatever reason, cancel the dump. + Countdown tmSinkBusyCd = Countdown(60 * 1000); + + bool storesInitialized = false; + bool fileHasSwapped = false; + + void readCommandQueue(void); + + virtual bool initStoresIfPossible() = 0; + virtual void startTransition(Mode_t mode, Submode_t submode) = 0; + + void cancelDump(DumpContext& ctx, PersistentTmStore& store, bool isTxOn); + /** + * + * Handling for one store. Returns if anything was done. + * @param store + * @return + */ + bool handleOneStore(PersistentTmStoreWithTmQueue& store, DumpContext& dumpContext); + + ReturnValue_t handleOneDump(PersistentTmStoreWithTmQueue& store, DumpContext& dumpContext, + bool& dumpPerformed); + ReturnValue_t performDump(PersistentTmStoreWithTmQueue& store, DumpContext& dumpContext, + bool& dumpPerformed); + + /** + * Occasionally check whether SD card is okay to be used. If not, poll whether it is ready to + * be used again and re-initialize stores. Returns whether store is okay to be used. + */ + bool cyclicStoreCheck(); + + MessageQueueId_t getCommandQueue() const override; + + void getMode(Mode_t* mode, Submode_t* submode) override; + + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + void dumpDoneHandler(PersistentTmStore& store, DumpContext& dumpContext); + + void announceMode(bool recursive) override; + object_id_t getObjectId() const override; + const HasHealthIF* getOptHealthIF() const override; + const HasModesIF& getModeIF() const override; + ModeTreeChildIF& getModeTreeChildIF() override; +}; + +#endif /* MISSION_TMTC_TMSTORETASKBASE_H_ */ diff --git a/mission/com/VirtualChannel.cpp b/mission/com/VirtualChannel.cpp new file mode 100644 index 0000000..a75959e --- /dev/null +++ b/mission/com/VirtualChannel.cpp @@ -0,0 +1,80 @@ +#include "VirtualChannel.h" + +#include + +VirtualChannel::VirtualChannel(object_id_t objectId, uint8_t vcId, const char* vcName, PtmeIF& ptme, + const std::atomic_bool& txOn) + : SystemObject(objectId), ptme(ptme), vcId(vcId), vcName(vcName), txOn(txOn) {} + +ReturnValue_t VirtualChannel::initialize() { return returnvalue::OK; } + +ReturnValue_t VirtualChannel::sendNextTm(const uint8_t* data, size_t size, size_t& writtenSize) { + return write(data, size, writtenSize); +} + +ReturnValue_t VirtualChannel::write(const uint8_t* data, size_t size, size_t& writtenSize) { + if (!ptme.containsVc(vcId)) { + return CHANNEL_DOES_NOT_EXIST; + } + return ptme.getVirtChannel(vcId)->write(data, size, writtenSize); +} + +uint8_t VirtualChannel::getVcid() const { return vcId; } + +ReturnValue_t VirtualChannel::advanceWrite(size_t& writtenSize) { + if (!ptme.containsVc(vcId)) { + return CHANNEL_DOES_NOT_EXIST; + } + return ptme.getVirtChannel(vcId)->advanceWrite(writtenSize); +} + +bool VirtualChannel::writeActive() const { + if (!ptme.containsVc(vcId)) { + return CHANNEL_DOES_NOT_EXIST; + } + return ptme.getVirtChannel(vcId)->writeActive(); +} + +const char* VirtualChannel::getName() const { return vcName.c_str(); } + +bool VirtualChannel::isBusy() const { + if (!ptme.containsVc(vcId)) { + return CHANNEL_DOES_NOT_EXIST; + } + return ptme.getVirtChannel(vcId)->isBusy(); +} + +void VirtualChannel::cancelTransfer() { + if (!ptme.containsVc(vcId)) { + return; + } + ptme.getVirtChannel(vcId)->cancelTransfer(); +} + +bool VirtualChannel::isTxOn() const { return txOn; } + +ReturnValue_t VirtualChannel::handleWriteCompletionSynchronously(size_t& writtenSize, + unsigned maxCompletionTimeMs) { + unsigned delayMs = 0; + while (true) { + if (isBusy()) { + if (delayMs >= maxCompletionTimeMs) { + break; + } + TaskFactory::delayTask(10); + delayMs += 10; + continue; + } + ReturnValue_t result = advanceWrite(writtenSize); + if (result == returnvalue::OK) { + // Transfer complete + return result; + } else if (result != PARTIALLY_WRITTEN) { + // Some error where we can not or should not continue the transfer. + return result; + } + } + // Timeout. Cancel the transfer + cancelTransfer(); + return TIMEOUT; +} diff --git a/mission/com/VirtualChannel.h b/mission/com/VirtualChannel.h new file mode 100644 index 0000000..84cdafb --- /dev/null +++ b/mission/com/VirtualChannel.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include +#include + +/** + * @brief This class represents a virtual channel. Sending a tm message to an object of this class + * will forward the tm packet to the respective virtual channel of the PTME IP Core. + * + * @author J. Meier + */ +class VirtualChannel : public SystemObject, public VirtualChannelIF { + public: + static constexpr uint8_t CLASS_ID = CLASS_ID::VIRTUAL_CHANNEL; + + static constexpr ReturnValue_t CHANNEL_DOES_NOT_EXIST = returnvalue::makeCode(CLASS_ID, 0); + + /** + * @brief Constructor + * + * @param vcId The virtual channel id assigned to this object + * @param tmQueueDepth Queue depth of queue receiving telemetry from other objects + */ + VirtualChannel(object_id_t objectId, uint8_t vcId, const char* vcName, PtmeIF& ptme, + const std::atomic_bool& linkStateProvider); + + ReturnValue_t initialize() override; + ReturnValue_t sendNextTm(const uint8_t* data, size_t size, size_t& writtenSize); + bool isBusy() const override; + ReturnValue_t write(const uint8_t* data, size_t size, size_t& writtenSize) override; + ReturnValue_t advanceWrite(size_t& writtenSize) override; + ReturnValue_t handleWriteCompletionSynchronously(size_t& writtenSize, + unsigned maxCompletionTimeMs); + bool writeActive() const override; + void cancelTransfer() override; + uint8_t getVcid() const; + bool isTxOn() const; + + const char* getName() const; + + private: + PtmeIF& ptme; + uint8_t vcId = 0; + std::string vcName; + const std::atomic_bool& txOn; +}; diff --git a/mission/com/VirtualChannelWithQueue.cpp b/mission/com/VirtualChannelWithQueue.cpp new file mode 100644 index 0000000..fb3375d --- /dev/null +++ b/mission/com/VirtualChannelWithQueue.cpp @@ -0,0 +1,61 @@ +#include "VirtualChannelWithQueue.h" + +#include "OBSWConfig.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/objectmanager/ObjectManager.h" +#include "fsfw/serviceinterface/ServiceInterfaceStream.h" +#include "fsfw/tmtcservices/TmTcMessage.h" +#include "mission/com/CcsdsIpCoreHandler.h" + +VirtualChannelWithQueue::VirtualChannelWithQueue(object_id_t objectId, uint8_t vcId, + const char* vcName, PtmeIF& ptme, + const std::atomic_bool& linkStateProvider, + StorageManagerIF& tmStore, uint32_t tmQueueDepth) + : VirtualChannel(objectId, vcId, vcName, ptme, linkStateProvider), tmStore(tmStore) { + auto mqArgs = MqArgs(getObjectId(), reinterpret_cast(getVcid())); + tmQueue = QueueFactory::instance()->createMessageQueue( + tmQueueDepth, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); +} + +const char* VirtualChannelWithQueue::getName() const { return VirtualChannel::getName(); } + +ReturnValue_t VirtualChannelWithQueue::handleNextTm(bool performWriteOp) { + TmTcMessage message; + ReturnValue_t result = tmQueue->receiveMessage(&message); + if (result == MessageQueueIF::EMPTY) { + return result; + } + store_address_t storeId = message.getStorageId(); + const uint8_t* data = nullptr; + size_t size = 0; + result = tmStore.getData(storeId, &data, &size); + if (result != returnvalue::OK) { + sif::warning << "VirtualChannel::performOperation: Failed to read data from TM store" + << std::endl; + tmStore.deleteData(storeId); + return result; + } + + // TODO: Hnadle partial write handling + size_t writtenSize = 0; + if (performWriteOp) { + result = write(data, size, writtenSize); + if (result == PARTIALLY_WRITTEN) { + result = handleWriteCompletionSynchronously(writtenSize, 400); + if (result != returnvalue::OK) { + // TODO: Event? Might lead to dangerous spam though.. + sif::warning + << "VirtualChannelWithQueue: Synchronous write of last segment failed with code 0x" + << std::setw(4) << std::hex << result << std::dec << std::endl; + } + } + } + // Try delete in any case, ignore failures (which should not happen), it is more important to + // propagate write errors. + tmStore.deleteData(storeId); + return result; +} + +MessageQueueId_t VirtualChannelWithQueue::getReportReceptionQueue(uint8_t virtualChannel) const { + return tmQueue->getId(); +} diff --git a/mission/com/VirtualChannelWithQueue.h b/mission/com/VirtualChannelWithQueue.h new file mode 100644 index 0000000..04c7f96 --- /dev/null +++ b/mission/com/VirtualChannelWithQueue.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "OBSWConfig.h" +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/tmtcservices/AcceptsTelemetryIF.h" + +class StorageManagerIF; + +/** + * @brief This class represents a virtual channel. Sending a tm message to an object of this class + * will forward the tm packet to the respective virtual channel of the PTME IP Core. + * + * @author J. Meier + */ +class VirtualChannelWithQueue : public VirtualChannel, public AcceptsTelemetryIF { + public: + /** + * @brief Constructor + * + * @param vcId The virtual channel id assigned to this object + * @param tmQueueDepth Queue depth of queue receiving telemetry from other objects + */ + VirtualChannelWithQueue(object_id_t objectId, uint8_t vcId, const char* vcName, PtmeIF& ptme, + const std::atomic_bool& linkStateProvider, StorageManagerIF& tmStore, + uint32_t tmQueueDepth); + + MessageQueueId_t getReportReceptionQueue(uint8_t virtualChannel = 0) const override; + [[nodiscard]] const char* getName() const override; + ReturnValue_t handleNextTm(bool performWriteOp); + + private: + MessageQueueIF* tmQueue = nullptr; + StorageManagerIF& tmStore; +}; diff --git a/mission/com/defs.h b/mission/com/defs.h new file mode 100644 index 0000000..b066210 --- /dev/null +++ b/mission/com/defs.h @@ -0,0 +1,33 @@ +#ifndef MISSION_COMDEFS_H_ +#define MISSION_COMDEFS_H_ + +#include + +namespace com { + +enum class Datarate : uint8_t { + LOW_RATE_MODULATION_BPSK, + HIGH_RATE_MODULATION_0QPSK, + NUM_DATARATES +}; + +enum Submode : uint8_t { + RX_ONLY = 10, + RX_AND_TX_DEFAULT_DATARATE = 11, + RX_AND_TX_LOW_DATARATE = 12, + RX_AND_TX_HIGH_DATARATE = 13, + RX_AND_TX_CW = 14, + NUM_SUBMODES +}; + +enum class CcsdsSubmode : uint8_t { + UNSET = 0, + DATARATE_LOW = 1, + DATARATE_HIGH = 2, + DATARATE_DEFAULT = 3 +}; +enum class ParameterId : uint8_t { DATARATE = 0, TRANSMITTER_TIMEOUT = 1 }; + +} // namespace com + +#endif /* MISSION_COMDEFS_H_ */ diff --git a/mission/com/syrlinksDefs.h b/mission/com/syrlinksDefs.h new file mode 100644 index 0000000..b0f1e95 --- /dev/null +++ b/mission/com/syrlinksDefs.h @@ -0,0 +1,127 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_SYRLINKSDEFINITIONS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_SYRLINKSDEFINITIONS_H_ + +#include "eive/eventSubsystemIds.h" +#include "fsfw/devicehandlers/DeviceHandlerBase.h" + +namespace syrlinks { + +enum class ParameterId : uint8_t { DATARATE = 0 }; + +static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::SYRLINKS; + +//! [EXPORT] : [COMMENT] Transmitter is on now. P1: Submode, P2: Current default datarate. +static constexpr Event TX_ON = event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO); +//! [EXPORT] : [COMMENT] Transmitter is off now. +static constexpr Event TX_OFF = event::makeEvent(SUBSYSTEM_ID, 2, severity::INFO); + +static const DeviceCommandId_t NONE = 0; +static const DeviceCommandId_t RESET_UNIT = 1; +/** Reads out all status registers */ +static const DeviceCommandId_t READ_RX_STATUS_REGISTERS = 2; +/** Sets Tx mode to standby */ +static const DeviceCommandId_t SET_TX_MODE_STANDBY = 3; +/** Starts transmission mode. Only reached when clock signal is applying to the data tx input */ +static const DeviceCommandId_t SET_TX_MODE_MODULATION = 4; +/** Sends out a single carrier wave for testing purpose */ +static const DeviceCommandId_t SET_TX_MODE_CW = 5; +static const DeviceCommandId_t ACK_REPLY = 6; +static const DeviceCommandId_t READ_TX_STATUS = 7; +static const DeviceCommandId_t READ_TX_WAVEFORM = 8; +static const DeviceCommandId_t READ_TX_AGC_VALUE_HIGH_BYTE = 9; +static const DeviceCommandId_t READ_TX_AGC_VALUE_LOW_BYTE = 10; +static const DeviceCommandId_t WRITE_LCL_CONFIG = 11; +static const DeviceCommandId_t READ_LCL_CONFIG = 12; +static const DeviceCommandId_t TEMP_POWER_AMPLIFIER_HIGH_BYTE = 13; +static const DeviceCommandId_t TEMP_POWER_AMPLIFIER_LOW_BYTE = 14; +static const DeviceCommandId_t TEMP_BASEBAND_BOARD_HIGH_BYTE = 15; +static const DeviceCommandId_t TEMP_BASEBAND_BOARD_LOW_BYTE = 16; +static const DeviceCommandId_t SET_WAVEFORM_0QPSK = 17; +// After startup syrlinks always in BSPK configuration +static const DeviceCommandId_t SET_WAVEFORM_BPSK = 18; +static const DeviceCommandId_t ENABLE_DEBUG = 20; +static const DeviceCommandId_t DISABLE_DEBUG = 21; + +/** Size of a simple transmission success response */ +static const uint8_t ACK_SIZE = 12; +static const uint8_t SIZE_CRC_AND_TERMINATION = 5; +/** The size of the header with the message identifier and the payload size field */ +static const uint8_t MESSAGE_HEADER_SIZE = 5; +/** Size of reply to an rx status registers request */ +static const uint8_t RX_STATUS_REGISTERS_REPLY_SIZE = 49; +static const uint8_t READ_ONE_REGISTER_REPLY_SIE = 13; + +static const uint8_t RX_DATASET_ID = 0x1; +static const uint8_t TX_DATASET_ID = 0x2; +static const uint8_t TEMPERATURE_SET_ID = 0x3; + +static const size_t MAX_REPLY_SIZE = RX_STATUS_REGISTERS_REPLY_SIZE; +static const size_t MAX_COMMAND_SIZE = 15; + +static const size_t CRC_FIELD_SIZE = 4; + +static const uint8_t RX_DATASET_SIZE = 8; +static const uint8_t TX_DATASET_SIZE = 3; +static const uint8_t TEMPERATURE_SET_SIZE = 3; + +enum SyrlinksPoolIds : lp_id_t { + RX_STATUS, + RX_SENSITIVITY, + RX_FREQUENCY_SHIFT, + RX_IQ_POWER, + RX_AGC_VALUE, + RX_DEMOD_EB, + RX_DEMOD_N0, + RX_DATA_RATE, + TX_STATUS, + TX_CONV_DIFF, + TX_CONV_FILTER, + TX_WAVEFORM, + TX_PCM_INDEX, + TX_AGC_VALUE, + TEMP_POWER_AMPLIFIER, + TEMP_BASEBAND_BOARD +}; + +class RxDataset : public StaticLocalDataSet { + public: + RxDataset(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, RX_DATASET_ID) {} + + RxDataset(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, RX_DATASET_ID)) {} + + lp_var_t rxStatus = lp_var_t(sid.objectId, RX_STATUS, this); + lp_var_t rxSensitivity = lp_var_t(sid.objectId, RX_SENSITIVITY, this); + lp_var_t rxFrequencyShift = lp_var_t(sid.objectId, RX_FREQUENCY_SHIFT, this); + lp_var_t rxIqPower = lp_var_t(sid.objectId, RX_IQ_POWER, this); + lp_var_t rxAgcValue = lp_var_t(sid.objectId, RX_AGC_VALUE, this); + lp_var_t rxDemodEb = lp_var_t(sid.objectId, RX_DEMOD_EB, this); + lp_var_t rxDemodN0 = lp_var_t(sid.objectId, RX_DEMOD_N0, this); + lp_var_t rxDataRate = lp_var_t(sid.objectId, RX_DATA_RATE, this); +}; + +class TxDataset : public StaticLocalDataSet { + public: + TxDataset(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, TX_DATASET_ID) {} + + TxDataset(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, TX_DATASET_ID)) {} + + lp_var_t txStatus = lp_var_t(sid.objectId, TX_STATUS, this); + lp_var_t txWaveform = lp_var_t(sid.objectId, TX_WAVEFORM, this); + lp_var_t txAgcValue = lp_var_t(sid.objectId, TX_AGC_VALUE, this); +}; + +class TemperatureSet : public StaticLocalDataSet { + public: + TemperatureSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, TEMPERATURE_SET_ID) {} + + TemperatureSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, TEMPERATURE_SET_ID)) {} + + lp_var_t temperaturePowerAmplifier = + lp_var_t(sid.objectId, TEMP_POWER_AMPLIFIER, this); + lp_var_t temperatureBasebandBoard = + lp_var_t(sid.objectId, TEMP_BASEBAND_BOARD, this); +}; + +} // namespace syrlinks + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_SYRLINKSDEFINITIONS_H_ */ diff --git a/mission/config/CMakeLists.txt b/mission/config/CMakeLists.txt new file mode 100644 index 0000000..5cacbf0 --- /dev/null +++ b/mission/config/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${LIB_EIVE_MISSION} PRIVATE comCfg.cpp torquer.cpp) diff --git a/mission/config/comCfg.cpp b/mission/config/comCfg.cpp new file mode 100644 index 0000000..664a2e5 --- /dev/null +++ b/mission/config/comCfg.cpp @@ -0,0 +1,28 @@ +#include "comCfg.h" + +#include +#include + +#include + +com::Datarate DATARATE_CFG_RAW = com::Datarate::LOW_RATE_MODULATION_BPSK; +MutexIF* DATARATE_LOCK = nullptr; + +MutexIF* lazyLock(); + +com::Datarate com::getCurrentDatarate() { + MutexGuard mg(lazyLock(), MutexIF::TimeoutType::WAITING, 20, "com"); + return DATARATE_CFG_RAW; +} + +void com::setCurrentDatarate(com::Datarate newRate) { + MutexGuard mg(lazyLock(), MutexIF::TimeoutType::WAITING, 20, "com"); + DATARATE_CFG_RAW = newRate; +} + +MutexIF* lazyLock() { + if (DATARATE_LOCK == nullptr) { + return MutexFactory::instance()->createMutex(); + } + return DATARATE_LOCK; +} diff --git a/mission/config/comCfg.h b/mission/config/comCfg.h new file mode 100644 index 0000000..575fe6f --- /dev/null +++ b/mission/config/comCfg.h @@ -0,0 +1,15 @@ +#ifndef MISSION_COMCFG_H_ +#define MISSION_COMCFG_H_ + +#include + +#include "mission/com/defs.h" + +namespace com { + +com::Datarate getCurrentDatarate(); +void setCurrentDatarate(com::Datarate newRate); + +} // namespace com + +#endif /* MISSION_COMCFG_H_ */ diff --git a/mission/config/configfile.h b/mission/config/configfile.h new file mode 100644 index 0000000..3fa9f07 --- /dev/null +++ b/mission/config/configfile.h @@ -0,0 +1,9 @@ +#ifndef MISSION_CONFIG_CONFIGFILE_H_ +#define MISSION_CONFIG_CONFIGFILE_H_ + +namespace configfile { +// Name of global config file relative to currently mounted SD card +static const char sdrelative[] = "config/global_config.json"; +} // namespace configfile + +#endif /* MISSION_CONFIG_CONFIGFILE_H_ */ diff --git a/mission/config/torquer.cpp b/mission/config/torquer.cpp new file mode 100644 index 0000000..27a32be --- /dev/null +++ b/mission/config/torquer.cpp @@ -0,0 +1,27 @@ +#include "torquer.h" + +#include + +MutexIF* TORQUE_LOCK = nullptr; + +namespace torquer { + +bool TORQUEING = false; +bool NEW_ACTUATION_FLAG = false; +Countdown TORQUE_COUNTDOWN = Countdown(); + +bool mgtIsTorqueing(dur_millis_t* remainingTorqueDuration) { + if (TORQUEING and remainingTorqueDuration != nullptr) { + *remainingTorqueDuration = TORQUE_COUNTDOWN.getRemainingMillis() + TORQUE_BUFFER_TIME_MS; + } + return TORQUEING; +} + +MutexIF* lazyLock() { + if (TORQUE_LOCK == nullptr) { + TORQUE_LOCK = MutexFactory::instance()->createMutex(); + } + return TORQUE_LOCK; +} + +} // namespace torquer diff --git a/mission/config/torquer.h b/mission/config/torquer.h new file mode 100644 index 0000000..bb5a101 --- /dev/null +++ b/mission/config/torquer.h @@ -0,0 +1,25 @@ +#ifndef MISSION_DEVICES_TOQUER_H_ +#define MISSION_DEVICES_TOQUER_H_ + +#include +#include + +namespace torquer { + +// Additional buffer time to accont for time until I2C command arrives and ramp up / ramp down +// time of the MGT +static constexpr dur_millis_t TORQUE_BUFFER_TIME_MS = 20; +static constexpr MutexIF::TimeoutType LOCK_TYPE = MutexIF::TimeoutType::WAITING; +static constexpr uint32_t LOCK_TIMEOUT = 20; +static constexpr char LOCK_CTX[] = "torquer"; + +MutexIF* lazyLock(); +extern bool TORQUEING; +extern bool NEW_ACTUATION_FLAG; +extern Countdown TORQUE_COUNTDOWN; + +bool mgtIsTorqueing(dur_millis_t* remainingTorqueDuration); + +} // namespace torquer + +#endif /* MISSION_DEVICES_TOQUER_H_ */ diff --git a/mission/controller/AcsController.cpp b/mission/controller/AcsController.cpp new file mode 100644 index 0000000..d75a2d5 --- /dev/null +++ b/mission/controller/AcsController.cpp @@ -0,0 +1,1216 @@ +#include "AcsController.h" + +AcsController::AcsController(object_id_t objectId, bool enableHkSets, SdCardMountedIF &sdcMan) + : ExtendedControllerBase(objectId), + enableHkSets(enableHkSets), + sdcMan(sdcMan), + attitudeEstimation(&acsParameters), + fusedRotationEstimation(&acsParameters), + navigation(&acsParameters), + guidance(&acsParameters), + safeCtrl(&acsParameters), + ptgCtrl(&acsParameters), + parameterHelper(this), + mgmDataRaw(this), + mgmDataProcessed(this), + susDataRaw(this), + susDataProcessed(this), + gyrDataRaw(this), + gyrDataProcessed(this), + gpsDataProcessed(this), + attitudeEstimationData(this), + ctrlValData(this), + actuatorCmdData(this), + fusedRotRateData(this), + fusedRotRateSourcesData(this) {} + +ReturnValue_t AcsController::initialize() { + ReturnValue_t result = parameterHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + return ExtendedControllerBase::initialize(); +} + +ReturnValue_t AcsController::handleCommandMessage(CommandMessage *message) { + ReturnValue_t result = actionHelper.handleActionMessage(message); + if (result == returnvalue::OK) { + return result; + } + result = parameterHelper.handleParameterMessage(message); + if (result == returnvalue::OK) { + return result; + } + return result; +} + +ReturnValue_t AcsController::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t *data, size_t size) { + switch (actionId) { + case SOLAR_ARRAY_DEPLOYMENT_SUCCESSFUL: { + ReturnValue_t result = guidance.solarArrayDeploymentComplete(); + if (result == returnvalue::FAILED) { + return acsctrl::FILE_DELETION_FAILED; + } + return HasActionsIF::EXECUTION_FINISHED; + } + case RESET_MEKF: { + navigation.resetMekf(&attitudeEstimationData); + return HasActionsIF::EXECUTION_FINISHED; + } + case RESTORE_MEKF_NONFINITE_RECOVERY: { + mekfLost = false; + return HasActionsIF::EXECUTION_FINISHED; + } + case UPDATE_TLE: { + if (size != 69 * 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + ReturnValue_t result = updateTle(data, data + 69, false); + if (result != returnvalue::OK) { + return result; + } + result = writeTleToFs(data); + if (result != returnvalue::OK) { + return result; + } + return HasActionsIF::EXECUTION_FINISHED; + } + case (READ_TLE): { + uint8_t tle[69 * 2] = {}; + uint8_t line2[69] = {}; + ReturnValue_t result = readTleFromFs(tle, line2); + if (result != returnvalue::OK) { + return result; + } + std::memcpy(tle + 69, line2, 69); + actionHelper.reportData(commandedBy, actionId, tle, 69 * 2); + return HasActionsIF::EXECUTION_FINISHED; + } + case (UPDATE_MEKF_STANDARD_DEVIATIONS): + navigation.updateMekfStandardDeviations(&acsParameters); + return HasActionsIF::EXECUTION_FINISHED; + default: { + return HasActionsIF::INVALID_ACTION_ID; + } + } +} + +MessageQueueId_t AcsController::getCommandQueue() const { return commandQueue->getId(); } + +ReturnValue_t AcsController::getParameter(uint8_t domainId, uint8_t parameterId, + ParameterWrapper *parameterWrapper, + const ParameterWrapper *newValues, + uint16_t startAtIndex) { + return acsParameters.getParameter(domainId, parameterId, parameterWrapper, newValues, + startAtIndex); +} + +void AcsController::performControlOperation() { +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "ACS & TCS PST"); +#endif + { + PoolReadGuard pg(&mgmDataRaw); + if (pg.getReadResult() == returnvalue::OK) { + copyMgmData(); + } + } + { + PoolReadGuard pg(&susDataRaw); + if (pg.getReadResult() == returnvalue::OK) { + copySusData(); + } + } + { + PoolReadGuard pg(&gyrDataRaw); + if (pg.getReadResult() == returnvalue::OK) { + copyGyrData(); + } + } + + switch (internalState) { + case InternalState::STARTUP: { + initialCountdown.resetTimer(); + internalState = InternalState::INITIAL_DELAY; + return; + } + case InternalState::INITIAL_DELAY: { + if (initialCountdown.hasTimedOut()) { + uint8_t line1[69] = {}; + uint8_t line2[69] = {}; + readTleFromFs(line1, line2); + updateTle(line1, line2, true); + internalState = InternalState::READY; + } + return; + } + case InternalState::READY: { + if (mode != MODE_OFF) { + performAttitudeControl(); + } + break; + } + default: + break; + } +} + +void AcsController::performAttitudeControl() { + Clock::getClock_timeval(&timeAbsolute); + Clock::getClockMonotonic(&timeRelative); + + if (timeRelative.tv_sec != 0 and oldTimeRelative.tv_sec != 0) { + timeDelta = timevalOperations::toDouble(timeRelative - oldTimeRelative); + } + oldTimeRelative = timeRelative; + + ReturnValue_t result = navigation.useSpg4(timeAbsolute, &gpsDataProcessed); + if (result == Sgp4Propagator::TLE_TOO_OLD and not tleTooOldFlag) { + triggerEvent(acs::TLE_TOO_OLD); + tleTooOldFlag = true; + } else if (result != Sgp4Propagator::TLE_TOO_OLD) { + tleTooOldFlag = false; + } + + sensorProcessing.process(timeAbsolute, timeDelta, &sensorValues, &mgmDataProcessed, + &susDataProcessed, &gyrDataProcessed, &gpsDataProcessed, &acsParameters); + attitudeEstimation.quest(&susDataProcessed, &mgmDataProcessed, &attitudeEstimationData); + fusedRotationEstimation.estimateFusedRotationRate( + mode, &susDataProcessed, &mgmDataProcessed, &gyrDataProcessed, &sensorValues, + &attitudeEstimationData, timeDelta, &fusedRotRateSourcesData, &fusedRotRateData); + result = navigation.useMekf(&sensorValues, &gyrDataProcessed, &mgmDataProcessed, + &susDataProcessed, timeDelta, &attitudeEstimationData, + acsParameters.kalmanFilterParameters.allowStr); + + if (result != MultiplicativeKalmanFilter::MEKF_RUNNING and + result != MultiplicativeKalmanFilter::MEKF_INITIALIZED) { + if (not mekfInvalidFlag) { + triggerEvent(acs::MEKF_INVALID_INFO, + static_cast(attitudeEstimationData.mekfStatus.value)); + mekfInvalidFlag = true; + } + if (result == MultiplicativeKalmanFilter::MEKF_NOT_FINITE and not mekfLost) { + triggerEvent(acs::MEKF_AUTOMATIC_RESET); + navigation.resetMekf(&attitudeEstimationData); + mekfLost = true; + } + } else if (mekfInvalidFlag) { + triggerEvent(acs::MEKF_RECOVERY); + mekfInvalidFlag = false; + } + + handleDetumbling(); + + switch (mode) { + case acs::SAFE: + switch (submode) { + case SUBMODE_NONE: + performSafe(); + break; + case acs::DETUMBLE: + performDetumble(); + break; + } + break; + case acs::PTG_IDLE: + case acs::PTG_TARGET: + case acs::PTG_TARGET_GS: + case acs::PTG_NADIR: + case acs::PTG_INERTIAL: + performPointingCtrl(); + break; + } +} + +void AcsController::performSafe() { + // get desired satellite rate, sun direction to align to and inertia + double sunTargetDir[3] = {0, 0, 0}; + guidance.getTargetParamsSafe(sunTargetDir); + + double magMomMtq[3] = {0, 0, 0}, errAng = 0.0; + acs::ControlModeStrategy safeCtrlStrat = safeCtrl.safeCtrlStrategy( + mgmDataProcessed.mgmVecTot.isValid(), not mekfInvalidFlag, + gyrDataProcessed.gyrVecTot.isValid(), susDataProcessed.susVecTot.isValid(), + fusedRotRateSourcesData.rotRateTotalSusMgm.isValid(), + acsParameters.safeModeControllerParameters.useMekf, + acsParameters.safeModeControllerParameters.useGyr, + acsParameters.safeModeControllerParameters.dampingDuringEclipse); + switch (safeCtrlStrat) { + case (acs::ControlModeStrategy::SAFECTRL_MEKF): + safeCtrl.safeMekf(mgmDataProcessed.mgmVecTot.value, + attitudeEstimationData.satRotRateMekf.value, + susDataProcessed.sunIjkModel.value, attitudeEstimationData.quatMekf.value, + sunTargetDir, magMomMtq, errAng); + safeCtrlFailureFlag = false; + safeCtrlFailureCounter = 0; + break; + case (acs::ControlModeStrategy::SAFECTRL_GYR): + safeCtrl.safeGyr(mgmDataProcessed.mgmVecTot.value, gyrDataProcessed.gyrVecTot.value, + susDataProcessed.susVecTot.value, sunTargetDir, magMomMtq, errAng); + safeCtrlFailureFlag = false; + safeCtrlFailureCounter = 0; + break; + case (acs::ControlModeStrategy::SAFECTRL_SUSMGM): + safeCtrl.safeSusMgm(mgmDataProcessed.mgmVecTot.value, + fusedRotRateSourcesData.rotRateTotalSusMgm.value, + fusedRotRateSourcesData.rotRateParallelSusMgm.value, + fusedRotRateSourcesData.rotRateOrthogonalSusMgm.value, + susDataProcessed.susVecTot.value, sunTargetDir, magMomMtq, errAng); + safeCtrlFailureFlag = false; + safeCtrlFailureCounter = 0; + break; + case (acs::ControlModeStrategy::SAFECTRL_ECLIPSE_DAMPING_GYR): + safeCtrl.safeRateDampingGyr(mgmDataProcessed.mgmVecTot.value, + gyrDataProcessed.gyrVecTot.value, sunTargetDir, magMomMtq, + errAng); + safeCtrlFailureFlag = false; + safeCtrlFailureCounter = 0; + break; + case (acs::ControlModeStrategy::SAFECTRL_ECLIPSE_DAMPING_SUSMGM): + safeCtrl.safeRateDampingSusMgm(mgmDataProcessed.mgmVecTot.value, + fusedRotRateSourcesData.rotRateTotalSusMgm.value, sunTargetDir, + magMomMtq, errAng); + safeCtrlFailureFlag = false; + safeCtrlFailureCounter = 0; + break; + case (acs::ControlModeStrategy::SAFECTRL_ECLIPSE_IDELING): + errAng = NAN; + safeCtrlFailureFlag = false; + safeCtrlFailureCounter = 0; + break; + case (acs::ControlModeStrategy::CTRL_NO_MAG_FIELD_FOR_CONTROL): + safeCtrlFailure(1, 0); + break; + case (acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL): + safeCtrlFailure(0, 1); + break; + default: + sif::error << "AcsController: Invalid safe mode strategy for performSafe" << std::endl; + break; + } + + actuatorCmd.cmdDipoleMtq(*acsParameters.magnetorquerParameter.inverseAlignment, + acsParameters.magnetorquerParameter.dipoleMax, magMomMtq, cmdDipoleMtqs); + + updateCtrlValData(errAng, safeCtrlStrat); + updateActuatorCmdData(cmdDipoleMtqs); + commandActuators(cmdDipoleMtqs[0], cmdDipoleMtqs[1], cmdDipoleMtqs[2], + acsParameters.magnetorquerParameter.torqueDuration); +} + +void AcsController::performDetumble() { + acs::ControlModeStrategy safeCtrlStrat = detumble.detumbleStrategy( + mgmDataProcessed.mgmVecTot.isValid(), gyrDataProcessed.gyrVecTot.isValid(), + mgmDataProcessed.mgmVecTotDerivative.isValid(), + acsParameters.detumbleParameter.useFullDetumbleLaw); + double magMomMtq[3] = {0, 0, 0}; + switch (safeCtrlStrat) { + case (acs::ControlModeStrategy::SAFECTRL_DETUMBLE_FULL): + detumble.bDotLawFull(gyrDataProcessed.gyrVecTot.value, mgmDataProcessed.mgmVecTot.value, + magMomMtq, acsParameters.detumbleParameter.gainFull); + break; + case (acs::ControlModeStrategy::SAFECTRL_DETUMBLE_DETERIORATED): + detumble.bDotLaw(mgmDataProcessed.mgmVecTotDerivative.value, mgmDataProcessed.mgmVecTot.value, + magMomMtq, acsParameters.detumbleParameter.gainBdot); + break; + case (acs::ControlModeStrategy::CTRL_NO_MAG_FIELD_FOR_CONTROL): + safeCtrlFailure(1, 0); + break; + case (acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL): + safeCtrlFailure(0, 1); + break; + default: + sif::error << "AcsController: Invalid safe mode strategy for performDetumble" << std::endl; + break; + } + + actuatorCmd.cmdDipoleMtq(*acsParameters.magnetorquerParameter.inverseAlignment, + acsParameters.magnetorquerParameter.dipoleMax, magMomMtq, cmdDipoleMtqs); + + updateCtrlValData(safeCtrlStrat); + updateActuatorCmdData(cmdDipoleMtqs); + commandActuators(cmdDipoleMtqs[0], cmdDipoleMtqs[1], cmdDipoleMtqs[2], + acsParameters.magnetorquerParameter.torqueDuration); +} + +void AcsController::performPointingCtrl() { + bool strValid = (sensorValues.strSet.caliQw.isValid() and sensorValues.strSet.caliQx.isValid() and + sensorValues.strSet.caliQy.isValid() and sensorValues.strSet.caliQz.isValid()); + uint8_t useMekf = false; + switch (mode) { + case acs::PTG_IDLE: + useMekf = acsParameters.idleModeControllerParameters.useMekf; + break; + case acs::PTG_TARGET: + useMekf = acsParameters.targetModeControllerParameters.useMekf; + break; + case acs::PTG_TARGET_GS: + useMekf = acsParameters.gsTargetModeControllerParameters.useMekf; + break; + case acs::PTG_NADIR: + useMekf = acsParameters.nadirModeControllerParameters.useMekf; + break; + case acs::PTG_INERTIAL: + useMekf = acsParameters.inertialModeControllerParameters.useMekf; + break; + } + acs::ControlModeStrategy ptgCtrlStrat = ptgCtrl.pointingCtrlStrategy( + mgmDataProcessed.mgmVecTot.isValid(), not mekfInvalidFlag, strValid, + attitudeEstimationData.quatQuest.isValid(), fusedRotRateData.rotRateTotalSource.isValid(), + fusedRotRateData.rotRateSource.value, useMekf); + + if (ptgCtrlStrat == acs::ControlModeStrategy::CTRL_NO_MAG_FIELD_FOR_CONTROL or + ptgCtrlStrat == acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL) { + ptgCtrlLostCounter++; + if (ptgCtrlLostCounter > acsParameters.onBoardParams.ptgCtrlLostTimer) { + triggerEvent(acs::PTG_CTRL_NO_ATTITUDE_INFORMATION); + ptgCtrlLostCounter = 0; + } + guidance.resetValues(); + updateCtrlValData(ptgCtrlStrat); + updateActuatorCmdData(ZERO_VEC4, cmdSpeedRws, ZERO_VEC3_INT16); + commandActuators(0, 0, 0, acsParameters.magnetorquerParameter.torqueDuration, cmdSpeedRws[0], + cmdSpeedRws[1], cmdSpeedRws[2], cmdSpeedRws[3], + acsParameters.rwHandlingParameters.rampTime); + return; + } else { + ptgCtrlLostCounter = 0; + } + + double quatBI[4] = {0, 0, 0, 0}, rotRateB[3] = {0, 0, 0}; + switch (ptgCtrlStrat) { + case acs::ControlModeStrategy::PTGCTRL_MEKF: + std::memcpy(quatBI, attitudeEstimationData.quatMekf.value, sizeof(quatBI)); + std::memcpy(rotRateB, attitudeEstimationData.satRotRateMekf.value, sizeof(rotRateB)); + break; + case acs::ControlModeStrategy::PTGCTRL_STR: + quatBI[0] = sensorValues.strSet.caliQx.value; + quatBI[1] = sensorValues.strSet.caliQy.value; + quatBI[2] = sensorValues.strSet.caliQz.value; + quatBI[3] = sensorValues.strSet.caliQw.value; + std::memcpy(rotRateB, fusedRotRateData.rotRateTotalSource.value, sizeof(rotRateB)); + break; + case acs::ControlModeStrategy::PTGCTRL_QUEST: + std::memcpy(quatBI, attitudeEstimationData.quatQuest.value, sizeof(quatBI)); + std::memcpy(rotRateB, fusedRotRateData.rotRateTotalSource.value, sizeof(rotRateB)); + break; + default: + sif::error << "AcsController: Invalid pointing mode strategy for performPointingCtrl" + << std::endl; + break; + } + + bool allRwAvailable = true; + double rwPseudoInv[4][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + ReturnValue_t result = guidance.getDistributionMatrixRw(&sensorValues, *rwPseudoInv, &rwAvail); + if (result == acsctrl::MULTIPLE_RW_UNAVAILABLE) { + if (multipleRwUnavailableCounter >= + acsParameters.rwHandlingParameters.multipleRwInvalidTimeout) { + triggerEvent(acs::MULTIPLE_RW_INVALID); + multipleRwUnavailableCounter = 0; + } + multipleRwUnavailableCounter++; + return; + } + multipleRwUnavailableCounter = 0; + if (result == acsctrl::SINGLE_RW_UNAVAILABLE) { + allRwAvailable = false; + } + + // Variables required for guidance + double targetQuat[4] = {0, 0, 0, 1}, targetSatRotRate[3] = {0, 0, 0}, errorQuat[4] = {0, 0, 0, 1}, + errorAngle = 0, errorSatRotRate[3] = {0, 0, 0}; + // Variables required for setting actuators + double torquePtgRws[4] = {0, 0, 0, 0}, rwTrqNs[4] = {0, 0, 0, 0}, torqueRws[4] = {0, 0, 0, 0}, + mgtDpDes[3] = {0, 0, 0}; + + switch (mode) { + case acs::PTG_IDLE: + guidance.targetQuatPtgIdle(timeAbsolute, timeDelta, susDataProcessed.sunIjkModel.value, + gpsDataProcessed.gpsPosition.value, targetQuat, targetSatRotRate); + guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, errorQuat, + errorSatRotRate, errorAngle); + ptgCtrl.ptgLaw(&acsParameters.idleModeControllerParameters, errorQuat, errorSatRotRate, + *rwPseudoInv, torquePtgRws); + ptgCtrl.ptgNullspace(allRwAvailable, &acsParameters.idleModeControllerParameters, + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, + rwTrqNs); + VectorOperations::add(torquePtgRws, rwTrqNs, torqueRws, 4); + actuatorCmd.scalingTorqueRws(torqueRws, acsParameters.rwHandlingParameters.maxTrq); + ptgCtrl.ptgDesaturation( + allRwAvailable, &rwAvail, &acsParameters.idleModeControllerParameters, + mgmDataProcessed.mgmVecTot.value, mgmDataProcessed.mgmVecTot.isValid(), + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, mgtDpDes); + break; + + case acs::PTG_TARGET: + guidance.targetQuatPtgTarget(timeAbsolute, timeDelta, gpsDataProcessed.gpsPosition.value, + gpsDataProcessed.gpsVelocity.value, targetQuat, + targetSatRotRate); + guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, + acsParameters.targetModeControllerParameters.quatRef, + acsParameters.targetModeControllerParameters.refRotRate, errorQuat, + errorSatRotRate, errorAngle); + ptgCtrl.ptgLaw(&acsParameters.targetModeControllerParameters, errorQuat, errorSatRotRate, + *rwPseudoInv, torquePtgRws); + ptgCtrl.ptgNullspace(allRwAvailable, &acsParameters.targetModeControllerParameters, + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, + rwTrqNs); + VectorOperations::add(torquePtgRws, rwTrqNs, torqueRws, 4); + actuatorCmd.scalingTorqueRws(torqueRws, acsParameters.rwHandlingParameters.maxTrq); + ptgCtrl.ptgDesaturation( + allRwAvailable, &rwAvail, &acsParameters.targetModeControllerParameters, + mgmDataProcessed.mgmVecTot.value, mgmDataProcessed.mgmVecTot.isValid(), + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, mgtDpDes); + break; + + case acs::PTG_TARGET_GS: + guidance.targetQuatPtgGs(timeAbsolute, timeDelta, gpsDataProcessed.gpsPosition.value, + susDataProcessed.sunIjkModel.value, targetQuat, targetSatRotRate); + guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, errorQuat, + errorSatRotRate, errorAngle); + ptgCtrl.ptgLaw(&acsParameters.gsTargetModeControllerParameters, errorQuat, errorSatRotRate, + *rwPseudoInv, torquePtgRws); + ptgCtrl.ptgNullspace(allRwAvailable, &acsParameters.gsTargetModeControllerParameters, + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, + rwTrqNs); + VectorOperations::add(torquePtgRws, rwTrqNs, torqueRws, 4); + actuatorCmd.scalingTorqueRws(torqueRws, acsParameters.rwHandlingParameters.maxTrq); + ptgCtrl.ptgDesaturation( + allRwAvailable, &rwAvail, &acsParameters.gsTargetModeControllerParameters, + mgmDataProcessed.mgmVecTot.value, mgmDataProcessed.mgmVecTot.isValid(), + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, mgtDpDes); + break; + + case acs::PTG_NADIR: + guidance.targetQuatPtgNadir(timeAbsolute, timeDelta, gpsDataProcessed.gpsPosition.value, + gpsDataProcessed.gpsVelocity.value, targetQuat, targetSatRotRate); + guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, + acsParameters.nadirModeControllerParameters.quatRef, + acsParameters.nadirModeControllerParameters.refRotRate, errorQuat, + errorSatRotRate, errorAngle); + ptgCtrl.ptgLaw(&acsParameters.nadirModeControllerParameters, errorQuat, errorSatRotRate, + *rwPseudoInv, torquePtgRws); + ptgCtrl.ptgNullspace(allRwAvailable, &acsParameters.nadirModeControllerParameters, + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, + rwTrqNs); + VectorOperations::add(torquePtgRws, rwTrqNs, torqueRws, 4); + actuatorCmd.scalingTorqueRws(torqueRws, acsParameters.rwHandlingParameters.maxTrq); + ptgCtrl.ptgDesaturation( + allRwAvailable, &rwAvail, &acsParameters.nadirModeControllerParameters, + mgmDataProcessed.mgmVecTot.value, mgmDataProcessed.mgmVecTot.isValid(), + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, mgtDpDes); + break; + + case acs::PTG_INERTIAL: + std::memcpy(targetQuat, acsParameters.inertialModeControllerParameters.tgtQuat, + sizeof(targetQuat)); + guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, + acsParameters.inertialModeControllerParameters.quatRef, + acsParameters.inertialModeControllerParameters.refRotRate, errorQuat, + errorSatRotRate, errorAngle); + ptgCtrl.ptgLaw(&acsParameters.inertialModeControllerParameters, errorQuat, errorSatRotRate, + *rwPseudoInv, torquePtgRws); + ptgCtrl.ptgNullspace(allRwAvailable, &acsParameters.inertialModeControllerParameters, + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, + rwTrqNs); + VectorOperations::add(torquePtgRws, rwTrqNs, torqueRws, 4); + actuatorCmd.scalingTorqueRws(torqueRws, acsParameters.rwHandlingParameters.maxTrq); + ptgCtrl.ptgDesaturation( + allRwAvailable, &rwAvail, &acsParameters.inertialModeControllerParameters, + mgmDataProcessed.mgmVecTot.value, mgmDataProcessed.mgmVecTot.isValid(), + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, mgtDpDes); + break; + default: + sif::error << "AcsController: Invalid mode for performPointingCtrl" << std::endl; + break; + } + + actuatorCmd.cmdSpeedToRws( + sensorValues.rw1Set.currSpeed.value, sensorValues.rw2Set.currSpeed.value, + sensorValues.rw3Set.currSpeed.value, sensorValues.rw4Set.currSpeed.value, + acsParameters.onBoardParams.sampleTime, acsParameters.rwHandlingParameters.inertiaWheel, + acsParameters.rwHandlingParameters.maxRwSpeed, torqueRws, cmdSpeedRws); + ptgCtrl.rwAntistiction(&sensorValues, cmdSpeedRws); + actuatorCmd.cmdDipoleMtq(*acsParameters.magnetorquerParameter.inverseAlignment, + acsParameters.magnetorquerParameter.dipoleMax, mgtDpDes, cmdDipoleMtqs); + + updateCtrlValData(targetQuat, errorQuat, errorAngle, targetSatRotRate, ptgCtrlStrat); + updateActuatorCmdData(torqueRws, cmdSpeedRws, cmdDipoleMtqs); + commandActuators(cmdDipoleMtqs[0], cmdDipoleMtqs[1], cmdDipoleMtqs[2], + acsParameters.magnetorquerParameter.torqueDuration, cmdSpeedRws[0], + cmdSpeedRws[1], cmdSpeedRws[2], cmdSpeedRws[3], + acsParameters.rwHandlingParameters.rampTime); +} + +void AcsController::handleDetumbling() { + switch (detumbleState) { + case DetumbleState::NO_DETUMBLE: + if (fusedRotRateData.rotRateTotalSusMgm.isValid() and + VectorOperations::norm(fusedRotRateData.rotRateTotalSusMgm.value, 3) > + acsParameters.detumbleParameter.omegaDetumbleStart) { + detumbleCounter++; + } else if (detumbleCounter > 0) { + detumbleCounter -= 1; + } + if (detumbleCounter > acsParameters.detumbleParameter.detumblecounter) { + if (mode == acs::AcsMode::SAFE) { + detumbleState = DetumbleState::DETUMBLE_FROM_SAFE; + break; + } + detumbleState = DetumbleState::DETUMBLE_FROM_PTG; + } + break; + case DetumbleState::DETUMBLE_FROM_PTG: + triggerEvent(acs::PTG_RATE_VIOLATION); + detumbleTransitionCountdow.resetTimer(); + detumbleState = DetumbleState::PTG_TO_SAFE_TRANSITION; + break; + case DetumbleState::PTG_TO_SAFE_TRANSITION: + if (detumbleTransitionCountdow.hasTimedOut()) { + triggerEvent(acs::DETUMBLE_TRANSITION_FAILED, 2); + detumbleCounter = 0; + detumbleState = DetumbleState::NO_DETUMBLE; + break; + } + if (mode == acs::AcsMode::SAFE) { + detumbleState = DetumbleState::DETUMBLE_FROM_SAFE; + } + break; + case DetumbleState::DETUMBLE_FROM_SAFE: + detumbleCounter = 0; + // Triggers detumble mode transition in subsystem + if (mode == acs::AcsMode::SAFE) { + triggerEvent(acs::SAFE_RATE_VIOLATION); + startTransition(mode, acs::SafeSubmode::DETUMBLE); + detumbleState = DetumbleState::IN_DETUMBLE; + break; + } + triggerEvent(acs::DETUMBLE_TRANSITION_FAILED, 3); + detumbleState = DetumbleState::NO_DETUMBLE; + break; + case DetumbleState::IN_DETUMBLE: + if (fusedRotRateData.rotRateTotalSusMgm.isValid() and + VectorOperations::norm(fusedRotRateData.rotRateTotalSusMgm.value, 3) < + acsParameters.detumbleParameter.omegaDetumbleEnd) { + detumbleCounter++; + } else if (detumbleCounter > 0) { + detumbleCounter -= 1; + } + + if (detumbleCounter > acsParameters.detumbleParameter.detumblecounter) { + detumbleCounter = 0; + // Triggers safe mode transition in subsystem + triggerEvent(acs::RATE_RECOVERY); + startTransition(mode, acs::SafeSubmode::DEFAULT); + detumbleState = DetumbleState::NO_DETUMBLE; + } + break; + default: + sif::error << "AcsController: Invalid DetumbleState" << std::endl; + } +} + +void AcsController::safeCtrlFailure(uint8_t mgmFailure, uint8_t sensorFailure) { + if (not safeCtrlFailureFlag) { + triggerEvent(acs::SAFE_MODE_CONTROLLER_FAILURE, mgmFailure, sensorFailure); + safeCtrlFailureFlag = true; + } + safeCtrlFailureCounter++; + if (safeCtrlFailureCounter > 150) { + safeCtrlFailureFlag = false; + safeCtrlFailureCounter = 0; + } +} + +ReturnValue_t AcsController::commandActuators(int16_t xDipole, int16_t yDipole, int16_t zDipole, + uint16_t dipoleTorqueDuration) { + { + PoolReadGuard pg(&dipoleSet); + MutexGuard mg(torquer::lazyLock(), torquer::LOCK_TYPE, torquer::LOCK_TIMEOUT, + torquer::LOCK_CTX); + torquer::NEW_ACTUATION_FLAG = true; + dipoleSet.setDipoles(xDipole, yDipole, zDipole, dipoleTorqueDuration); + } + return returnvalue::OK; +} + +ReturnValue_t AcsController::commandActuators(int16_t xDipole, int16_t yDipole, int16_t zDipole, + uint16_t dipoleTorqueDuration, int32_t rw1Speed, + int32_t rw2Speed, int32_t rw3Speed, int32_t rw4Speed, + uint16_t rampTime) { + { + PoolReadGuard pg(&dipoleSet); + MutexGuard mg(torquer::lazyLock(), torquer::LOCK_TYPE, torquer::LOCK_TIMEOUT, + torquer::LOCK_CTX); + torquer::NEW_ACTUATION_FLAG = true; + dipoleSet.setDipoles(xDipole, yDipole, zDipole, dipoleTorqueDuration); + } + { + PoolReadGuard pg(&rw1SpeedSet); + rw1SpeedSet.setRwSpeed(rw1Speed, rampTime); + } + { + PoolReadGuard pg(&rw2SpeedSet); + rw2SpeedSet.setRwSpeed(rw2Speed, rampTime); + } + { + PoolReadGuard pg(&rw3SpeedSet); + rw3SpeedSet.setRwSpeed(rw3Speed, rampTime); + } + { + PoolReadGuard pg(&rw4SpeedSet); + rw4SpeedSet.setRwSpeed(rw4Speed, rampTime); + } + return returnvalue::OK; +} + +void AcsController::updateActuatorCmdData(const int16_t *mtqTargetDipole) { + updateActuatorCmdData(RW_OFF_TORQUE, RW_OFF_SPEED, mtqTargetDipole); +} + +void AcsController::updateActuatorCmdData(const double *rwTargetTorque, + const int32_t *rwTargetSpeed, + const int16_t *mtqTargetDipole) { + PoolReadGuard pg(&actuatorCmdData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(actuatorCmdData.rwTargetTorque.value, rwTargetTorque, 4 * sizeof(double)); + std::memcpy(actuatorCmdData.rwTargetSpeed.value, rwTargetSpeed, 4 * sizeof(int32_t)); + std::memcpy(actuatorCmdData.mtqTargetDipole.value, mtqTargetDipole, 3 * sizeof(int16_t)); + actuatorCmdData.setValidity(true, true); + } +} + +void AcsController::updateCtrlValData(acs::ControlModeStrategy ctrlStrat) { + PoolReadGuard pg(&ctrlValData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(ctrlValData.tgtQuat.value, ZERO_VEC4, 4 * sizeof(double)); + ctrlValData.tgtQuat.setValid(false); + std::memcpy(ctrlValData.errQuat.value, ZERO_VEC4, 4 * sizeof(double)); + ctrlValData.errQuat.setValid(false); + ctrlValData.errAng.value = 0; + ctrlValData.errAng.setValid(false); + std::memcpy(ctrlValData.tgtRotRate.value, ZERO_VEC3, 3 * sizeof(double)); + ctrlValData.tgtRotRate.setValid(false); + ctrlValData.safeStrat.value = ctrlStrat; + ctrlValData.safeStrat.setValid(true); + ctrlValData.setValidity(true, false); + } +} + +void AcsController::updateCtrlValData(double errAng, acs::ControlModeStrategy ctrlStrat) { + PoolReadGuard pg(&ctrlValData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(ctrlValData.tgtQuat.value, ZERO_VEC4, 4 * sizeof(double)); + ctrlValData.tgtQuat.setValid(false); + std::memcpy(ctrlValData.errQuat.value, ZERO_VEC4, 4 * sizeof(double)); + ctrlValData.errQuat.setValid(false); + ctrlValData.errAng.value = errAng; + ctrlValData.errAng.setValid(true); + std::memcpy(ctrlValData.tgtRotRate.value, ZERO_VEC3, 3 * sizeof(double)); + ctrlValData.tgtRotRate.setValid(false); + ctrlValData.safeStrat.value = ctrlStrat; + ctrlValData.safeStrat.setValid(true); + ctrlValData.setValidity(true, false); + } +} + +void AcsController::updateCtrlValData(const double *tgtQuat, const double *errQuat, double errAng, + const double *tgtRotRate, + acs::ControlModeStrategy ctrlStrat) { + PoolReadGuard pg(&ctrlValData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(ctrlValData.tgtQuat.value, tgtQuat, 4 * sizeof(double)); + std::memcpy(ctrlValData.errQuat.value, errQuat, 4 * sizeof(double)); + ctrlValData.errAng.value = errAng; + std::memcpy(ctrlValData.tgtRotRate.value, tgtRotRate, 3 * sizeof(double)); + ctrlValData.safeStrat.value = ctrlStrat; + ctrlValData.setValidity(true, true); + } +} + +ReturnValue_t AcsController::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + // MGM Raw + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_0_LIS3_UT, &mgm0VecRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_1_RM3100_UT, &mgm1VecRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_2_LIS3_UT, &mgm2VecRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_3_RM3100_UT, &mgm3VecRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_IMTQ_CAL_NT, &imtqMgmVecRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_IMTQ_CAL_ACT_STATUS, &imtqCalActStatus); + poolManager.subscribeForRegularPeriodicPacket({mgmDataRaw.getSid(), false, 60.0}); + // MGM Processed + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_0_VEC, &mgm0VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_1_VEC, &mgm1VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_2_VEC, &mgm2VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_3_VEC, &mgm3VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_4_VEC, &mgm4VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_VEC_TOT, &mgmVecTot); + localDataPoolMap.emplace(acsctrl::PoolIds::MGM_VEC_TOT_DERIVATIVE, &mgmVecTotDer); + localDataPoolMap.emplace(acsctrl::PoolIds::MAG_IGRF_MODEL, &magIgrf); + poolManager.subscribeForRegularPeriodicPacket({mgmDataProcessed.getSid(), enableHkSets, 60.0}); + // SUS Raw + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_0_N_LOC_XFYFZM_PT_XF, &sus0ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_1_N_LOC_XBYFZM_PT_XB, &sus1ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_2_N_LOC_XFYBZB_PT_YB, &sus2ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_3_N_LOC_XFYBZF_PT_YF, &sus3ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_4_N_LOC_XMYFZF_PT_ZF, &sus4ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_5_N_LOC_XFYMZB_PT_ZB, &sus5ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_6_R_LOC_XFYBZM_PT_XF, &sus6ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_7_R_LOC_XBYBZM_PT_XB, &sus7ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_8_R_LOC_XBYBZB_PT_YB, &sus8ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_9_R_LOC_XBYBZB_PT_YF, &sus9ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_10_N_LOC_XMYBZF_PT_ZF, &sus10ValRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_11_R_LOC_XBYMZB_PT_ZB, &sus11ValRaw); + poolManager.subscribeForRegularPeriodicPacket({susDataRaw.getSid(), false, 60.0}); + // SUS Processed + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_0_VEC, &sus0VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_1_VEC, &sus1VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_2_VEC, &sus2VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_3_VEC, &sus3VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_4_VEC, &sus4VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_5_VEC, &sus5VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_6_VEC, &sus6VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_7_VEC, &sus7VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_8_VEC, &sus8VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_9_VEC, &sus9VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_10_VEC, &sus10VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_11_VEC, &sus11VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_VEC_TOT, &susVecTot); + localDataPoolMap.emplace(acsctrl::PoolIds::SUS_VEC_TOT_DERIVATIVE, &susVecTotDer); + localDataPoolMap.emplace(acsctrl::PoolIds::SUN_IJK_MODEL, &sunIjk); + poolManager.subscribeForRegularPeriodicPacket({susDataProcessed.getSid(), enableHkSets, 60.0}); + // GYR Raw + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_0_ADIS, &gyr0VecRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_1_L3, &gyr1VecRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_2_ADIS, &gyr2VecRaw); + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_3_L3, &gyr3VecRaw); + poolManager.subscribeForDiagPeriodicPacket({gyrDataRaw.getSid(), false, 60.0}); + // GYR Processed + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_0_VEC, &gyr0VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_1_VEC, &gyr1VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_2_VEC, &gyr2VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_3_VEC, &gyr3VecProc); + localDataPoolMap.emplace(acsctrl::PoolIds::GYR_VEC_TOT, &gyrVecTot); + poolManager.subscribeForDiagPeriodicPacket({gyrDataProcessed.getSid(), enableHkSets, 60.0}); + // GPS Processed + localDataPoolMap.emplace(acsctrl::PoolIds::GC_LATITUDE, &gcLatitude); + localDataPoolMap.emplace(acsctrl::PoolIds::GD_LONGITUDE, &gdLongitude); + localDataPoolMap.emplace(acsctrl::PoolIds::ALTITUDE, &altitude); + localDataPoolMap.emplace(acsctrl::PoolIds::GPS_POSITION, &gpsPosition); + localDataPoolMap.emplace(acsctrl::PoolIds::GPS_VELOCITY, &gpsVelocity); + localDataPoolMap.emplace(acsctrl::PoolIds::SOURCE, &gpsSource); + poolManager.subscribeForRegularPeriodicPacket({gpsDataProcessed.getSid(), enableHkSets, 60.0}); + // Attitude Estimation + localDataPoolMap.emplace(acsctrl::PoolIds::QUAT_MEKF, &quatMekf); + localDataPoolMap.emplace(acsctrl::PoolIds::SAT_ROT_RATE_MEKF, &satRotRateMekf); + localDataPoolMap.emplace(acsctrl::PoolIds::MEKF_STATUS, &mekfStatus); + localDataPoolMap.emplace(acsctrl::PoolIds::QUAT_QUEST, &quatQuest); + poolManager.subscribeForDiagPeriodicPacket({attitudeEstimationData.getSid(), enableHkSets, 30.0}); + // Ctrl Values + localDataPoolMap.emplace(acsctrl::PoolIds::SAFE_STRAT, &safeStrat); + localDataPoolMap.emplace(acsctrl::PoolIds::TGT_QUAT, &tgtQuat); + localDataPoolMap.emplace(acsctrl::PoolIds::ERROR_QUAT, &errQuat); + localDataPoolMap.emplace(acsctrl::PoolIds::ERROR_ANG, &errAng); + localDataPoolMap.emplace(acsctrl::PoolIds::TGT_ROT_RATE, &tgtRotRate); + poolManager.subscribeForRegularPeriodicPacket({ctrlValData.getSid(), enableHkSets, 30.0}); + // Actuator CMD + localDataPoolMap.emplace(acsctrl::PoolIds::RW_TARGET_TORQUE, &rwTargetTorque); + localDataPoolMap.emplace(acsctrl::PoolIds::RW_TARGET_SPEED, &rwTargetSpeed); + localDataPoolMap.emplace(acsctrl::PoolIds::MTQ_TARGET_DIPOLE, &mtqTargetDipole); + poolManager.subscribeForRegularPeriodicPacket({actuatorCmdData.getSid(), enableHkSets, 30.0}); + // Fused Rot Rate + localDataPoolMap.emplace(acsctrl::PoolIds::ROT_RATE_TOT_SUSMGM, &rotRateTotSusMgm); + localDataPoolMap.emplace(acsctrl::PoolIds::ROT_RATE_TOT_SOURCE, &rotRateTotSource); + localDataPoolMap.emplace(acsctrl::PoolIds::ROT_RATE_SOURCE, &rotRateSource); + poolManager.subscribeForRegularPeriodicPacket({fusedRotRateData.getSid(), enableHkSets, 30.0}); + // Fused Rot Rate Sources + localDataPoolMap.emplace(acsctrl::PoolIds::ROT_RATE_ORTHOGONAL_SUSMGM, &rotRateOrthogonalSusMgm); + localDataPoolMap.emplace(acsctrl::PoolIds::ROT_RATE_PARALLEL_SUSMGM, &rotRateParallelSusMgm); + localDataPoolMap.emplace(acsctrl::PoolIds::ROT_RATE_TOTAL_SUSMGM, &rotRateTotalSusMgm); + localDataPoolMap.emplace(acsctrl::PoolIds::ROT_RATE_TOTAL_QUEST, &rotRateTotalQuest); + localDataPoolMap.emplace(acsctrl::PoolIds::ROT_RATE_TOTAL_STR, &rotRateTotalStr); + poolManager.subscribeForRegularPeriodicPacket({fusedRotRateSourcesData.getSid(), false, 60.0}); + return returnvalue::OK; +} + +LocalPoolDataSetBase *AcsController::getDataSetHandle(sid_t sid) { + switch (sid.ownerSetId) { + case acsctrl::MGM_SENSOR_DATA: + return &mgmDataRaw; + case acsctrl::MGM_PROCESSED_DATA: + return &mgmDataProcessed; + case acsctrl::SUS_SENSOR_DATA: + return &susDataRaw; + case acsctrl::SUS_PROCESSED_DATA: + return &susDataProcessed; + case acsctrl::GYR_SENSOR_DATA: + return &gyrDataRaw; + case acsctrl::GYR_PROCESSED_DATA: + return &gyrDataProcessed; + case acsctrl::GPS_PROCESSED_DATA: + return &gpsDataProcessed; + case acsctrl::MEKF_DATA: + return &attitudeEstimationData; + case acsctrl::CTRL_VAL_DATA: + return &ctrlValData; + case acsctrl::ACTUATOR_CMD_DATA: + return &actuatorCmdData; + case acsctrl::FUSED_ROTATION_RATE_DATA: + return &fusedRotRateData; + case acsctrl::FUSED_ROTATION_RATE_SOURCES_DATA: + return &fusedRotRateSourcesData; + default: + return nullptr; + } + return nullptr; +} + +ReturnValue_t AcsController::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t *msToReachTheMode) { + if (mode == MODE_OFF) { + if (submode == SUBMODE_NONE) { + return returnvalue::OK; + } else { + return INVALID_SUBMODE; + } + } else if (not((mode < acs::AcsMode::SAFE) or (mode > acs::AcsMode::PTG_INERTIAL))) { + if (mode == acs::AcsMode::SAFE) { + if (not((submode == SUBMODE_NONE) or (submode == acs::SafeSubmode::DETUMBLE))) { + return INVALID_SUBMODE; + } else { + return returnvalue::OK; + } + } else if (not(submode == SUBMODE_NONE)) { + return INVALID_SUBMODE; + } else { + return returnvalue::OK; + } + } + return INVALID_MODE; +} + +void AcsController::modeChanged(Mode_t mode, Submode_t submode) { + guidance.resetValues(); + if (mode == acs::AcsMode::SAFE) { + { + PoolReadGuard pg(&rw1SpeedSet); + rw1SpeedSet.setRwSpeed(0, 10); + } + { + PoolReadGuard pg(&rw2SpeedSet); + rw2SpeedSet.setRwSpeed(0, 10); + } + { + PoolReadGuard pg(&rw3SpeedSet); + rw3SpeedSet.setRwSpeed(0, 10); + } + { + PoolReadGuard pg(&rw4SpeedSet); + rw4SpeedSet.setRwSpeed(0, 10); + } + } + if (submode == acs::SafeSubmode::DETUMBLE) { + detumbleState = DetumbleState::IN_DETUMBLE; + } + if (detumbleState == DetumbleState::IN_DETUMBLE and submode != acs::SafeSubmode::DETUMBLE) { + detumbleState = DetumbleState::NO_DETUMBLE; + } + if (mode > acs::AcsMode::PTG_IDLE) { + poolManager.changeCollectionInterval(ctrlValData.getSid(), 10); + poolManager.changeCollectionInterval(actuatorCmdData.getSid(), 10); + poolManager.changeCollectionInterval(fusedRotRateData.getSid(), 10); + poolManager.changeCollectionInterval(attitudeEstimationData.getSid(), 10); + } else { + poolManager.changeCollectionInterval(ctrlValData.getSid(), 30); + poolManager.changeCollectionInterval(actuatorCmdData.getSid(), 30); + poolManager.changeCollectionInterval(fusedRotRateData.getSid(), 30); + poolManager.changeCollectionInterval(attitudeEstimationData.getSid(), 30); + } + return ExtendedControllerBase::modeChanged(mode, submode); +} + +void AcsController::announceMode(bool recursive) { + const char *modeStr = "UNKNOWN"; + if (mode == HasModesIF::MODE_OFF) { + modeStr = "OFF"; + } else { + modeStr = acs::getModeStr(static_cast(mode)); + } + const char *submodeStr = "UNKNOWN"; + if (submode == HasModesIF::SUBMODE_NONE) { + submodeStr = "NONE"; + } + if (mode == acs::AcsMode::SAFE) { + acs::SafeSubmode safeSubmode = static_cast(this->submode); + if (safeSubmode == acs::SafeSubmode::DETUMBLE) { + submodeStr = "DETUMBLE"; + } + } + sif::info << "ACS controller is now in " << modeStr << " mode with " << submodeStr << " submode" + << std::endl; + return ExtendedControllerBase::announceMode(recursive); +} + +void AcsController::copyMgmData() { + { + PoolReadGuard pg(&sensorValues.mgm0Lis3Set); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(mgmDataRaw.mgm0Lis3.value, sensorValues.mgm0Lis3Set.fieldStrengths.value, + 3 * sizeof(float)); + mgmDataRaw.mgm0Lis3.setValid(sensorValues.mgm0Lis3Set.fieldStrengths.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.mgm1Rm3100Set); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(mgmDataRaw.mgm1Rm3100.value, sensorValues.mgm1Rm3100Set.fieldStrengths.value, + 3 * sizeof(float)); + mgmDataRaw.mgm1Rm3100.setValid(sensorValues.mgm1Rm3100Set.fieldStrengths.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.mgm2Lis3Set); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(mgmDataRaw.mgm2Lis3.value, sensorValues.mgm2Lis3Set.fieldStrengths.value, + 3 * sizeof(float)); + mgmDataRaw.mgm2Lis3.setValid(sensorValues.mgm2Lis3Set.fieldStrengths.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.mgm3Rm3100Set); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(mgmDataRaw.mgm3Rm3100.value, sensorValues.mgm3Rm3100Set.fieldStrengths.value, + 3 * sizeof(float)); + mgmDataRaw.mgm3Rm3100.setValid(sensorValues.mgm3Rm3100Set.fieldStrengths.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.imtqMgmSet); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(mgmDataRaw.imtqRaw.value, sensorValues.imtqMgmSet.mtmRawNt.value, + 3 * sizeof(float)); + mgmDataRaw.imtqRaw.setValid(sensorValues.imtqMgmSet.mtmRawNt.isValid()); + mgmDataRaw.actuationCalStatus.value = sensorValues.imtqMgmSet.coilActuationStatus.value; + mgmDataRaw.actuationCalStatus.setValid(sensorValues.imtqMgmSet.coilActuationStatus.isValid()); + } + } +} + +void AcsController::copySusData() { + { + PoolReadGuard pg(&sensorValues.susSets[0]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus0.value, sensorValues.susSets[0].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus0.setValid(sensorValues.susSets[0].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[1]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus1.value, sensorValues.susSets[1].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus1.setValid(sensorValues.susSets[1].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[2]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus2.value, sensorValues.susSets[2].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus2.setValid(sensorValues.susSets[2].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[3]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus3.value, sensorValues.susSets[3].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus3.setValid(sensorValues.susSets[3].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[4]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus4.value, sensorValues.susSets[4].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus4.setValid(sensorValues.susSets[4].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[5]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus5.value, sensorValues.susSets[5].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus5.setValid(sensorValues.susSets[5].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[6]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus6.value, sensorValues.susSets[6].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus6.setValid(sensorValues.susSets[6].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[7]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus7.value, sensorValues.susSets[7].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus7.setValid(sensorValues.susSets[7].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[8]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus8.value, sensorValues.susSets[8].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus8.setValid(sensorValues.susSets[8].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[9]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus9.value, sensorValues.susSets[9].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus9.setValid(sensorValues.susSets[9].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[10]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus10.value, sensorValues.susSets[10].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus10.setValid(sensorValues.susSets[10].channels.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.susSets[11]); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataRaw.sus11.value, sensorValues.susSets[11].channels.value, + 6 * sizeof(uint16_t)); + susDataRaw.sus11.setValid(sensorValues.susSets[11].channels.isValid()); + } + } +} + +ReturnValue_t AcsController::updateTle(const uint8_t *line1, const uint8_t *line2, bool fromFile) { + ReturnValue_t result = navigation.updateTle(line1, line2); + if (result != returnvalue::OK) { + if (not fromFile) { + uint8_t fileLine1[69] = {}; + uint8_t fileLine2[69] = {}; + readTleFromFs(fileLine1, fileLine2); + navigation.updateTle(fileLine1, fileLine2); + } + return result; + } + return returnvalue::OK; +} + +ReturnValue_t AcsController::writeTleToFs(const uint8_t *tle) { + auto mntPrefix = sdcMan.getCurrentMountPrefix(); + if (mntPrefix == nullptr or !sdcMan.isSdCardUsable(std::nullopt)) { + return returnvalue::FAILED; + } + std::string path = mntPrefix + TLE_FILE; + // Clear existing TLE from file + std::ofstream tleFile(path.c_str(), std::ofstream::out | std::ofstream::trunc); + if (tleFile.is_open()) { + tleFile.write(reinterpret_cast(tle), 69); + tleFile << "\n"; + tleFile.write(reinterpret_cast(tle + 69), 69); + } else { + return acsctrl::WRITE_FILE_FAILED; + } + tleFile.close(); + return returnvalue::OK; +} + +ReturnValue_t AcsController::readTleFromFs(uint8_t *line1, uint8_t *line2) { + auto mntPrefix = sdcMan.getCurrentMountPrefix(); + if (mntPrefix == nullptr or !sdcMan.isSdCardUsable(std::nullopt)) { + return returnvalue::FAILED; + } + std::string path = mntPrefix + TLE_FILE; + std::error_code e; + if (std::filesystem::exists(path, e)) { + // Read existing TLE from file + std::fstream tleFile = std::fstream(path.c_str(), std::fstream::in); + if (tleFile.is_open()) { + std::string tleLine1, tleLine2; + getline(tleFile, tleLine1); + std::memcpy(line1, tleLine1.c_str(), 69); + getline(tleFile, tleLine2); + std::memcpy(line2, tleLine2.c_str(), 69); + } else { + triggerEvent(acs::TLE_FILE_READ_FAILED); + return acsctrl::READ_FILE_FAILED; + } + tleFile.close(); + } else { + triggerEvent(acs::TLE_FILE_READ_FAILED); + return acsctrl::READ_FILE_FAILED; + } + return returnvalue::OK; +} + +void AcsController::copyGyrData() { + { + PoolReadGuard pg(&sensorValues.gyr0AdisSet); + if (pg.getReadResult() == returnvalue::OK) { + gyrDataRaw.gyr0Adis.value[0] = sensorValues.gyr0AdisSet.angVelocX.value; + gyrDataRaw.gyr0Adis.value[1] = sensorValues.gyr0AdisSet.angVelocY.value; + gyrDataRaw.gyr0Adis.value[2] = sensorValues.gyr0AdisSet.angVelocZ.value; + gyrDataRaw.gyr0Adis.setValid(sensorValues.gyr0AdisSet.angVelocX.isValid() && + sensorValues.gyr0AdisSet.angVelocY.isValid() && + sensorValues.gyr0AdisSet.angVelocZ.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.gyr1L3gSet); + if (pg.getReadResult() == returnvalue::OK) { + gyrDataRaw.gyr1L3.value[0] = sensorValues.gyr1L3gSet.angVelocX.value; + gyrDataRaw.gyr1L3.value[1] = sensorValues.gyr1L3gSet.angVelocY.value; + gyrDataRaw.gyr1L3.value[2] = sensorValues.gyr1L3gSet.angVelocZ.value; + gyrDataRaw.gyr1L3.setValid(sensorValues.gyr1L3gSet.angVelocX.isValid() && + sensorValues.gyr1L3gSet.angVelocY.isValid() && + sensorValues.gyr1L3gSet.angVelocZ.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.gyr2AdisSet); + if (pg.getReadResult() == returnvalue::OK) { + gyrDataRaw.gyr2Adis.value[0] = sensorValues.gyr2AdisSet.angVelocX.value; + gyrDataRaw.gyr2Adis.value[1] = sensorValues.gyr2AdisSet.angVelocY.value; + gyrDataRaw.gyr2Adis.value[2] = sensorValues.gyr2AdisSet.angVelocZ.value; + gyrDataRaw.gyr2Adis.setValid(sensorValues.gyr2AdisSet.angVelocX.isValid() && + sensorValues.gyr2AdisSet.angVelocY.isValid() && + sensorValues.gyr2AdisSet.angVelocZ.isValid()); + } + } + { + PoolReadGuard pg(&sensorValues.gyr3L3gSet); + if (pg.getReadResult() == returnvalue::OK) { + gyrDataRaw.gyr3L3.value[0] = sensorValues.gyr3L3gSet.angVelocX.value; + gyrDataRaw.gyr3L3.value[1] = sensorValues.gyr3L3gSet.angVelocY.value; + gyrDataRaw.gyr3L3.value[2] = sensorValues.gyr3L3gSet.angVelocZ.value; + gyrDataRaw.gyr3L3.setValid(sensorValues.gyr3L3gSet.angVelocX.isValid() && + sensorValues.gyr3L3gSet.angVelocY.isValid() && + sensorValues.gyr3L3gSet.angVelocZ.isValid()); + } + } +} diff --git a/mission/controller/AcsController.h b/mission/controller/AcsController.h new file mode 100644 index 0000000..74ff8fd --- /dev/null +++ b/mission/controller/AcsController.h @@ -0,0 +1,294 @@ +#ifndef MISSION_CONTROLLER_ACSCONTROLLER_H_ +#define MISSION_CONTROLLER_ACSCONTROLLER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class AcsController : public ExtendedControllerBase, public ReceivesParameterMessagesIF { + public: + static constexpr dur_millis_t INIT_DELAY = 500; + + AcsController(object_id_t objectId, bool enableHkSets, SdCardMountedIF& sdcMan); + + MessageQueueId_t getCommandQueue() const; + ReturnValue_t getParameter(uint8_t domainId, uint8_t parameterId, + ParameterWrapper* parameterWrapper, const ParameterWrapper* newValues, + uint16_t startAtIndex) override; + + protected: + private: + static constexpr int16_t ZERO_VEC3_INT16[3] = {0, 0, 0}; + static constexpr double ZERO_VEC3[3] = {0, 0, 0}; + static constexpr double ZERO_VEC4[4] = {0, 0, 0, 0}; + static constexpr double RW_OFF_TORQUE[4] = {0, 0, 0, 0}; + static constexpr int32_t RW_OFF_SPEED[4] = {0, 0, 0, 0}; + + bool enableHkSets = false; + + SdCardMountedIF& sdcMan; + + timeval timeAbsolute; + timeval timeRelative; + double timeDelta = 0.0; + timeval oldTimeRelative; + + AcsParameters acsParameters; + SensorProcessing sensorProcessing; + AttitudeEstimation attitudeEstimation; + FusedRotationEstimation fusedRotationEstimation; + Navigation navigation; + ActuatorCmd actuatorCmd; + Guidance guidance; + + SafeCtrl safeCtrl; + Detumble detumble; + PtgCtrl ptgCtrl; + + ParameterHelper parameterHelper; + + bool tleTooOldFlag = false; + uint8_t detumbleCounter = 0; + uint8_t multipleRwUnavailableCounter = 0; + bool mekfInvalidFlag = false; + uint16_t ptgCtrlLostCounter = 0; + bool safeCtrlFailureFlag = false; + uint8_t safeCtrlFailureCounter = 0; + uint8_t resetMekfCount = 0; + bool mekfLost = false; + + int32_t cmdSpeedRws[4] = {0, 0, 0, 0}; + int16_t cmdDipoleMtqs[3] = {0, 0, 0}; + + acsctrl::RwAvail rwAvail; + +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter = 0; +#endif + + enum class InternalState { STARTUP, INITIAL_DELAY, READY }; + InternalState internalState = InternalState::STARTUP; + + enum class DetumbleState { + NO_DETUMBLE, + DETUMBLE_FROM_PTG, + PTG_TO_SAFE_TRANSITION, + DETUMBLE_FROM_SAFE, + IN_DETUMBLE + }; + DetumbleState detumbleState = DetumbleState::NO_DETUMBLE; + + /** Device command IDs */ + static const DeviceCommandId_t SOLAR_ARRAY_DEPLOYMENT_SUCCESSFUL = 0x0; + static const DeviceCommandId_t RESET_MEKF = 0x1; + static const DeviceCommandId_t RESTORE_MEKF_NONFINITE_RECOVERY = 0x2; + static const DeviceCommandId_t UPDATE_TLE = 0x3; + static const DeviceCommandId_t READ_TLE = 0x4; + static const DeviceCommandId_t UPDATE_MEKF_STANDARD_DEVIATIONS = 0x5; + + ReturnValue_t initialize() override; + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + void performControlOperation() override; + + /* HasActionsIF overrides */ + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + // Mode abstract functions + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + void modeChanged(Mode_t mode, Submode_t submode); + void announceMode(bool recursive); + + void performAttitudeControl(); + void performSafe(); + void performDetumble(); + void performPointingCtrl(); + + void handleDetumbling(); + + void safeCtrlFailure(uint8_t mgmFailure, uint8_t sensorFailure); + + ReturnValue_t commandActuators(int16_t xDipole, int16_t yDipole, int16_t zDipole, + uint16_t dipoleTorqueDuration); + ReturnValue_t commandActuators(int16_t xDipole, int16_t yDipole, int16_t zDipole, + uint16_t dipoleTorqueDuration, int32_t rw1Speed, int32_t rw2Speed, + int32_t rw3Speed, int32_t rw4Speed, uint16_t rampTime); + void updateActuatorCmdData(const int16_t* mtqTargetDipole); + void updateActuatorCmdData(const double* rwTargetTorque, const int32_t* rwTargetSpeed, + const int16_t* mtqTargetDipole); + void updateCtrlValData(acs::ControlModeStrategy ctrlStrat); + void updateCtrlValData(double errAng, acs::ControlModeStrategy ctrlStrat); + void updateCtrlValData(const double* tgtQuat, const double* errQuat, double errAng, + const double* tgtRotRate, acs::ControlModeStrategy cStrat); + + ReturnValue_t updateTle(const uint8_t* line1, const uint8_t* line2, bool fromFile); + ReturnValue_t writeTleToFs(const uint8_t* tle); + ReturnValue_t readTleFromFs(uint8_t* line1, uint8_t* line2); + + const std::string TLE_FILE = "/conf/tle.txt"; + + /* ACS Sensor Values */ + ACS::SensorValues sensorValues; + + /* ACS Actuation Datasets */ + imtq::DipoleActuationSet dipoleSet = imtq::DipoleActuationSet(objects::IMTQ_HANDLER); + rws::RwSpeedActuationSet rw1SpeedSet = rws::RwSpeedActuationSet(objects::RW1); + rws::RwSpeedActuationSet rw2SpeedSet = rws::RwSpeedActuationSet(objects::RW2); + rws::RwSpeedActuationSet rw3SpeedSet = rws::RwSpeedActuationSet(objects::RW3); + rws::RwSpeedActuationSet rw4SpeedSet = rws::RwSpeedActuationSet(objects::RW4); + /* ACS Datasets */ + // MGMs + acsctrl::MgmDataRaw mgmDataRaw; + PoolEntry mgm0VecRaw = PoolEntry(3); + PoolEntry mgm1VecRaw = PoolEntry(3); + PoolEntry mgm2VecRaw = PoolEntry(3); + PoolEntry mgm3VecRaw = PoolEntry(3); + PoolEntry imtqMgmVecRaw = PoolEntry(3); + PoolEntry imtqCalActStatus = PoolEntry(); + void copyMgmData(); + + acsctrl::MgmDataProcessed mgmDataProcessed; + PoolEntry mgm0VecProc = PoolEntry(3); + PoolEntry mgm1VecProc = PoolEntry(3); + PoolEntry mgm2VecProc = PoolEntry(3); + PoolEntry mgm3VecProc = PoolEntry(3); + PoolEntry mgm4VecProc = PoolEntry(3); + PoolEntry mgmVecTot = PoolEntry(3); + PoolEntry mgmVecTotDer = PoolEntry(3); + PoolEntry magIgrf = PoolEntry(3); + + // SUSs + acsctrl::SusDataRaw susDataRaw; + PoolEntry sus0ValRaw = PoolEntry(6); + PoolEntry sus1ValRaw = PoolEntry(6); + PoolEntry sus2ValRaw = PoolEntry(6); + PoolEntry sus3ValRaw = PoolEntry(6); + PoolEntry sus4ValRaw = PoolEntry(6); + PoolEntry sus5ValRaw = PoolEntry(6); + PoolEntry sus6ValRaw = PoolEntry(6); + PoolEntry sus7ValRaw = PoolEntry(6); + PoolEntry sus8ValRaw = PoolEntry(6); + PoolEntry sus9ValRaw = PoolEntry(6); + PoolEntry sus10ValRaw = PoolEntry(6); + PoolEntry sus11ValRaw = PoolEntry(6); + void copySusData(); + + acsctrl::SusDataProcessed susDataProcessed; + PoolEntry sus0VecProc = PoolEntry(3); + PoolEntry sus1VecProc = PoolEntry(3); + PoolEntry sus2VecProc = PoolEntry(3); + PoolEntry sus3VecProc = PoolEntry(3); + PoolEntry sus4VecProc = PoolEntry(3); + PoolEntry sus5VecProc = PoolEntry(3); + PoolEntry sus6VecProc = PoolEntry(3); + PoolEntry sus7VecProc = PoolEntry(3); + PoolEntry sus8VecProc = PoolEntry(3); + PoolEntry sus9VecProc = PoolEntry(3); + PoolEntry sus10VecProc = PoolEntry(3); + PoolEntry sus11VecProc = PoolEntry(3); + PoolEntry susVecTot = PoolEntry(3); + PoolEntry susVecTotDer = PoolEntry(3); + PoolEntry sunIjk = PoolEntry(3); + + // GYRs + acsctrl::GyrDataRaw gyrDataRaw; + PoolEntry gyr0VecRaw = PoolEntry(3); + PoolEntry gyr1VecRaw = PoolEntry(3); + PoolEntry gyr2VecRaw = PoolEntry(3); + PoolEntry gyr3VecRaw = PoolEntry(3); + void copyGyrData(); + + acsctrl::GyrDataProcessed gyrDataProcessed; + PoolEntry gyr0VecProc = PoolEntry(3); + PoolEntry gyr1VecProc = PoolEntry(3); + PoolEntry gyr2VecProc = PoolEntry(3); + PoolEntry gyr3VecProc = PoolEntry(3); + PoolEntry gyrVecTot = PoolEntry(3); + + // GPS + acsctrl::GpsDataProcessed gpsDataProcessed; + PoolEntry gcLatitude = PoolEntry(); + PoolEntry gdLongitude = PoolEntry(); + PoolEntry altitude = PoolEntry(); + PoolEntry gpsPosition = PoolEntry(3); + PoolEntry gpsVelocity = PoolEntry(3); + PoolEntry gpsSource = PoolEntry(); + + // Attitude Estimation + acsctrl::AttitudeEstimationData attitudeEstimationData; + PoolEntry quatMekf = PoolEntry(4); + PoolEntry satRotRateMekf = PoolEntry(3); + PoolEntry mekfStatus = PoolEntry(); + PoolEntry quatQuest = PoolEntry(4); + + // Ctrl Values + acsctrl::CtrlValData ctrlValData; + PoolEntry safeStrat = PoolEntry(); + PoolEntry tgtQuat = PoolEntry(4); + PoolEntry errQuat = PoolEntry(4); + PoolEntry errAng = PoolEntry(); + PoolEntry tgtRotRate = PoolEntry(3); + + // Actuator CMD + acsctrl::ActuatorCmdData actuatorCmdData; + PoolEntry rwTargetTorque = PoolEntry(4); + PoolEntry rwTargetSpeed = PoolEntry(4); + PoolEntry mtqTargetDipole = PoolEntry(3); + + // Fused Rot Rate + acsctrl::FusedRotRateData fusedRotRateData; + PoolEntry rotRateTotSusMgm = PoolEntry(3); + PoolEntry rotRateTotSource = PoolEntry(3); + PoolEntry rotRateSource = PoolEntry(); + + // Fused Rot Rate Sources + acsctrl::FusedRotRateSourcesData fusedRotRateSourcesData; + PoolEntry rotRateOrthogonalSusMgm = PoolEntry(3); + PoolEntry rotRateParallelSusMgm = PoolEntry(3); + PoolEntry rotRateTotalSusMgm = PoolEntry(3); + PoolEntry rotRateTotalQuest = PoolEntry(3); + PoolEntry rotRateTotalStr = PoolEntry(3); + + // Initial delay to make sure all pool variables have been initialized their owners + Countdown initialCountdown = Countdown(INIT_DELAY); + + // Countdown after which the detumbling mode change should have been finished + static constexpr dur_millis_t MAX_DURATION = 60 * 1e3; + Countdown detumbleTransitionCountdow = Countdown(MAX_DURATION); +}; + +#endif /* MISSION_CONTROLLER_ACSCONTROLLER_H_ */ diff --git a/mission/controller/CMakeLists.txt b/mission/controller/CMakeLists.txt new file mode 100644 index 0000000..690ede6 --- /dev/null +++ b/mission/controller/CMakeLists.txt @@ -0,0 +1,7 @@ +if(TGT_BSP MATCHES "arm/q7s" OR TGT_BSP MATCHES "") + target_sources( + ${LIB_EIVE_MISSION} PRIVATE ThermalController.cpp AcsController.cpp + PowerController.cpp) +endif() + +add_subdirectory(acs) diff --git a/mission/controller/PowerController.cpp b/mission/controller/PowerController.cpp new file mode 100644 index 0000000..e4ad9f8 --- /dev/null +++ b/mission/controller/PowerController.cpp @@ -0,0 +1,372 @@ +#include + +PowerController::PowerController(object_id_t objectId, bool enableHkSets) + : ExtendedControllerBase(objectId), + enableHkSets(enableHkSets), + parameterHelper(this), + pwrCtrlCoreHk(this), + enablePl(this) {} + +ReturnValue_t PowerController::initialize() { + ReturnValue_t result = parameterHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + return ExtendedControllerBase::initialize(); +} + +ReturnValue_t PowerController::handleCommandMessage(CommandMessage *message) { + ReturnValue_t result = actionHelper.handleActionMessage(message); + if (result == returnvalue::OK) { + return result; + } + result = parameterHelper.handleParameterMessage(message); + if (result == returnvalue::OK) { + return result; + } + return result; +} + +MessageQueueId_t PowerController::getCommandQueue() const { return commandQueue->getId(); } + +ReturnValue_t PowerController::getParameter(uint8_t domainId, uint8_t parameterId, + ParameterWrapper *parameterWrapper, + const ParameterWrapper *newValues, + uint16_t startAtIndex) { + switch (domainId) { + case 0x0: // direct members + switch (parameterId) { + case 0x0: + parameterWrapper->set(batteryInternalResistance); + break; + case 0x1: + parameterWrapper->set(batteryMaximumCapacity); + break; + case 0x2: { + float oldCoulombCounterVoltageUpperThreshold = coulombCounterVoltageUpperThreshold; + ReturnValue_t result = newValues->getElement(&coulombCounterVoltageUpperThreshold); + if (result != returnvalue::OK) { + coulombCounterVoltageUpperThreshold = oldCoulombCounterVoltageUpperThreshold; + return result; + } + result = calculateCoulombCounterChargeUpperThreshold(); + if (result != returnvalue::OK) { + coulombCounterVoltageUpperThreshold = oldCoulombCounterVoltageUpperThreshold; + return result; + } + parameterWrapper->set(coulombCounterVoltageUpperThreshold); + break; + } + case 0x3: + parameterWrapper->set(maxAllowedTimeDiff); + break; + case 0x4: + parameterWrapper->set(payloadOpLimitOn); + break; + case 0x5: + parameterWrapper->set(payloadOpLimitLow); + break; + case 0x6: + parameterWrapper->set(higherModesLimit); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + default: + return INVALID_DOMAIN_ID; + }; + return returnvalue::OK; +} + +void PowerController::performControlOperation() { + switch (internalState) { + case InternalState::STARTUP: { + initialCountdown.resetTimer(); + internalState = InternalState::INITIAL_DELAY; + return; + } + case InternalState::INITIAL_DELAY: { + if (initialCountdown.hasTimedOut()) { + internalState = InternalState::INIT; + } + return; + } + case InternalState::INIT: { + ReturnValue_t result = calculateCoulombCounterChargeUpperThreshold(); + if (result == returnvalue::OK) { + internalState = InternalState::READY; + } + return; + } + case InternalState::READY: { + if (mode != MODE_NORMAL) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + enablePl.setValidity(false, true); + } + } + if (mode != MODE_OFF) { + calculateStateOfCharge(); + if (mode == MODE_NORMAL) { + watchStateOfCharge(); + } + } + break; + } + default: + break; + } +} + +ReturnValue_t PowerController::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(pwrctrl::PoolIds::TOTAL_BATTERY_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(pwrctrl::PoolIds::OPEN_CIRCUIT_VOLTAGE_CHARGE, + new PoolEntry({0.0})); + localDataPoolMap.emplace(pwrctrl::PoolIds::COULOMB_COUNTER_CHARGE, new PoolEntry({0.0})); + poolManager.subscribeForRegularPeriodicPacket({pwrCtrlCoreHk.getSid(), enableHkSets, 60.0}); + localDataPoolMap.emplace(pwrctrl::PoolIds::PAYLOAD_FLAG, new PoolEntry({false})); + poolManager.subscribeForRegularPeriodicPacket({enablePl.getSid(), false, 60.0}); + return returnvalue::OK; +} + +LocalPoolDataSetBase *PowerController::getDataSetHandle(sid_t sid) { + switch (sid.ownerSetId) { + case pwrctrl::CORE_HK: + return &pwrCtrlCoreHk; + case pwrctrl::ENABLE_PL: + return &enablePl; + default: + return nullptr; + } + return nullptr; +} + +ReturnValue_t PowerController::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t *msToReachTheMode) { + if (mode == MODE_OFF or mode == MODE_ON or mode == MODE_NORMAL) { + if (submode == SUBMODE_NONE) { + return returnvalue::OK; + } else { + return INVALID_SUBMODE; + } + } + return INVALID_MODE; +} + +void PowerController::calculateStateOfCharge() { + // get time + Clock::getClockMonotonic(&now); + double timeDelta = 0.0; + if (now.tv_sec != 0 and oldTime.tv_sec != 0) { + timeDelta = timevalOperations::toDouble(now - oldTime); + } + oldTime = now; + + // update EPS HK values + ReturnValue_t result = updateEpsData(); + if (result != returnvalue::OK) { + triggerEvent(power::DATASET_READ_FAILED); + sif::error << "Power Controller::Reading of Datasets has failed" << std::endl; + { + PoolReadGuard pg(&pwrCtrlCoreHk); + if (pg.getReadResult() == returnvalue::OK) { + pwrCtrlCoreHk.totalBatteryCurrent.value = INVALID_TOTAL_BATTERY_CURRENT; + pwrCtrlCoreHk.openCircuitVoltageCharge.value = INVALID_SOC; + pwrCtrlCoreHk.coulombCounterCharge.value = INVALID_SOC; + pwrCtrlCoreHk.setValidity(false, true); + } + } + return; + } + + // calculate total battery current + iBat = p60CoreHk.batteryCurrent.value - bpxBatteryHk.dischargeCurrent.value; + + result = calculateOpenCircuitVoltageCharge(); + if (result != returnvalue::OK) { + // notifying events have already been triggered + { + PoolReadGuard pg(&pwrCtrlCoreHk); + if (pg.getReadResult() == returnvalue::OK) { + pwrCtrlCoreHk.totalBatteryCurrent.value = iBat; + pwrCtrlCoreHk.totalBatteryCurrent.setValid(true); + pwrCtrlCoreHk.openCircuitVoltageCharge.value = INVALID_SOC; + pwrCtrlCoreHk.openCircuitVoltageCharge.setValid(false); + pwrCtrlCoreHk.coulombCounterCharge.value = INVALID_SOC; + pwrCtrlCoreHk.coulombCounterCharge.setValid(false); + } + } + return; + } + + result = calculateCoulombCounterCharge(timeDelta); + if (result != returnvalue::OK) { + // notifying events have already been triggered + { + PoolReadGuard pg(&pwrCtrlCoreHk); + if (pg.getReadResult() == returnvalue::OK) { + pwrCtrlCoreHk.totalBatteryCurrent.value = iBat; + pwrCtrlCoreHk.totalBatteryCurrent.setValid(true); + pwrCtrlCoreHk.openCircuitVoltageCharge.value = + charge2stateOfCharge(openCircuitVoltageCharge, false); + pwrCtrlCoreHk.openCircuitVoltageCharge.setValid(true); + pwrCtrlCoreHk.coulombCounterCharge.value = INVALID_SOC; + pwrCtrlCoreHk.coulombCounterCharge.setValid(false); + } + } + return; + } + + // commit to dataset + { + PoolReadGuard pg(&pwrCtrlCoreHk); + if (pg.getReadResult() == returnvalue::OK) { + pwrCtrlCoreHk.totalBatteryCurrent.value = iBat; + pwrCtrlCoreHk.openCircuitVoltageCharge.value = + charge2stateOfCharge(openCircuitVoltageCharge, false); + pwrCtrlCoreHk.coulombCounterCharge.value = charge2stateOfCharge(coulombCounterCharge, true); + pwrCtrlCoreHk.setValidity(true, true); + } + } +} + +void PowerController::watchStateOfCharge() { + if (pwrCtrlCoreHk.coulombCounterCharge.isValid()) { + if (pwrCtrlCoreHk.coulombCounterCharge.value < payloadOpLimitOn) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + enablePl.plUseAllowed.value = false; + enablePl.setValidity(true, true); + } + } else { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + enablePl.plUseAllowed.value = true; + enablePl.setValidity(true, true); + } + } + if (not pwrLvlLowFlag and pwrCtrlCoreHk.coulombCounterCharge.value < payloadOpLimitLow) { + triggerEvent(power::POWER_LEVEL_LOW); + pwrLvlLowFlag = true; + } else if (pwrLvlLowFlag and pwrCtrlCoreHk.coulombCounterCharge.value > payloadOpLimitLow) { + pwrLvlLowFlag = false; + } + if (not pwrLvlCriticalFlag and pwrCtrlCoreHk.coulombCounterCharge.value < higherModesLimit) { + triggerEvent(power::POWER_LEVEL_CRITICAL); + pwrLvlCriticalFlag = true; + } else if (pwrLvlCriticalFlag and pwrCtrlCoreHk.coulombCounterCharge.value > higherModesLimit) { + pwrLvlCriticalFlag = false; + } + } else { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + enablePl.plUseAllowed.value = false; + enablePl.setValidity(true, true); + } + } +} + +ReturnValue_t PowerController::calculateOpenCircuitVoltageCharge() { + float vBatCorrected = + (bpxBatteryHk.battVoltage.value - iBat * batteryInternalResistance) * CONVERT_FROM_MILLI; + uint8_t lookUpTableIdx = LOOK_UP_TABLE_MAX_IDX; + ReturnValue_t result = lookUpTableOcvIdxFinder(vBatCorrected, lookUpTableIdx, false); + if (result != returnvalue::OK) { + return result; + } + openCircuitVoltageCharge = linearInterpolation( + vBatCorrected, lookUpTableOcv[1][lookUpTableIdx], lookUpTableOcv[1][lookUpTableIdx + 1], + lookUpTableOcv[0][lookUpTableIdx], lookUpTableOcv[0][lookUpTableIdx + 1]); + return returnvalue::OK; +} + +ReturnValue_t PowerController::calculateCoulombCounterCharge(double timeDelta) { + if (timeDelta == 0.0) { + return returnvalue::FAILED; + } + if (timeDelta > maxAllowedTimeDiff) { + // should not be a permanent state so no spam protection required + triggerEvent(power::TIMEDELTA_OUT_OF_BOUNDS, static_cast(timeDelta * 10)); + sif::error << "Power Controller::Time delta too large for Coulomb Counter: " << timeDelta + << std::endl; + return returnvalue::FAILED; + } + if (not pwrCtrlCoreHk.coulombCounterCharge.isValid()) { + coulombCounterCharge = openCircuitVoltageCharge; + } else { + coulombCounterCharge = + coulombCounterCharge + iBat * CONVERT_FROM_MILLI * timeDelta * SECONDS_TO_HOURS; + if (coulombCounterCharge >= coulombCounterChargeUpperThreshold) { + coulombCounterCharge = coulombCounterChargeUpperThreshold; + } + } + return returnvalue::OK; +} + +ReturnValue_t PowerController::updateEpsData() { + std::vector results; + { + PoolReadGuard pgBat(&bpxBatteryHk); + results.push_back(pgBat.getReadResult()); + } + { + PoolReadGuard pgP60(&p60CoreHk); + results.push_back(pgP60.getReadResult()); + } + for (const auto &result : results) { + if (result != returnvalue::OK) { + return result; + } + } + return returnvalue::OK; +} + +float PowerController::charge2stateOfCharge(float capacity, bool coulombCounter) { + if (coulombCounter) { + return capacity / coulombCounterChargeUpperThreshold; + } + return capacity / batteryMaximumCapacity; +} + +float PowerController::linearInterpolation(float x, float x0, float x1, float y0, float y1) { + return y0 + (x - x0) * (y1 - y0) / (x1 - x0); +} + +ReturnValue_t PowerController::lookUpTableOcvIdxFinder(float voltage, uint8_t &idx, bool paramCmd) { + if (voltage >= lookUpTableOcv[1][99]) { + if (not voltageOutOfBoundsFlag and not paramCmd) { + triggerEvent(power::VOLTAGE_OUT_OF_BOUNDS, 0, static_cast(voltage * 10)); + voltageOutOfBoundsFlag = true; + } + sif::error << "Power Controller::Voltage is too high: " << voltage << std::endl; + return returnvalue::FAILED; + } else if (voltage <= lookUpTableOcv[1][0]) { + if (not voltageOutOfBoundsFlag and not paramCmd) { + triggerEvent(power::VOLTAGE_OUT_OF_BOUNDS, 1, static_cast(voltage * 10)); + voltageOutOfBoundsFlag = true; + } + sif::error << "Power Controller::Voltage is too low: " << voltage << std::endl; + return returnvalue::FAILED; + } + voltageOutOfBoundsFlag = false; + while (lookUpTableOcv[1][idx] > voltage) { + idx--; + } + return returnvalue::OK; +} + +ReturnValue_t PowerController::calculateCoulombCounterChargeUpperThreshold() { + uint8_t lookUpTableIdx = LOOK_UP_TABLE_MAX_IDX; + ReturnValue_t result = + lookUpTableOcvIdxFinder(coulombCounterVoltageUpperThreshold, lookUpTableIdx, true); + if (result != returnvalue::OK) { + return result; + } + coulombCounterChargeUpperThreshold = + linearInterpolation(coulombCounterVoltageUpperThreshold, lookUpTableOcv[1][lookUpTableIdx], + lookUpTableOcv[1][lookUpTableIdx + 1], lookUpTableOcv[0][lookUpTableIdx], + lookUpTableOcv[0][lookUpTableIdx + 1]); + return returnvalue::OK; +} diff --git a/mission/controller/PowerController.h b/mission/controller/PowerController.h new file mode 100644 index 0000000..db3fced --- /dev/null +++ b/mission/controller/PowerController.h @@ -0,0 +1,131 @@ +#ifndef MISSION_CONTROLLER_POWERCONTROLLER_H_ +#define MISSION_CONTROLLER_POWERCONTROLLER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class PowerController : public ExtendedControllerBase, public ReceivesParameterMessagesIF { + public: + static constexpr dur_millis_t INIT_DELAY = 500; + + PowerController(object_id_t objectId, bool enableHkSets); + + MessageQueueId_t getCommandQueue() const; + ReturnValue_t getParameter(uint8_t domainId, uint8_t parameterId, + ParameterWrapper* parameterWrapper, const ParameterWrapper* newValues, + uint16_t startAtIndex) override; + + private: + bool enableHkSets = false; + ParameterHelper parameterHelper; + + enum class InternalState { STARTUP, INITIAL_DELAY, INIT, READY }; + InternalState internalState = InternalState::STARTUP; + + // Initial delay to make sure all pool variables have been initialized their owners + Countdown initialCountdown = Countdown(INIT_DELAY); + + ReturnValue_t initialize() override; + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + void performControlOperation() override; + + void calculateStateOfCharge(); + void watchStateOfCharge(); + ReturnValue_t calculateOpenCircuitVoltageCharge(); + ReturnValue_t calculateCoulombCounterCharge(double timeDelta); + ReturnValue_t updateEpsData(); + float charge2stateOfCharge(float capacity, bool coulombCounter); + ReturnValue_t lookUpTableOcvIdxFinder(float voltage, uint8_t& idx, bool paramCmd); + float linearInterpolation(float x, float x0, float x1, float y0, float y1); + ReturnValue_t calculateCoulombCounterChargeUpperThreshold(); + + // Parameters + float batteryInternalResistance = 0.06798200367; // [Ohm] + float batteryMaximumCapacity = 2.6 * 2; // [Ah] + float coulombCounterVoltageUpperThreshold = 16.2; // [V] + double maxAllowedTimeDiff = 1.5; // [s] + float payloadOpLimitOn = 0.80; // [%] + float payloadOpLimitLow = 0.65; // [%] + float higherModesLimit = 0.60; // [%] + + // OCV Look-up-Table {[Ah],[V]} + static constexpr uint8_t LOOK_UP_TABLE_MAX_IDX = 99; + float lookUpTableOcv[2][100] = { + {0.00000000e+00, 3.16227766e-04, 4.52809661e-04, 6.48382625e-04, 9.28425483e-04, + 1.32942162e-03, 1.90361194e-03, 2.72580074e-03, 3.90310099e-03, 5.58888885e-03, + 8.00278514e-03, 1.14592671e-02, 1.64086377e-02, 2.34956903e-02, 3.36437110e-02, + 4.81747620e-02, 6.89819174e-02, 9.87758887e-02, 1.41438170e-01, 2.02526713e-01, + 2.90000000e-01, 3.00000000e-01, 3.62820513e-01, 4.25641026e-01, 4.88461538e-01, + 5.51282051e-01, 6.14102564e-01, 6.76923077e-01, 7.39743590e-01, 8.02564103e-01, + 8.65384615e-01, 9.28205128e-01, 9.91025641e-01, 1.05384615e+00, 1.11666667e+00, + 1.17948718e+00, 1.24230769e+00, 1.30512821e+00, 1.36794872e+00, 1.43076923e+00, + 1.49358974e+00, 1.55641026e+00, 1.61923077e+00, 1.68205128e+00, 1.74487179e+00, + 1.80769231e+00, 1.87051282e+00, 1.93333333e+00, 1.99615385e+00, 2.05897436e+00, + 2.12179487e+00, 2.18461538e+00, 2.24743590e+00, 2.31025641e+00, 2.37307692e+00, + 2.43589744e+00, 2.49871795e+00, 2.56153846e+00, 2.62435897e+00, 2.68717949e+00, + 2.75000000e+00, 2.81282051e+00, 2.87564103e+00, 2.93846154e+00, 3.00128205e+00, + 3.06410256e+00, 3.12692308e+00, 3.18974359e+00, 3.25256410e+00, 3.31538462e+00, + 3.37820513e+00, 3.44102564e+00, 3.50384615e+00, 3.56666667e+00, 3.62948718e+00, + 3.69230769e+00, 3.75512821e+00, 3.81794872e+00, 3.88076923e+00, 3.94358974e+00, + 4.00641026e+00, 4.06923077e+00, 4.13205128e+00, 4.19487179e+00, 4.25769231e+00, + 4.32051282e+00, 4.38333333e+00, 4.44615385e+00, 4.50897436e+00, 4.57179487e+00, + 4.63461538e+00, 4.69743590e+00, 4.76025641e+00, 4.82307692e+00, 4.88589744e+00, + 4.94871795e+00, 5.01153846e+00, 5.07435897e+00, 5.13717949e+00, 5.20000000e+00}, + {12.52033533, 12.58720948, 12.61609309, 12.65612591, 12.67105282, 12.69242681, 12.72303245, + 12.76685696, 12.80313768, 12.83600741, 12.8830739, 12.94720576, 13.00112629, 13.07833563, + 13.17486308, 13.27128842, 13.37713879, 13.49275604, 13.60395193, 13.68708863, 13.75196335, + 13.7582376, 13.79298643, 13.82885799, 13.87028849, 13.91585718, 13.96701874, 14.02343574, + 14.07665641, 14.12626342, 14.1675095, 14.20582917, 14.23342159, 14.25724476, 14.27264301, + 14.28922389, 14.30898535, 14.32750837, 14.34358057, 14.35965277, 14.37698366, 14.3943261, + 14.41079196, 14.42679817, 14.44261008, 14.45771025, 14.47281042, 14.48751461, 14.50193089, + 14.5164887, 14.53193477, 14.54738084, 14.56341235, 14.58054578, 14.59799552, 14.61632769, + 14.63716465, 14.66935073, 14.70511347, 14.74315094, 14.77251031, 14.80005585, 14.8315427, + 14.86078285, 14.89444687, 14.93495892, 14.97114013, 15.01055751, 15.0538516, 15.09698825, + 15.14850029, 15.18947994, 15.24249483, 15.28521713, 15.335695, 15.37950723, 15.43241224, + 15.48082213, 15.53314287, 15.58907248, 15.64030253, 15.68385331, 15.74149122, 15.80051882, + 15.84959348, 15.90443241, 15.95743724, 16.01283068, 16.07629253, 16.13470801, 16.1890518, + 16.24200781, 16.30521118, 16.37368429, 16.43661267, 16.49604875, 16.56223813, 16.62741412, + 16.67249918, 16.74926904}}; + + // Variables + timeval now; + timeval oldTime; + int16_t iBat = 0; // [mA] + float openCircuitVoltageCharge = 0.0; // [Ah] + float coulombCounterCharge = 0.0; // [Ah] + float coulombCounterChargeUpperThreshold = 0.0; // [Ah] + float oldCoulombCounterVoltageUpperThreshold = 0.0; // [V] + + static constexpr float CONVERT_FROM_MILLI = 1e-3; + static constexpr float SECONDS_TO_HOURS = 1. / (60. * 60.); + + static constexpr int16_t INVALID_TOTAL_BATTERY_CURRENT = 0; + static constexpr float INVALID_SOC = -1; + + bool pwrLvlLowFlag = false; + bool pwrLvlCriticalFlag = false; + bool voltageOutOfBoundsFlag = false; + + // HK Datasets for Calculation + BpxBatteryHk bpxBatteryHk = BpxBatteryHk(objects::BPX_BATT_HANDLER); + P60Dock::CoreHkSet p60CoreHk = P60Dock::CoreHkSet(objects::P60DOCK_HANDLER); + // Output Dataset + pwrctrl::CoreHk pwrCtrlCoreHk; + // Dataset for PL Flag + pwrctrl::EnablePl enablePl; +}; + +#endif /* MISSION_CONTROLLER_POWERCONTROLLER_H_ */ diff --git a/mission/controller/ThermalController.cpp b/mission/controller/ThermalController.cpp new file mode 100644 index 0000000..c406a5b --- /dev/null +++ b/mission/controller/ThermalController.cpp @@ -0,0 +1,1938 @@ +#include "ThermalController.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Enabling this should trigger a special event which in turn should trigger a system reaction. +#define LOWER_SYRLINKS_UPPER_LIMITS 0 +#define LOWER_EBAND_UPPER_LIMITS 0 +#define LOWER_PLOC_UPPER_LIMITS 0 +#define LOWER_MGT_UPPER_LIMITS 0 +#define LOWER_RW_UPPER_LIMITS 0 + +ThermalController::ThermalController(object_id_t objectId, HeaterHandler& heater, + const std::atomic_bool& tcsBoardShortUnavailable, + bool pollPcdu1Tmp) + : ExtendedControllerBase(objectId), + heaterHandler(heater), + pollPcdu1Tmp(pollPcdu1Tmp), + sensorTemperatures(this), + susTemperatures(this), + deviceTemperatures(this), + heaterInfo(this), + tcsCtrlInfo(this), + imtqThermalSet(objects::IMTQ_HANDLER, ThermalStateCfg()), + maxSet0PlocHspd(objects::RTD_0_IC3_PLOC_HEATSPREADER, + EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet1PlocMissionBrd(objects::RTD_1_IC4_PLOC_MISSIONBOARD, + EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet2PlCam(objects::RTD_2_IC5_4K_CAMERA, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet3DacHspd(objects::RTD_3_IC6_DAC_HEATSPREADER, + EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet4Str(objects::RTD_4_IC7_STARTRACKER, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet5Rw1MxMy(objects::RTD_5_IC8_RW1_MX_MY, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet6Dro(objects::RTD_6_IC9_DRO, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet7Scex(objects::RTD_7_IC10_SCEX, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet8X8(objects::RTD_8_IC11_X8, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet9Hpa(objects::RTD_9_IC12_HPA, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet10EbandTx(objects::RTD_10_IC13_PL_TX, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet11Mpa(objects::RTD_11_IC14_MPA, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet31865Set12(objects::RTD_12_IC15_ACU, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet13PlPcduHspd(objects::RTD_13_IC16_PLPCDU_HEATSPREADER, + EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet14TcsBrd(objects::RTD_14_IC17_TCS_BOARD, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + maxSet15Imtq(objects::RTD_15_IC18_IMTQ, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + tmp1075SetTcs0(objects::TMP1075_HANDLER_TCS_0), + tmp1075SetTcs1(objects::TMP1075_HANDLER_TCS_1), + tmp1075SetPlPcdu0(objects::TMP1075_HANDLER_PLPCDU_0), + tmp1075SetIfBoard(objects::TMP1075_HANDLER_IF_BOARD), + susSet0(objects::SUS_0_N_LOC_XFYFZM_PT_XF), + susSet1(objects::SUS_1_N_LOC_XBYFZM_PT_XB), + susSet2(objects::SUS_2_N_LOC_XFYBZB_PT_YB), + susSet3(objects::SUS_3_N_LOC_XFYBZF_PT_YF), + susSet4(objects::SUS_4_N_LOC_XMYFZF_PT_ZF), + susSet5(objects::SUS_5_N_LOC_XFYMZB_PT_ZB), + susSet6(objects::SUS_6_R_LOC_XFYBZM_PT_XF), + susSet7(objects::SUS_7_R_LOC_XBYBZM_PT_XB), + susSet8(objects::SUS_8_R_LOC_XBYBZB_PT_YB), + susSet9(objects::SUS_9_R_LOC_XBYBZB_PT_YF), + susSet10(objects::SUS_10_N_LOC_XMYBZF_PT_ZF), + susSet11(objects::SUS_11_R_LOC_XBYMZB_PT_ZB), + tcsBrdShortlyUnavailable(tcsBoardShortUnavailable) { + if (pollPcdu1Tmp) { + tmp1075SetPlPcdu1 = new TMP1075::Tmp1075Dataset(objects::TMP1075_HANDLER_PLPCDU_1); + } + resetSensorsArray(); +} + +ReturnValue_t ThermalController::initialize() { + auto* camSwitcher = ObjectManager::instance()->get(objects::CAM_SWITCHER); + if (camSwitcher == nullptr) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + camId = camSwitcher->getCommandQueue(); + return ExtendedControllerBase::initialize(); +} + +ReturnValue_t ThermalController::handleCommandMessage(CommandMessage* message) { + return returnvalue::FAILED; +} + +void ThermalController::performControlOperation() { +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "TCS Task"); +#endif + + switch (internalState) { + case InternalState::STARTUP: { + initialCountdown.resetTimer(); + internalState = InternalState::INITIAL_DELAY; + return; + } + case InternalState::INITIAL_DELAY: { + if (initialCountdown.hasTimedOut()) { + sif::info << "Starting thermal control operations" << std::endl; + internalState = InternalState::READY; + break; + } + return; + } + case InternalState::READY: { + break; + } + default: + break; + } + + if (cycles == 40) { + bool changedLimits = false; +#if LOWER_SYRLINKS_UPPER_LIMITS == 1 + changedLimits = true; + sBandTransceiverLimits.cutOffLimit = 0; + sBandTransceiverLimits.opUpperLimit = 0; + sBandTransceiverLimits.nopUpperLimit = 0; +#endif +#if LOWER_PLOC_UPPER_LIMITS == 1 + changedLimits = true; + plocMissionBoardLimits.cutOffLimit = 0; + plocMissionBoardLimits.opUpperLimit = 0; + plocMissionBoardLimits.nopUpperLimit = 0; +#endif +#if LOWER_EBAND_UPPER_LIMITS == 1 + changedLimits = true; + hpaLimits.cutOffLimit = 0; + hpaLimits.opUpperLimit = 0; + hpaLimits.nopUpperLimit = 0; +#endif +#if LOWER_MGT_UPPER_LIMITS == 1 + changedLimits = true; + mgtLimits.cutOffLimit = 0; + mgtLimits.opUpperLimit = 0; + mgtLimits.nopUpperLimit = 0; +#endif +#if LOWER_RW_UPPER_LIMITS == 1 + changedLimits = true; + rwLimits.cutOffLimit = 0; + rwLimits.opUpperLimit = 0; + rwLimits.nopUpperLimit = 0; +#endif + if (changedLimits) { + sif::debug << "ThermalController: changing limits" << std::endl; + } + } + + if (not tcsBrdShortlyUnavailable) { + { + PoolReadGuard pg(&sensorTemperatures); + if (pg.getReadResult() == returnvalue::OK) { + copySensors(); + } + } + } + { + PoolReadGuard pg(&susTemperatures); + if (pg.getReadResult() == returnvalue::OK) { + copySus(); + } + } + { + PoolReadGuard pg(&deviceTemperatures); + if (pg.getReadResult() == returnvalue::OK) { + copyDevices(); + } + } + + tcsCtrl::HeaterSwitchStates heaterSwitchStateArray{}; + heaterHandler.getAllSwitchStates(heaterSwitchStateArray); + { + PoolReadGuard pg(&heaterInfo); + std::memcpy(heaterInfo.heaterSwitchState.value, heaterSwitchStateArray.data(), 8); + { + PoolReadGuard pg2(¤tVecPdu2); + if (pg.getReadResult() == returnvalue::OK and pg2.getReadResult() == returnvalue::OK) { + heaterInfo.heaterCurrent.value = currentVecPdu2.value[PDU2::Channels::TCS_HEATER_IN]; + } + } + } + + cycles++; + if (transitionWhenHeatersOff) { + bool allSwitchersOff = true; + for (size_t idx = 0; idx < heaterSwitchStateArray.size(); idx++) { + if (heaterSwitchStateArray[idx] != heater::SwitchState::OFF) { + allSwitchersOff = false; + // if heater still ON after 3 cycles, switch OFF again + if (transitionWhenHeatersOffCycles == 3) { + heaterHandler.switchHeater(static_cast(idx), heater::SwitchState::OFF); + triggerEvent(tcsCtrl::HEATER_NOT_OFF_FOR_OFF_MODE); + } + } + } + if (allSwitchersOff or transitionWhenHeatersOffCycles == 6) { + // Finish the transition + transitionWhenHeatersOff = false; + resetThermalStates(); + setMode(targetMode, targetSubmode); + } else { + transitionWhenHeatersOffCycles++; + } + } else if (mode != MODE_OFF) { + if (not tcsBrdShortlyUnavailable) { + performThermalModuleCtrl(heaterSwitchStateArray); + } + heaterTransitionControl(heaterSwitchStateArray); + heaterMaxDurationControl(heaterSwitchStateArray); + // This dataset makes the TCS CTRL observable. + PoolReadGuard pg(&tcsCtrlInfo); + for (uint8_t i = 0; i < thermalStates.size(); i++) { + tcsCtrlInfo.heatingOnVec[i] = thermalStates[i].heating; + tcsCtrlInfo.sensorIdxUsedForTcsCtrl[i] = thermalStates[i].sensorIndex; + tcsCtrlInfo.heaterSwitchIdx[i] = thermalStates[i].heaterSwitch; + tcsCtrlInfo.heaterStartTimes[i] = thermalStates[i].heaterStartTime; + tcsCtrlInfo.heaterEndTimes[i] = thermalStates[i].heaterEndTime; + } + } +} + +ReturnValue_t ThermalController::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(tcsCtrl::SENSOR_PLOC_HEATSPREADER, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_PLOC_MISSIONBOARD, new PoolEntry({1.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_4K_CAMERA, new PoolEntry({2.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_DAC_HEATSPREADER, new PoolEntry({3.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_STARTRACKER, new PoolEntry({4.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_RW1, new PoolEntry({5.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_DRO, new PoolEntry({6.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_SCEX, new PoolEntry({7.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_X8, new PoolEntry({8.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_HPA, new PoolEntry({9.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_TX_MODUL, new PoolEntry({10.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_MPA, new PoolEntry({11.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_ACU, new PoolEntry({12.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_PLPCDU_HEATSPREADER, new PoolEntry({13.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_TCS_BOARD, new PoolEntry({14.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_MAGNETTORQUER, new PoolEntry({15.0})); + localDataPoolMap.emplace(tcsCtrl::SENSOR_TMP1075_TCS_0, &tmp1075Tcs0); + localDataPoolMap.emplace(tcsCtrl::SENSOR_TMP1075_TCS_1, &tmp1075Tcs1); + localDataPoolMap.emplace(tcsCtrl::SENSOR_TMP1075_PLPCDU_0, &tmp1075PlPcdu0); + localDataPoolMap.emplace(tcsCtrl::SENSOR_TMP1075_PLPCDU_1, &tmp1075PlPcdu1); + localDataPoolMap.emplace(tcsCtrl::SENSOR_TMP1075_IF_BOARD, &tmp1075IfBrd); + + localDataPoolMap.emplace(tcsCtrl::SUS_0_N_LOC_XFYFZM_PT_XF, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_6_R_LOC_XFYBZM_PT_XF, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_1_N_LOC_XBYFZM_PT_XB, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_7_R_LOC_XBYBZM_PT_XB, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_2_N_LOC_XFYBZB_PT_YB, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_8_R_LOC_XBYBZB_PT_YB, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_3_N_LOC_XFYBZF_PT_YF, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_9_R_LOC_XBYBZB_PT_YF, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_4_N_LOC_XMYFZF_PT_ZF, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_10_N_LOC_XMYBZF_PT_ZF, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_5_N_LOC_XFYMZB_PT_ZB, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::SUS_11_R_LOC_XBYMZB_PT_ZB, new PoolEntry({0.0})); + + localDataPoolMap.emplace(tcsCtrl::COMPONENT_RW, new PoolEntry({0.0})); + + localDataPoolMap.emplace(tcsCtrl::TEMP_Q7S, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::BATTERY_TEMP_1, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::BATTERY_TEMP_2, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::BATTERY_TEMP_3, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::BATTERY_TEMP_4, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_RW1, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_RW2, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_RW3, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_RW4, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_STAR_TRACKER, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_SYRLINKS_POWER_AMPLIFIER, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_SYRLINKS_BASEBAND_BOARD, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_MGT, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_ACU, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_PDU1, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_PDU2, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_1_P60DOCK, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_2_P60DOCK, new PoolEntry({0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_GYRO_0_SIDE_A, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_GYRO_1_SIDE_A, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_GYRO_2_SIDE_B, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_GYRO_3_SIDE_B, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_MGM_0_SIDE_A, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_MGM_2_SIDE_B, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::TEMP_ADC_PAYLOAD_PCDU, new PoolEntry({0.0})); + localDataPoolMap.emplace(tcsCtrl::HEATER_SWITCH_LIST, &heaterSwitchStates); + localDataPoolMap.emplace(tcsCtrl::HEATER_CURRENT, &heaterCurrent); + localDataPoolMap.emplace(tcsCtrl::HEATER_ON_FOR_COMPONENT_VEC, &tcsCtrlHeaterOn); + localDataPoolMap.emplace(tcsCtrl::SENSOR_USED_FOR_TCS_CTRL, &tcsCtrlSensorIdx); + localDataPoolMap.emplace(tcsCtrl::HEATER_IDX_USED_FOR_TCS_CTRL, &tcsCtrlHeaterIdx); + localDataPoolMap.emplace(tcsCtrl::HEATER_START_TIME, &tcsCtrlStartTimes); + localDataPoolMap.emplace(tcsCtrl::HEATER_END_TIME, &tcsCtrlEndTimes); + + bool enableHkSets = false; +#if OBSW_ENABLE_PERIODIC_HK == 1 + enableHkSets = true; +#endif + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(sensorTemperatures.getSid(), enableHkSets, 120.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(susTemperatures.getSid(), enableHkSets, 240.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(deviceTemperatures.getSid(), enableHkSets, 120.0)); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(heaterInfo.getSid(), enableHkSets, 120.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(tcsCtrlInfo.getSid(), enableHkSets, 120.0)); + + return returnvalue::OK; +} + +LocalPoolDataSetBase* ThermalController::getDataSetHandle(sid_t sid) { + switch (sid.ownerSetId) { + case tcsCtrl::SENSOR_TEMPERATURES: + return &sensorTemperatures; + case tcsCtrl::SUS_TEMPERATURES: + return &susTemperatures; + case tcsCtrl::DEVICE_TEMPERATURES: + return &deviceTemperatures; + case tcsCtrl::HEATER_SET: + return &heaterInfo; + case tcsCtrl::TCS_CTRL_INFO: + return &tcsCtrlInfo; + default: + return nullptr; + } +} + +ReturnValue_t ThermalController::checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) { + if ((mode != MODE_OFF) and (mode != MODE_ON)) { + return INVALID_MODE; + } + if (mode == MODE_ON) { + if (submode != SUBMODE_NONE and submode != SUBMODE_NO_HEATER_CTRL) { + return HasModesIF::INVALID_SUBMODE; + } + return returnvalue::OK; + } + if (submode != SUBMODE_NONE) { + return INVALID_SUBMODE; + } + return returnvalue::OK; +} + +void ThermalController::copySensors() { + { + PoolReadGuard pg0(&maxSet0PlocHspd, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg0.getReadResult() == returnvalue::OK) { + sensorTemperatures.plocHeatspreader.value = maxSet0PlocHspd.temperatureCelcius.value; + sensorTemperatures.plocHeatspreader.setValid(maxSet0PlocHspd.temperatureCelcius.isValid()); + if (not sensorTemperatures.plocHeatspreader.isValid()) { + sensorTemperatures.plocHeatspreader.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg1(&maxSet1PlocMissionBrd, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg1.getReadResult() == returnvalue::OK) { + sensorTemperatures.plocMissionboard.value = maxSet1PlocMissionBrd.temperatureCelcius.value; + sensorTemperatures.plocMissionboard.setValid( + maxSet1PlocMissionBrd.temperatureCelcius.isValid()); + if (not sensorTemperatures.plocMissionboard.isValid()) { + sensorTemperatures.plocMissionboard.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg2(&maxSet2PlCam, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg2.getReadResult() == returnvalue::OK) { + sensorTemperatures.payload4kCamera.value = maxSet2PlCam.temperatureCelcius.value; + sensorTemperatures.payload4kCamera.setValid(maxSet2PlCam.temperatureCelcius.isValid()); + if (not sensorTemperatures.payload4kCamera.isValid()) { + sensorTemperatures.payload4kCamera.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg3(&maxSet3DacHspd, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg3.getReadResult() == returnvalue::OK) { + sensorTemperatures.dacHeatspreader.value = maxSet3DacHspd.temperatureCelcius.value; + sensorTemperatures.dacHeatspreader.setValid(maxSet3DacHspd.temperatureCelcius.isValid()); + if (not sensorTemperatures.dacHeatspreader.isValid()) { + sensorTemperatures.dacHeatspreader.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg4(&maxSet4Str, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg4.getReadResult() == returnvalue::OK) { + sensorTemperatures.startracker.value = maxSet4Str.temperatureCelcius.value; + sensorTemperatures.startracker.setValid(maxSet4Str.temperatureCelcius.isValid()); + if (not sensorTemperatures.startracker.isValid()) { + sensorTemperatures.startracker.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg5(&maxSet5Rw1MxMy, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg5.getReadResult() == returnvalue::OK) { + sensorTemperatures.rw1.value = maxSet5Rw1MxMy.temperatureCelcius.value; + sensorTemperatures.rw1.setValid(maxSet5Rw1MxMy.temperatureCelcius.isValid()); + if (not sensorTemperatures.rw1.isValid()) { + sensorTemperatures.rw1.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg6(&maxSet6Dro, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg6.getReadResult() == returnvalue::OK) { + sensorTemperatures.dro.value = maxSet6Dro.temperatureCelcius.value; + sensorTemperatures.dro.setValid(maxSet6Dro.temperatureCelcius.isValid()); + if (not sensorTemperatures.dro.isValid()) { + sensorTemperatures.dro.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg7(&maxSet7Scex, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg7.getReadResult() == returnvalue::OK) { + sensorTemperatures.scex.value = maxSet7Scex.temperatureCelcius.value; + sensorTemperatures.scex.setValid(maxSet7Scex.temperatureCelcius.isValid()); + if (not sensorTemperatures.scex.isValid()) { + sensorTemperatures.scex.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg8(&maxSet8X8, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg8.getReadResult() == returnvalue::OK) { + sensorTemperatures.x8.value = maxSet8X8.temperatureCelcius.value; + sensorTemperatures.x8.setValid(maxSet8X8.temperatureCelcius.isValid()); + if (not sensorTemperatures.x8.isValid()) { + sensorTemperatures.x8.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg9(&maxSet9Hpa, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg9.getReadResult() == returnvalue::OK) { + sensorTemperatures.hpa.value = maxSet9Hpa.temperatureCelcius.value; + sensorTemperatures.hpa.setValid(maxSet9Hpa.temperatureCelcius.isValid()); + if (not sensorTemperatures.hpa.isValid()) { + sensorTemperatures.hpa.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg10(&maxSet10EbandTx, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg10.getReadResult() == returnvalue::OK) { + sensorTemperatures.eBandTx.value = maxSet10EbandTx.temperatureCelcius.value; + sensorTemperatures.eBandTx.setValid(maxSet10EbandTx.temperatureCelcius.isValid()); + if (not sensorTemperatures.eBandTx.isValid()) { + sensorTemperatures.eBandTx.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg11(&maxSet11Mpa, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg11.getReadResult() == returnvalue::OK) { + sensorTemperatures.mpa.value = maxSet11Mpa.temperatureCelcius.value; + sensorTemperatures.mpa.setValid(maxSet11Mpa.temperatureCelcius.isValid()); + if (not sensorTemperatures.mpa.isValid()) { + sensorTemperatures.mpa.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg12(&maxSet31865Set12, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg12.getReadResult() == returnvalue::OK) { + sensorTemperatures.acu.value = maxSet31865Set12.temperatureCelcius.value; + sensorTemperatures.acu.setValid(maxSet31865Set12.temperatureCelcius.isValid()); + if (not sensorTemperatures.acu.isValid()) { + sensorTemperatures.acu.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg13(&maxSet13PlPcduHspd, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg13.getReadResult() == returnvalue::OK) { + sensorTemperatures.plpcduHeatspreader.value = maxSet13PlPcduHspd.temperatureCelcius.value; + sensorTemperatures.plpcduHeatspreader.setValid( + maxSet13PlPcduHspd.temperatureCelcius.isValid()); + if (not sensorTemperatures.plpcduHeatspreader.isValid()) { + sensorTemperatures.plpcduHeatspreader.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg14(&maxSet14TcsBrd, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg14.getReadResult() == returnvalue::OK) { + sensorTemperatures.tcsBoard.value = maxSet14TcsBrd.temperatureCelcius.value; + sensorTemperatures.tcsBoard.setValid(maxSet14TcsBrd.temperatureCelcius.isValid()); + if (not sensorTemperatures.tcsBoard.isValid()) { + sensorTemperatures.tcsBoard.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg15(&maxSet15Imtq, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg15.getReadResult() == returnvalue::OK) { + sensorTemperatures.mgt.value = maxSet15Imtq.temperatureCelcius.value; + sensorTemperatures.mgt.setValid(maxSet15Imtq.temperatureCelcius.isValid()); + if (not sensorTemperatures.mgt.isValid()) { + sensorTemperatures.mgt.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg(&tmp1075SetTcs0, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() == returnvalue::OK) { + sensorTemperatures.tmp1075Tcs0.value = tmp1075SetTcs0.temperatureCelcius.value; + sensorTemperatures.tmp1075Tcs0.setValid(tmp1075SetTcs0.temperatureCelcius.isValid()); + if (not tmp1075SetTcs0.temperatureCelcius.isValid()) { + sensorTemperatures.tmp1075Tcs0.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg(&tmp1075SetTcs1, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() == returnvalue::OK) { + sensorTemperatures.tmp1075Tcs1.value = tmp1075SetTcs1.temperatureCelcius.value; + sensorTemperatures.tmp1075Tcs1.setValid(tmp1075SetTcs1.temperatureCelcius.isValid()); + if (not tmp1075SetTcs1.temperatureCelcius.isValid()) { + sensorTemperatures.tmp1075Tcs1.value = INVALID_TEMPERATURE; + } + } + } + { + PoolReadGuard pg(&tmp1075SetPlPcdu0, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() == returnvalue::OK) { + sensorTemperatures.tmp1075PlPcdu0.value = tmp1075SetPlPcdu0.temperatureCelcius.value; + sensorTemperatures.tmp1075PlPcdu0.setValid(tmp1075SetPlPcdu0.temperatureCelcius.isValid()); + if (not tmp1075SetPlPcdu0.temperatureCelcius.isValid()) { + sensorTemperatures.tmp1075PlPcdu0.value = INVALID_TEMPERATURE; + } + } + } + // damaged on FM, and no dummies for now + if (pollPcdu1Tmp) { + { + PoolReadGuard pg(tmp1075SetPlPcdu1, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() == returnvalue::OK) { + sensorTemperatures.tmp1075PlPcdu1.value = tmp1075SetPlPcdu1->temperatureCelcius.value; + sensorTemperatures.tmp1075PlPcdu1.setValid(tmp1075SetPlPcdu1->temperatureCelcius.isValid()); + if (not tmp1075SetPlPcdu1->temperatureCelcius.isValid()) { + sensorTemperatures.tmp1075PlPcdu1.value = INVALID_TEMPERATURE; + } + } + } + } + { + PoolReadGuard pg(&tmp1075SetIfBoard, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() == returnvalue::OK) { + sensorTemperatures.tmp1075IfBrd.value = tmp1075SetIfBoard.temperatureCelcius.value; + sensorTemperatures.tmp1075IfBrd.setValid(tmp1075SetIfBoard.temperatureCelcius.isValid()); + if (not tmp1075SetIfBoard.temperatureCelcius.isValid()) { + sensorTemperatures.tmp1075IfBrd.value = INVALID_TEMPERATURE; + } + } + } +} + +void ThermalController::copySus() { + { + PoolReadGuard pg0(&susSet0, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg0.getReadResult() == returnvalue::OK) { + susTemperatures.sus_0_n_loc_xfyfzm_pt_xf.value = susSet0.tempC.value; + susTemperatures.sus_0_n_loc_xfyfzm_pt_xf.setValid(susSet0.tempC.isValid()); + if (not susTemperatures.sus_0_n_loc_xfyfzm_pt_xf.isValid()) { + susTemperatures.sus_0_n_loc_xfyfzm_pt_xf.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg1(&susSet1, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg1.getReadResult() == returnvalue::OK) { + susTemperatures.sus_6_r_loc_xfybzm_pt_xf.value = susSet1.tempC.value; + susTemperatures.sus_6_r_loc_xfybzm_pt_xf.setValid(susSet1.tempC.isValid()); + if (not susTemperatures.sus_6_r_loc_xfybzm_pt_xf.isValid()) { + susTemperatures.sus_6_r_loc_xfybzm_pt_xf.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg2(&susSet2, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg2.getReadResult() == returnvalue::OK) { + susTemperatures.sus_1_n_loc_xbyfzm_pt_xb.value = susSet2.tempC.value; + susTemperatures.sus_1_n_loc_xbyfzm_pt_xb.setValid(susSet2.tempC.isValid()); + if (not susTemperatures.sus_1_n_loc_xbyfzm_pt_xb.isValid()) { + susTemperatures.sus_1_n_loc_xbyfzm_pt_xb.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg3(&susSet3, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg3.getReadResult() == returnvalue::OK) { + susTemperatures.sus_7_r_loc_xbybzm_pt_xb.value = susSet3.tempC.value; + susTemperatures.sus_7_r_loc_xbybzm_pt_xb.setValid(susSet3.tempC.isValid()); + if (not susTemperatures.sus_7_r_loc_xbybzm_pt_xb.isValid()) { + susTemperatures.sus_7_r_loc_xbybzm_pt_xb.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg4(&susSet4, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg4.getReadResult() == returnvalue::OK) { + susTemperatures.sus_2_n_loc_xfybzb_pt_yb.value = susSet4.tempC.value; + susTemperatures.sus_2_n_loc_xfybzb_pt_yb.setValid(susSet4.tempC.isValid()); + if (not susTemperatures.sus_2_n_loc_xfybzb_pt_yb.isValid()) { + susTemperatures.sus_2_n_loc_xfybzb_pt_yb.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg5(&susSet5, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg5.getReadResult() == returnvalue::OK) { + susTemperatures.sus_8_r_loc_xbybzb_pt_yb.value = susSet5.tempC.value; + susTemperatures.sus_8_r_loc_xbybzb_pt_yb.setValid(susSet5.tempC.isValid()); + if (not susTemperatures.sus_8_r_loc_xbybzb_pt_yb.isValid()) { + susTemperatures.sus_8_r_loc_xbybzb_pt_yb.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg6(&susSet6, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg6.getReadResult() == returnvalue::OK) { + susTemperatures.sus_3_n_loc_xfybzf_pt_yf.value = susSet6.tempC.value; + susTemperatures.sus_3_n_loc_xfybzf_pt_yf.setValid(susSet6.tempC.isValid()); + if (not susTemperatures.sus_3_n_loc_xfybzf_pt_yf.isValid()) { + susTemperatures.sus_3_n_loc_xfybzf_pt_yf.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg7(&susSet7, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg7.getReadResult() == returnvalue::OK) { + susTemperatures.sus_9_r_loc_xbybzb_pt_yf.value = susSet7.tempC.value; + susTemperatures.sus_9_r_loc_xbybzb_pt_yf.setValid(susSet7.tempC.isValid()); + if (not susTemperatures.sus_9_r_loc_xbybzb_pt_yf.isValid()) { + susTemperatures.sus_9_r_loc_xbybzb_pt_yf.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg8(&susSet8, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg8.getReadResult() == returnvalue::OK) { + susTemperatures.sus_4_n_loc_xmyfzf_pt_zf.value = susSet8.tempC.value; + susTemperatures.sus_4_n_loc_xmyfzf_pt_zf.setValid(susSet8.tempC.isValid()); + if (not susTemperatures.sus_4_n_loc_xmyfzf_pt_zf.isValid()) { + susTemperatures.sus_4_n_loc_xmyfzf_pt_zf.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg9(&susSet9, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg9.getReadResult() == returnvalue::OK) { + susTemperatures.sus_10_n_loc_xmybzf_pt_zf.value = susSet9.tempC.value; + susTemperatures.sus_10_n_loc_xmybzf_pt_zf.setValid(susSet9.tempC.isValid()); + if (not susTemperatures.sus_10_n_loc_xmybzf_pt_zf.isValid()) { + susTemperatures.sus_10_n_loc_xmybzf_pt_zf.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg10(&susSet10, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg10.getReadResult() == returnvalue::OK) { + susTemperatures.sus_5_n_loc_xfymzb_pt_zb.value = susSet10.tempC.value; + susTemperatures.sus_5_n_loc_xfymzb_pt_zb.setValid(susSet10.tempC.isValid()); + if (not susTemperatures.sus_5_n_loc_xfymzb_pt_zb.isValid()) { + susTemperatures.sus_5_n_loc_xfymzb_pt_zb.value = INVALID_TEMPERATURE; + } + } + } + + { + PoolReadGuard pg11(&susSet11, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg11.getReadResult() == returnvalue::OK) { + susTemperatures.sus_11_r_loc_xbymzb_pt_zb.value = susSet11.tempC.value; + susTemperatures.sus_11_r_loc_xbymzb_pt_zb.setValid(susSet11.tempC.isValid()); + if (not susTemperatures.sus_11_r_loc_xbymzb_pt_zb.isValid()) { + susTemperatures.sus_11_r_loc_xbymzb_pt_zb.value = INVALID_TEMPERATURE; + } + } + } +} + +void ThermalController::copyDevices() { + { + PoolReadGuard pg(&tempQ7s, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() == returnvalue::OK) { + deviceTemperatures.q7s = tempQ7s; + deviceTemperatures.q7s.setValid(tempQ7s.isValid()); + } else { + deviceTemperatures.q7s.setValid(false); + deviceTemperatures.q7s = static_cast(INVALID_TEMPERATURE); + } + } + + { + PoolReadGuard pg(&battTemp1, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read battery temperature 1" << std::endl; + deviceTemperatures.batteryTemp1.setValid(false); + deviceTemperatures.batteryTemp1 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.batteryTemp1 = battTemp1; + deviceTemperatures.batteryTemp1.setValid(battTemp1.isValid()); + } + } + + { + PoolReadGuard pg(&battTemp2, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read battery temperature 2" << std::endl; + deviceTemperatures.batteryTemp2.setValid(false); + deviceTemperatures.batteryTemp2 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.batteryTemp2 = battTemp2; + deviceTemperatures.batteryTemp2.setValid(battTemp2.isValid()); + } + } + + { + PoolReadGuard pg(&battTemp3, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read battery temperature 3" << std::endl; + deviceTemperatures.batteryTemp3.setValid(false); + deviceTemperatures.batteryTemp3 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.batteryTemp3 = battTemp3; + deviceTemperatures.batteryTemp3.setValid(battTemp3.isValid()); + } + } + + { + PoolReadGuard pg(&battTemp4, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read battery temperature 4" << std::endl; + deviceTemperatures.batteryTemp4.setValid(false); + deviceTemperatures.batteryTemp4 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.batteryTemp4 = battTemp4; + deviceTemperatures.batteryTemp4.setValid(battTemp4.isValid()); + } + } + + { + PoolReadGuard pg(&tempRw1, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read reaction wheel 1 temperature" << std::endl; + deviceTemperatures.rw1.setValid(false); + deviceTemperatures.rw1 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.rw1.setValid(tempRw1.isValid()); + deviceTemperatures.rw1 = tempRw1; + } + } + + { + PoolReadGuard pg(&tempRw2, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read reaction wheel 2 temperature" << std::endl; + deviceTemperatures.rw2.setValid(false); + deviceTemperatures.rw2 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.rw2.setValid(tempRw2.isValid()); + deviceTemperatures.rw2 = tempRw2; + } + } + + { + PoolReadGuard pg(&tempRw3, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read reaction wheel 3 temperature" << std::endl; + deviceTemperatures.rw3.setValid(false); + deviceTemperatures.rw3 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.rw3.setValid(tempRw3.isValid()); + deviceTemperatures.rw3 = tempRw3; + } + } + + { + PoolReadGuard pg(&tempRw4, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read reaction wheel 4 temperature" << std::endl; + deviceTemperatures.rw4.setValid(false); + deviceTemperatures.rw4 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.rw4.setValid(tempRw4.isValid()); + deviceTemperatures.rw4 = tempRw4; + } + } + + { + PoolReadGuard pg(&tempStartracker, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read startracker temperature" << std::endl; + deviceTemperatures.startracker.setValid(false); + deviceTemperatures.startracker = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.startracker.setValid(tempStartracker.isValid()); + deviceTemperatures.startracker = tempStartracker; + } + } + + { + PoolReadGuard pg(&tempSyrlinksPowerAmplifier, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read syrlinks power amplifier temperature" + << std::endl; + deviceTemperatures.syrlinksPowerAmplifier.setValid(false); + deviceTemperatures.syrlinksPowerAmplifier = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.syrlinksPowerAmplifier.setValid(tempSyrlinksPowerAmplifier.isValid()); + deviceTemperatures.syrlinksPowerAmplifier = tempSyrlinksPowerAmplifier; + } + } + + { + PoolReadGuard pg(&tempSyrlinksBasebandBoard, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read syrlinks baseband board temperature" + << std::endl; + deviceTemperatures.syrlinksBasebandBoard.setValid(false); + deviceTemperatures.syrlinksBasebandBoard = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.syrlinksBasebandBoard.setValid(tempSyrlinksBasebandBoard.isValid()); + deviceTemperatures.syrlinksBasebandBoard = tempSyrlinksBasebandBoard; + } + } + + { + PoolReadGuard pg(&tempMgt, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read MGT temperature" << std::endl; + deviceTemperatures.mgt.setValid(false); + deviceTemperatures.mgt = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.mgt.setValid(tempMgt.isValid()); + deviceTemperatures.mgt = tempMgt; + } + } + + { + PoolReadGuard pg(&tempAcu, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read ACU temperatures" << std::endl; + deviceTemperatures.acu.setValid(false); + deviceTemperatures.acu[0] = static_cast(INVALID_TEMPERATURE); + deviceTemperatures.acu[1] = static_cast(INVALID_TEMPERATURE); + deviceTemperatures.acu[2] = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.acu.setValid(tempAcu.isValid()); + deviceTemperatures.acu = tempAcu; + } + } + + { + PoolReadGuard pg(&tempPdu1, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read PDU1 temperature" << std::endl; + deviceTemperatures.pdu1.setValid(false); + deviceTemperatures.pdu1 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.pdu1.setValid(tempPdu1.isValid()); + deviceTemperatures.pdu1 = tempPdu1; + } + } + + { + PoolReadGuard pg(&tempPdu2, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read PDU2 temperature" << std::endl; + deviceTemperatures.pdu2.setValid(false); + deviceTemperatures.pdu2 = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.pdu2.setValid(tempPdu2.isValid()); + deviceTemperatures.pdu2 = tempPdu2; + } + } + + { + PoolReadGuard pg(&temp1P60dock, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read P60 dock temperature 1" << std::endl; + deviceTemperatures.temp1P60dock.setValid(false); + deviceTemperatures.temp1P60dock = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.temp1P60dock.setValid(temp1P60dock.isValid()); + deviceTemperatures.temp1P60dock = temp1P60dock; + } + } + + { + PoolReadGuard pg(&temp2P60dock, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read P60 dock temperature 2" << std::endl; + deviceTemperatures.temp2P60dock.setValid(false); + deviceTemperatures.temp2P60dock = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.temp2P60dock.setValid(temp2P60dock.isValid()); + deviceTemperatures.temp2P60dock = temp2P60dock; + } + } + + { + PoolReadGuard pg(&tempGyro0, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read gyro 0 temperature" << std::endl; + deviceTemperatures.gyro0SideA.setValid(false); + deviceTemperatures.gyro0SideA = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.gyro0SideA.setValid(tempGyro0.isValid()); + deviceTemperatures.gyro0SideA = tempGyro0; + } + } + + { + PoolReadGuard pg(&tempGyro1, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read gyro 1 temperature" << std::endl; + deviceTemperatures.gyro1SideA.setValid(false); + deviceTemperatures.gyro1SideA = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.gyro1SideA.setValid(tempGyro1.isValid()); + deviceTemperatures.gyro1SideA = tempGyro1; + } + } + + { + PoolReadGuard pg(&tempGyro2, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read gyro 2 temperature" << std::endl; + deviceTemperatures.gyro2SideB.setValid(false); + deviceTemperatures.gyro2SideB = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.gyro2SideB.setValid(tempGyro2.isValid()); + deviceTemperatures.gyro2SideB = tempGyro2; + } + } + + { + PoolReadGuard pg(&tempGyro3, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read gyro 3 temperature" << std::endl; + deviceTemperatures.gyro3SideB.setValid(false); + deviceTemperatures.gyro3SideB = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.gyro3SideB.setValid(tempGyro3.isValid()); + deviceTemperatures.gyro3SideB = tempGyro3; + } + } + + { + PoolReadGuard pg(&tempMgm0, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read MGM 0 temperature" << std::endl; + deviceTemperatures.mgm0SideA.setValid(false); + deviceTemperatures.mgm0SideA = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.mgm0SideA.setValid(tempMgm0.isValid()); + deviceTemperatures.mgm0SideA = tempMgm0; + } + } + + { + PoolReadGuard pg(&tempMgm2, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read MGM 2 temperature" << std::endl; + deviceTemperatures.mgm2SideB.setValid(false); + deviceTemperatures.mgm2SideB = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.mgm2SideB.setValid(tempMgm2.isValid()); + deviceTemperatures.mgm2SideB = tempMgm2; + } + } + + { + PoolReadGuard pg(&tempAdcPayloadPcdu, MutexIF::TimeoutType::WAITING, MUTEX_TIMEOUT); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "ThermalController: Failed to read payload PCDU ADC temperature" << std::endl; + deviceTemperatures.adcPayloadPcdu.setValid(false); + deviceTemperatures.adcPayloadPcdu = static_cast(INVALID_TEMPERATURE); + } else { + deviceTemperatures.adcPayloadPcdu.setValid(tempAdcPayloadPcdu.isValid()); + deviceTemperatures.adcPayloadPcdu = tempAdcPayloadPcdu; + } + } +} + +void ThermalController::ctrlAcsBoard() { + heater::Switch switchNr = heater::HEATER_2_ACS_BRD; + heater::Switch redSwitchNr = heater::HEATER_3_OBC_BRD; + + // A side + ctrlCtx.thermalComponent = tcsCtrl::ACS_BOARD; + sensors[0].first = deviceTemperatures.gyro0SideA.isValid(); + sensors[0].second = deviceTemperatures.gyro0SideA.value; + sensors[1].first = deviceTemperatures.gyro2SideB.isValid(); + sensors[1].second = deviceTemperatures.gyro2SideB.value; + sensors[2].first = deviceTemperatures.mgm0SideA.isValid(); + sensors[2].second = deviceTemperatures.mgm0SideA.value; + sensors[3].first = deviceTemperatures.mgm2SideB.isValid(); + sensors[3].second = deviceTemperatures.mgm2SideB.value; + sensors[4].first = sensorTemperatures.tcsBoard.isValid(); + sensors[4].second = sensorTemperatures.tcsBoard.value; + numSensors = 5; + { + HeaterContext htrCtx(switchNr, redSwitchNr, acsBoardLimits); + if (selectAndReadSensorTemp(htrCtx)) { + if (chooseHeater(switchNr, redSwitchNr)) { + checkLimitsAndCtrlHeater(htrCtx); + } + resetSensorsArray(); + return; + } + } + resetSensorsArray(); + // B side + sensors[0].first = deviceTemperatures.gyro2SideB.isValid(); + sensors[0].second = deviceTemperatures.gyro2SideB.value; + sensors[1].first = deviceTemperatures.mgm2SideB.isValid(); + sensors[1].second = deviceTemperatures.mgm2SideB.value; + sensors[2].first = deviceTemperatures.gyro3SideB.isValid(); + sensors[2].second = deviceTemperatures.gyro3SideB.value; + sensors[3].first = sensorTemperatures.tcsBoard.isValid(); + sensors[3].second = sensorTemperatures.tcsBoard.value; + numSensors = 4; + + { + HeaterContext htrCtx(switchNr, redSwitchNr, acsBoardLimits); + if (selectAndReadSensorTemp(htrCtx)) { + if (chooseHeater(switchNr, redSwitchNr)) { + checkLimitsAndCtrlHeater(htrCtx); + } + } else { + if (chooseHeater(switchNr, redSwitchNr)) { + if (heaterHandler.getSwitchState(switchNr)) { + if (submode != SUBMODE_NO_HEATER_CTRL) { + heaterSwitchHelper(switchNr, heater::SwitchState::OFF, ctrlCtx.thermalComponent); + } + } + } + } + } + resetSensorsArray(); +} + +void ThermalController::ctrlMgt() { + ctrlCtx.thermalComponent = tcsCtrl::MGT; + sensors[0].first = sensorTemperatures.mgt.isValid(); + sensors[0].second = sensorTemperatures.mgt.value; + sensors[1].first = deviceTemperatures.mgt.isValid(); + sensors[1].second = deviceTemperatures.mgt.value; + sensors[2].first = sensorTemperatures.plpcduHeatspreader.isValid(); + sensors[2].second = sensorTemperatures.plpcduHeatspreader.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_2_ACS_BRD, heater::HEATER_1_PCDU_PDU, mgtLimits); + ctrlComponentTemperature(htrCtx); + if (ctrlCtx.componentAboveUpperLimit and not tooHotFlags.mgtTooHotFlag) { + triggerEvent(tcsCtrl::MGT_OVERHEATING, tempFloatToU32()); + tooHotFlags.mgtTooHotFlag = true; + } else if (not ctrlCtx.componentAboveUpperLimit) { + tooHotFlags.mgtTooHotFlag = false; + } +} + +void ThermalController::ctrlRw() { + Event eventToTrigger = 0; + bool oneIsAboveLimit = false; + + std::array sensorTemps{}; + + // RW1 + ctrlCtx.thermalComponent = tcsCtrl::RW; + sensors[0].first = sensorTemperatures.rw1.isValid(); + sensors[0].second = sensorTemperatures.rw1.value; + sensors[1].first = deviceTemperatures.rw1.isValid(); + sensors[1].second = deviceTemperatures.rw1.value; + sensors[2].first = deviceTemperatures.rw4.isValid(); + sensors[2].second = deviceTemperatures.rw4.value; + sensors[3].first = sensorTemperatures.dro.isValid(); + sensors[3].second = sensorTemperatures.dro.value; + numSensors = 4; + { + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_6_DRO, rwLimits); + ctrlComponentTemperature(htrCtx); + sensorTemps[0] = tempFloatToU32(); + if (ctrlCtx.componentAboveUpperLimit) { + oneIsAboveLimit = true; + eventToTrigger = ctrlCtx.overHeatEventToTrigger; + } + } + + // RW2 + ctrlCtx.thermalComponent = tcsCtrl::RW; + sensors[0].first = deviceTemperatures.rw2.isValid(); + sensors[0].second = deviceTemperatures.rw2.value; + sensors[1].first = deviceTemperatures.rw3.isValid(); + sensors[1].second = deviceTemperatures.rw3.value; + sensors[2].first = sensorTemperatures.rw1.isValid(); + sensors[2].second = sensorTemperatures.rw1.value; + sensors[3].first = sensorTemperatures.dro.isValid(); + sensors[3].second = sensorTemperatures.dro.value; + numSensors = 4; + { + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_6_DRO, rwLimits); + ctrlComponentTemperature(htrCtx); + sensorTemps[1] = tempFloatToU32(); + if (ctrlCtx.componentAboveUpperLimit) { + oneIsAboveLimit = true; + if (eventToTrigger != ThermalComponentIF::COMPONENT_TEMP_OOL_HIGH) { + eventToTrigger = ctrlCtx.overHeatEventToTrigger; + } + } + } + // RW3 + ctrlCtx.thermalComponent = tcsCtrl::RW; + sensors[0].first = deviceTemperatures.rw3.isValid(); + sensors[0].second = deviceTemperatures.rw3.value; + sensors[1].first = deviceTemperatures.rw4.isValid(); + sensors[1].second = deviceTemperatures.rw4.value; + sensors[2].first = sensorTemperatures.rw1.isValid(); + sensors[2].second = sensorTemperatures.rw1.value; + sensors[3].first = sensorTemperatures.dro.isValid(); + sensors[3].second = sensorTemperatures.dro.value; + numSensors = 4; + { + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_6_DRO, rwLimits); + ctrlComponentTemperature(htrCtx); + sensorTemps[2] = tempFloatToU32(); + if (ctrlCtx.componentAboveUpperLimit) { + oneIsAboveLimit = true; + if (eventToTrigger != ThermalComponentIF::COMPONENT_TEMP_OOL_HIGH) { + eventToTrigger = ctrlCtx.overHeatEventToTrigger; + } + } + } + + // RW4 + ctrlCtx.thermalComponent = tcsCtrl::RW; + sensors[0].first = deviceTemperatures.rw4.isValid(); + sensors[0].second = deviceTemperatures.rw4.value; + sensors[1].first = deviceTemperatures.rw1.isValid(); + sensors[1].second = deviceTemperatures.rw1.value; + sensors[2].first = sensorTemperatures.rw1.isValid(); + sensors[2].second = sensorTemperatures.rw1.value; + sensors[3].first = sensorTemperatures.dro.isValid(); + sensors[3].second = sensorTemperatures.dro.value; + numSensors = 4; + { + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_6_DRO, rwLimits); + ctrlComponentTemperature(htrCtx); + sensorTemps[3] = tempFloatToU32(); + if (ctrlCtx.componentAboveUpperLimit) { + oneIsAboveLimit = true; + if (eventToTrigger != ThermalComponentIF::COMPONENT_TEMP_OOL_HIGH) { + eventToTrigger = ctrlCtx.overHeatEventToTrigger; + } + } + } + + if (oneIsAboveLimit and not tooHotFlags.rwTooHotFlag) { + EventManagerIF::triggerEvent(objects::RW1, eventToTrigger, sensorTemps[0]); + EventManagerIF::triggerEvent(objects::RW2, eventToTrigger, sensorTemps[1]); + EventManagerIF::triggerEvent(objects::RW3, eventToTrigger, sensorTemps[2]); + EventManagerIF::triggerEvent(objects::RW4, eventToTrigger, sensorTemps[3]); + tooHotFlags.rwTooHotFlag = true; + } else if (not oneIsAboveLimit) { + tooHotFlags.rwTooHotFlag = false; + } +} + +void ThermalController::ctrlStr() { + ctrlCtx.thermalComponent = tcsCtrl::STR; + sensors[0].first = sensorTemperatures.startracker.isValid(); + sensors[0].second = sensorTemperatures.startracker.value; + sensors[1].first = deviceTemperatures.startracker.isValid(); + sensors[1].second = deviceTemperatures.startracker.value; + sensors[2].first = sensorTemperatures.dro.isValid(); + sensors[2].second = sensorTemperatures.dro.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_5_STR, heater::HEATER_6_DRO, strLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandlerWhichClearsOneShotFlag(objects::STAR_TRACKER, tooHotFlags.strTooHotFlag); +} + +void ThermalController::ctrlIfBoard() { + ctrlCtx.thermalComponent = tcsCtrl::IF_BOARD; + sensors[0].first = sensorTemperatures.tmp1075IfBrd.isValid(); + sensors[0].second = sensorTemperatures.tmp1075IfBrd.value; + sensors[1].first = sensorTemperatures.mgt.isValid(); + sensors[1].second = sensorTemperatures.mgt.value; + sensors[2].first = deviceTemperatures.mgm2SideB.isValid(); + sensors[2].second = deviceTemperatures.mgm2SideB.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_2_ACS_BRD, heater::HEATER_1_PCDU_PDU, ifBoardLimits); + ctrlComponentTemperature(htrCtx); + // TODO: special event overheating + could go back to safe mode +} + +void ThermalController::ctrlTcsBoard() { + ctrlCtx.thermalComponent = tcsCtrl::TCS_BOARD; + sensors[0].first = sensorTemperatures.tcsBoard.isValid(); + sensors[0].second = sensorTemperatures.tcsBoard.value; + sensors[1].first = sensorTemperatures.tmp1075Tcs0.isValid(); + sensors[1].second = sensorTemperatures.tmp1075Tcs0.value; + sensors[2].first = sensorTemperatures.tmp1075Tcs1.isValid(); + sensors[2].second = sensorTemperatures.tmp1075Tcs1.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_3_OBC_BRD, heater::HEATER_2_ACS_BRD, tcsBoardLimits); + ctrlComponentTemperature(htrCtx); + // TODO: special event overheating + could go back to safe mode +} + +void ThermalController::ctrlObc() { + ctrlCtx.thermalComponent = tcsCtrl::OBC; + sensors[0].first = deviceTemperatures.q7s.isValid(); + sensors[0].second = deviceTemperatures.q7s.value; + sensors[1].first = sensorTemperatures.tmp1075Tcs1.isValid(); + sensors[1].second = sensorTemperatures.tmp1075Tcs1.value; + sensors[2].first = sensorTemperatures.tmp1075Tcs0.isValid(); + sensors[2].second = sensorTemperatures.tmp1075Tcs0.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_3_OBC_BRD, heater::HEATER_2_ACS_BRD, obcLimits); + ctrlComponentTemperature(htrCtx); + if (ctrlCtx.componentAboveUpperLimit and not tooHotFlags.obcTooHotFlag) { + triggerEvent(tcsCtrl::OBC_OVERHEATING, tempFloatToU32()); + tooHotFlags.obcTooHotFlag = true; + } else if (not ctrlCtx.componentAboveUpperLimit) { + tooHotFlags.obcTooHotFlag = false; + } +} + +void ThermalController::ctrlSBandTransceiver() { + ctrlCtx.thermalComponent = tcsCtrl::SBAND_TRANSCEIVER; + sensors[0].first = deviceTemperatures.syrlinksPowerAmplifier.isValid(); + sensors[0].second = deviceTemperatures.syrlinksPowerAmplifier.value; + sensors[1].first = deviceTemperatures.syrlinksBasebandBoard.isValid(); + sensors[1].second = deviceTemperatures.syrlinksBasebandBoard.value; + sensors[2].first = sensorTemperatures.payload4kCamera.isValid(); + sensors[2].second = sensorTemperatures.payload4kCamera.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_7_S_BAND, heater::HEATER_4_CAMERA, sBandTransceiverLimits); + ctrlComponentTemperature(htrCtx); + if (ctrlCtx.componentAboveUpperLimit and not tooHotFlags.syrlinksTooHotFlag) { + triggerEvent(tcsCtrl::SYRLINKS_OVERHEATING, tempFloatToU32()); + tooHotFlags.syrlinksTooHotFlag = true; + } else if (not ctrlCtx.componentAboveUpperLimit) { + tooHotFlags.syrlinksTooHotFlag = false; + } +} +void ThermalController::ctrlPcduP60Board() { + ctrlCtx.thermalComponent = tcsCtrl::PCDUP60_BOARD; + sensors[0].first = deviceTemperatures.temp1P60dock.isValid(); + sensors[0].second = deviceTemperatures.temp1P60dock.value; + sensors[1].first = deviceTemperatures.temp2P60dock.isValid(); + sensors[1].second = deviceTemperatures.temp2P60dock.value; + numSensors = 2; + HeaterContext htrCtx(heater::HEATER_1_PCDU_PDU, heater::HEATER_2_ACS_BRD, pcduP60BoardLimits); + ctrlComponentTemperature(htrCtx); + if (ctrlCtx.componentAboveUpperLimit and not tooHotFlags.pcduSystemTooHotFlag) { + triggerEvent(tcsCtrl::PCDU_SYSTEM_OVERHEATING, tempFloatToU32()); + tooHotFlags.pcduSystemTooHotFlag = true; + } else if (not ctrlCtx.componentAboveUpperLimit) { + tooHotFlags.pcduSystemTooHotFlag = false; + } // TODO: ! +} + +void ThermalController::ctrlPcduAcu() { + ctrlCtx.thermalComponent = tcsCtrl::PCDUACU; + heater::Switch switchNr = heater::HEATER_1_PCDU_PDU; + heater::Switch redSwitchNr = heater::HEATER_2_ACS_BRD; + + if (chooseHeater(switchNr, redSwitchNr)) { + bool sensorTempAvailable = true; + // TODO: check + if (deviceTemperatures.acu.value[0] != INVALID_TEMPERATURE) { + ctrlCtx.sensorTemp = deviceTemperatures.acu.value[0]; + } else if (deviceTemperatures.acu.value[1] != INVALID_TEMPERATURE) { + ctrlCtx.sensorTemp = deviceTemperatures.acu.value[1]; + } else if (deviceTemperatures.acu.value[2] != INVALID_TEMPERATURE) { + ctrlCtx.sensorTemp = deviceTemperatures.acu.value[2]; + } else if (sensorTemperatures.acu.isValid()) { + ctrlCtx.sensorTemp = sensorTemperatures.acu.value; + } else { + triggerEvent(tcsCtrl::NO_VALID_SENSOR_TEMPERATURE, ctrlCtx.thermalComponent); + sensorTempAvailable = false; + } + if (sensorTempAvailable) { + HeaterContext htrCtx(switchNr, redSwitchNr, pcduAcuLimits); + checkLimitsAndCtrlHeater(htrCtx); + } + } + if (ctrlCtx.componentAboveUpperLimit and not tooHotFlags.pcduSystemTooHotFlag) { + triggerEvent(tcsCtrl::PCDU_SYSTEM_OVERHEATING, tempFloatToU32()); + tooHotFlags.pcduSystemTooHotFlag = true; + } else if (not ctrlCtx.componentAboveUpperLimit) { + tooHotFlags.pcduSystemTooHotFlag = false; + } +} + +void ThermalController::ctrlPcduPdu() { + ctrlCtx.thermalComponent = tcsCtrl::PCDUPDU; + sensors[0].first = deviceTemperatures.pdu1.isValid(); + sensors[0].second = deviceTemperatures.pdu1.value; + sensors[1].first = deviceTemperatures.pdu2.isValid(); + sensors[1].second = deviceTemperatures.pdu2.value; + sensors[2].first = sensorTemperatures.tmp1075Tcs0.isValid(); + sensors[2].second = sensorTemperatures.tmp1075Tcs0.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_1_PCDU_PDU, heater::HEATER_2_ACS_BRD, pcduPduLimits); + ctrlComponentTemperature(htrCtx); + if (ctrlCtx.componentAboveUpperLimit and not tooHotFlags.pcduSystemTooHotFlag) { + triggerEvent(tcsCtrl::PCDU_SYSTEM_OVERHEATING, tempFloatToU32()); + tooHotFlags.pcduSystemTooHotFlag = true; + } else if (not ctrlCtx.componentAboveUpperLimit) { + tooHotFlags.pcduSystemTooHotFlag = false; + } +} + +void ThermalController::ctrlPlPcduBoard() { + ctrlCtx.thermalComponent = tcsCtrl::PLPCDU_BOARD; + sensors[0].first = sensorTemperatures.tmp1075PlPcdu0.isValid(); + sensors[0].second = sensorTemperatures.tmp1075PlPcdu0.value; + sensors[1].first = sensorTemperatures.tmp1075PlPcdu1.isValid(); + sensors[1].second = sensorTemperatures.tmp1075PlPcdu1.value; + sensors[2].first = deviceTemperatures.adcPayloadPcdu.isValid(); + sensors[2].second = deviceTemperatures.adcPayloadPcdu.value; + sensors[3].first = sensorTemperatures.plpcduHeatspreader.isValid(); + sensors[3].second = sensorTemperatures.plpcduHeatspreader.value; + numSensors = 4; + HeaterContext htrCtx(heater::HEATER_1_PCDU_PDU, heater::HEATER_2_ACS_BRD, plPcduBoardLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLPCDU_HANDLER, tooHotFlags.eBandTooHotFlag); +} + +// ToDo: remove one of the following 2 +void ThermalController::ctrlPlocMissionBoard() { + ctrlCtx.thermalComponent = tcsCtrl::PLOCMISSION_BOARD; + sensors[0].first = sensorTemperatures.plocMissionboard.isValid(); + sensors[0].second = sensorTemperatures.plocMissionboard.value; + sensors[1].first = sensorTemperatures.plocHeatspreader.isValid(); + sensors[1].second = sensorTemperatures.plocHeatspreader.value; + sensors[2].first = sensorTemperatures.dacHeatspreader.isValid(); + sensors[2].second = sensorTemperatures.dacHeatspreader.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_0_PLOC_PROC_BRD, heater::HEATER_3_OBC_BRD, + plocMissionBoardLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLOC_SUPERVISOR_HANDLER, tooHotFlags.plocTooHotFlag); +} + +void ThermalController::ctrlPlocProcessingBoard() { + ctrlCtx.thermalComponent = tcsCtrl::PLOCPROCESSING_BOARD; + sensors[0].first = sensorTemperatures.plocMissionboard.isValid(); + sensors[0].second = sensorTemperatures.plocMissionboard.value; + sensors[1].first = sensorTemperatures.plocHeatspreader.isValid(); + sensors[1].second = sensorTemperatures.plocHeatspreader.value; + sensors[2].first = sensorTemperatures.dacHeatspreader.isValid(); + sensors[2].second = sensorTemperatures.dacHeatspreader.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_0_PLOC_PROC_BRD, heater::HEATER_3_OBC_BRD, + plocProcessingBoardLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLOC_SUPERVISOR_HANDLER, tooHotFlags.plocTooHotFlag); +} + +void ThermalController::ctrlDac() { + ctrlCtx.thermalComponent = tcsCtrl::DAC; + sensors[0].first = sensorTemperatures.dacHeatspreader.isValid(); + sensors[0].second = sensorTemperatures.dacHeatspreader.value; + sensors[1].first = sensorTemperatures.plocMissionboard.isValid(); + sensors[1].second = sensorTemperatures.plocMissionboard.value; + sensors[2].first = sensorTemperatures.plocHeatspreader.isValid(); + sensors[2].second = sensorTemperatures.plocHeatspreader.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_0_PLOC_PROC_BRD, heater::HEATER_3_OBC_BRD, dacLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLPCDU_HANDLER, tooHotFlags.eBandTooHotFlag); +} + +void ThermalController::ctrlCameraBody() { + ctrlCtx.thermalComponent = tcsCtrl::CAMERA; + sensors[0].first = sensorTemperatures.payload4kCamera.isValid(); + sensors[0].second = sensorTemperatures.payload4kCamera.value; + sensors[1].first = sensorTemperatures.dro.isValid(); + sensors[1].second = sensorTemperatures.dro.value; + sensors[2].first = sensorTemperatures.mpa.isValid(); + sensors[2].second = sensorTemperatures.mpa.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_4_CAMERA, heater::HEATER_6_DRO, cameraLimits); + ctrlComponentTemperature(htrCtx); + if (ctrlCtx.componentAboveUpperLimit and not tooHotFlags.camTooHotOneShotFlag) { + triggerEvent(tcsCtrl::CAMERA_OVERHEATING, tempFloatToU32()); + CommandMessage msg; + HealthMessage::setHealthMessage(&msg, HealthMessage::HEALTH_SET, HealthState::FAULTY); + ReturnValue_t result = commandQueue->sendMessage(camId, &msg); + if (result != returnvalue::OK) { + sif::error << "ThermalController::ctrlCameraBody(): Sending health message failed" + << std::endl; + } + tooHotFlags.camTooHotOneShotFlag = true; + } else if (not ctrlCtx.componentAboveUpperLimit) { + tooHotFlags.camTooHotOneShotFlag = false; + } +} + +void ThermalController::ctrlDro() { + ctrlCtx.thermalComponent = tcsCtrl::DRO; + sensors[0].first = sensorTemperatures.dro.isValid(); + sensors[0].second = sensorTemperatures.dro.value; + sensors[1].first = sensorTemperatures.payload4kCamera.isValid(); + sensors[1].second = sensorTemperatures.payload4kCamera.value; + sensors[2].first = sensorTemperatures.mpa.isValid(); + sensors[2].second = sensorTemperatures.mpa.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_4_CAMERA, droLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLPCDU_HANDLER, tooHotFlags.eBandTooHotFlag); +} + +void ThermalController::ctrlX8() { + ctrlCtx.thermalComponent = tcsCtrl::X8; + sensors[0].first = sensorTemperatures.x8.isValid(); + sensors[0].second = sensorTemperatures.x8.value; + sensors[1].first = sensorTemperatures.hpa.isValid(); + sensors[1].second = sensorTemperatures.hpa.value; + sensors[2].first = sensorTemperatures.eBandTx.isValid(); + sensors[2].second = sensorTemperatures.eBandTx.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_4_CAMERA, x8Limits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLPCDU_HANDLER, tooHotFlags.eBandTooHotFlag); +} + +void ThermalController::ctrlTx() { + ctrlCtx.thermalComponent = tcsCtrl::TX; + sensors[0].first = sensorTemperatures.eBandTx.isValid(); + sensors[0].second = sensorTemperatures.eBandTx.value; + sensors[1].first = sensorTemperatures.x8.isValid(); + sensors[1].second = sensorTemperatures.x8.value; + sensors[2].first = sensorTemperatures.mpa.isValid(); + sensors[2].second = sensorTemperatures.mpa.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_4_CAMERA, txLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLPCDU_HANDLER, tooHotFlags.eBandTooHotFlag); +} + +void ThermalController::ctrlMpa() { + ctrlCtx.thermalComponent = tcsCtrl::MPA; + sensors[0].first = sensorTemperatures.mpa.isValid(); + sensors[0].second = sensorTemperatures.mpa.value; + sensors[1].first = sensorTemperatures.hpa.isValid(); + sensors[1].second = sensorTemperatures.hpa.value; + sensors[2].first = sensorTemperatures.eBandTx.isValid(); + sensors[2].second = sensorTemperatures.eBandTx.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_4_CAMERA, mpaLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLPCDU_HANDLER, tooHotFlags.eBandTooHotFlag); +} + +void ThermalController::ctrlHpa() { + ctrlCtx.thermalComponent = tcsCtrl::HPA; + sensors[0].first = sensorTemperatures.hpa.isValid(); + sensors[0].second = sensorTemperatures.hpa.value; + sensors[1].first = sensorTemperatures.x8.isValid(); + sensors[1].second = sensorTemperatures.x8.value; + sensors[2].first = sensorTemperatures.mpa.isValid(); + sensors[2].second = sensorTemperatures.mpa.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_4_CAMERA, hpaLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandler(objects::PLPCDU_HANDLER, tooHotFlags.eBandTooHotFlag); +} + +void ThermalController::ctrlScexBoard() { + ctrlCtx.thermalComponent = tcsCtrl::SCEX_BOARD; + sensors[0].first = sensorTemperatures.scex.isValid(); + sensors[0].second = sensorTemperatures.scex.value; + sensors[1].first = sensorTemperatures.x8.isValid(); + sensors[1].second = sensorTemperatures.x8.value; + sensors[2].first = sensorTemperatures.hpa.isValid(); + sensors[2].second = sensorTemperatures.hpa.value; + numSensors = 3; + HeaterContext htrCtx(heater::HEATER_6_DRO, heater::HEATER_5_STR, scexBoardLimits); + ctrlComponentTemperature(htrCtx); + tooHotHandlerWhichClearsOneShotFlag(objects::SCEX, tooHotFlags.scexTooHotFlag); +} + +void ThermalController::performThermalModuleCtrl( + const tcsCtrl::HeaterSwitchStates& heaterSwitchStates) { + ctrlAcsBoard(); + ctrlMgt(); + ctrlRw(); + ctrlStr(); + ctrlIfBoard(); + ctrlTcsBoard(); + ctrlObc(); + ctrlSBandTransceiver(); + ctrlPcduP60Board(); + ctrlPcduAcu(); + ctrlPcduPdu(); + + // Payload components + std::array plocInAllowedRange{}; + ctrlPlocMissionBoard(); + plocInAllowedRange.at(0) = not ctrlCtx.componentAboveUpperLimit; + ctrlPlocProcessingBoard(); + plocInAllowedRange.at(1) = not ctrlCtx.componentAboveUpperLimit; + + if (tooHotFlags.plocTooHotFlag) { + bool clearFlag = true; + for (const auto& inRange : plocInAllowedRange) { + if (not inRange) { + clearFlag = false; + } + } + if (clearFlag) { + tooHotFlags.plocTooHotFlag = false; + } + } + ctrlCameraBody(); + ctrlScexBoard(); + + // E-Band + std::array eBandInAllowedRange{}; + ctrlPlPcduBoard(); + eBandInAllowedRange.at(0) = not ctrlCtx.componentAboveUpperLimit; + ctrlDac(); + eBandInAllowedRange.at(1) = not ctrlCtx.componentAboveUpperLimit; + ctrlDro(); + eBandInAllowedRange.at(2) = not ctrlCtx.componentAboveUpperLimit; + ctrlX8(); + eBandInAllowedRange.at(3) = not ctrlCtx.componentAboveUpperLimit; + ctrlHpa(); + eBandInAllowedRange.at(4) = not ctrlCtx.componentAboveUpperLimit; + ctrlTx(); + eBandInAllowedRange.at(5) = not ctrlCtx.componentAboveUpperLimit; + ctrlMpa(); + eBandInAllowedRange.at(6) = not ctrlCtx.componentAboveUpperLimit; + + if (tooHotFlags.eBandTooHotFlag) { + bool clearFlag = true; + for (const auto& inRange : eBandInAllowedRange) { + if (not inRange) { + clearFlag = false; + } + } + if (clearFlag) { + tooHotFlags.eBandTooHotFlag = false; + } + } +} + +void ThermalController::ctrlComponentTemperature(HeaterContext& htrCtx) { + if (selectAndReadSensorTemp(htrCtx)) { + if (chooseHeater(htrCtx.switchNr, htrCtx.redSwitchNr)) { + // Core loop for a thermal component, after sensors and heaters were selected. + checkLimitsAndCtrlHeater(htrCtx); + } + } else { + // No sensors available, so switch the heater off. We can not perform control tasks if we + // are blind.. + if (chooseHeater(htrCtx.switchNr, htrCtx.redSwitchNr)) { + // Also track the counter to prevent heater handler message spam. The heater handle can only + // process 2 messages per cycle. + if (heaterCtrlAllowed() and + (thermalStates[ctrlCtx.thermalComponent].noSensorAvailableCounter < 3)) { + heaterSwitchHelper(htrCtx.switchNr, heater::SwitchState::OFF, ctrlCtx.thermalComponent); + } + } + } + resetSensorsArray(); +} +bool ThermalController::selectAndReadSensorTemp(HeaterContext& htrCtx) { + for (unsigned i = 0; i < numSensors; i++) { + if (sensors[i].first and sensors[i].second != INVALID_TEMPERATURE and + sensors[i].second > SANITY_LIMIT_LOWER_TEMP and + sensors[i].second < SANITY_LIMIT_UPPER_TEMP) { + ctrlCtx.sensorTemp = sensors[i].second; + ctrlCtx.currentSensorIndex = i; + thermalStates[ctrlCtx.thermalComponent].noSensorAvailableCounter = 0; + return true; + } + } + + thermalStates[ctrlCtx.thermalComponent].noSensorAvailableCounter++; + if (ctrlCtx.thermalComponent != tcsCtrl::RW and ctrlCtx.thermalComponent != tcsCtrl::ACS_BOARD) { + if (thermalStates[ctrlCtx.thermalComponent].noSensorAvailableCounter <= 3) { + triggerEvent(tcsCtrl::NO_VALID_SENSOR_TEMPERATURE, ctrlCtx.thermalComponent); + } + } else { + if (thermalStates[ctrlCtx.thermalComponent].noSensorAvailableCounter <= 8) { + triggerEvent(tcsCtrl::NO_VALID_SENSOR_TEMPERATURE, ctrlCtx.thermalComponent); + } + } + + return false; +} +bool ThermalController::chooseHeater(heater::Switch& switchNr, heater::Switch redSwitchNr) { + bool heaterAvailable = true; + + HasHealthIF::HealthState mainHealth = heaterHandler.getHealth(switchNr); + heater::SwitchState mainState = heaterHandler.getSwitchState(switchNr); + HasHealthIF::HealthState redHealth = heaterHandler.getHealth(redSwitchNr); + if (mainHealth == HasHealthIF::EXTERNAL_CONTROL and mainState == heater::SwitchState::ON) { + return false; + } + if (mainHealth != HasHealthIF::HEALTHY) { + if (redHealth == HasHealthIF::HEALTHY) { + switchNr = redSwitchNr; + ctrlCtx.redSwitchNrInUse = true; + } else { + heaterAvailable = false; + // Special case: Ground might command/do something with the heaters, so prevent spam. + if (not(mainHealth == EXTERNAL_CONTROL and redHealth == EXTERNAL_CONTROL)) { + triggerEvent(tcsCtrl::NO_HEALTHY_HEATER_AVAILABLE, switchNr, redSwitchNr); + } + } + } else { + ctrlCtx.redSwitchNrInUse = false; + } + + return heaterAvailable; +} + +void ThermalController::heaterCtrlTempTooHighHandler(HeaterContext& htrCtx, const char* whatLimit) { + if (not heaterCtrlAllowed()) { + return; + } + if (htrCtx.switchState == heater::SwitchState::ON) { + sif::info << "TCS: Component " << static_cast(ctrlCtx.thermalComponent) + << " too warm, above " << whatLimit << ", switching off heater" << std::endl; + heaterSwitchHelper(htrCtx.switchNr, heater::SwitchState::OFF, ctrlCtx.thermalComponent); + heaterStates[htrCtx.switchNr].switchTransition = true; + heaterStates[htrCtx.switchNr].target = heater::SwitchState::OFF; + } + if (heaterHandler.getSwitchState(htrCtx.redSwitchNr) == heater::SwitchState::ON) { + heaterSwitchHelper(htrCtx.redSwitchNr, heater::SwitchState::OFF, ctrlCtx.thermalComponent); + heaterStates[htrCtx.redSwitchNr].switchTransition = true; + heaterStates[htrCtx.redSwitchNr].target = heater::SwitchState::OFF; + } +} + +void ThermalController::checkLimitsAndCtrlHeater(HeaterContext& htrCtx) { + ctrlCtx.componentAboveCutOffLimit = false; + ctrlCtx.componentAboveUpperLimit = false; + // Stay passive during switch transitions, wait for heater switching to complete. Otherwise, + // still check whether components are out of range, which might be important information for the + // top level control loop. + if (heaterStates[htrCtx.switchNr].switchTransition) { + htrCtx.doHeaterHandling = false; + heaterCtrlCheckUpperLimits(htrCtx); + return; + } + + htrCtx.switchState = + static_cast(heaterInfo.heaterSwitchState[htrCtx.switchNr]); + // Heater off + if (htrCtx.switchState == heater::SwitchState::OFF) { + if (ctrlCtx.sensorTemp < htrCtx.tempLimit.opLowerLimit and heaterCtrlAllowed()) { + sif::info << "TCS: Heater " << static_cast(ctrlCtx.thermalComponent) << " ON" + << std::endl; + heaterSwitchHelper(htrCtx.switchNr, heater::SwitchState::ON, ctrlCtx.thermalComponent); + } else { + // Even if heater control is now allowed, we can update the state. + thermalStates[ctrlCtx.thermalComponent].heating = false; + } + heaterCtrlCheckUpperLimits(htrCtx); + return; + } + + // Heater on + if (htrCtx.switchState == heater::SwitchState::ON) { + if (thermalStates[ctrlCtx.thermalComponent].heating) { + // We are already in a heating cycle, so need to check whether heating task is complete. + if (ctrlCtx.sensorTemp >= htrCtx.tempLimit.opLowerLimit + TEMP_OFFSET and + heaterCtrlAllowed()) { + sif::info << "TCS: Heater " << static_cast(ctrlCtx.thermalComponent) << " OFF" + << std::endl; + heaterSwitchHelper(htrCtx.switchNr, heater::SwitchState::OFF, ctrlCtx.thermalComponent); + heaterStates[htrCtx.switchNr].switchTransition = true; + heaterStates[htrCtx.switchNr].target = heater::SwitchState::OFF; + } + return; + } + // This can happen if heater is used as alternative heater (no regular heating cycle), so we + // should still check the upper limits. + bool tooHighHandlerAlreadyCalled = heaterCtrlCheckUpperLimits(htrCtx); + if (ctrlCtx.sensorTemp >= htrCtx.tempLimit.cutOffLimit) { + ctrlCtx.componentAboveCutOffLimit = true; + if (not tooHighHandlerAlreadyCalled) { + heaterCtrlTempTooHighHandler(htrCtx, "CutOff-Limit"); + } + } + } +} + +bool ThermalController::heaterCtrlCheckUpperLimits(HeaterContext& htrCtx) { + if (ctrlCtx.sensorTemp >= htrCtx.tempLimit.nopUpperLimit) { + ctrlCtx.componentAboveUpperLimit = true; + if (htrCtx.doHeaterHandling) { + heaterCtrlTempTooHighHandler(htrCtx, "NOP-Limit"); + } + ctrlCtx.overHeatEventToTrigger = ThermalComponentIF::COMPONENT_TEMP_OOL_HIGH; + return true; + } else if (ctrlCtx.sensorTemp >= htrCtx.tempLimit.opUpperLimit) { + ctrlCtx.componentAboveUpperLimit = true; + if (htrCtx.doHeaterHandling) { + heaterCtrlTempTooHighHandler(htrCtx, "OP-Limit"); + } + ctrlCtx.overHeatEventToTrigger = ThermalComponentIF::COMPONENT_TEMP_HIGH; + return true; + } + return false; +} + +void ThermalController::resetSensorsArray() { + for (auto& validValuePair : sensors) { + validValuePair.first = false; + validValuePair.second = INVALID_TEMPERATURE; + } + ctrlCtx.thermalComponent = tcsCtrl::NONE; +} + +void ThermalController::heaterTransitionControl( + const tcsCtrl::HeaterSwitchStates& currentHeaterStates) { + for (unsigned i = 0; i < heater::Switch::NUMBER_OF_SWITCHES; i++) { + if (heaterStates[i].switchTransition) { + if (currentHeaterStates[i] == heaterStates[i].target) { + // Required for max heater period control + if (currentHeaterStates[i] == heater::SwitchState::ON) { + heaterStates[i].heaterOnMaxBurnTime.setTimeout(MAX_HEATER_ON_DURATIONS_MS[i]); + heaterStates[i].heaterOnMaxBurnTime.resetTimer(); + heaterStates[i].trackHeaterMaxBurnTime = true; + } else { + heaterStates[i].trackHeaterMaxBurnTime = false; + // The heater might still be one for some thermal components, so cross-check + // those components + crossCheckHeaterStateOfComponentsWhenHeaterGoesOff(static_cast(i)); + } + heaterStates[i].switchTransition = false; + heaterStates[i].heaterSwitchControlCycles = 0; + continue; + } + if (heaterStates[i].heaterSwitchControlCycles > 5) { + heaterStates[i].switchTransition = false; + heaterStates[i].heaterSwitchControlCycles = 0; + } + heaterStates[i].heaterSwitchControlCycles++; + } + } +} + +void ThermalController::heaterMaxDurationControl( + const tcsCtrl::HeaterSwitchStates& currentHeaterStates) { + for (unsigned i = 0; i < heater::Switch::NUMBER_OF_SWITCHES; i++) { + // Right now, we only track the maximum duration for heater which were commanded by the TCS + // controller. + if (heaterHandler.getHealth(static_cast(i)) != HasHealthIF::EXTERNAL_CONTROL and + currentHeaterStates[i] == heater::SwitchState::ON and + heaterStates[i].trackHeaterMaxBurnTime and + heaterStates[i].heaterOnMaxBurnTime.hasTimedOut()) { + heaterStates[i].switchTransition = false; + heaterStates[i].heaterSwitchControlCycles = 0; + heaterStates[i].trackHeaterMaxBurnTime = false; + triggerEvent(tcsCtrl::TCS_HEATER_MAX_BURN_TIME_REACHED, static_cast(i), + MAX_HEATER_ON_DURATIONS_MS[i]); + heaterSwitchHelper(static_cast(i), heater::SwitchState::OFF, std::nullopt); + // The heater might still be one for some thermal components, so cross-check + // those components + crossCheckHeaterStateOfComponentsWhenHeaterGoesOff(static_cast(i)); + } + } +} + +uint32_t ThermalController::tempFloatToU32() const { + auto sensorTempAsFloat = static_cast(ctrlCtx.sensorTemp); + uint32_t tempRaw = 0; + size_t dummyLen = 0; + SerializeAdapter::serialize(&sensorTempAsFloat, reinterpret_cast(&tempRaw), &dummyLen, + sizeof(tempRaw), SerializeIF::Endianness::NETWORK); + return tempRaw; +} + +void ThermalController::setMode(Mode_t mode, Submode_t submode) { + if (mode == MODE_OFF) { + transitionWhenHeatersOff = false; + } + this->mode = mode; + this->submode = submode; + modeHelper.modeChanged(mode, submode); + announceMode(false); +} + +bool ThermalController::tooHotHandler(object_id_t object, bool& oneShotFlag) { + if (ctrlCtx.componentAboveUpperLimit and not oneShotFlag) { + // Too hot -> returns true + EventManagerIF::triggerEvent(object, ctrlCtx.overHeatEventToTrigger, tempFloatToU32()); + oneShotFlag = true; + return true; + } + return false; +} + +bool ThermalController::heaterCtrlAllowed() const { return submode != SUBMODE_NO_HEATER_CTRL; } + +void ThermalController::resetThermalStates() { + for (auto& thermalState : thermalStates) { + thermalState.heating = false; + thermalState.noSensorAvailableCounter = 0; + thermalState.heaterStartTime = 0; + thermalState.heaterEndTime = 0; + thermalState.sensorIndex = 0; + thermalState.heaterSwitch = heater::Switch::NUMBER_OF_SWITCHES; + } +} + +void ThermalController::heaterSwitchHelper(heater::Switch switchNr, heater::SwitchState targetState, + std::optional componentIdx) { + timeval currentTime; + Clock::getClock(¤tTime); + if (targetState == heater::SwitchState::ON) { + heaterHandler.switchHeater(switchNr, targetState); + heaterStates[switchNr].target = heater::SwitchState::ON; + heaterStates[switchNr].switchTransition = true; + if (componentIdx.has_value()) { + unsigned componentIdxVal = componentIdx.value(); + thermalStates[componentIdxVal].sensorIndex = ctrlCtx.currentSensorIndex; + thermalStates[componentIdxVal].heaterSwitch = switchNr; + thermalStates[componentIdxVal].heating = true; + thermalStates[componentIdxVal].heaterStartTime = currentTime.tv_sec; + } + triggerEvent(tcsCtrl::TCS_SWITCHING_HEATER_ON, static_cast(ctrlCtx.thermalComponent), + static_cast(switchNr)); + } else { + heaterHandler.switchHeater(switchNr, targetState); + if (componentIdx.has_value()) { + thermalStates[componentIdx.value()].heating = false; + thermalStates[componentIdx.value()].heaterEndTime = currentTime.tv_sec; + } + triggerEvent(tcsCtrl::TCS_SWITCHING_HEATER_OFF, static_cast(ctrlCtx.thermalComponent), + static_cast(switchNr)); + } +} + +void ThermalController::heaterSwitchHelperAllOff() { + timeval currentTime; + Clock::getClock(¤tTime); + size_t idx = 0; + for (; idx < heater::Switch::NUMBER_OF_SWITCHES; idx++) { + heaterHandler.switchHeater(static_cast(idx), heater::SwitchState::OFF); + } + for (idx = 0; idx < thermalStates.size(); idx++) { + thermalStates[idx].heating = false; + thermalStates[idx].heaterEndTime = currentTime.tv_sec; + } +} + +ThermalController::~ThermalController() { + if (tmp1075SetPlPcdu1 != nullptr) { + delete tmp1075SetPlPcdu1; + } +} + +void ThermalController::crossCheckHeaterStateOfComponentsWhenHeaterGoesOff( + heater::Switch switchIdx) { + for (unsigned j = 0; j < thermalStates.size(); j++) { + if (thermalStates[j].heating and thermalStates[j].heaterSwitch == switchIdx) { + timeval currentTime; + Clock::getClock(¤tTime); + thermalStates[j].heating = false; + thermalStates[j].heaterEndTime = currentTime.tv_sec; + } + } +} + +void ThermalController::tooHotHandlerWhichClearsOneShotFlag(object_id_t object, bool& oneShotFlag) { + // Clear the one shot flag is the component is in acceptable temperature range. + if (not tooHotHandler(object, oneShotFlag) and not ctrlCtx.componentAboveUpperLimit) { + oneShotFlag = false; + } +} + +void ThermalController::startTransition(Mode_t mode_, Submode_t submode_) { + triggerEvent(CHANGING_MODE, mode_, submode_); + // For MODE_OFF and the no heater control submode, we command all switches to off before + // completing the transition. This ensures a consistent state when commanding these modes. + if ((mode_ == MODE_OFF) or ((mode_ == MODE_ON) and (submode_ == SUBMODE_NO_HEATER_CTRL))) { + heaterSwitchHelperAllOff(); + transitionWhenHeatersOff = true; + targetMode = mode_; + targetSubmode = submode_; + transitionWhenHeatersOffCycles = 0; + } else { + setMode(mode_, submode_); + } +} diff --git a/mission/controller/ThermalController.h b/mission/controller/ThermalController.h new file mode 100644 index 0000000..1062fe9 --- /dev/null +++ b/mission/controller/ThermalController.h @@ -0,0 +1,338 @@ +#ifndef MISSION_CONTROLLER_THERMALCONTROLLER_H_ +#define MISSION_CONTROLLER_THERMALCONTROLLER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class ThermalController : public ExtendedControllerBase { + public: + static constexpr uint8_t SUBMODE_NO_HEATER_CTRL = 1; + + static const uint16_t INVALID_TEMPERATURE = 999; + static const uint8_t NUMBER_OF_SENSORS = 16; + static constexpr int16_t SANITY_LIMIT_LOWER_TEMP = -80; + static constexpr int16_t SANITY_LIMIT_UPPER_TEMP = 160; + + // 1 hour + static constexpr uint32_t DEFAULT_MAX_HEATER_ON_DURATION_MS = 60 * 60 * 1000; + static constexpr uint32_t MAX_HEATER_ON_DURATIONS_MS[8] = {// PLOC PROC board + DEFAULT_MAX_HEATER_ON_DURATION_MS, + // PCDU PDU + DEFAULT_MAX_HEATER_ON_DURATION_MS, + // ACS Board + DEFAULT_MAX_HEATER_ON_DURATION_MS, + // OBC Board + DEFAULT_MAX_HEATER_ON_DURATION_MS, + // Camera + DEFAULT_MAX_HEATER_ON_DURATION_MS, + // STR + DEFAULT_MAX_HEATER_ON_DURATION_MS, + // DRO + DEFAULT_MAX_HEATER_ON_DURATION_MS, + // S-Band + DEFAULT_MAX_HEATER_ON_DURATION_MS}; + + ThermalController(object_id_t objectId, HeaterHandler& heater, + const std::atomic_bool& tcsBoardShortUnavailable, bool pollPcdu1Tmp); + virtual ~ThermalController(); + + ReturnValue_t initialize() override; + + protected: + struct HeaterContext { + public: + HeaterContext(heater::Switch switchNr, heater::Switch redundantSwitchNr, + const tcsCtrl::TempLimits& tempLimit) + : switchNr(switchNr), redSwitchNr(redundantSwitchNr), tempLimit(tempLimit) {} + bool doHeaterHandling = true; + heater::Switch switchNr; + heater::SwitchState switchState = heater::SwitchState::OFF; + heater::Switch redSwitchNr; + const tcsCtrl::TempLimits& tempLimit; + }; + + void performThermalModuleCtrl(const tcsCtrl::HeaterSwitchStates& heaterSwitchStates); + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + void performControlOperation() override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + // Mode abstract functions + ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; + + private: + static const uint32_t INIT_DELAY = 1500; + + static const uint32_t TEMP_OFFSET = 5; + + enum class InternalState { STARTUP, INITIAL_DELAY, READY }; + + InternalState internalState = InternalState::STARTUP; + + HeaterHandler& heaterHandler; + + bool pollPcdu1Tmp; + tcsCtrl::SensorTemperatures sensorTemperatures; + tcsCtrl::SusTemperatures susTemperatures; + tcsCtrl::DeviceTemperatures deviceTemperatures; + tcsCtrl::HeaterInfo heaterInfo; + tcsCtrl::TcsCtrlInfo tcsCtrlInfo; + + DeviceHandlerThermalSet imtqThermalSet; + + // Temperature Sensors + MAX31865::PrimarySet maxSet0PlocHspd; + MAX31865::PrimarySet maxSet1PlocMissionBrd; + MAX31865::PrimarySet maxSet2PlCam; + MAX31865::PrimarySet maxSet3DacHspd; + MAX31865::PrimarySet maxSet4Str; + MAX31865::PrimarySet maxSet5Rw1MxMy; + MAX31865::PrimarySet maxSet6Dro; + MAX31865::PrimarySet maxSet7Scex; + MAX31865::PrimarySet maxSet8X8; + MAX31865::PrimarySet maxSet9Hpa; + MAX31865::PrimarySet maxSet10EbandTx; + MAX31865::PrimarySet maxSet11Mpa; + MAX31865::PrimarySet maxSet31865Set12; + MAX31865::PrimarySet maxSet13PlPcduHspd; + MAX31865::PrimarySet maxSet14TcsBrd; + MAX31865::PrimarySet maxSet15Imtq; + + TMP1075::Tmp1075Dataset tmp1075SetTcs0; + TMP1075::Tmp1075Dataset tmp1075SetTcs1; + TMP1075::Tmp1075Dataset tmp1075SetPlPcdu0; + // damaged + TMP1075::Tmp1075Dataset* tmp1075SetPlPcdu1; + TMP1075::Tmp1075Dataset tmp1075SetIfBoard; + + // SUS + susMax1227::SusDataset susSet0; + susMax1227::SusDataset susSet1; + susMax1227::SusDataset susSet2; + susMax1227::SusDataset susSet3; + susMax1227::SusDataset susSet4; + susMax1227::SusDataset susSet5; + susMax1227::SusDataset susSet6; + susMax1227::SusDataset susSet7; + susMax1227::SusDataset susSet8; + susMax1227::SusDataset susSet9; + susMax1227::SusDataset susSet10; + susMax1227::SusDataset susSet11; + + // If the TCS board in unavailable, for example due to a recovery, skip + // some TCS controller tasks to avoid unnecessary events. + const std::atomic_bool& tcsBrdShortlyUnavailable = false; + + lp_var_t tempQ7s = lp_var_t(objects::CORE_CONTROLLER, core::PoolIds::TEMPERATURE); + lp_var_t battTemp1 = lp_var_t(objects::BPX_BATT_HANDLER, bpxBat::BATT_TEMP_1); + lp_var_t battTemp2 = lp_var_t(objects::BPX_BATT_HANDLER, bpxBat::BATT_TEMP_2); + lp_var_t battTemp3 = lp_var_t(objects::BPX_BATT_HANDLER, bpxBat::BATT_TEMP_3); + lp_var_t battTemp4 = lp_var_t(objects::BPX_BATT_HANDLER, bpxBat::BATT_TEMP_4); + lp_var_t tempRw1 = lp_var_t(objects::RW1, rws::TEMPERATURE_C); + lp_var_t tempRw2 = lp_var_t(objects::RW2, rws::TEMPERATURE_C); + lp_var_t tempRw3 = lp_var_t(objects::RW3, rws::TEMPERATURE_C); + lp_var_t tempRw4 = lp_var_t(objects::RW4, rws::TEMPERATURE_C); + lp_var_t tempStartracker = + lp_var_t(objects::STAR_TRACKER, startracker::MCU_TEMPERATURE); + lp_var_t tempSyrlinksPowerAmplifier = + lp_var_t(objects::SYRLINKS_HANDLER, syrlinks::TEMP_POWER_AMPLIFIER); + lp_var_t tempSyrlinksBasebandBoard = + lp_var_t(objects::SYRLINKS_HANDLER, syrlinks::TEMP_BASEBAND_BOARD); + lp_var_t tempMgt = lp_var_t(objects::IMTQ_HANDLER, imtq::MCU_TEMPERATURE); + lp_vec_t tempAcu = + lp_vec_t(objects::ACU_HANDLER, ACU::pool::ACU_TEMPERATURES); + lp_var_t tempPdu1 = lp_var_t(objects::PDU1_HANDLER, PDU::pool::PDU_TEMPERATURE); + lp_var_t tempPdu2 = lp_var_t(objects::PDU2_HANDLER, PDU::pool::PDU_TEMPERATURE); + lp_var_t temp1P60dock = + lp_var_t(objects::P60DOCK_HANDLER, P60Dock::pool::P60DOCK_TEMPERATURE_1); + lp_var_t temp2P60dock = + lp_var_t(objects::P60DOCK_HANDLER, P60Dock::pool::P60DOCK_TEMPERATURE_2); + lp_var_t tempGyro0 = lp_var_t(objects::GYRO_0_ADIS_HANDLER, adis1650x::TEMPERATURE); + lp_var_t tempGyro1 = lp_var_t(objects::GYRO_1_L3G_HANDLER, l3gd20h::TEMPERATURE); + lp_var_t tempGyro2 = lp_var_t(objects::GYRO_2_ADIS_HANDLER, adis1650x::TEMPERATURE); + lp_var_t tempGyro3 = lp_var_t(objects::GYRO_3_L3G_HANDLER, l3gd20h::TEMPERATURE); + lp_var_t tempMgm0 = + lp_var_t(objects::MGM_0_LIS3_HANDLER, mgmLis3::TEMPERATURE_CELCIUS); + lp_var_t tempMgm2 = + lp_var_t(objects::MGM_2_LIS3_HANDLER, mgmLis3::TEMPERATURE_CELCIUS); + lp_var_t tempAdcPayloadPcdu = lp_var_t(objects::PLPCDU_HANDLER, plpcdu::TEMP); + lp_vec_t currentVecPdu2 = + lp_vec_t(gp_id_t(objects::PDU2_HANDLER, PDU::pool::PDU_CURRENTS)); + + // TempLimits + tcsCtrl::TempLimits acsBoardLimits = tcsCtrl::TempLimits(-40.0, -40.0, 80.0, 85.0, 85.0); + tcsCtrl::TempLimits mgtLimits = tcsCtrl::TempLimits(-40.0, -40.0, 65.0, 70.0, 70.0); + tcsCtrl::TempLimits rwLimits = tcsCtrl::TempLimits(-40.0, -40.0, 80.0, 85.0, 85.0); + tcsCtrl::TempLimits strLimits = tcsCtrl::TempLimits(-30.0, -20.0, 65.0, 70.0, 80.0); + tcsCtrl::TempLimits ifBoardLimits = tcsCtrl::TempLimits(-65.0, -40.0, 80.0, 85.0, 150.0); + tcsCtrl::TempLimits tcsBoardLimits = tcsCtrl::TempLimits(-60.0, -40.0, 80.0, 85.0, 130.0); + tcsCtrl::TempLimits obcLimits = tcsCtrl::TempLimits(-40.0, -40.0, 80.0, 85.0, 85.0); + tcsCtrl::TempLimits obcIfBoardLimits = tcsCtrl::TempLimits(-65.0, -40.0, 80.0, 85.0, 125.0); + tcsCtrl::TempLimits sBandTransceiverLimits = tcsCtrl::TempLimits(-40.0, -25.0, 35.0, 40.0, 65.0); + tcsCtrl::TempLimits pcduP60BoardLimits = tcsCtrl::TempLimits(-35.0, -35.0, 80.0, 85.0, 85.0); + tcsCtrl::TempLimits pcduAcuLimits = tcsCtrl::TempLimits(-35.0, -35.0, 80.0, 85.0, 85.0); + tcsCtrl::TempLimits pcduPduLimits = tcsCtrl::TempLimits(-35.0, -35.0, 80.0, 85.0, 85.0); + tcsCtrl::TempLimits plPcduBoardLimits = tcsCtrl::TempLimits(-55.0, -40.0, 80.0, 85.0, 125.0); + tcsCtrl::TempLimits plocMissionBoardLimits = tcsCtrl::TempLimits(-30.0, -10.0, 40.0, 45.0, 60); + tcsCtrl::TempLimits plocProcessingBoardLimits = + tcsCtrl::TempLimits(-30.0, -10.0, 40.0, 45.0, 60.0); + tcsCtrl::TempLimits dacLimits = tcsCtrl::TempLimits(-65.0, -40.0, 113.0, 118.0, 150.0); + tcsCtrl::TempLimits cameraLimits = tcsCtrl::TempLimits(-40.0, -30.0, 60.0, 65.0, 85.0); + tcsCtrl::TempLimits droLimits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0); + tcsCtrl::TempLimits x8Limits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0); + tcsCtrl::TempLimits hpaLimits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0); + tcsCtrl::TempLimits txLimits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0); + tcsCtrl::TempLimits mpaLimits = tcsCtrl::TempLimits(-40.0, -30.0, 75.0, 80.0, 90.0); + tcsCtrl::TempLimits scexBoardLimits = tcsCtrl::TempLimits(-60.0, -40.0, 80.0, 85.0, 150.0); + + struct CtrlContext { + double sensorTemp = INVALID_TEMPERATURE; + uint8_t currentSensorIndex = 0; + tcsCtrl::ThermalComponents thermalComponent = tcsCtrl::NONE; + bool redSwitchNrInUse = false; + bool componentAboveCutOffLimit = false; + bool componentAboveUpperLimit = false; + Event overHeatEventToTrigger; + } ctrlCtx; + + MessageQueueId_t camId = MessageQueueIF::NO_QUEUE; + + struct TooHotFlags { + bool eBandTooHotFlag = false; + bool camTooHotOneShotFlag = false; + bool scexTooHotFlag = false; + bool plocTooHotFlag = false; + bool pcduSystemTooHotFlag = false; + bool syrlinksTooHotFlag = false; + bool obcTooHotFlag = false; + bool mgtTooHotFlag = false; + bool strTooHotFlag = false; + bool rwTooHotFlag = false; + } tooHotFlags; + + bool transitionWhenHeatersOff = false; + uint32_t transitionWhenHeatersOffCycles = 0; + Mode_t targetMode = MODE_OFF; + Submode_t targetSubmode = SUBMODE_NONE; + uint32_t cycles = 0; + std::array thermalStates{}; + std::array heaterStates{}; + + // Initial delay to make sure all pool variables have been initialized their owners. + // Also, wait for system initialization to complete. + Countdown initialCountdown = Countdown(INIT_DELAY); + +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter = 0; +#endif + + std::array, 5> sensors; + uint8_t numSensors = 0; + + PoolEntry tmp1075Tcs0 = PoolEntry({10.0}); + PoolEntry tmp1075Tcs1 = PoolEntry({10.0}); + PoolEntry tmp1075PlPcdu0 = PoolEntry({10.0}); + PoolEntry tmp1075PlPcdu1 = PoolEntry({10.0}); + PoolEntry tmp1075IfBrd = PoolEntry({10.0}); + PoolEntry heaterSwitchStates = PoolEntry(heater::NUMBER_OF_SWITCHES); + PoolEntry heaterCurrent = PoolEntry(); + + PoolEntry tcsCtrlHeaterOn = PoolEntry(tcsCtrl::NUM_THERMAL_COMPONENTS); + PoolEntry tcsCtrlSensorIdx = PoolEntry(tcsCtrl::NUM_THERMAL_COMPONENTS); + PoolEntry tcsCtrlHeaterIdx = PoolEntry(tcsCtrl::NUM_THERMAL_COMPONENTS); + PoolEntry tcsCtrlStartTimes = PoolEntry(tcsCtrl::NUM_THERMAL_COMPONENTS); + PoolEntry tcsCtrlEndTimes = PoolEntry(tcsCtrl::NUM_THERMAL_COMPONENTS); + + static constexpr dur_millis_t MUTEX_TIMEOUT = 50; + + void startTransition(Mode_t mode, Submode_t submode) override; + + bool heaterCtrlAllowed() const; + void resetThermalStates(); + + void resetSensorsArray(); + void copySensors(); + void copySus(); + void copyDevices(); + + void ctrlComponentTemperature(HeaterContext& heaterContext); + void checkLimitsAndCtrlHeater(HeaterContext& heaterContext); + bool heaterCtrlCheckUpperLimits(HeaterContext& heaterContext); + void heaterCtrlTempTooHighHandler(HeaterContext& heaterContext, const char* whatLimit); + + bool chooseHeater(heater::Switch& switchNr, heater::Switch redSwitchNr); + bool selectAndReadSensorTemp(HeaterContext& htrCtx); + + void heaterSwitchHelperAllOff(); + void heaterSwitchHelper(heater::Switch switchNr, heater::SwitchState state, + std::optional componentIdx); + + void ctrlAcsBoard(); + void ctrlMgt(); + void ctrlRw(); + void ctrlStr(); + void ctrlIfBoard(); + void ctrlTcsBoard(); + void ctrlObc(); + void ctrlSBandTransceiver(); + void ctrlPcduP60Board(); + void ctrlPcduAcu(); + void ctrlPcduPdu(); + void ctrlPlPcduBoard(); + void ctrlPlocMissionBoard(); + void ctrlPlocProcessingBoard(); + void ctrlDac(); + void ctrlCameraBody(); + void ctrlDro(); + void ctrlX8(); + void ctrlHpa(); + void ctrlTx(); + void ctrlMpa(); + void ctrlScexBoard(); + + /** + * The transition of heaters might take some time. As long as a transition is + * going on, the TCS controller works in a reduced form. This function takes care + * of tracking transition and capturing their completion. + * @param currentHeaterStates + */ + void heaterTransitionControl(const tcsCtrl::HeaterSwitchStates& currentHeaterStates); + /** + * Control tasks to prevent heaters being on for prolonged periods. Ideally, this + * should never happen, but this task prevents bugs from causing heaters to stay on + * for a long time, which draws a lot of power. + * @param currentHeaterStates + */ + void heaterMaxDurationControl(const tcsCtrl::HeaterSwitchStates& currentHeaterStates); + void crossCheckHeaterStateOfComponentsWhenHeaterGoesOff(heater::Switch switchIdx); + void setMode(Mode_t mode, Submode_t submode); + uint32_t tempFloatToU32() const; + bool tooHotHandler(object_id_t object, bool& oneShotFlag); + void tooHotHandlerWhichClearsOneShotFlag(object_id_t object, bool& oneShotFlag); +}; + +#endif /* MISSION_CONTROLLER_THERMALCONTROLLER_H_ */ diff --git a/mission/controller/acs/AcsParameters.cpp b/mission/controller/acs/AcsParameters.cpp new file mode 100644 index 0000000..2797dfa --- /dev/null +++ b/mission/controller/acs/AcsParameters.cpp @@ -0,0 +1,805 @@ +#include "AcsParameters.h" + +#include +#include + +AcsParameters::AcsParameters() {} + +AcsParameters::~AcsParameters() {} + +ReturnValue_t AcsParameters::getParameter(uint8_t domainId, uint8_t parameterId, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, + uint16_t startAtIndex) { + switch (domainId) { + case 0x0: // direct members + switch (parameterId) { + default: + return INVALID_IDENTIFIER_ID; + } + break; + case 0x1: // OnBoardParams + switch (parameterId) { + case 0x0: + parameterWrapper->set(onBoardParams.sampleTime); + break; + case 0x1: + parameterWrapper->set(onBoardParams.ptgCtrlLostTimer); + break; + case 0x2: + parameterWrapper->set(onBoardParams.fusedRateSafeDuringEclipse); + break; + case 0x3: + parameterWrapper->set(onBoardParams.fusedRateFromStr); + break; + case 0x4: + parameterWrapper->set(onBoardParams.fusedRateFromQuest); + break; + case 0x5: + parameterWrapper->set(onBoardParams.questFilterWeight); + break; + case 0x6: + parameterWrapper->set(onBoardParams.questAngleLimit); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case 0x2: // InertiaEIVE + switch (parameterId) { + case 0x0: + parameterWrapper->setMatrix(inertiaEIVE.inertiaMatrixDeployed); + break; + case 0x1: + parameterWrapper->setMatrix(inertiaEIVE.inertiaMatrixUndeployed); + break; + case 0x2: + parameterWrapper->setMatrix(inertiaEIVE.inertiaMatrixPanel1); + break; + case 0x3: + parameterWrapper->setMatrix(inertiaEIVE.inertiaMatrixPanel3); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case 0x3: // MgmHandlingParameters + switch (parameterId) { + case 0x0: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm0orientationMatrix); + break; + case 0x1: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm1orientationMatrix); + break; + case 0x2: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm2orientationMatrix); + break; + case 0x3: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm3orientationMatrix); + break; + case 0x4: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm4orientationMatrix); + break; + case 0x5: + parameterWrapper->setVector(mgmHandlingParameters.mgm0hardIronOffset); + break; + case 0x6: + parameterWrapper->setVector(mgmHandlingParameters.mgm1hardIronOffset); + break; + case 0x7: + parameterWrapper->setVector(mgmHandlingParameters.mgm2hardIronOffset); + break; + case 0x8: + parameterWrapper->setVector(mgmHandlingParameters.mgm3hardIronOffset); + break; + case 0x9: + parameterWrapper->setVector(mgmHandlingParameters.mgm4hardIronOffset); + break; + case 0xA: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm0softIronInverse); + break; + case 0xB: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm1softIronInverse); + break; + case 0xC: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm2softIronInverse); + break; + case 0xD: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm3softIronInverse); + break; + case 0xE: + parameterWrapper->setMatrix(mgmHandlingParameters.mgm4softIronInverse); + break; + case 0xF: + parameterWrapper->setVector(mgmHandlingParameters.mgm02variance); + break; + case 0x10: + parameterWrapper->setVector(mgmHandlingParameters.mgm13variance); + break; + case 0x11: + parameterWrapper->setVector(mgmHandlingParameters.mgm4variance); + break; + case 0x12: + parameterWrapper->set(mgmHandlingParameters.mgmVectorFilterWeight); + break; + case 0x13: + parameterWrapper->set(mgmHandlingParameters.mgmDerivativeFilterWeight); + break; + case 0x14: + parameterWrapper->set(mgmHandlingParameters.useMgm4); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case 0x4: // SusHandlingParameters + switch (parameterId) { + case 0x0: + parameterWrapper->setMatrix(susHandlingParameters.sus0orientationMatrix); + break; + case 0x1: + parameterWrapper->setMatrix(susHandlingParameters.sus1orientationMatrix); + break; + case 0x2: + parameterWrapper->setMatrix(susHandlingParameters.sus2orientationMatrix); + break; + case 0x3: + parameterWrapper->setMatrix(susHandlingParameters.sus3orientationMatrix); + break; + case 0x4: + parameterWrapper->setMatrix(susHandlingParameters.sus4orientationMatrix); + break; + case 0x5: + parameterWrapper->setMatrix(susHandlingParameters.sus5orientationMatrix); + break; + case 0x6: + parameterWrapper->setMatrix(susHandlingParameters.sus6orientationMatrix); + break; + case 0x7: + parameterWrapper->setMatrix(susHandlingParameters.sus7orientationMatrix); + break; + case 0x8: + parameterWrapper->setMatrix(susHandlingParameters.sus8orientationMatrix); + break; + case 0x9: + parameterWrapper->setMatrix(susHandlingParameters.sus9orientationMatrix); + break; + case 0xA: + parameterWrapper->setMatrix(susHandlingParameters.sus10orientationMatrix); + break; + case 0xB: + parameterWrapper->setMatrix(susHandlingParameters.sus11orientationMatrix); + break; + case 0xC: + parameterWrapper->setMatrix(susHandlingParameters.sus0coeffAlpha); + break; + case 0xD: + parameterWrapper->setMatrix(susHandlingParameters.sus0coeffBeta); + break; + case 0xE: + parameterWrapper->setMatrix(susHandlingParameters.sus1coeffAlpha); + break; + case 0xF: + parameterWrapper->setMatrix(susHandlingParameters.sus1coeffBeta); + break; + case 0x10: + parameterWrapper->setMatrix(susHandlingParameters.sus2coeffAlpha); + break; + case 0x11: + parameterWrapper->setMatrix(susHandlingParameters.sus2coeffBeta); + break; + case 0x12: + parameterWrapper->setMatrix(susHandlingParameters.sus3coeffAlpha); + break; + case 0x13: + parameterWrapper->setMatrix(susHandlingParameters.sus3coeffBeta); + break; + case 0x14: + parameterWrapper->setMatrix(susHandlingParameters.sus4coeffAlpha); + break; + case 0x15: + parameterWrapper->setMatrix(susHandlingParameters.sus4coeffBeta); + break; + case 0x16: + parameterWrapper->setMatrix(susHandlingParameters.sus5coeffAlpha); + break; + case 0x17: + parameterWrapper->setMatrix(susHandlingParameters.sus5coeffBeta); + break; + case 0x18: + parameterWrapper->setMatrix(susHandlingParameters.sus6coeffAlpha); + break; + case 0x19: + parameterWrapper->setMatrix(susHandlingParameters.sus6coeffBeta); + break; + case 0x1A: + parameterWrapper->setMatrix(susHandlingParameters.sus7coeffAlpha); + break; + case 0x1B: + parameterWrapper->setMatrix(susHandlingParameters.sus7coeffBeta); + break; + case 0x1C: + parameterWrapper->setMatrix(susHandlingParameters.sus8coeffAlpha); + break; + case 0x1D: + parameterWrapper->setMatrix(susHandlingParameters.sus8coeffBeta); + break; + case 0x1E: + parameterWrapper->setMatrix(susHandlingParameters.sus9coeffAlpha); + break; + case 0x1F: + parameterWrapper->setMatrix(susHandlingParameters.sus9coeffBeta); + break; + case 0x20: + parameterWrapper->setMatrix(susHandlingParameters.sus10coeffAlpha); + break; + case 0x21: + parameterWrapper->setMatrix(susHandlingParameters.sus10coeffBeta); + break; + case 0x22: + parameterWrapper->setMatrix(susHandlingParameters.sus11coeffAlpha); + break; + case 0x23: + parameterWrapper->setMatrix(susHandlingParameters.sus11coeffBeta); + break; + case 0x24: + parameterWrapper->set(susHandlingParameters.susBrightnessThreshold); + break; + case 0x25: + parameterWrapper->set(susHandlingParameters.susVectorFilterWeight); + break; + case 0x26: + parameterWrapper->set(susHandlingParameters.susRateFilterWeight); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x5): // GyrHandlingParameters + switch (parameterId) { + case 0x0: + parameterWrapper->setMatrix(gyrHandlingParameters.gyr0orientationMatrix); + break; + case 0x1: + parameterWrapper->setMatrix(gyrHandlingParameters.gyr1orientationMatrix); + break; + case 0x2: + parameterWrapper->setMatrix(gyrHandlingParameters.gyr2orientationMatrix); + break; + case 0x3: + parameterWrapper->setMatrix(gyrHandlingParameters.gyr3orientationMatrix); + break; + case 0x4: + parameterWrapper->setVector(gyrHandlingParameters.gyr0bias); + break; + case 0x5: + parameterWrapper->setVector(gyrHandlingParameters.gyr1bias); + break; + case 0x6: + parameterWrapper->setVector(gyrHandlingParameters.gyr2bias); + break; + case 0x7: + parameterWrapper->setVector(gyrHandlingParameters.gyr3bias); + break; + case 0x8: + parameterWrapper->setVector(gyrHandlingParameters.gyr02variance); + break; + case 0x9: + parameterWrapper->setVector(gyrHandlingParameters.gyr13variance); + break; + case 0xA: + parameterWrapper->set(gyrHandlingParameters.preferAdis); + break; + case 0xB: + parameterWrapper->set(gyrHandlingParameters.gyrFilterWeight); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x6): // RwHandlingParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(rwHandlingParameters.inertiaWheel); + break; + case 0x1: + parameterWrapper->set(rwHandlingParameters.maxTrq); + break; + case 0x2: + parameterWrapper->set(rwHandlingParameters.maxRwSpeed); + break; + case 0x3: + parameterWrapper->set(rwHandlingParameters.stictionSpeed); + break; + case 0x4: + parameterWrapper->set(rwHandlingParameters.stictionReleaseSpeed); + break; + case 0x5: + parameterWrapper->set(rwHandlingParameters.stictionTorque); + break; + case 0x6: + parameterWrapper->set(rwHandlingParameters.rampTime); + break; + case 0x7: + parameterWrapper->set(rwHandlingParameters.multipleRwInvalidTimeout); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x7): // RwMatrices + switch (parameterId) { + case 0x0: + parameterWrapper->setMatrix(rwMatrices.alignmentMatrix); + break; + case 0x1: + parameterWrapper->setMatrix(rwMatrices.pseudoInverse); + break; + case 0x2: + parameterWrapper->setMatrix(rwMatrices.pseudoInverseWithoutRW1); + break; + case 0x3: + parameterWrapper->setMatrix(rwMatrices.pseudoInverseWithoutRW2); + break; + case 0x4: + parameterWrapper->setMatrix(rwMatrices.pseudoInverseWithoutRW3); + break; + case 0x5: + parameterWrapper->setMatrix(rwMatrices.pseudoInverseWithoutRW4); + break; + case 0x6: + parameterWrapper->setVector(rwMatrices.nullspaceVector); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x8): // SafeModeControllerParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(safeModeControllerParameters.k_orthoMekf); + break; + case 0x1: + parameterWrapper->set(safeModeControllerParameters.k_alignMekf); + break; + case 0x2: + parameterWrapper->set(safeModeControllerParameters.k_parallelMekf); + break; + case 0x3: + parameterWrapper->set(safeModeControllerParameters.k_orthoGyr); + break; + case 0x4: + parameterWrapper->set(safeModeControllerParameters.k_alignGyr); + break; + case 0x5: + parameterWrapper->set(safeModeControllerParameters.k_parallelGyr); + break; + case 0x6: + parameterWrapper->set(safeModeControllerParameters.k_orthoSusMgm); + break; + case 0x7: + parameterWrapper->set(safeModeControllerParameters.k_alignSusMgm); + break; + case 0x8: + parameterWrapper->set(safeModeControllerParameters.k_parallelSusMgm); + break; + case 0x9: + parameterWrapper->setVector(safeModeControllerParameters.sunTargetDirLeop); + break; + case 0xA: + parameterWrapper->setVector(safeModeControllerParameters.sunTargetDir); + break; + case 0xB: + parameterWrapper->set(safeModeControllerParameters.useMekf); + break; + case 0xC: + parameterWrapper->set(safeModeControllerParameters.useGyr); + break; + case 0xD: + parameterWrapper->set(safeModeControllerParameters.dampingDuringEclipse); + break; + case 0xE: + parameterWrapper->set(safeModeControllerParameters.sineLimitSunRotRate); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x9): // IdleModeControllerParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(idleModeControllerParameters.zeta); + break; + case 0x1: + parameterWrapper->set(idleModeControllerParameters.om); + break; + case 0x2: + parameterWrapper->set(idleModeControllerParameters.omMax); + break; + case 0x3: + parameterWrapper->set(idleModeControllerParameters.qiMin); + break; + case 0x4: + parameterWrapper->set(idleModeControllerParameters.gainNullspace); + break; + case 0x5: + parameterWrapper->set(idleModeControllerParameters.nullspaceSpeed); + break; + case 0x6: + parameterWrapper->setVector(idleModeControllerParameters.desatMomentumRef); + break; + case 0x7: + parameterWrapper->set(idleModeControllerParameters.deSatGainFactor); + break; + case 0x8: + parameterWrapper->set(idleModeControllerParameters.desatOn); + break; + case 0x9: + parameterWrapper->set(idleModeControllerParameters.useMekf); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0xA): // TargetModeControllerParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(targetModeControllerParameters.zeta); + break; + case 0x1: + parameterWrapper->set(targetModeControllerParameters.om); + break; + case 0x2: + parameterWrapper->set(targetModeControllerParameters.omMax); + break; + case 0x3: + parameterWrapper->set(targetModeControllerParameters.qiMin); + break; + case 0x4: + parameterWrapper->set(targetModeControllerParameters.gainNullspace); + break; + case 0x5: + parameterWrapper->set(targetModeControllerParameters.nullspaceSpeed); + break; + case 0x6: + parameterWrapper->setVector(targetModeControllerParameters.desatMomentumRef); + break; + case 0x7: + parameterWrapper->set(targetModeControllerParameters.deSatGainFactor); + break; + case 0x8: + parameterWrapper->set(targetModeControllerParameters.desatOn); + break; + case 0x9: + parameterWrapper->set(targetModeControllerParameters.useMekf); + break; + case 0xA: + parameterWrapper->setVector(targetModeControllerParameters.refDirection); + break; + case 0xB: + parameterWrapper->setVector(targetModeControllerParameters.refRotRate); + break; + case 0xC: + parameterWrapper->setVector(targetModeControllerParameters.quatRef); + break; + case 0xD: + parameterWrapper->set(targetModeControllerParameters.timeElapsedMax); + break; + case 0xE: + parameterWrapper->set(targetModeControllerParameters.latitudeTgt); + break; + case 0xF: + parameterWrapper->set(targetModeControllerParameters.longitudeTgt); + break; + case 0x10: + parameterWrapper->set(targetModeControllerParameters.altitudeTgt); + break; + case 0x11: + parameterWrapper->set(targetModeControllerParameters.avoidBlindStr); + break; + case 0x12: + parameterWrapper->set(targetModeControllerParameters.blindAvoidStart); + break; + case 0x13: + parameterWrapper->set(targetModeControllerParameters.blindAvoidStop); + break; + case 0x14: + parameterWrapper->set(targetModeControllerParameters.blindRotRate); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0xB): // GsTargetModeControllerParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(gsTargetModeControllerParameters.zeta); + break; + case 0x1: + parameterWrapper->set(gsTargetModeControllerParameters.om); + break; + case 0x2: + parameterWrapper->set(gsTargetModeControllerParameters.omMax); + break; + case 0x3: + parameterWrapper->set(gsTargetModeControllerParameters.qiMin); + break; + case 0x4: + parameterWrapper->set(gsTargetModeControllerParameters.gainNullspace); + break; + case 0x5: + parameterWrapper->set(gsTargetModeControllerParameters.nullspaceSpeed); + break; + case 0x6: + parameterWrapper->setVector(gsTargetModeControllerParameters.desatMomentumRef); + break; + case 0x7: + parameterWrapper->set(gsTargetModeControllerParameters.deSatGainFactor); + break; + case 0x8: + parameterWrapper->set(gsTargetModeControllerParameters.desatOn); + break; + case 0x9: + parameterWrapper->set(gsTargetModeControllerParameters.useMekf); + break; + case 0xA: + parameterWrapper->setVector(gsTargetModeControllerParameters.refDirection); + break; + case 0xB: + parameterWrapper->set(gsTargetModeControllerParameters.timeElapsedMax); + break; + case 0xC: + parameterWrapper->set(gsTargetModeControllerParameters.latitudeTgt); + break; + case 0xD: + parameterWrapper->set(gsTargetModeControllerParameters.longitudeTgt); + break; + case 0xE: + parameterWrapper->set(gsTargetModeControllerParameters.altitudeTgt); + break; + case 0xF: + parameterWrapper->set(gsTargetModeControllerParameters.rotRateLimit); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0xC): // NadirModeControllerParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(nadirModeControllerParameters.zeta); + break; + case 0x1: + parameterWrapper->set(nadirModeControllerParameters.om); + break; + case 0x2: + parameterWrapper->set(nadirModeControllerParameters.omMax); + break; + case 0x3: + parameterWrapper->set(nadirModeControllerParameters.qiMin); + break; + case 0x4: + parameterWrapper->set(nadirModeControllerParameters.gainNullspace); + break; + case 0x5: + parameterWrapper->set(nadirModeControllerParameters.nullspaceSpeed); + break; + case 0x6: + parameterWrapper->setVector(nadirModeControllerParameters.desatMomentumRef); + break; + case 0x7: + parameterWrapper->set(nadirModeControllerParameters.deSatGainFactor); + break; + case 0x8: + parameterWrapper->set(nadirModeControllerParameters.desatOn); + break; + case 0x9: + parameterWrapper->set(nadirModeControllerParameters.useMekf); + break; + case 0xA: + parameterWrapper->setVector(nadirModeControllerParameters.refDirection); + break; + case 0xB: + parameterWrapper->setVector(nadirModeControllerParameters.quatRef); + break; + case 0xC: + parameterWrapper->setVector(nadirModeControllerParameters.refRotRate); + break; + case 0xD: + parameterWrapper->set(nadirModeControllerParameters.timeElapsedMax); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0xD): // InertialModeControllerParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(inertialModeControllerParameters.zeta); + break; + case 0x1: + parameterWrapper->set(inertialModeControllerParameters.om); + break; + case 0x2: + parameterWrapper->set(inertialModeControllerParameters.omMax); + break; + case 0x3: + parameterWrapper->set(inertialModeControllerParameters.qiMin); + break; + case 0x4: + parameterWrapper->set(inertialModeControllerParameters.gainNullspace); + break; + case 0x5: + parameterWrapper->set(inertialModeControllerParameters.nullspaceSpeed); + break; + case 0x6: + parameterWrapper->setVector(inertialModeControllerParameters.desatMomentumRef); + break; + case 0x7: + parameterWrapper->set(inertialModeControllerParameters.deSatGainFactor); + break; + case 0x8: + parameterWrapper->set(inertialModeControllerParameters.desatOn); + break; + case 0x9: + parameterWrapper->set(inertialModeControllerParameters.useMekf); + break; + case 0xA: + parameterWrapper->setVector(inertialModeControllerParameters.tgtQuat); + break; + case 0xB: + parameterWrapper->setVector(inertialModeControllerParameters.refRotRate); + break; + case 0xC: + parameterWrapper->setVector(inertialModeControllerParameters.quatRef); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0xE): // StrParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(strParameters.exclusionAngle); + break; + case 0x1: + parameterWrapper->setVector(strParameters.boresightAxis); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0xF): // GpsParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(gpsParameters.timeDiffVelocityMax); + break; + case 0x1: + parameterWrapper->set(gpsParameters.minimumFdirAltitude); + break; + case 0x2: + parameterWrapper->set(gpsParameters.maximumFdirAltitude); + break; + case 0x3: + parameterWrapper->set(gpsParameters.fdirAltitude); + break; + case 0x4: + parameterWrapper->set(gpsParameters.useSpg4); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x10): // SunModelParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(sunModelParameters.domega); + break; + case 0x1: + parameterWrapper->set(sunModelParameters.omega_0); + break; + case 0x2: + parameterWrapper->set(sunModelParameters.m_0); + break; + case 0x3: + parameterWrapper->set(sunModelParameters.dm); + break; + case 0x4: + parameterWrapper->set(sunModelParameters.e); + break; + case 0x5: + parameterWrapper->set(sunModelParameters.e1); + break; + case 0x6: + parameterWrapper->set(sunModelParameters.p1); + break; + case 0x7: + parameterWrapper->set(sunModelParameters.p2); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x11): // KalmanFilterParameters + switch (parameterId) { + case 0x0: + parameterWrapper->set(kalmanFilterParameters.sensorNoiseStr); + break; + case 0x1: + parameterWrapper->set(kalmanFilterParameters.sensorNoiseSus); + break; + case 0x2: + parameterWrapper->set(kalmanFilterParameters.sensorNoiseMgm); + break; + case 0x3: + parameterWrapper->set(kalmanFilterParameters.sensorNoiseGyr); + break; + case 0x4: + parameterWrapper->set(kalmanFilterParameters.sensorNoiseGyrArw); + break; + case 0x5: + parameterWrapper->set(kalmanFilterParameters.sensorNoiseGyrBs); + break; + case 0x6: + parameterWrapper->set(kalmanFilterParameters.allowStr); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x12): // MagnetorquesParameter + switch (parameterId) { + case 0x0: + parameterWrapper->setMatrix(magnetorquerParameter.mtq0orientationMatrix); + break; + case 0x1: + parameterWrapper->setMatrix(magnetorquerParameter.mtq1orientationMatrix); + break; + case 0x2: + parameterWrapper->setMatrix(magnetorquerParameter.mtq2orientationMatrix); + break; + case 0x3: + parameterWrapper->setMatrix(magnetorquerParameter.alignmentMatrixMtq); + break; + case 0x4: + parameterWrapper->setMatrix(magnetorquerParameter.inverseAlignment); + break; + case 0x5: + parameterWrapper->set(magnetorquerParameter.dipoleMax); + break; + case 0x6: + parameterWrapper->set(magnetorquerParameter.torqueDuration); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + case (0x13): // DetumbleParameter + switch (parameterId) { + case 0x0: + parameterWrapper->set(detumbleParameter.detumblecounter); + break; + case 0x1: + parameterWrapper->set(detumbleParameter.omegaDetumbleStart); + break; + case 0x2: + parameterWrapper->set(detumbleParameter.omegaDetumbleEnd); + break; + case 0x3: + parameterWrapper->set(detumbleParameter.gainBdot); + break; + case 0x4: + parameterWrapper->set(detumbleParameter.gainFull); + break; + case 0x5: + parameterWrapper->set(detumbleParameter.useFullDetumbleLaw); + break; + default: + return INVALID_IDENTIFIER_ID; + } + break; + default: + return INVALID_DOMAIN_ID; + } + return returnvalue::OK; +} diff --git a/mission/controller/acs/AcsParameters.h b/mission/controller/acs/AcsParameters.h new file mode 100644 index 0000000..6319f90 --- /dev/null +++ b/mission/controller/acs/AcsParameters.h @@ -0,0 +1,977 @@ +#ifndef ACSPARAMETERS_H_ +#define ACSPARAMETERS_H_ + +#include + +#include + +typedef unsigned char uint8_t; + +class AcsParameters : public HasParametersIF { + private: + static constexpr double DEG2RAD = M_PI / 180.; + + public: + AcsParameters(); + virtual ~AcsParameters(); + + ReturnValue_t getParameter(uint8_t domainId, uint8_t parameterId, + ParameterWrapper *parameterWrapper, const ParameterWrapper *newValues, + uint16_t startAtIndex) override; + + struct OnBoardParams { + double sampleTime = 0.4; // [s] + uint16_t ptgCtrlLostTimer = 750; + uint8_t fusedRateSafeDuringEclipse = true; + uint8_t fusedRateFromStr = true; + uint8_t fusedRateFromQuest = true; + double questFilterWeight = 0.9; + double questAngleLimit = 5 * DEG2RAD; + } onBoardParams; + + struct InertiaEIVE { + /* Possible inertia matrices + * 2023-04-14 + * all matrices derived from the CAD model with CAD mass of 8.72 kg + * all matrices are scaled by final measured mass of 8.756 kg + * all matrices are in [kg m^2] + */ + double inertiaMatrixDeployed[3][3] = { + {0.128333461640235, -0.000151805020803, -0.004178414952517}, + {-0.000151805020803, 0.141791062870050, 0.000395791231451}, + {-0.004178414952517, 0.000395791231451, 0.069793610830099}}; + double inertiaMatrixUndeployed[3][3] = { + {0.102082611845982, -0.000149885620646, -0.004174050971653}, + {-0.000149885620646, 0.135214836333048, 0.000396374487363}, + {-0.004174050971653, 0.000396374487363, 0.050118987572848}}; + double inertiaMatrixPanel1[3][3] = {{0.115207454653725, -0.000153027308288, -0.004177193002842}, + {-0.000153027308288, 0.138513727361148, 0.006962185987503}, + {-0.004177193002842, 0.006962185987503, 0.059944939352491}}; + double inertiaMatrixPanel3[3][3] = { + {0.115208618832493, -0.000148663333161, -0.004175272921328}, + {-0.000148663333161, 0.138492171841952, -0.006170020268690}, + {-0.004175272921328, -0.006170020268690, 0.059967659050454}}; + } inertiaEIVE; + + struct MgmHandlingParameters { + float mgm0orientationMatrix[3][3] = {{0, 0, -1}, {0, 1, 0}, {1, 0, 0}}; + float mgm1orientationMatrix[3][3] = {{0, 0, 1}, {0, -1, 0}, {1, 0, 0}}; + float mgm2orientationMatrix[3][3] = {{0, 0, -1}, {0, 1, 0}, {1, 0, 0}}; + float mgm3orientationMatrix[3][3] = {{0, 0, 1}, {0, -1, 0}, {1, 0, 0}}; + float mgm4orientationMatrix[3][3] = {{0, 0, -1}, {-1, 0, 0}, {0, 1, 0}}; + + float mgm0hardIronOffset[3] = {6.116487, 6.796264, -19.188060}; + float mgm1hardIronOffset[3] = {-1.077152, 2.080583, 1.974483}; + float mgm2hardIronOffset[3] = {-19.285857, 5.401821, -16.096297}; + float mgm3hardIronOffset[3] = {-0.634033, 2.787695, 0.092036}; + float mgm4hardIronOffset[3] = {2.702743, 5.236043, 0.726229}; + + float mgm0softIronInverse[3][3] = {{0.910192, -0.188413, -0.161522}, + {-0.188413, 1.642303, -0.033184}, + {-0.161522, -0.033184, 0.943904}}; + float mgm1softIronInverse[3][3] = {{1.053508, -0.170225, -0.041678}, + {-0.170225, 1.274465, -0.040231}, + {-0.041678, -0.040231, 1.086352}}; + float mgm2softIronInverse[3][3] = {{0.931086, 0.172675, -0.043084}, + {0.172675, 1.541296, 0.065489}, + {-0.043084, 0.065489, 1.001238}}; + float mgm3softIronInverse[3][3] = {{1.073353, 0.177266, -0.058832}, + {0.177266, 1.262156, 0.010478}, + {-0.058832, 0.010478, 1.068345}}; + float mgm4softIronInverse[3][3] = {{1.114887, -0.007534, -0.037072}, + {-0.007534, 1.253879, 0.006812}, + {-0.037072, 0.006812, 1.313158}}; + + double mgm02variance[3] = {pow(3.2e-7, 2), pow(3.2e-7, 2), pow(4.1e-7, 2)}; + double mgm13variance[3] = {pow(1.5e-8, 2), pow(1.5e-8, 2), pow(1.5e-8, 2)}; + double mgm4variance[3] = {pow(1.7e-6, 2), pow(1.7e-6, 2), pow(1.7e-6, 2)}; + float mgmVectorFilterWeight = 0.85; + float mgmDerivativeFilterWeight = 0.99; + uint8_t useMgm4 = false; + } mgmHandlingParameters; + + struct SusHandlingParameters { + float sus0orientationMatrix[3][3] = {{0, 0, 1}, {1, 0, 0}, {0, 1, 0}}; // FM10 + float sus1orientationMatrix[3][3] = {{0, 0, -1}, {-1, 0, 0}, {0, 1, 0}}; // FM06 + float sus2orientationMatrix[3][3] = {{-1, 0, 0}, {0, 0, -1}, {0, -1, 0}}; // FM13 + float sus3orientationMatrix[3][3] = {{1, 0, 0}, {0, 0, 1}, {0, -1, 0}}; // FM14 + float sus4orientationMatrix[3][3] = {{0, -1, 0}, {1, 0, 0}, {0, 0, 1}}; // FM05 + float sus5orientationMatrix[3][3] = {{1, 0, 0}, {0, -1, 0}, {0, 0, -1}}; // FM02 + float sus6orientationMatrix[3][3] = {{0, 0, 1}, {1, 0, 0}, {0, 1, 0}}; // FM07 + float sus7orientationMatrix[3][3] = {{0, 0, -1}, {-1, 0, 0}, {0, 1, 0}}; // FM01 + float sus8orientationMatrix[3][3] = {{-1, 0, 0}, {0, 0, -1}, {0, -1, 0}}; // FM03 + float sus9orientationMatrix[3][3] = {{1, 0, 0}, {0, 0, 1}, {0, -1, 0}}; // FM11 + float sus10orientationMatrix[3][3] = {{0, -1, 0}, {1, 0, 0}, {0, 0, 1}}; // FM09 + float sus11orientationMatrix[3][3] = {{1, 0, 0}, {0, -1, 0}, {0, 0, -1}}; // FM08 + + float sus0coeffAlpha[9][10] = { + {13.0465222152293, 0.0639132159808454, 2.98083557560227, -0.0773202212713293, + 0.0949075412003712, 0.0503055998355815, -0.00104133434256204, 0.000633099036136146, + 0.00091428505258307, 0.000259857066722932}, + {1.66740227859888, 1.55804368674744, 0.209274741749388, 0.0123798418560859, + 0.00724950517167516, -0.000577445375457582, 8.94374551545955e-05, 6.94513586221567e-05, + -1.06065583714065e-05, -1.43899892666699e-05}, + {8.71610925597519, 1.42112818752419, -0.549859300501301, 0.0374581774684577, + 0.0617635595955198, 0.0447491072679598, 0.00069998577106559, 0.00101018723225412, + -4.88501228194031e-06, -0.000434861113274231}, + {-2.3555601314395, 1.29430213886389, 0.179499593411187, 0.00440896450927253, + 0.00352052300927628, 0.00434187143967281, -9.66615195654703e-05, 3.64923075694275e-05, + 6.09619017310129e-05, 4.23908862836885e-05}, + {-0.858019663974047, 1.10138705956076, 0.278789852526915, -0.000199798507752607, + 0.00112092406838628, -0.00177346866231588, 0.000217816070307086, -0.000240713988238257, + 0.000150795563555828, -0.000279246491927943}, + {7.93661480471297, 1.33902098855997, -0.64010306493848, -0.00307944184518557, + -0.00511421127083497, 0.0204008636376403, -9.50042323904954e-05, 6.01530207062221e-05, + 9.13233708460098e-05, -0.000206717750924323}, + {16.2658124154565, 0.191301571705827, 1.02390350838635, 0.0258487436355216, + -0.0219752092833362, 0.0236916776412211, -0.000350496453661261, -0.000123849795280597, + -0.000532190902882765, 9.36018171121253e-05}, + {-1.53023612303052, 1.29132951637076, 0.181159073530008, -0.0023490608317645, + -0.00370741703297037, -0.000229071300377431, -1.6634455407558e-05, 1.11387154630828e-05, + 1.02609175615251e-05, -9.64717658954667e-06}, + {-32.9918791079688, 0.093536793089853, 4.76858627395571, 0.0595845684553358, + -0.054845749101257, -0.133247382500001, -0.000688999201915199, 7.67286265747961e-05, + 0.000868163357631254, 0.00120099606910313}}; + float sus0coeffBeta[9][10] = { + {12.7380220453847, -0.6087309901836, 2.60957722462363, -0.0415319939920917, + 0.0444944768824276, 0.0223231464060241, -0.000421503508733887, -9.39560038638717e-05, + 0.000821479971871302, -4.5330528329465e-05}, + {1.96846333975847, -0.33921438143463, 1.23957110477613, -0.00948832495296823, + 0.00107211134687287, -0.00410820045700199, -9.33679611473279e-05, 3.72984782145427e-05, + -4.04514487800062e-05, -7.6296149087237e-05}, + {5.7454444934481, -1.58476383793609, -0.418479494289251, -0.0985177320630941, + -0.0862179276808015, 0.0126762052037897, -0.00118207758271301, -0.000190361442918412, + 0.0011723869613426, 0.000122882034141316}, + {2.11042287406433, -0.225942746245056, 1.18084080712528, -0.00103013931607172, + -0.00675606790663387, -0.00106646109062746, 1.7708839355979e-05, -3.13642668374253e-05, + -5.87601932564404e-05, -3.92033314627704e-05}, + {2.96049248725882, -0.286261455028255, 1.09122556181319, -0.000672369023155898, + 0.000574446975796023, 0.000120303729680796, 0.000292285799270644, 0.000170497873487264, + 0.000259925974231328, 0.000222437797823852}, + {1.65218061201483, -0.19535446105784, 1.39609640918411, 0.000961524354787167, + 0.00592400381724333, -0.0078500192096718, -7.02791628080906e-07, -2.07197580883822e-05, + -4.33518182614169e-05, 4.66993119419691e-05}, + {-19.56673237415, 1.06558565338761, 0.151160448373445, -0.0252628659378108, + 0.0281230551050938, -0.0217328869907185, 0.000241309440918385, -0.000116449585258429, + 0.000401546410974577, -0.000147563886502726}, + {1.56167171538684, -0.155299366654736, 1.20084049723279, 0.00457348893890231, + 0.00118888040006052, 0.0029920178735941, -5.583448120596e-05, -2.34496315691865e-05, + -5.3309466243918e-05, 6.20289310356821e-06}, + {1.95050549495182, -2.74909818412705, 3.80268788018641, 0.0629242254381785, + 0.0581479035315726, -0.111361283351269, -0.00047845777495158, -0.00075354297736741, + -0.000186887396585446, 0.00119710704771344}}; + float sus1coeffAlpha[9][10] = { + {-27.6783250420482, -0.964805032861791, -0.503974297997131, -0.0446471081874084, + -0.048219538329297, 0.000958491361905381, -0.000290972187162876, -0.000657145721554176, + -0.000178087038629721, 4.09208968678946e-05}, + {2.24803085641869, 1.42938692406645, 0.30104994020693, 0.00756499999397385, + 0.0117765927439368, -0.000743685980641362, 4.69920803836194e-05, 0.000129815636957956, + -9.10792250542345e-06, -2.03870119873411e-05}, + {26.9943033817917, 0.147791175366868, -3.48256070200564, -0.0303332422478656, + 0.0183377266255394, 0.124593616125966, -0.000466003049304431, -0.000272000698791331, + -0.00063621309529853, -0.00158363678978767}, + {-0.221893380318465, 1.29919955307083, 0.21872487901019, 0.0049448219667127, + 0.00291224091529189, 0.00654651987282984, -9.86842469311185e-05, 8.20057454706638e-05, + 6.42331081725944e-05, 7.11656918299053e-05}, + {1.40178843964621, 1.1733111455249, 0.287485528779234, -0.000793970428759834, + 0.000170529273905818, -0.00268807864923086, 9.09553964483881e-05, -0.000271892733575409, + 8.52016306311741e-05, -0.000291797625433646}, + {0.65549617899457, 1.25716478394514, 0.301396415134214, -0.00357289640403958, + -0.000473416364133431, -0.010760332636205, -9.77220176481185e-05, 4.40798040046875e-05, + 2.84958344955681e-05, 0.000128583400693359}, + {6.20958048145025, 1.9528406481596, 1.32915657614139, -0.0326944423378284, + -0.0158258335207969, 0.0328249756354635, 0.00027113042931131, -0.000133980867173428, + -0.000357964552318811, 0.000224235061786191}, + {2.46222812180944, 1.1731834908026, 0.17440330925151, -0.00132279581980401, + -0.00447202005426964, -0.000804321602550913, -1.59526570766446e-05, 2.62946483533391e-05, + 3.28466749016414e-05, -6.63837547601294e-06}, + {42.615758859473, 2.46617281707273, -5.742515881283, -0.131942799763164, 0.20250702826603, + 0.0981562802911027, 0.00189939440077981, -0.0018591621618441, -0.00161121179693977, + -0.00058814458116749}}; + float sus1coeffBeta[9][10] = { + {-12.300032617206, -1.06640894101328, 0.33950802247214, -0.00890867870617722, + -0.04872758086642, -0.0114263851027856, 0.000141061196404012, -0.000675469545483099, + -0.000138249928781575, -0.000138871036200597}, + {10.1631114109768, 0.261654603839785, 1.2376413405181, 0.00888558138614535, + 0.00151674939001532, -0.00534577602313027, 9.19430013005559e-05, 5.39804599087081e-05, + -4.15760162347772e-05, -7.60797902457032e-05}, + {-30.142329062199, 1.26939195100229, 6.14467186367471, 0.0464163689935328, + 0.00379001947505376, -0.165444163648109, 0.000516545385538741, 1.56053219154647e-05, + -5.58651971370719e-05, 0.00173185063955313}, + {12.1454103989862, -0.243589095509132, 2.02543716988677, -0.000857989774598331, + -0.00705278543432513, 0.0250580538307654, 3.50683653081847e-05, -2.63093897408875e-05, + -5.67352645830913e-05, 0.000232270832022029}, + {4.4338108906594, -0.305276965994378, 1.17293558142526, 0.000152618994429577, + 0.00134432642920902, -0.00104036813342885, 0.000334476082056995, 6.74826804343671e-05, + 0.000275311897725414, 7.58157740577916e-05}, + {3.47680700379043, -0.154163381023597, 1.389579838768, 0.000799705880026268, + 0.00401980026462874, -0.00915311817354667, -2.54817301605075e-06, -2.27422984169921e-05, + -2.61224817848938e-05, 6.00381132540332e-05}, + {29.469181543703, -0.722888948550437, 3.3623377135197, 0.00148445490093232, + -0.0474780142430845, 0.0486755575785462, 0.000126295091963757, 0.000526632230895258, + -0.000259305985126003, 0.000412751148048724}, + {2.67029041722834, -0.0837968038501666, 1.37628504937018, 0.00165061312885753, + -0.00953813055064273, 0.0032433005486936, -1.6522452172598e-05, 0.000144574078261271, + -8.47348746872376e-05, -1.92509604512729e-06}, + {-20.959201441285, -2.23605897639125, 5.73044624806043, 0.0354141964763815, + 0.0887545371234514, -0.193862330062381, 0.000216532998121618, -0.00207707610520973, + 0.000552928905346826, 0.00190182163597828}}; + float sus2coeffAlpha[9][10] = { + {6.51602979328333, 0.690575501042577, 1.18185457002269, -0.0153161662266588, + 0.00145972227341484, 0.0351496474730776, -0.000172645571366945, -6.04213053580018e-05, + 9.74494676304114e-05, 0.000334122888261002}, + {0.954398509323963, 1.10996214782069, 0.313314231563221, -0.00367553051112208, + 0.0110290193380194, 0.000240079475656232, -6.93444423181303e-05, 0.000107433381295167, + 1.30750132315838e-05, -2.43580795300515e-05}, + {-55.1159841655056, -1.47449655191106, 3.40106264596874, -0.0621428271456258, + 0.0659788065633613, -0.0791732068323335, -0.000524264070592741, 0.000582093651418709, + -0.000586102213707195, 0.000658133691098817}, + {1.98614148820353, 1.32058724763677, 0.156843003413303, 0.002748082456053, + 0.00202677073171519, 0.00382360695862248, -0.000122364309010211, 5.33354637965168e-05, + 3.93641210098335e-05, 4.06398431916703e-05}, + {3.41223117010734, 1.1597568029329, 0.31881674291653, -0.000382400010917784, + -0.000754945672515052, -0.00079200882313927, 0.000145713118224563, -0.00026910957285589, + 0.000137876961532787, -0.000326798596746712}, + {6.23333031852853, 1.24902998148103, -0.0162317540018123, -0.00338184464699201, + 0.000420329743164687, 0.00202038442335185, -7.10435889754986e-05, -6.04039458988991e-06, + 7.25318569569788e-06, -2.5930447720704e-05}, + {191.759784636909, -10.5228276216193, 8.48306234734519, 0.243240262512846, + -0.344226468125615, 0.126267158197535, -0.00186612281541009, 0.00304415728817747, + -0.00304958575196089, 0.000457236034569107}, + {5.61375025356727, 1.1692295110657, 0.224665256727786, -0.00230481633344849, + -0.00746693012026367, -0.00172583925345173, -7.00823444553058e-06, 7.31362778266959e-05, + 5.81988007269583e-05, 1.3723604109425e-05}, + {98.0250669452855, -2.18500123986039, -6.68238707939385, 0.000754807832106659, + 0.256133336978808, 0.110826583415768, 0.000457663127670018, -0.00197655629847616, + -0.00254305206375073, -0.000466731538082995}}; + float sus2coeffBeta[9][10] = { + {41.1102358678699, 2.3034699186519, 2.74551448799899, 0.061701310929235, 0.0317074142089495, + 0.0308171492962288, 0.00049453042200054, 0.000519222896270701, 2.85420168881716e-05, + 0.000259197384126413}, + {4.46821725251333, 0.0125273331991983, 1.32640678842532, 0.000543566569079156, + 0.00396616601484022, -0.00488408099728387, -3.05734704054868e-06, 7.3424831303621e-05, + -5.49439160235527e-05, -8.30708110469922e-05}, + {64.773396165255, 2.97057686090134, -1.90770757709096, 0.062747116236773, + -0.077990648565002, 0.0613989204238974, 0.00055512113297293, -0.000347045533958329, + 0.00104059576098392, -0.000348638726253297}, + {3.10352939390402, -0.2376108554276, 1.60523925160222, 0.00116454605680723, + -0.0067958260462381, 0.0136561370875238, 2.59929059167486e-05, 3.33825895937897e-05, + -5.55828531601728e-05, 0.000109833374761172}, + {0.156052891975873, -0.320721597024578, 1.15208488414874, 0.00164743688819939, + 0.000534718891498932, 0.000469870758457642, 0.000308432468885207, 0.00011789470679678, + 0.000292373398965513, 0.000183599033441813}, + {2.84967971406268, -0.21374251183113, 1.09938586447269, 2.34894704600407e-05, + 0.00588345375399262, 0.00296966835738407, 8.42707308834155e-06, 2.81870099202641e-06, + -3.56732787246631e-05, -7.04534663356379e-05}, + {-7.59892007483895, 0.358662160515702, 0.805137646978357, 0.00222144376998348, + 0.0464438387809707, 0.00847551828841782, 3.24805702347551e-05, 5.45500807838332e-05, + 0.000941378089367713, 0.000353137737023192}, + {-4.65367165487109, 0.201306010390421, 1.19135575710523, -0.00692801521395975, + 0.00394118754078443, 0.00426360093528599, 6.297683536736e-05, -7.15794236895102e-05, + -7.47076172176468e-05, -1.94516917836346e-05}, + {-59.5882618930651, 3.84530212586425, 3.50497032358686, -0.116100453177197, + -0.0380997421813177, -0.0581898335691109, 0.00111464935006159, 0.000559313074537689, + 0.000168067749764069, 0.000563224178849256}}; + float sus3coeffAlpha[9][10] = { + {-174.687021034355, -7.53454036765748, -9.33798316371397, -0.18212338430986, + -0.242523652239734, -0.202086838965846, -0.00138648793335223, -0.00225430176012882, + -0.00198887215340364, -0.00160678535160774}, + {6.92009692410602, 1.8192037428209, 0.254908171908415, 0.0179273243472017, + 0.00894059238779664, -0.000436952529644, 0.000138070523903458, 9.22759645920339e-05, + -9.4312261303588e-06, -1.76647897892869e-05}, + {-17.9720006944368, 0.230510201259892, 1.10751755772907, -0.00973621304161327, + 0.0554450499198677, -0.00590970792122449, -2.92393772526592e-05, 0.000444329929586969, + -0.000436055839773919, -9.5869891049503e-05}, + {-4.9880829382985, 1.33627775121504, -0.330382157073243, 0.00306744056311184, + 0.00376353074674973, -0.0107453978169225, -0.00010680477021693, 5.17225535432745e-05, + 7.4423443938376e-05, -0.000107927900087035}, + {0.952867982900728, 1.14513280899596, 0.307744203675505, 0.000404669974219378, + -0.000737988606997615, 0.00120218232577844, 0.000191147653645603, -0.000275058867995882, + 0.000137187356620739, -0.000320202731145004}, + {8.076706574364, 1.31338618710295, -0.334634356394277, -0.00209719438033295, + -0.00381753503582303, 0.0100347823323616, -7.00550548221671e-05, -1.97626956996069e-05, + 7.80079707003333e-05, -8.95904360920744e-05}, + {-82.4748312650249, 8.63074484663009, -0.949295700187556, -0.178618807265278, + 0.130143669167547, 0.0284326533865768, 0.00149831261351137, -0.0011583692969717, + 0.0010560778729661, 0.000635404380970666}, + {3.34457857521978, 1.09242517408071, 0.241722402244944, 0.00381629887587041, + -0.00863580122530851, 0.00137050492069702, -8.91046701171713e-05, 8.44169683308007e-05, + -3.54608413548779e-05, 8.54042677832451e-06}, + {78.1540457908649, -1.30266922193303, -5.33605443700115, 0.0184226131926499, + 0.146629920899062, 0.110698519952472, 6.64041537651749e-05, -0.00120174584530713, + -0.00133177694921411, -0.000796422644338886}}; + float sus3coeffBeta[9][10] = { + {-31.5704266802979, -5.10700699133189, 2.84549700473812, -0.122701561048957, + -0.11257100034746, 0.102120576206517, -0.000796645106694696, -0.00192211266325167, + -4.99981232866237e-05, 0.00104036677004523}, + {-0.734294938181273, -0.0694317595592039, 1.34746975389878, -0.00103465544451119, + 0.00389798465946559, -0.00308561832194191, -2.91843250099708e-06, 7.59634622232999e-05, + -6.54571602919161e-05, -0.000104146832644606}, + {24.2649069708536, 3.08145095664586, 1.88975821636026, 0.0767528234206466, + -0.0526971951753399, -0.0477053831942802, 0.000613806533422364, -0.000631628059238499, + 0.00026217621127941, 0.000555307997961608}, + {0.62884078560034, -0.152668817824194, 1.70304497205574, 0.000894387499536142, + -0.00306495168098874, 0.0180087418010658, 1.74990847586174e-05, 3.1263263531046e-05, + -7.1643235604579e-06, 0.000147876621100347}, + {-3.05400297018165, -0.316256447664344, 1.14841722699638, 0.000671621084688467, + -0.000906765726598906, 0.000687041032077189, 0.000323419818039841, 0.000128019308781935, + 0.000286018723737538, 0.000192248693306256}, + {-4.39855066935163, -0.322858945262125, 1.44405016355615, -4.93181749911261e-05, + 0.0127396834052722, -0.00523149676786941, 2.56561922352657e-05, 7.61202764874326e-06, + -0.00014623717850039, 8.12219846932013e-06}, + {110.820397525173, -10.9497307382094, 2.48939759290446, 0.296585618718034, + -0.142611297893517, -0.0141810186612052, -0.00275127095595919, 0.00160686698368569, + -0.000872029428758877, -0.000410522437887563}, + {-7.15740446281205, 0.104233532313688, 1.13155893729292, -0.00350418544400852, + 0.00532058598508803, 0.00459314980222008, 3.09155436939349e-05, -7.60935741692174e-05, + -5.87922606348196e-05, 2.56146268588382e-05}, + {44.8818060495112, -7.94729992210875, 3.59286389225051, 0.217944601088562, + 0.108087933176612, -0.116711715153385, -0.00194260120960441, -0.0015752762498594, + -0.000331129410732722, 0.00125896996438418}}; + float sus4coeffAlpha[9][10] = { + {-12.4581187126738, 0.398038572289047, -0.438887880988151, -0.00965382887938283, + -0.0309322349328842, -0.00359106522420111, -7.79546112299913e-06, -0.000432733997178497, + -9.79031907635314e-05, -1.49299384451257e-05}, + {8.41054378583447, 1.87462327360707, 0.266809999719952, 0.0216455385250676, + 0.00879426079919981, -0.00142295319820553, 0.000194819780653264, 8.57549705064449e-05, + -3.56478452552367e-05, -1.65680920554434e-05}, + {16.4141780945815, 2.57697842088604, 0.373972171754278, 0.0498264199400303, + 0.0183175817756131, -0.008545409848878, 0.000422696533006382, -0.000268245978898508, + -0.000663188021815416, -7.51144017137827e-05}, + {0.796692054977593, 1.26773229735266, 0.247715261673662, 0.00358183885438128, + 0.00216435175662881, 0.00713732829335305, -0.000110129715615857, 3.56051594182427e-05, + 5.03074365340535e-05, 8.40279146176271e-05}, + {2.37491588500165, 1.05997969088519, 0.309540461340971, -0.000405047711742513, + 0.000462224730316111, -0.00201887171945793, 0.000260159805167265, -0.000282867209803598, + 0.000201613303652666, -0.000277796442847579}, + {6.36749007598708, 1.31659760017973, -0.122724934153231, -0.00328808937096891, + -0.00577347207798776, 0.00403172074457999, -7.45676459772001e-05, 1.79838644222274e-05, + 0.000104552066440564, -2.78115121929346e-05}, + {-47.9667098848496, 3.97703197139796, -1.96403894754299, -0.0577989657406978, + 0.0634225576208007, -0.0346023445055141, 0.00045886475369098, -0.000326132951996844, + 0.000716490441845967, -0.000136132038635483}, + {6.21505474256094, 0.871830486201601, 0.286906473833627, 0.007875292606045, + -0.00974634725746389, 0.00128416935792136, -0.000111796743751489, 0.000102016719989187, + -3.3503088289589e-05, -1.03874407813931e-05}, + {102.09801265482, -4.12715152309748, -5.04594403360339, 0.075499959116996, + 0.216574192561683, 0.0750031215784663, -0.000147358932612646, -0.0023710703422108, + -0.00143310719642393, -0.000431914403446768}}; + float sus4coeffBeta[9][10] = { + {-21.5077132684032, -1.60004839699939, -0.0298995033958561, -0.0315563250430659, + -0.0424403625879891, -0.0245426225510417, -0.000209861203016225, -0.000422150973104431, + -0.00030514398458781, -0.000211986731019738}, + {9.07644247897601, 0.207457289788099, 1.26735366597312, 0.00768477352180427, + 0.00429230749575816, -0.00514802326062087, 7.56149591998578e-05, 8.42794730840662e-05, + -3.62215715492783e-05, -5.24384190165239e-05}, + {-33.5225408043693, -3.11167857248829, 1.91760591695775, -0.0963752386435729, + 0.00026620241534153, -0.0256680391021823, -0.00102188712837393, 2.63753563968978e-05, + 0.000113172463974702, 0.000271939918507175}, + {19.1379025029401, -0.225979661987912, 2.72337120022998, -0.00136982412154458, + -0.00447301210555274, 0.046496718064139, 2.09123846958985e-05, -4.30383094864847e-05, + -1.22808643520768e-05, 0.000440555709696048}, + {2.957867714783, -0.316069593806939, 1.06379930645214, 0.00103244713047271, + 0.00148059212230411, 0.000557885068990542, 0.000288633931072557, 0.000172775380291659, + 0.000269738457990237, 0.000254577019084984}, + {2.04155199929521, -0.318303488378033, 1.37820715117028, 0.00114788656817743, + 0.0130051117909245, -0.00743109928493789, 1.22403390396844e-05, -3.19245785131217e-05, + -0.000156735218010879, 3.81458400945988e-05}, + {27.314954181241, -1.43916155634084, 2.48967706992348, 0.0278695408478388, + -0.0341141456915131, 0.0281959785297513, -0.000252996164135396, 0.000163365679366542, + -0.000380129463154642, 0.000159350154429114}, + {-0.274693278266294, 0.0199711721436635, 1.26676843352524, -0.0006713759238817, + -0.00389715205101059, 0.00294298337610857, -9.58643121413979e-06, 6.30700938550725e-05, + -6.07188867796123e-05, 7.72199861279611e-06}, + {-74.1601853968901, 2.55641628908672, 6.38533530714782, -0.0582345132980647, + -0.0653804553172819, -0.138850555683872, 0.000489364157827405, 0.000469559629292745, + 0.000698140692952438, 0.00123017528239406}}; + float sus5coeffAlpha[9][10] = { + {-12.1398741236355, 1.99425442858125, -1.9303044815802, 0.0418421279520049, + -0.0309683799946315, -0.0562201123081437, 0.000522607299552916, -0.000375386573815007, + -0.000183899715035788, -0.000600349486293698}, + {4.51862054729553, 1.72396080253297, 0.274562680698765, 0.0162681383591035, + 0.0108410181586673, -0.000272215427359511, 0.000124164068046579, 0.000125586897851351, + -1.24082224214974e-05, -1.63339067540159e-05}, + {63.0100748193658, 7.78014670478172, 0.327263471268564, 0.181264302704374, + -0.0652454854214506, -0.03906716801285, 0.00166924078925478, -0.000749939315526625, + 0.000320696101132374, 0.000499934751180042}, + {-2.14377722994325, 1.33617641673436, 0.0973465660282871, 0.00389526886867845, + 0.00526064997381395, 0.00244964888333519, -8.59416490903541e-05, 4.58871931007681e-05, + 8.6123353128647e-05, 2.85447259858337e-05}, + {0.164792977301912, 1.17541977248641, 0.348838798760518, -0.000180865118417534, + 0.000331789515553421, -0.000734333865631793, 9.76677859410759e-05, -0.000324347075049525, + 8.66683396011167e-05, -0.000385839566009832}, + {-0.228934187493575, 1.30552820143752, 0.306779576899158, -0.00508763741184706, + -0.00318524263093038, -0.00878095392529144, -6.59040013073836e-05, 8.69122529321691e-05, + 5.73853071731283e-05, 8.56628414466758e-05}, + {22.6047744510684, -0.591739857860868, 0.566728856847393, 0.0498124268150265, + -0.0214126910277926, 0.00538091942017912, -0.000391517685229849, 0.000554321668236216, + 0.000191004410219065, 0.000102775124022018}, + {4.54704081104052, 0.844841244606025, 0.181355971462193, 0.0109743851006749, + -0.00363467884122547, 0.00108873046814694, -0.000153236888951059, 3.14623342713789e-06, + -2.78503202185463e-05, 3.99983788680736e-06}, + {-30.878359404848, 5.20536009886854, -0.674455093700773, -0.10801865891189, + -0.0514805639475938, 0.0503660452068572, 0.00072776817295273, 0.00120288537038655, + -0.000301602375634166, -0.000477098479809266}}; + float sus5coeffBeta[9][10] = { + {16.8155737032787, 0.65475660868259, 1.95532810363711, 0.000295624718662669, + 0.0426379914736747, 0.00192544771588337, -4.94534888281508e-05, 8.32299142575155e-05, + 0.000645497238623369, -0.000234155227840799}, + {9.48268090632318, 0.528942263930744, 1.34030963800712, 0.0173605129814363, + 0.00581086655972212, -0.00365006277801141, 0.000180048140973223, 0.000102002650672644, + -4.10833110241736e-05, -8.7810396165556e-05}, + {-47.8325489165383, -4.78262055949503, 1.66912859871505, -0.143518014673292, + 0.0288441527062856, -0.00322823115861497, -0.00148509910480755, 0.000284265179004289, + -0.000175299737313045, -7.04175618676909e-05}, + {3.70510151312723, -0.272200626024415, 1.5527519845099, 0.000589727630962265, + -0.00889682554869096, 0.0109857452472628, 3.05876215574877e-05, 2.09194236165814e-05, + -8.33769024439277e-05, 6.90991113575066e-05}, + {0.820199776906695, -0.355683467192776, 1.17142130858009, -0.000160174871610729, + 4.09723480153701e-05, 0.000209103751629257, 0.000390331989170637, 6.45642836249667e-05, + 0.000318092703362044, 0.000107158633760141}, + {5.52084497768914, -0.227775345312466, 0.845897282556327, 0.00157426476122436, + 0.00657189797805861, 0.0103797665963117, 2.51479848048895e-05, -4.78371400399983e-05, + -5.20221896473413e-05, -0.000143840492906166}, + {-33.4875689683454, 0.937557276329106, -1.02741065470967, -0.0140023273976314, + 0.0401908729477037, -0.0512457211360142, 7.05537967426573e-05, -0.00027521752411122, + 0.000407657552700476, -0.000458411000693613}, + {0.931346887326171, -0.320804452025793, 1.28866325376154, 0.00912456151698805, + -0.00404367403569981, 0.00477543659981282, -9.43987917474817e-05, 4.66464249533497e-05, + -7.89362487264572e-05, -1.0951496495443e-05}, + {-38.3689359928435, 3.8540516906071, 1.26391725545116, -0.108584643500539, + -0.0542697403292778, 0.0285360568428252, 0.000845084580479371, 0.00114184315411245, + -0.000169538153750085, -0.000336529204350355}}; + float sus6coeffAlpha[9][10] = { + {10.4400948050067, 1.38202655603079, 0.975299591736672, 0.0172133914423707, + -0.0163482459492803, 0.035730152619911, 0.00021725657060767, -0.000181685375645396, + -0.000124096561459262, 0.00040790566176981}, + {6.38281281805793, 1.81388255990089, 0.28679524291736, 0.0218036823758417, + 0.010516766426651, 0.000446101708841615, 0.00020187044149361, 0.000114957457831415, + 1.63114413539632e-05, -2.0187452317724e-05}, + {-29.3049094555, -0.506844002611835, 1.64911970541112, -0.0336282997119334, + 0.053185806861685, -0.028164943139695, -0.00021098074590512, 0.000643681643489995, + -0.000249094601806692, 0.000231466668650876}, + {-4.76233790255328, 1.1780710601961, -0.194257188545164, 0.00471817228628384, + -0.00183773644319332, -0.00570261621182479, -7.99203367291902e-05, 7.75752247926601e-05, + -9.78534772816957e-06, -4.72083745991256e-05}, + {0.692159025649028, 1.11895461388667, 0.341706834956496, 0.000237989648019541, + -0.000188322779563912, 0.000227310789253953, 0.000133001646828401, -0.000305810826248463, + 0.00010150571088124, -0.000367705461590854}, + {3.38094203317731, 1.24778838596815, 0.067807236112956, -0.00379395536123526, + -0.00339180589343601, -0.00188754615986649, -7.52406312245606e-05, 4.58398750278147e-05, + 6.97244631313601e-05, 2.50519145070895e-05}, + {-7.10546287716029, 0.459472977452686, -1.12251049944014, 0.0175406972371191, + -0.0310525406867782, -0.0531315970690727, -0.000121107664597462, 0.000544665437051928, + -1.78466217018177e-05, -0.00058976234038192}, + {1.60633684055984, 1.1975095485662, 0.180159204664965, -0.00259157601062089, + -0.0038106317634397, 0.000956686555225968, 4.28416721502134e-06, 5.84532336259517e-06, + -2.73407888222758e-05, 5.45131881032866e-06}, + {43.3732235586222, 0.528096786861784, -3.41255850703983, -0.0161629934278675, + 0.0790998053536612, 0.0743822668655928, 0.000237176965460634, -0.000426691336904078, + -0.000889196131314391, -0.000509766491897672}}; + float sus6coeffBeta[9][10] = { + {1.03872648284911, -0.213507239271552, 1.43193059498181, -0.000972717820830235, + -0.00661046096415371, 0.00974284211491888, 2.96098456891215e-05, -8.2933115634257e-05, + -5.52178824394723e-06, 5.73935295303589e-05}, + {3.42242235823356, 0.0848392511283237, 1.24574390342586, 0.00356248195980133, + 0.00100415659893053, -0.00460120247716139, 3.84891005422427e-05, 2.70236417852327e-05, + -7.58501977656551e-05, -8.79809730730992e-05}, + {14.0092526123741, 1.03126714946215, 1.0611008563785, 0.04076462444523, 0.0114106419194518, + 0.00746959159048058, 0.000388033225774727, -0.000124645014888926, -0.000296639947532341, + -0.00020861690864945}, + {1.3562422681189, -0.241585615891602, 1.49170424068611, 0.000179184170448335, + -0.00712399257616284, 0.0121433526723498, 3.29770580642447e-05, 8.78960210966787e-06, + -6.00508568552101e-05, 0.000101583822589461}, + {-0.718855428908583, -0.344067476078684, 1.12397093701762, 0.000236505431484729, + -0.000406441415248947, 0.00032834991502413, 0.000359422093285086, 8.18895560425272e-05, + 0.000316835483508523, 0.000151442890664899}, + {-0.268764016434841, -0.275272048639511, 1.26239753050527, -0.000511224336925231, + 0.0095628568270856, -0.00397960092451418, 1.39587366293607e-05, 1.31409051361129e-05, + -9.83662017231755e-05, 1.87078667116619e-05}, + {27.168106989145, -2.43346872338192, 1.91135512970771, 0.0553180826818016, + -0.0481878292619383, 0.0052773235604729, -0.000428011927975304, 0.000528018208222772, + -0.000285438191474895, -5.71327627917386e-05}, + {-0.169494136517622, -0.350851545482921, 1.19922076033643, 0.0101120903675328, + -0.00151674465424115, 0.00548694086125656, -0.000108240000970513, 1.57202185024105e-05, + -9.77555098179959e-05, 2.09624089449761e-05}, + {-32.3807957489507, 1.8271436443167, 2.51530814328123, -0.0532334586403461, + -0.0355980127727253, -0.0213373892796204, 0.00045506092539885, 0.000545065581027688, + 0.000141998709314758, 0.000101051304611037}}; + float sus7coeffAlpha[9][10] = { + {-92.1126183408754, -3.77261746189525, -4.50604668349213, -0.0909560776043523, + -0.15646903318971, -0.0766293642415356, -0.00059452135473577, -0.00144790037129283, + -0.00119021101127241, -0.000460110780350978}, + {1.60822506792345, 1.12993931449931, 0.300781032865641, -0.00405149856360946, + 0.0116663280665617, -0.000746071920075153, -8.36092173253351e-05, 0.000126762041147563, + -1.57820750462019e-05, -2.13840141586661e-05}, + {-151.403952985468, -5.77049222793992, 9.71132757422642, -0.113259116970462, + 0.284142453949027, -0.198625061659164, -0.000836450164210354, 0.00174062771509636, + -0.00323746390757859, 0.00124721932086258}, + {3.47391964888809, 1.28788318973591, 0.358380140281919, 0.0033863520864927, + 0.00154601909793475, 0.0103457296050314, -9.56426572270873e-05, 5.48838958555808e-05, + 2.97537427220847e-05, 0.000104735911514185}, + {3.32650947866065, 1.16701012685798, 0.293514063672376, -0.00065850791542434, + -8.61746510464303e-05, -0.00212038990772211, 0.00010377123197, -0.000262818127593837, + 0.000103360882478383, -0.000296739688930329}, + {-0.440176043435378, 1.18923278867097, 0.519516382652818, -0.00138846714677511, + 0.00266491699926247, -0.014254675949624, -4.20279929822439e-05, -5.49260281515447e-05, + -1.00328708454487e-05, 0.000138142092498215}, + {9.54962966738358, 1.83809145920811, 1.82162819067959, -0.0116786627338505, + -0.00496037444422313, 0.0590883547819332, 7.48465315787857e-05, 0.000221693951602584, + 7.96466345174136e-06, 0.000638822537725177}, + {7.04862901290925, 0.876813777672465, 0.16368093989381, 0.00928717461441627, + -0.00276538956293246, 0.00117995419940653, -0.000141511492474493, -6.09796031786385e-06, + -2.62114930414747e-05, -2.88713611443788e-06}, + {135.349147631811, -7.21933296299596, -6.02379024934871, 0.19557354282067, + 0.207680233512614, 0.12880101618361, -0.00169832076532024, -0.00192216719797732, + -0.00188763612041332, -0.00103101801961442}}; + float sus7coeffBeta[9][10] = { + {-12.7115487367622, -1.08890790360556, 0.0579616268854079, -0.0212303293514951, + -0.0395948453851818, -0.0275564242614342, -0.000228652851842222, -0.000148106159109458, + -0.000555136649469199, -0.000198260004582737}, + {-0.988147625946871, -0.759018567468546, 1.20998292002818, -0.0241231836977845, + -0.000572110443300516, -0.00294835038249426, -0.00026533039022186, 6.82250069765274e-06, + 7.21038415209318e-06, -6.54881435118179e-05}, + {98.0979345921564, 4.27381413621355, -4.39956005193548, 0.0709109587666745, + -0.172774236139236, 0.107243391488741, 0.000421832640471043, -0.00140450884710288, + 0.00158019019392239, -0.00078512547169536}, + {4.10892685652543, -0.229301778557857, 1.33380992987117, -0.000250095848720304, + -0.00555205065514645, 0.00355052914398176, 1.62727119770752e-05, -1.26026527654764e-05, + -3.25505031810898e-05, 5.79970895921158e-06}, + {3.09432502337258, -0.300556003790433, 1.17085811008124, 0.00128679594824324, + 0.00148229981422985, 9.15267474159147e-05, 0.000300497843413856, 6.31378865575566e-05, + 0.000258447032558814, 9.79142983264352e-05}, + {8.92336134924575, -0.197306981784312, 0.659908505354084, 0.00175572239373996, + 0.006801023678097, 0.0189775987436792, 9.2187857727721e-06, -4.8706332690626e-05, + -6.887009887486e-05, -0.000266455617735054}, + {-52.0734887320227, 2.64822385560272, -1.72387600304694, -0.0383944891609251, + 0.110873671161269, -0.0475247245070445, 0.000194652401328063, -0.000697307928990137, + 0.00124021816001, -0.000194213899980878}, + {2.08203985879155, -0.127503525368396, 1.17628056094647, 0.00283288065938444, + 0.00394668214608305, 0.00314868636161131, -2.99504350569853e-05, -7.11070816314279e-05, + -6.30148122529749e-05, 2.28114298989664e-05}, + {191.321181158032, -12.2449557187473, -7.21933741885107, 0.267954293388644, + 0.331529493933124, 0.149867703984027, -0.00222279201444128, -0.00284724570619954, + -0.00298774060233964, -0.000988903783752156}}; + float sus8coeffAlpha[9][10] = { + {5.46354311880959, 1.15370126035432, 0.568432485840475, -0.00105094692478431, + -0.000472899673842554, 0.015581320536192, 2.26460844314248e-05, -0.000254397947062058, + 0.000198938007250408, 0.000102026690279006}, + {8.8976133108173, 1.89502416095352, 0.268670471819199, 0.0217013413241972, + 0.00973925295182384, -0.00116357269193765, 0.000185865842232419, 0.000103311614912702, + -2.46539447920969e-05, -2.06292928734686e-05}, + {-45.4550803910752, 1.27220123406993, 5.21483855848504, 0.0315791081623634, + 0.0725172355124129, -0.13947591535243, 0.000412577580637848, 0.000434545096994917, + -0.000840043932292312, 0.00126857487044307}, + {1.81302768546433, 1.20563501267535, 0.344815267182167, 0.00546879453241056, + -0.00115382996865884, 0.010597876132341, -7.75885604486581e-05, 8.99568815949154e-05, + -2.98129544974679e-06, 0.000108913239345604}, + {2.19111439539173, 1.06951675598148, 0.283707798607213, 0.00016478588207518, + 0.000196086067268121, -0.00214980231173703, 0.000237820475654357, -0.000256402967908595, + 0.000165966620658577, -0.000268394081675921}, + {15.0858674915897, 1.27922724811168, -1.0803137812576, -0.00184009775302466, + -0.00458792284209219, 0.0359393555418547, -6.05121024079603e-05, -1.2288384024143e-05, + 8.55484605384438e-05, -0.000379241348638065}, + {-14.9594190080906, 1.79473182195746, -1.00830704063572, 0.000890685410857856, + 0.0408932029176081, -0.0165460857151619, -0.000170544299916973, -0.000370901607010145, + 0.000324089709129097, -9.33010240878062e-05}, + {0.867614491733251, 1.38248194737027, 0.233408537422123, -0.00772942878114575, + -0.00783126068079782, -0.000413713955432221, 4.5775750146291e-05, 6.97323029940275e-05, + 1.70664456940787e-05, 6.75517901233086e-06}, + {2.34474364146174, -0.777275400251477, 2.09531381577911, 0.0170780716714389, + 0.102855060371092, -0.1203441505925, 0.000187004964420911, -0.00141720441050986, + -0.000336251285258365, 0.00145175125888695}}; + float sus8coeffBeta[9][10] = { + {28.3033101237397, 1.77504446792811, 1.70758838986317, 0.0307800697044683, + 0.0598759344275936, -0.014461432284373, 0.000128415617799076, 0.000664419128546701, + 0.000312923304130995, -0.000269026446641855}, + {7.73040563051023, 0.0267291479555493, 1.16189582308493, 0.000611047892976521, + -0.00213680506915073, -0.00517435586596902, -3.60304406049766e-06, -1.74452976404459e-05, + -3.95396925228538e-05, -7.01948519410633e-05}, + {-48.0766126130725, -3.77981206700298, 3.03482861087335, -0.0678496412519532, + 0.115260678424016, -0.0109681510065038, -0.000438011443691466, 0.00097230136258486, + -0.000930875177732769, -0.000203144239955507}, + {12.1881935626341, -0.234345089308583, 2.01134619426134, 0.000181529284001169, + -0.00642848065105061, 0.0243985799415726, 2.0224042581776e-05, 5.22503286757285e-06, + -4.75196303016323e-05, 0.000221160482364556}, + {3.49559433498742, -0.294995112674766, 1.07892379698257, 0.000861664794052587, + 0.00138978933062055, 0.000436385106465176, 0.000288095124755908, 0.000147259769247883, + 0.000256686898599516, 0.000198982412957039}, + {9.36663996178607, -0.171266136751803, 0.799869891484541, -0.000896305696610864, + 0.00477919972789653, 0.0077876110326094, 9.16475263625076e-06, 3.02461250100473e-05, + -3.63917701783264e-05, -0.000101376940843402}, + {9.93372683055145, 1.02056557854246, 3.01635426591734, -0.0477113881140277, + -0.0280426434406976, 0.0438813017696874, 0.000470431190169998, -7.55753674679743e-05, + -0.000516444906323815, 0.000253844418223843}, + {4.12868774589741, -0.305711790187688, 1.15739216407191, 0.00942395115281056, + 0.00264758462357433, 0.00227985850688479, -0.000107537164019682, -4.91396736189963e-05, + -5.3803493814502e-05, 6.80587059728718e-06}, + {64.9193383444005, -1.57724255547465, -3.82166532626293, 0.0104712238987591, + 0.0898786950946473, 0.128910456296131, -8.27123227422217e-05, -0.000143979624107479, + -0.00146684876653306, -0.00102226799570239}}; + float sus9coeffAlpha[9][10] = { + {65.8975109449121, 2.19115342242175, 6.11069527811832, -0.0219884864133703, + 0.119985456538482, 0.142746712551924, -0.000465882328687976, 0.000606525132125852, + 0.00141667074621881, 0.00109715845894006}, + {5.70337356029945, 1.86705636976809, 0.235584190291708, 0.0194937327615426, + 0.00973291465247784, -0.00155675297510773, 0.000147099297988423, 0.000115708967219349, + -4.1462310493722e-05, -9.80097031103588e-06}, + {138.221145997284, 6.07665575619595, -9.08085914250542, 0.0839801072927519, + -0.143071750033303, 0.237868300719915, 0.000626693630444932, -0.000579788170871402, + 0.00181740650944343, -0.00207086879728281}, + {-7.78295582666151, 1.37506685179192, -0.507596181420042, 0.00350118305456038, + 0.00380814310115541, -0.0174012437563343, -0.000124801268056815, 2.96314830184492e-05, + 6.3416992450033e-05, -0.000190177262510221}, + {0.13102597129751, 1.24228303845143, 0.328808873447393, 2.6858679536165e-05, + 0.000231428138164498, -0.000584089095259736, 5.5322167970451e-05, -0.000322205709821716, + 7.71348293209208e-05, -0.000393885990364776}, + {4.64571633968935, 1.2668223691397, -0.158952088650432, -0.0038344859267428, + 0.0030051503726095, 0.00455578826025588, -9.42520993914957e-05, 5.81633314412289e-05, + -4.43545804544095e-05, -4.83524454851519e-05}, + {99.2385930314563, -3.65569343617926, 5.54203926675588, 0.0975630395981933, + -0.15701634159692, 0.107834711298836, -0.000885326636237814, 0.000960753844480462, + -0.00179894024848343, 0.000583066757644971}, + {2.82671549736619, 1.11214198870501, 0.214735318432744, 0.00284415167563662, + -0.00743289575690122, 0.000382705440762292, -7.43232442872501e-05, 6.96994098083348e-05, + -4.15108111710131e-06, 1.33520085213482e-05}, + {36.9013743125415, -0.522392401546163, -1.52452843963663, 0.0261375433218879, + 0.060573568610239, 0.0182582125221054, -0.000244373383911157, -0.000271385147292484, + -0.000723799969427732, 6.76324880239196e-05}}; + float sus9coeffBeta[9][10] = { + {128.70886435409, 7.27355509732751, 7.18142203531244, 0.1536100459329, 0.199455846541636, + 0.101824964939793, 0.00116666116789421, 0.00181595584079788, 0.00159271319494017, + 0.000556768406475719}, + {-7.07933839681024, -0.979062424441878, 1.21792546815617, -0.0295740143783226, + -0.00442780611714201, -0.00329612819203176, -0.000291373125216143, -7.47259350176359e-05, + -4.87265282482212e-05, -7.87490350444332e-05}, + {41.1357193180502, 2.75138456414254, -0.0160889117718198, 0.0274001112562423, + -0.145644717742057, -0.0316076203283094, -0.000136443337244472, -0.00153945199081365, + 0.000938960439977633, 0.000599987111822885}, + {2.7980384746608, -0.234741037383589, 1.5342193016705, -0.000993791566721689, + -0.00787533639513478, 0.00927468655141365, 2.63308697896639e-05, -3.42816267184975e-05, + -8.48879419798771e-05, 3.84043821333798e-05}, + {0.427687530667804, -0.346076633694936, 1.22968527483851, -4.95098138311122e-05, + 0.000298245372198029, 0.000332756250024796, 0.00040375986210644, 5.20675972504572e-05, + 0.000327042170278218, 5.93011568264671e-05}, + {4.50337810133314, -0.279364254817202, 0.945812187846199, 0.000116182663432306, + 0.0115646046622083, 0.00908289960302886, 1.90394667311541e-05, -4.4360223646434e-06, + -0.000131398914898614, -0.000145568992865512}, + {-36.3377213654193, 2.21047221783626, 0.0609982245149821, -0.0670546774988572, + 0.016827777144747, -0.0277834084058314, 0.000778301409125556, 0.000135846745194401, + 0.00043261858797068, -0.00021172728254561}, + {-0.737678205841529, -0.217352122193475, 1.23494846329297, 0.00748173441779792, + 0.0019595873704705, 0.00567253723266176, -8.34768773292938e-05, -3.50608394184873e-05, + -0.000107500091550635, -5.1379722947632e-07}, + {-36.6150844777671, 3.24952006904945, 1.7222457840185, -0.0846362445435584, + -0.0625549615377418, 0.019178365782485, 0.000664877496455304, 0.000942971403881222, + 0.000190754698755098, -0.000372226659190439}}; + float sus10coeffAlpha[9][10] = { + {14.4562393748324, 0.669162330324919, 2.13895255446541, -0.0161997097021299, + 0.00185995785065838, 0.0621351118528379, -0.000278999272493087, 0.000238469666491965, + -0.000279407497782961, 0.000726904943739837}, + {-4.45678285887022, 0.92869611919737, 0.186752102727282, -0.00706160758952316, + 0.00532680276723634, -0.00119102617674229, -0.000105283880098953, 3.90673052334419e-05, + -3.13338277344246e-05, 5.32977236959767e-06}, + {30.4255268053197, 3.00991076401191, -1.4855621363519, 0.033934286288413, + -0.0553588742704929, 0.0299275582316466, 0.000167915322354466, -0.00050925078118232, + 0.000463662961330962, -0.000232919143454163}, + {2.45076465343337, 1.30206564388838, 0.635121046212765, 0.00517109639797675, + 0.00360579544364496, 0.0198490668911362, -9.31556816982662e-05, 6.7313653707875e-05, + 6.4669137025142e-05, 0.000209727581169138}, + {-0.784841314851562, 1.10058314980836, 0.314063830836532, 0.000583003703415889, + 0.000312635453606579, -0.000183738114552387, 0.000214096205760617, -0.000286744686021244, + 0.000159157597180407, -0.00032235099420715}, + {7.19568036510586, 1.33307479701657, -0.465585141952456, -0.0031910726544199, + -0.00546273504371797, 0.0145494754402526, -7.9863949693769e-05, 4.83681329120104e-05, + 8.85844309936609e-05, -0.000143217870916994}, + {-12.8344546267449, 1.36023633150143, -0.728527724854506, 0.019982118403416, + 0.0385056413989437, -0.00468598272326268, -0.000303957957649245, -6.37783846968216e-05, + 0.000514049116643205, 0.000112015427600697}, + {-2.58279031298065, 1.42167821629586, 0.208769467511292, -0.00640190372145885, + -0.0056405289717473, 0.000509611313918708, 2.23310562107823e-05, 3.23685469522147e-05, + -7.55982776243849e-06, 2.78417756661088e-06}, + {-29.7178996143914, 2.636972251183, 1.97316329325243, -0.03983524158327, + -0.0193152048730234, -0.0600902798379509, 0.00031786916010672, 0.000162178988605602, + 0.000224550786416246, 0.000614337977361927}}; + float sus10coeffBeta[9][10] = { + {12.4771349792459, 1.44317849705414, 0.975637226331561, 0.0430284146301043, + 0.0220810531548995, -0.0220926906772, 0.000310052324529521, 0.000658151808869523, + -0.000288026365111098, -0.000214619731807045}, + {-0.113203260140131, -0.272424061092191, 1.27704377191184, -0.00791746619331075, + 0.00278646694862191, -0.00398881099259934, -8.09569694307212e-05, 5.99617384829016e-05, + -5.4550919751855e-05, -8.6314530565085e-05}, + {-48.585664295448, -2.04899787231903, 4.48757129623549, -0.0226180460431321, + 0.090326735447661, -0.0722998813632622, -6.77623771415477e-05, 0.000562585419036509, + -0.000956171370931993, 0.000491554402311223}, + {-1.20986884955482, -0.215604107185474, 1.22123198786617, 0.000256508527822089, + -0.00625056735692847, 0.00262961582224303, 2.27433984698861e-05, 1.60471509861372e-05, + -4.85061736834262e-05, -1.8387092782907e-06}, + {-0.250205907903409, -0.315819331560782, 1.09018364376391, -0.000521787614293089, + -0.000500747760913489, 2.48184307342838e-05, 0.000313799238640988, 0.000136669146368744, + 0.000278914324565192, 0.000218512838469476}, + {-1.38512578184076, -0.240456589364121, 1.34170304231345, 0.00017499230372669, + 0.0070862275911073, -0.00460640844814105, 1.27594111036696e-05, -4.73855624902052e-06, + -5.41141037812903e-05, 8.50767021818388e-06}, + {58.9917559342216, -2.28705697628345, 5.35995190407842, 0.0214721399750612, + -0.112195722921667, 0.0890150265857542, -0.000100675657768708, 0.000493488022135339, + -0.00137672908303878, 0.000518683157694955}, + {3.18905073365834, -0.633376549706314, 1.17190259811174, 0.0188817945597344, + 0.00107470708915782, 0.00400880471375267, -0.000197312295539118, -2.46543035998379e-05, + -6.07871064300252e-05, 1.91822310311955e-05}, + {-21.6881499304099, -0.563186103920008, 3.70747028664292, 0.021112883967427, + -0.00650020689049325, -0.0778533644688476, -0.000131921888670268, -0.000402754836445439, + 0.000551249824375055, 0.00062236627391337}}; + float sus11coeffAlpha[9][10] = { + {-5.23569698615548, -1.45500092391928, 2.7643243644756, -0.0762912296128707, + -0.0201645929971608, 0.0997226845779083, -0.000741669441569556, -0.000485368004931528, + 0.000166230212359982, 0.00103455037278067}, + {-7.7405077383712, 0.892040861541276, 0.39014957203484, -0.00952030929935314, + 0.0185577462685363, 0.000500600568760257, -0.000151227821554572, 0.000245334737283439, + 1.89380065823205e-05, 1.83481122973969e-07}, + {-27.462143709831, -1.68192474287299, 0.689411302961069, -0.0146021086710062, + 0.227153492753605, 0.0815806579791421, 2.92919049429149e-05, 0.00153760357651792, + -0.00247865821192621, -0.00166333309739387}, + {-6.74664748624368, 1.43279156053015, 0.0212787292914553, 0.00764792230365732, + 0.00796410301290615, 0.0014384998868733, -8.95239151813685e-05, 9.55245417090909e-05, + 0.000127251739461239, 3.26943341606363e-05}, + {-2.20391533717674, 1.32902400478083, 0.38633027011889, 0.00104660852197061, + 0.00105228824412283, -0.00242067551428214, -6.98346290136652e-05, -0.000369075232184835, + -1.59510520000704e-05, -0.000448565104826966}, + {-5.29476778147188, 1.4549556336236, 0.561334186252557, -0.00260896342687109, + -0.00855934179001141, -0.0182515354646452, -8.79385828606048e-05, 5.98357681659175e-05, + 0.000146570207542509, 0.000201596912834554}, + {-45.7906613832612, 3.77630104475902, -1.67494598155515, -0.0545433897761635, + 0.047897938410221, -0.0355687158405231, 0.000374738707508583, -0.000448453494537518, + 0.000377784972619365, -0.000276573228333836}, + {-9.11681182090372, 2.06933872940742, 0.26131496122122, -0.0259534033367855, + -0.00777266937872862, -0.00262135395843891, 0.000223790782655445, 6.40488537928934e-05, + 7.75581514100296e-05, -9.25934285039627e-06}, + {183.243883340634, -8.02281039502717, -10.0756951652703, 0.168750521462303, + 0.314006821405967, 0.200264755034566, -0.0011895153717447, -0.00253812476819896, + -0.00291324393641628, -0.00140062522117514}}; + float sus11coeffBeta[9][10] = { + {34.4499366074013, -0.438583698052091, 4.72111001451028, -0.041810050989433, + 0.0562461093661426, 0.0856849892524893, -0.000477813051406167, -3.16404257494464e-05, + 0.00102633196865105, 0.000552974013759876}, + {7.6366298088699, 0.150314752958302, 1.31364679484924, 0.00557696667395871, + 0.00163731860604376, -0.00454759608980269, 5.83979683674572e-05, 4.45944881220665e-05, + -4.27874746147066e-05, -8.77418673597557e-05}, + {130.156215011893, 1.85759000444524, -10.986892391833, -0.00686275191260681, + -0.188837138116058, 0.346177462085361, -0.000183276946352264, -0.000702183496893294, + 0.00293145272693857, -0.00318194442670715}, + {-1.67854820161036, -0.358899332859806, 0.956690839640595, -4.93862910503803e-05, + -0.0136134783014874, -0.00848731301504507, 3.75950499927045e-05, 1.35374694383289e-06, + -0.000156596507890443, -0.000123254220377897}, + {3.67569209537767, -0.387260959713287, 1.31343215605952, -0.00206444615206506, + 0.00145334813110285, -0.00151259497696238, 0.000449492568365603, 6.95883968949488e-07, + 0.000368585523744765, -6.3420715525635e-05}, + {14.3296323024886, -0.182979476956897, 0.306817119309235, -0.00022212115978293, + 0.00463485302909649, 0.0277574953550035, 1.1422454625565e-05, 1.06053257479502e-05, + -2.05720000720608e-05, -0.000338584671430337}, + {-18.7534921817754, 1.14272710923224, 0.460498062012866, -0.00995826989278202, + 0.0658502318647112, 0.00616942819937029, -7.70857153768402e-05, -0.000641755741925561, + 0.00047849204592989, 0.000158509018296766}, + {1.26543621388607, -0.176674379740481, 1.38814920935488, 0.00545485262295305, + -0.00499775616702264, 0.0038057039142173, -6.59604252054511e-05, 6.40211116049053e-05, + -6.74778593434431e-05, -2.81973589469059e-05}, + {116.975421945286, -5.53022680362263, -5.61081660666997, 0.109754904982136, + 0.167666815691513, 0.163137400730063, -0.000609874123906977, -0.00205336098697513, + -0.000889232196185857, -0.00168429567131815}}; + float susBrightnessThreshold = 0.7; + float susVectorFilterWeight = .95; + float susRateFilterWeight = .99; + } susHandlingParameters; + + struct GyrHandlingParameters { + double gyr0orientationMatrix[3][3] = {{0, 0, -1}, {0, -1, 0}, {-1, 0, 0}}; + double gyr1orientationMatrix[3][3] = {{0, 0, -1}, {0, 1, 0}, {1, 0, 0}}; + double gyr2orientationMatrix[3][3] = {{0, 0, -1}, {0, -1, 0}, {-1, 0, 0}}; + double gyr3orientationMatrix[3][3] = {{0, 0, -1}, {0, 1, 0}, {1, 0, 0}}; + + double gyr0bias[3] = {0.0, 0.4, -0.1}; + double gyr1bias[3] = {0.0956745283018868, 2.0854575471698116, 1.2505990566037737}; + double gyr2bias[3] = {0.1, 0.7, -0.2}; + double gyr3bias[3] = {-0.10721698113207549, -0.6111650943396226, 0.1716462264150944}; + + /* var = sigma^2, sigma = RND*sqrt(freq), following values are RND^2 and not var as freq is + * assumed to be equal for the same class of sensors */ + double gyr02variance[3] = {pow(4.6e-3, 2), // RND_x = 3.0e-3 deg/s/sqrt(Hz) rms + pow(4.6e-3, 2), // RND_y = 3.0e-3 deg/s/sqrt(Hz) rms + pow(6.1e-3, 2)}; // RND_z = 4.3e-3 deg/s/sqrt(Hz) rms + double gyr13variance[3] = {pow(11e-3, 2), pow(11e-3, 2), pow(11e-3, 2)}; + uint8_t preferAdis = false; + float gyrFilterWeight = 0.6; + } gyrHandlingParameters; + + struct RwHandlingParameters { + double inertiaWheel = 0.000028198; + double maxTrq = 0.0032; // 3.2 [mNm] + int32_t maxRwSpeed = 65000; // 0.1 RPM + int32_t stictionSpeed = 1000; // 0.1 RPM + int32_t stictionReleaseSpeed = 1000; // 0.1 RPM + double stictionTorque = 0.0006; + + uint16_t rampTime = 10; + + uint32_t multipleRwInvalidTimeout = 25; + } rwHandlingParameters; + + struct RwMatrices { + double alignmentMatrix[3][4] = {{-0.9205, 0.0000, 0.9205, 0.0000}, + {0.0000, 0.9205, 0.0000, -0.9205}, + {-0.3907, -0.3907, -0.3907, -0.3907}}; + double pseudoInverse[4][3] = { + {-0.5432, 0, -0.6399}, {0, 0.5432, -0.6399}, {0.5432, 0, -0.6399}, {0, -0.5432, -0.6399}}; + double pseudoInverseWithoutRW1[4][3] = { + {0, 0, 0}, {-0.5432, 0.5432, -1.2798}, {1.0864, 0, 0}, {-0.5432, -0.5432, -1.2798}}; + double pseudoInverseWithoutRW2[4][3] = { + {-0.5432, 0.5432, -1.2798}, {0, 0, 0}, {0.5432, 0.5432, -1.2798}, {0, -1.0864, 0}}; + double pseudoInverseWithoutRW3[4][3] = { + {-1.0864, 0, 0}, {0.5432, 0.5432, -1.2798}, {0, 0, 0}, {0.5432, -0.5432, -1.2798}}; + double pseudoInverseWithoutRW4[4][3] = { + {-0.5432, -0.5432, -1.2798}, {0, 1.0864, 0}, {0.5432, -0.5432, -1.2798}, {0, 0, 0}}; + double nullspaceVector[4] = {-1, 1, -1, 1}; + } rwMatrices; + + struct SafeModeControllerParameters { + double k_orthoMekf = 4.4e-3; + double k_alignMekf = 4.0e-5; + double k_parallelMekf = 3.75e-4; + + double k_orthoGyr = 4.4e-3; + double k_alignGyr = 4.0e-5; + double k_parallelGyr = 3.75e-4; + + double k_orthoSusMgm = 4.4e-3; + double k_alignSusMgm = 4.0e-5; + double k_parallelSusMgm = 3.75e-4; + + double sunTargetDirLeop[3] = {0, sqrt(.5), sqrt(.5)}; + double sunTargetDir[3] = {0, 0, 1}; + + uint8_t useMekf = false; + uint8_t useGyr = false; + uint8_t dampingDuringEclipse = true; + + float sineLimitSunRotRate = 0.24; + } safeModeControllerParameters; + + struct PointingLawParameters { + double zeta = 0.3; + double om = 0.3; + double omMax = 1 * DEG2RAD; + double qiMin = 0.1; + + double gainNullspace = 0.01; + double nullspaceSpeed = 32500; // 0.1 RPM + + double desatMomentumRef[3] = {0, 0, 0}; + double deSatGainFactor = 1000; + uint8_t desatOn = true; + uint8_t useMekf = true; + } pointingLawParameters; + + struct IdleModeControllerParameters : PointingLawParameters { + } idleModeControllerParameters; + + struct TargetModeControllerParameters : PointingLawParameters { + double refDirection[3] = {-1, 0, 0}; // Antenna + double refRotRate[3] = {0, 0, 0}; + double quatRef[4] = {0, 0, 0, 1}; + uint8_t timeElapsedMax = 10; // rot rate calculations + + // Default is Stuttgart GS + double latitudeTgt = 48.7495 * DEG2RAD; // [rad] Latitude + double longitudeTgt = 9.10384 * DEG2RAD; // [rad] Longitude + double altitudeTgt = 500; // [m] + + // For one-axis control: + uint8_t avoidBlindStr = true; + double blindAvoidStart = 1.5; + double blindAvoidStop = 2.5; + double blindRotRate = 1. * DEG2RAD; + } targetModeControllerParameters; + + struct GsTargetModeControllerParameters : PointingLawParameters { + double refDirection[3] = {-1, 0, 0}; // Antenna + uint8_t timeElapsedMax = 10; // rot rate calculations + + // Default is Stuttgart GS + double latitudeTgt = 48.7495 * DEG2RAD; // [rad] Latitude + double longitudeTgt = 9.10384 * DEG2RAD; // [rad] Longitude + double altitudeTgt = 500; // [m] + double rotRateLimit = .75 * DEG2RAD; + } gsTargetModeControllerParameters; + + struct NadirModeControllerParameters : PointingLawParameters { + double refDirection[3] = {-1, 0, 0}; // Antenna + double quatRef[4] = {0, 0, 0, 1}; + double refRotRate[3] = {0, 0, 0}; + uint8_t timeElapsedMax = 10; // rot rate calculations + } nadirModeControllerParameters; + + struct InertialModeControllerParameters : PointingLawParameters { + double tgtQuat[4] = {0, 0, 0, 1}; + double refRotRate[3] = {0, 0, 0}; + double quatRef[4] = {0, 0, 0, 1}; + } inertialModeControllerParameters; + + struct StrParameters { + double exclusionAngle = 20. * DEG2RAD; + double boresightAxis[3] = {0.7593, 0.0000, -0.6508}; // body rf + } strParameters; + + struct GpsParameters { + double timeDiffVelocityMax = 30; // [s] + double minimumFdirAltitude = 475 * 1e3; // [m] + double maximumFdirAltitude = 575 * 1e3; // [m] + double fdirAltitude = 525 * 1e3; // [m] + uint8_t useSpg4 = true; + } gpsParameters; + + struct SunModelParameters { + float domega = 36000.771; + float omega_0 = 280.46 * DEG2RAD; // RAAN plus argument of + // perigee + float m_0 = 357.5277; // coefficients for mean anomaly + float dm = 35999.049; // coefficients for mean anomaly + float e = 23.4392911 * DEG2RAD; // angle of earth's rotation axis + float e1 = 0.74508 * DEG2RAD; + + float p1 = 6892. / 3600. * DEG2RAD; // some parameter + float p2 = 72. / 3600. * DEG2RAD; // some parameter + } sunModelParameters; + + struct KalmanFilterParameters { + double sensorNoiseStr = 0.0028 * DEG2RAD; + double sensorNoiseSus = 8. * DEG2RAD; + double sensorNoiseMgm = 4. * DEG2RAD; + double sensorNoiseGyr = 0.1 * DEG2RAD; + + double sensorNoiseGyrArw = 3. * 0.0043 / sqrt(10) * DEG2RAD; // Angular Random Walk + double sensorNoiseGyrBs = 3. / 3600. * DEG2RAD; // Bias Stability + + uint8_t allowStr = true; + } kalmanFilterParameters; + + struct MagnetorquerParameter { + double mtq0orientationMatrix[3][3] = {{1, 0, 0}, {0, 0, 1}, {0, -1, 0}}; + double mtq1orientationMatrix[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + double mtq2orientationMatrix[3][3] = {{0, 0, 1}, {0, 1, 0}, {-1, 0, 0}}; + double alignmentMatrixMtq[3][3] = {{0, 0, -1}, {-1, 0, 0}, {0, 1, 0}}; + double inverseAlignment[3][3] = {{0, -1, 0}, {0, 0, 1}, {-1, 0, 0}}; + double dipoleMax = 0.2; // [Am^2] + + uint16_t torqueDuration = 300; // [ms] + } magnetorquerParameter; + + struct DetumbleParameter { + uint8_t detumblecounter = 75; // 30 s + double omegaDetumbleStart = 2 * DEG2RAD; + double omegaDetumbleEnd = 1 * DEG2RAD; + double gainBdot = pow(10.0, -3.3); + double gainFull = pow(10.0, -2.3); + uint8_t useFullDetumbleLaw = false; + } detumbleParameter; +}; + +#endif /* ACSPARAMETERS_H_ */ diff --git a/mission/controller/acs/ActuatorCmd.cpp b/mission/controller/acs/ActuatorCmd.cpp new file mode 100644 index 0000000..0fb8bdf --- /dev/null +++ b/mission/controller/acs/ActuatorCmd.cpp @@ -0,0 +1,77 @@ +#include "ActuatorCmd.h" + +#include +#include +#include +#include + +ActuatorCmd::ActuatorCmd() {} + +ActuatorCmd::~ActuatorCmd() {} + +void ActuatorCmd::scalingTorqueRws(double *rwTrq, double maxTorque) { + uint8_t maxIdx = 0; + VectorOperations::maxAbsValue(rwTrq, 4, &maxIdx); + double maxValue = rwTrq[maxIdx]; + + if (maxValue > maxTorque) { + double scalingFactor = maxTorque / maxValue; + VectorOperations::mulScalar(rwTrq, scalingFactor, rwTrq, 4); + } +} + +void ActuatorCmd::cmdSpeedToRws(const int32_t speedRw0, const int32_t speedRw1, + const int32_t speedRw2, const int32_t speedRw3, + const double sampleTime, const double inertiaWheel, + const int32_t maxRwSpeed, const double *rwTorque, + int32_t *rwCmdSpeed) { + // group RW speed values (in 0.1 [RPM]) in vector + int32_t speedRws[4] = {speedRw0, speedRw1, speedRw2, speedRw3}; + + // calculate required RW speed as sum of current RW speed and RW speed delta + // delta w_rw = delta t / I_RW * torque_RW [rad/s] + double deltaSpeed[4] = {0, 0, 0, 0}; + const double factor = sampleTime / inertiaWheel * RAD_PER_SEC_TO_RPM * 10; + VectorOperations::mulScalar(rwTorque, factor, deltaSpeed, 4); + + // convert double to int32 + int32_t deltaSpeedInt[4] = {0, 0, 0, 0}; + for (int i = 0; i < 4; i++) { + deltaSpeedInt[i] = std::round(deltaSpeed[i]); + } + + // sum of current RW speed and RW speed delta + VectorOperations::add(speedRws, deltaSpeedInt, rwCmdSpeed, 4); + + // crop values which would exceed the maximum possible RPM + for (uint8_t i = 0; i < 4; i++) { + if (rwCmdSpeed[i] > maxRwSpeed) { + rwCmdSpeed[i] = maxRwSpeed; + } else if (rwCmdSpeed[i] < -maxRwSpeed) { + rwCmdSpeed[i] = -maxRwSpeed; + } + } +} + +void ActuatorCmd::cmdDipoleMtq(const double *inverseAlignment, const double maxDipole, + const double *dipoleMoment, int16_t *dipoleMomentActuator) { + // convert to actuator frame + double dipoleMomentActuatorDouble[3] = {0, 0, 0}; + MatrixOperations::multiply(inverseAlignment, dipoleMoment, dipoleMomentActuatorDouble, 3, + 3, 1); + // scaling along largest element if dipole exceeds maximum + uint8_t maxIdx = 0; + VectorOperations::maxAbsValue(dipoleMomentActuatorDouble, 3, &maxIdx); + double maxAbsValue = std::abs(dipoleMomentActuatorDouble[maxIdx]); + if (maxAbsValue > maxDipole) { + double scalingFactor = maxDipole / maxAbsValue; + VectorOperations::mulScalar(dipoleMomentActuatorDouble, scalingFactor, + dipoleMomentActuatorDouble, 3); + } + // scale dipole from 1 Am^2 to 1e^-4 Am^2 + VectorOperations::mulScalar(dipoleMomentActuatorDouble, 1e4, dipoleMomentActuatorDouble, + 3); + for (int i = 0; i < 3; i++) { + dipoleMomentActuator[i] = std::round(dipoleMomentActuatorDouble[i]); + } +} diff --git a/mission/controller/acs/ActuatorCmd.h b/mission/controller/acs/ActuatorCmd.h new file mode 100644 index 0000000..b14a4a2 --- /dev/null +++ b/mission/controller/acs/ActuatorCmd.h @@ -0,0 +1,46 @@ +#ifndef ACTUATORCMD_H_ +#define ACTUATORCMD_H_ + +#include + +class ActuatorCmd { + public: + ActuatorCmd(); + virtual ~ActuatorCmd(); + + /* + * @brief: scalingTorqueRws() scales the torque via maximum part in case this part is + * higher then the maximum torque + * @param: rwTrq given torque for reaction wheels which will be + * scaled if needed to be + */ + void scalingTorqueRws(double *rwTrq, double maxTorque); + + /* + * @brief: cmdSpeedToRws() Calculates the RPM for the reaction wheel configuration, + * given the required torque calculated by the controller. Will also scale down the RPM of the + * wheels if they exceed the maximum possible RPM + * @param: rwTrq given torque from pointing controller + * rwCmdSpeed output revolutions per minute for every + * reaction wheel + */ + void cmdSpeedToRws(const int32_t speedRw0, const int32_t speedRw1, const int32_t speedRw2, + const int32_t speedRw3, const double sampleTime, const double inertiaWheel, + const int32_t maxRwSpeed, const double *rwTorque, int32_t *rwCmdSpeed); + + /* + * @brief: cmdDipoleMtq() gives the commanded dipole moment for the + * magnetorquer + * + * @param: dipoleMoment given dipole moment in spacecraft frame + * dipoleMomentActuator resulting dipole moment in actuator reference frame + */ + void cmdDipoleMtq(const double *inverseAlignment, const double maxDipole, + const double *dipoleMoment, int16_t *dipoleMomentActuator); + + protected: + private: + static constexpr double RAD_PER_SEC_TO_RPM = 60 / (2 * M_PI); +}; + +#endif /* ACTUATORCMD_H_ */ diff --git a/mission/controller/acs/AttitudeEstimation.cpp b/mission/controller/acs/AttitudeEstimation.cpp new file mode 100644 index 0000000..d59afe8 --- /dev/null +++ b/mission/controller/acs/AttitudeEstimation.cpp @@ -0,0 +1,123 @@ +#include "AttitudeEstimation.h" + +AttitudeEstimation::AttitudeEstimation(AcsParameters *acsParameters_) { + acsParameters = acsParameters_; +} + +AttitudeEstimation::~AttitudeEstimation() {} + +void AttitudeEstimation::quest(acsctrl::SusDataProcessed *susData, + acsctrl::MgmDataProcessed *mgmData, + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + if (not(susData->susVecTot.isValid() and susData->sunIjkModel.isValid() and + mgmData->mgmVecTot.isValid() and mgmData->magIgrfModel.isValid())) { + { + PoolReadGuard pg{attitudeEstimationData}; + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(attitudeEstimationData->quatQuest.value, ZERO_VEC4, 4 * sizeof(double)); + attitudeEstimationData->quatQuest.setValid(false); + } + } + return; + } + + // Normalize Data + double normMgmB[3] = {0, 0, 0}, normMgmI[3] = {0, 0, 0}, normSusB[3] = {0, 0, 0}, + normSusI[3] = {0, 0, 0}; + VectorOperations::normalize(susData->susVecTot.value, normSusB, 3); + VectorOperations::normalize(susData->sunIjkModel.value, normSusI, 3); + VectorOperations::normalize(mgmData->mgmVecTot.value, normMgmB, 3); + VectorOperations::normalize(mgmData->magIgrfModel.value, normMgmI, 3); + + if ((std::acos(VectorOperations::dot(normSusB, normMgmB)) < + acsParameters->onBoardParams.questAngleLimit) or + (std::acos(VectorOperations::dot(normSusI, normMgmI)) < + acsParameters->onBoardParams.questAngleLimit)) { + { + PoolReadGuard pg{attitudeEstimationData}; + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(attitudeEstimationData->quatQuest.value, ZERO_VEC4, 4 * sizeof(double)); + attitudeEstimationData->quatQuest.setValid(false); + } + } + return; + } + + // Create Helper Vectors + double normHelperB[3] = {0, 0, 0}, normHelperI[3] = {0, 0, 0}, helperCross[3] = {0, 0, 0}, + helperSum[3] = {0, 0, 0}; + VectorOperations::cross(normSusB, normMgmB, normHelperB); + VectorOperations::cross(normSusI, normMgmI, normHelperI); + VectorOperations::normalize(normHelperB, normHelperB, 3); + VectorOperations::normalize(normHelperI, normHelperI, 3); + VectorOperations::cross(normHelperB, normHelperI, helperCross); + VectorOperations::add(normHelperB, normHelperI, helperSum, 3); + + // Sensor Weights + double kSus = 0, kMgm = 0; + kSus = std::pow(acsParameters->kalmanFilterParameters.sensorNoiseSus, -2); + kMgm = std::pow(acsParameters->kalmanFilterParameters.sensorNoiseMgm, -2); + + // Weighted Vectors + double weightedSusB[3] = {0, 0, 0}, weightedMgmB[3] = {0, 0, 0}, kSusVec[3] = {0, 0, 0}, + kMgmVec[3] = {0, 0, 0}, kSumVec[3] = {0, 0, 0}; + VectorOperations::mulScalar(normSusB, kSus, weightedSusB, 3); + VectorOperations::mulScalar(normMgmB, kMgm, weightedMgmB, 3); + VectorOperations::cross(weightedSusB, normSusI, kSusVec); + VectorOperations::cross(weightedMgmB, normMgmI, kMgmVec); + VectorOperations::add(kSusVec, kMgmVec, kSumVec, 3); + + // Some weird Angles + double alpha = (1 + VectorOperations::dot(normHelperB, normHelperI)) * + (VectorOperations::dot(weightedSusB, normSusI) + + VectorOperations::dot(weightedMgmB, normMgmI)) + + VectorOperations::dot(helperCross, kSumVec); + double beta = VectorOperations::dot(helperSum, kSumVec); + double gamma = std::sqrt(std::pow(alpha, 2) + std::pow(beta, 2)); + + // I don't even know what this is supposed to be + double constPlus = + 1. / (2 * std::sqrt(gamma * (gamma + alpha) * + (1 + VectorOperations::dot(normHelperB, normHelperI)))); + double constMinus = + 1. / (2 * std::sqrt(gamma * (gamma - alpha) * + (1 + VectorOperations::dot(normHelperB, normHelperI)))); + + // Calculate Quaternion + double qBI[4] = {0, 0, 0, 0}, qRotVecTot[3] = {0, 0, 0}, qRotVecPt0[3] = {0, 0, 0}, + qRotVecPt1[3] = {0, 0, 0}; + if (alpha >= 0) { + // Scalar Part + qBI[3] = (gamma + alpha) * (1 + VectorOperations::dot(normHelperB, normHelperI)); + // Rotational Vector Part + VectorOperations::mulScalar(helperCross, gamma + alpha, qRotVecPt0, 3); + VectorOperations::mulScalar(helperSum, beta, qRotVecPt1, 3); + VectorOperations::add(qRotVecPt0, qRotVecPt1, qRotVecTot, 3); + std::memcpy(qBI, qRotVecTot, sizeof(qRotVecTot)); + + VectorOperations::mulScalar(qBI, constPlus, qBI, 4); + QuaternionOperations::normalize(qBI, qBI); + } else { + // Scalar Part + qBI[3] = (beta) * (1 + VectorOperations::dot(normHelperB, normHelperI)); + // Rotational Vector Part + VectorOperations::mulScalar(helperCross, beta, qRotVecPt0, 3); + VectorOperations::mulScalar(helperSum, gamma - alpha, qRotVecPt1, 3); + VectorOperations::add(qRotVecPt0, qRotVecPt1, qRotVecTot, 3); + std::memcpy(qBI, qRotVecTot, sizeof(qRotVecTot)); + + VectorOperations::mulScalar(qBI, constMinus, qBI, 4); + QuaternionOperations::normalize(qBI, qBI); + } + // Low Pass + if (VectorOperations::norm(qOld, 4) != 0.0) { + QuaternionOperations::slerp(qBI, qOld, acsParameters->onBoardParams.questFilterWeight, qBI); + } + { + PoolReadGuard pg{attitudeEstimationData}; + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(attitudeEstimationData->quatQuest.value, qBI, 4 * sizeof(double)); + attitudeEstimationData->quatQuest.setValid(true); + } + } +} diff --git a/mission/controller/acs/AttitudeEstimation.h b/mission/controller/acs/AttitudeEstimation.h new file mode 100644 index 0000000..8f307cc --- /dev/null +++ b/mission/controller/acs/AttitudeEstimation.h @@ -0,0 +1,31 @@ +#ifndef MISSION_CONTROLLER_ACS_ATTITUDEESTIMATION_H_ +#define MISSION_CONTROLLER_ACS_ATTITUDEESTIMATION_H_ + +#include +#include +#include +#include +#include + +#include +#include + +class AttitudeEstimation { + public: + AttitudeEstimation(AcsParameters *acsParameters_); + virtual ~AttitudeEstimation(); + ; + + void quest(acsctrl::SusDataProcessed *susData, acsctrl::MgmDataProcessed *mgmData, + acsctrl::AttitudeEstimationData *attitudeEstimation); + + protected: + private: + AcsParameters *acsParameters; + + double qOld[4] = {0, 0, 0, 0}; + + static constexpr double ZERO_VEC4[4] = {0, 0, 0, 0}; +}; + +#endif /* MISSION_CONTROLLER_ACS_ATTITUDEESTIMATION_H_ */ diff --git a/mission/controller/acs/CMakeLists.txt b/mission/controller/acs/CMakeLists.txt new file mode 100644 index 0000000..4af05d8 --- /dev/null +++ b/mission/controller/acs/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE AcsParameters.cpp + ActuatorCmd.cpp + AttitudeEstimation.cpp + FusedRotationEstimation.cpp + Guidance.cpp + Igrf13Model.cpp + MultiplicativeKalmanFilter.cpp + Navigation.cpp + SensorProcessing.cpp + SensorValues.cpp + SusConverter.cpp) + +add_subdirectory(control) diff --git a/mission/controller/acs/FusedRotationEstimation.cpp b/mission/controller/acs/FusedRotationEstimation.cpp new file mode 100644 index 0000000..21c7f33 --- /dev/null +++ b/mission/controller/acs/FusedRotationEstimation.cpp @@ -0,0 +1,330 @@ +#include "FusedRotationEstimation.h" + +FusedRotationEstimation::FusedRotationEstimation(AcsParameters *acsParameters_) { + acsParameters = acsParameters_; +} + +void FusedRotationEstimation::estimateFusedRotationRate( + const Mode_t mode, acsctrl::SusDataProcessed *susDataProcessed, + acsctrl::MgmDataProcessed *mgmDataProcessed, acsctrl::GyrDataProcessed *gyrDataProcessed, + ACS::SensorValues *sensorValues, acsctrl::AttitudeEstimationData *attitudeEstimationData, + const double timeDelta, acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData, + acsctrl::FusedRotRateData *fusedRotRateData) { + estimateFusedRotationRateStr(sensorValues, timeDelta, fusedRotRateSourcesData); + estimateFusedRotationRateQuest(attitudeEstimationData, timeDelta, fusedRotRateSourcesData); + estimateFusedRotationRateSusMgm(susDataProcessed, mgmDataProcessed, gyrDataProcessed, + fusedRotRateSourcesData); + + if (not(mode == acs::AcsMode::SAFE) and (fusedRotRateSourcesData->rotRateTotalStr.isValid() and + acsParameters->onBoardParams.fusedRateFromStr)) { + PoolReadGuard pg(fusedRotRateData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateData->rotRateTotalSource.value, + fusedRotRateSourcesData->rotRateTotalStr.value, 3 * sizeof(double)); + fusedRotRateData->rotRateTotalSource.setValid(true); + fusedRotRateData->rotRateSource.value = acs::rotrate::Source::STR; + fusedRotRateData->rotRateSource.setValid(true); + } + } else if (not(mode == acs::AcsMode::SAFE) and + (fusedRotRateSourcesData->rotRateTotalQuest.isValid() and + acsParameters->onBoardParams.fusedRateFromQuest)) { + PoolReadGuard pg(fusedRotRateData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateData->rotRateTotalSource.value, + fusedRotRateSourcesData->rotRateTotalQuest.value, 3 * sizeof(double)); + fusedRotRateData->rotRateTotalSource.setValid(true); + fusedRotRateData->rotRateSource.value = acs::rotrate::Source::QUEST; + fusedRotRateData->rotRateSource.setValid(true); + } + } else if (fusedRotRateSourcesData->rotRateTotalSusMgm.isValid()) { + std::memcpy(fusedRotRateData->rotRateTotalSource.value, + fusedRotRateSourcesData->rotRateTotalSusMgm.value, 3 * sizeof(double)); + fusedRotRateData->rotRateTotalSource.setValid(true); + fusedRotRateData->rotRateSource.value = acs::rotrate::Source::SUSMGM; + fusedRotRateData->rotRateSource.setValid(true); + } else { + PoolReadGuard pg(fusedRotRateData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateData->rotRateTotalSource.value, ZERO_VEC3, 3 * sizeof(double)); + fusedRotRateData->rotRateTotalSource.setValid(false); + fusedRotRateData->rotRateSource.value = acs::rotrate::Source::NONE; + fusedRotRateData->rotRateSource.setValid(true); + } + } + if (fusedRotRateSourcesData->rotRateTotalSusMgm.isValid()) { + std::memcpy(fusedRotRateData->rotRateTotalSusMgm.value, + fusedRotRateSourcesData->rotRateTotalSusMgm.value, 3 * sizeof(double)); + fusedRotRateData->rotRateTotalSusMgm.setValid(true); + } else { + PoolReadGuard pg(fusedRotRateData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateData->rotRateTotalSusMgm.value, ZERO_VEC3, 3 * sizeof(double)); + fusedRotRateData->rotRateTotalSusMgm.setValid(false); + } + } +} + +void FusedRotationEstimation::estimateFusedRotationRateStr( + ACS::SensorValues *sensorValues, const double timeDelta, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData) { + if (not(sensorValues->strSet.caliQw.isValid() and sensorValues->strSet.caliQx.isValid() and + sensorValues->strSet.caliQy.isValid() and sensorValues->strSet.caliQz.isValid())) { + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateTotalStr.value, ZERO_VEC3, 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalStr.setValid(false); + } + } + std::memcpy(quatOldStr, ZERO_VEC4, sizeof(quatOldStr)); + return; + } + + double quatNew[4] = {sensorValues->strSet.caliQx.value, sensorValues->strSet.caliQy.value, + sensorValues->strSet.caliQz.value, sensorValues->strSet.caliQw.value}; + if (VectorOperations::norm(quatOldStr, 4) != 0 and timeDelta != 0) { + double quatOldInv[4] = {0, 0, 0, 0}; + double quatDelta[4] = {0, 0, 0, 0}; + + QuaternionOperations::inverse(quatOldStr, quatOldInv); + QuaternionOperations::multiply(quatNew, quatOldInv, quatDelta); + if (VectorOperations::norm(quatDelta, 4) != 0.0) { + QuaternionOperations::normalize(quatDelta); + } + + double rotVec[3] = {0, 0, 0}; + double angle = QuaternionOperations::getAngle(quatDelta); + if (VectorOperations::norm(quatDelta, 3) == 0.0) { + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateTotalStr.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalStr.setValid(true); + } + } + std::memcpy(quatOldStr, quatNew, sizeof(quatOldStr)); + return; + } + VectorOperations::normalize(quatDelta, rotVec, 3); + VectorOperations::mulScalar(rotVec, angle / timeDelta, rotVec, 3); + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateTotalStr.value, rotVec, 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalStr.setValid(true); + } + } + std::memcpy(quatOldStr, quatNew, sizeof(quatOldStr)); + return; + } + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateTotalStr.value, ZERO_VEC3, 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalStr.setValid(false); + } + } + std::memcpy(quatOldStr, quatNew, sizeof(quatOldStr)); + return; +} + +void FusedRotationEstimation::estimateFusedRotationRateQuest( + acsctrl::AttitudeEstimationData *attitudeEstimationData, const double timeDelta, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData) { + if (not attitudeEstimationData->quatQuest.isValid()) { + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateTotalQuest.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalQuest.setValid(false); + } + } + std::memcpy(quatOldQuest, ZERO_VEC4, sizeof(quatOldQuest)); + } + + if (VectorOperations::norm(quatOldQuest, 4) != 0 and timeDelta != 0) { + double quatOldInv[4] = {0, 0, 0, 0}; + double quatDelta[4] = {0, 0, 0, 0}; + + QuaternionOperations::inverse(quatOldQuest, quatOldInv); + QuaternionOperations::multiply(attitudeEstimationData->quatQuest.value, quatOldInv, quatDelta); + if (VectorOperations::norm(quatDelta, 4) != 0.0) { + QuaternionOperations::normalize(quatDelta); + } + + double rotVec[3] = {0, 0, 0}; + double angle = QuaternionOperations::getAngle(quatDelta); + if (VectorOperations::norm(quatDelta, 3) == 0.0) { + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateTotalQuest.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalQuest.setValid(true); + } + } + std::memcpy(quatOldQuest, attitudeEstimationData->quatQuest.value, sizeof(quatOldQuest)); + return; + } + VectorOperations::normalize(quatDelta, rotVec, 3); + VectorOperations::mulScalar(rotVec, angle / timeDelta, rotVec, 3); + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateTotalQuest.value, rotVec, 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalQuest.setValid(true); + } + } + std::memcpy(quatOldQuest, attitudeEstimationData->quatQuest.value, sizeof(quatOldQuest)); + return; + } + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateTotalQuest.value, ZERO_VEC3, 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalQuest.setValid(false); + } + } + std::memcpy(quatOldQuest, attitudeEstimationData->quatQuest.value, sizeof(quatOldQuest)); + return; +} + +void FusedRotationEstimation::estimateFusedRotationRateSusMgm( + acsctrl::SusDataProcessed *susDataProcessed, acsctrl::MgmDataProcessed *mgmDataProcessed, + acsctrl::GyrDataProcessed *gyrDataProcessed, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData) { + if ((not mgmDataProcessed->mgmVecTot.isValid() and not susDataProcessed->susVecTot.isValid() and + not fusedRotRateSourcesData->rotRateTotalSusMgm.isValid()) or + (not susDataProcessed->susVecTotDerivative.isValid() and + not mgmDataProcessed->mgmVecTotDerivative.isValid())) { + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateOrthogonalSusMgm.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateOrthogonalSusMgm.setValid(false); + std::memcpy(fusedRotRateSourcesData->rotRateParallelSusMgm.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateParallelSusMgm.setValid(false); + std::memcpy(fusedRotRateSourcesData->rotRateTotalSusMgm.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalSusMgm.setValid(false); + } + } + // store for calculation of angular acceleration + if (gyrDataProcessed->gyrVecTot.isValid()) { + std::memcpy(rotRateOldB, gyrDataProcessed->gyrVecTot.value, 3 * sizeof(double)); + } + return; + } + if (not susDataProcessed->susVecTot.isValid()) { + estimateFusedRotationRateEclipse(gyrDataProcessed, fusedRotRateSourcesData); + // store for calculation of angular acceleration + if (gyrDataProcessed->gyrVecTot.isValid()) { + std::memcpy(rotRateOldB, gyrDataProcessed->gyrVecTot.value, 3 * sizeof(double)); + } + return; + } + + // calculate rotation around the sun + double magSunCross[3] = {0, 0, 0}; + + VectorOperations::cross(mgmDataProcessed->mgmVecTot.value, + susDataProcessed->susVecTot.value, magSunCross); + double magSunCrossNorm = VectorOperations::norm(magSunCross, 3); + double magNorm = VectorOperations::norm(mgmDataProcessed->mgmVecTot.value, 3); + double fusedRotRateParallel[3] = {0, 0, 0}; + if (magSunCrossNorm > + (acsParameters->safeModeControllerParameters.sineLimitSunRotRate * magNorm)) { + double omegaParallel = + VectorOperations::dot(mgmDataProcessed->mgmVecTotDerivative.value, magSunCross) * + pow(magSunCrossNorm, -2); + VectorOperations::mulScalar(susDataProcessed->susVecTot.value, omegaParallel, + fusedRotRateParallel, 3); + } else { + estimateFusedRotationRateEclipse(gyrDataProcessed, fusedRotRateSourcesData); + // store for calculation of angular acceleration + if (gyrDataProcessed->gyrVecTot.isValid()) { + std::memcpy(rotRateOldB, gyrDataProcessed->gyrVecTot.value, 3 * sizeof(double)); + } + return; + } + + // calculate rotation orthogonal to the sun + double fusedRotRateOrthogonal[3] = {0, 0, 0}; + VectorOperations::cross(susDataProcessed->susVecTotDerivative.value, + susDataProcessed->susVecTot.value, fusedRotRateOrthogonal); + VectorOperations::mulScalar( + fusedRotRateOrthogonal, + pow(VectorOperations::norm(susDataProcessed->susVecTot.value, 3), -2), + fusedRotRateOrthogonal, 3); + + // calculate total rotation rate + double fusedRotRateTotal[3] = {0, 0, 0}; + VectorOperations::add(fusedRotRateParallel, fusedRotRateOrthogonal, fusedRotRateTotal); + + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateOrthogonalSusMgm.value, fusedRotRateOrthogonal, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateOrthogonalSusMgm.setValid(true); + std::memcpy(fusedRotRateSourcesData->rotRateParallelSusMgm.value, fusedRotRateParallel, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateParallelSusMgm.setValid(true); + std::memcpy(fusedRotRateSourcesData->rotRateTotalSusMgm.value, fusedRotRateTotal, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalSusMgm.setValid(true); + } + } + + // store for calculation of angular acceleration + if (gyrDataProcessed->gyrVecTot.isValid()) { + std::memcpy(rotRateOldB, gyrDataProcessed->gyrVecTot.value, 3 * sizeof(double)); + } +} + +void FusedRotationEstimation::estimateFusedRotationRateEclipse( + acsctrl::GyrDataProcessed *gyrDataProcessed, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData) { + if (not acsParameters->onBoardParams.fusedRateSafeDuringEclipse or + not gyrDataProcessed->gyrVecTot.isValid() or + VectorOperations::norm(fusedRotRateSourcesData->rotRateTotalSusMgm.value, 3) == 0) { + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateOrthogonalSusMgm.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateOrthogonalSusMgm.setValid(false); + std::memcpy(fusedRotRateSourcesData->rotRateParallelSusMgm.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateParallelSusMgm.setValid(false); + std::memcpy(fusedRotRateSourcesData->rotRateTotalSusMgm.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalSusMgm.setValid(false); + } + } + return; + } + double angAccelB[3] = {0, 0, 0}; + VectorOperations::subtract(gyrDataProcessed->gyrVecTot.value, rotRateOldB, angAccelB, 3); + double fusedRotRateTotal[3] = {0, 0, 0}; + VectorOperations::add(fusedRotRateSourcesData->rotRateTotalSusMgm.value, angAccelB, + fusedRotRateTotal, 3); + { + PoolReadGuard pg(fusedRotRateSourcesData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(fusedRotRateSourcesData->rotRateOrthogonalSusMgm.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateOrthogonalSusMgm.setValid(false); + std::memcpy(fusedRotRateSourcesData->rotRateParallelSusMgm.value, ZERO_VEC3, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateParallelSusMgm.setValid(false); + std::memcpy(fusedRotRateSourcesData->rotRateTotalSusMgm.value, fusedRotRateTotal, + 3 * sizeof(double)); + fusedRotRateSourcesData->rotRateTotalSusMgm.setValid(true); + } + } +} diff --git a/mission/controller/acs/FusedRotationEstimation.h b/mission/controller/acs/FusedRotationEstimation.h new file mode 100644 index 0000000..d8e0bda --- /dev/null +++ b/mission/controller/acs/FusedRotationEstimation.h @@ -0,0 +1,47 @@ +#ifndef MISSION_CONTROLLER_ACS_FUSEDROTATIONESTIMATION_H_ +#define MISSION_CONTROLLER_ACS_FUSEDROTATIONESTIMATION_H_ + +#include +#include +#include +#include +#include +#include + +class FusedRotationEstimation { + public: + FusedRotationEstimation(AcsParameters *acsParameters_); + + void estimateFusedRotationRate(const Mode_t mode, acsctrl::SusDataProcessed *susDataProcessed, + acsctrl::MgmDataProcessed *mgmDataProcessed, + acsctrl::GyrDataProcessed *gyrDataProcessed, + ACS::SensorValues *sensorValues, + acsctrl::AttitudeEstimationData *attitudeEstimationData, + const double timeDelta, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData, + acsctrl::FusedRotRateData *fusedRotRateData); + + protected: + private: + static constexpr double ZERO_VEC3[3] = {0, 0, 0}; + static constexpr double ZERO_VEC4[4] = {0, 0, 0, 0}; + + AcsParameters *acsParameters; + double quatOldQuest[4] = {0, 0, 0, 0}; + double quatOldStr[4] = {0, 0, 0, 0}; + double rotRateOldB[3] = {0, 0, 0}; + + void estimateFusedRotationRateSusMgm(acsctrl::SusDataProcessed *susDataProcessed, + acsctrl::MgmDataProcessed *mgmDataProcessed, + acsctrl::GyrDataProcessed *gyrDataProcessed, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData); + void estimateFusedRotationRateEclipse(acsctrl::GyrDataProcessed *gyrDataProcessed, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData); + void estimateFusedRotationRateQuest(acsctrl::AttitudeEstimationData *attitudeEstimationData, + const double timeDelta, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData); + void estimateFusedRotationRateStr(ACS::SensorValues *sensorValues, const double timeDelta, + acsctrl::FusedRotRateSourcesData *fusedRotRateSourcesData); +}; + +#endif /* MISSION_CONTROLLER_ACS_FUSEDROTATIONESTIMATION_H_ */ diff --git a/mission/controller/acs/Guidance.cpp b/mission/controller/acs/Guidance.cpp new file mode 100644 index 0000000..7d93156 --- /dev/null +++ b/mission/controller/acs/Guidance.cpp @@ -0,0 +1,403 @@ +#include "Guidance.h" + +Guidance::Guidance(AcsParameters *acsParameters_) { acsParameters = acsParameters_; } + +Guidance::~Guidance() {} + +void Guidance::targetQuatPtgIdle(timeval timeAbsolute, const double timeDelta, + const double sunDirI[3], const double posSatF[4], + double targetQuat[4], double targetSatRotRate[3]) { + // positive z-Axis of EIVE in direction of sun + double zAxisIX[3] = {0, 0, 0}; + VectorOperations::normalize(sunDirI, zAxisIX, 3); + + // determine helper vector to point x-Axis and therefore the STR away from Earth + double helperXI[3] = {0, 0, 0}, posSatI[3] = {0, 0, 0}; + CoordinateTransformations::positionEcfToEci(posSatF, posSatI, &timeAbsolute); + VectorOperations::normalize(posSatI, helperXI, 3); + + // construct y-axis from helper vector and z-axis + double yAxisIX[3] = {0, 0, 0}; + VectorOperations::cross(zAxisIX, helperXI, yAxisIX); + VectorOperations::normalize(yAxisIX, yAxisIX, 3); + + // x-axis completes RHS + double xAxisIX[3] = {0, 0, 0}; + VectorOperations::cross(yAxisIX, zAxisIX, xAxisIX); + VectorOperations::normalize(xAxisIX, xAxisIX, 3); + + // join transformation matrix + double dcmIX[3][3] = {{xAxisIX[0], yAxisIX[0], zAxisIX[0]}, + {xAxisIX[1], yAxisIX[1], zAxisIX[1]}, + {xAxisIX[2], yAxisIX[2], zAxisIX[2]}}; + QuaternionOperations::fromDcm(dcmIX, targetQuat); + + // calculate of reference rotation rate + targetRotationRate(timeDelta, targetQuat, targetSatRotRate); +} + +void Guidance::targetQuatPtgTarget(timeval timeAbsolute, const double timeDelta, + const double posSatF[3], const double velSatF[3], + double targetQuat[4], double targetSatRotRate[3]) { + //------------------------------------------------------------------------------------- + // Calculation of target quaternion for target pointing + //------------------------------------------------------------------------------------- + // transform longitude, latitude and altitude to cartesian coordiantes (ECEF) + double targetF[3] = {0, 0, 0}; + CoordinateTransformations::cartesianFromLatLongAlt( + acsParameters->targetModeControllerParameters.latitudeTgt, + acsParameters->targetModeControllerParameters.longitudeTgt, + acsParameters->targetModeControllerParameters.altitudeTgt, targetF); + + // target direction in the ECI frame + double posSatI[3] = {0, 0, 0}, targetI[3] = {0, 0, 0}, targetDirI[3] = {0, 0, 0}; + CoordinateTransformations::positionEcfToEci(posSatF, posSatI, &timeAbsolute); + CoordinateTransformations::positionEcfToEci(targetF, targetI, &timeAbsolute); + VectorOperations::subtract(targetI, posSatI, targetDirI, 3); + + // x-axis aligned with target direction + // this aligns with the camera, E- and S-band antennas + double xAxisIX[3] = {0, 0, 0}; + VectorOperations::normalize(targetDirI, xAxisIX, 3); + VectorOperations::mulScalar(xAxisIX, -1, xAxisIX, 3); + + // transform velocity into inertial frame + double velSatI[3] = {0, 0, 0}; + CoordinateTransformations::velocityEcfToEci(velSatF, posSatF, velSatI, &timeAbsolute); + + // orbital normal vector of target and velocity vector + double orbitalNormalI[3] = {0, 0, 0}; + VectorOperations::cross(posSatI, velSatI, orbitalNormalI); + VectorOperations::normalize(orbitalNormalI, orbitalNormalI, 3); + + // y-axis of satellite in orbit plane so that z-axis is parallel to long side of picture + // resolution + double yAxisIX[3] = {0, 0, 0}; + VectorOperations::cross(orbitalNormalI, xAxisIX, yAxisIX); + VectorOperations::normalize(yAxisIX, yAxisIX, 3); + + // z-axis completes RHS + double zAxisIX[3] = {0, 0, 0}; + VectorOperations::cross(xAxisIX, yAxisIX, zAxisIX); + + // join transformation matrix + double dcmIX[3][3] = {{xAxisIX[0], yAxisIX[0], zAxisIX[0]}, + {xAxisIX[1], yAxisIX[1], zAxisIX[1]}, + {xAxisIX[2], yAxisIX[2], zAxisIX[2]}}; + QuaternionOperations::fromDcm(dcmIX, targetQuat); + + targetRotationRate(timeDelta, targetQuat, targetSatRotRate); +} + +void Guidance::targetQuatPtgGs(timeval timeAbsolute, const double timeDelta, + const double posSatF[3], const double sunDirI[3], + double targetQuat[4], double targetSatRotRate[3]) { + //------------------------------------------------------------------------------------- + // Calculation of target quaternion for ground station pointing + //------------------------------------------------------------------------------------- + // transform longitude, latitude and altitude to cartesian coordiantes (ECEF) + double posGroundStationF[3] = {0, 0, 0}; + CoordinateTransformations::cartesianFromLatLongAlt( + acsParameters->gsTargetModeControllerParameters.latitudeTgt, + acsParameters->gsTargetModeControllerParameters.longitudeTgt, + acsParameters->gsTargetModeControllerParameters.altitudeTgt, posGroundStationF); + + // target direction in the ECI frame + double posSatI[3] = {0, 0, 0}, posGroundStationI[3] = {0, 0, 0}, groundStationDirI[3] = {0, 0, 0}; + CoordinateTransformations::positionEcfToEci(posSatF, posSatI, &timeAbsolute); + CoordinateTransformations::positionEcfToEci(posGroundStationF, posGroundStationI, &timeAbsolute); + VectorOperations::subtract(posGroundStationI, posSatI, groundStationDirI, 3); + + // negative x-axis aligned with target direction + // this aligns with the camera, E- and S-band antennas + double xAxisIX[3] = {0, 0, 0}; + VectorOperations::normalize(groundStationDirI, xAxisIX, 3); + VectorOperations::mulScalar(xAxisIX, -1, xAxisIX, 3); + + // get earth vector in ECI + double earthDirI[3] = {0, 0, 0}; + VectorOperations::normalize(posSatI, earthDirI, 3); + VectorOperations::mulScalar(earthDirI, -1, earthDirI, 3); + + // sun avoidance calculations + double sunPerpendicularX[3] = {0, 0, 0}, sunFloorYZ[3] = {0, 0, 0}, zAxisSun[3] = {0, 0, 0}; + VectorOperations::mulScalar(xAxisIX, VectorOperations::dot(xAxisIX, sunDirI), + sunPerpendicularX, 3); + VectorOperations::subtract(sunDirI, sunPerpendicularX, sunFloorYZ, 3); + VectorOperations::normalize(sunFloorYZ, sunFloorYZ, 3); + VectorOperations::mulScalar(sunFloorYZ, -1, zAxisSun, 3); + double sunWeight = 0, strVecSun[3] = {0, 0, 0}, strVecSunX[3] = {0, 0, 0}, + strVecSunZ[3] = {0, 0, 0}; + VectorOperations::mulScalar(xAxisIX, acsParameters->strParameters.boresightAxis[0], + strVecSunX, 3); + VectorOperations::mulScalar(zAxisSun, acsParameters->strParameters.boresightAxis[2], + strVecSunZ, 3); + VectorOperations::add(strVecSunX, strVecSunZ, strVecSun, 3); + VectorOperations::normalize(strVecSun, strVecSun, 3); + sunWeight = VectorOperations::dot(strVecSun, sunDirI); + + // earth avoidance calculations + double earthPerpendicularX[3] = {0, 0, 0}, earthFloorYZ[3] = {0, 0, 0}, zAxisEarth[3] = {0, 0, 0}; + VectorOperations::mulScalar(xAxisIX, VectorOperations::dot(xAxisIX, earthDirI), + earthPerpendicularX, 3); + VectorOperations::subtract(earthDirI, earthPerpendicularX, earthFloorYZ, 3); + VectorOperations::normalize(earthFloorYZ, earthFloorYZ, 3); + VectorOperations::mulScalar(earthFloorYZ, -1, zAxisEarth, 3); + double earthWeight = 0, strVecEarth[3] = {0, 0, 0}, strVecEarthX[3] = {0, 0, 0}, + strVecEarthZ[3] = {0, 0, 0}; + VectorOperations::mulScalar(xAxisIX, acsParameters->strParameters.boresightAxis[0], + strVecEarthX, 3); + VectorOperations::mulScalar(zAxisEarth, acsParameters->strParameters.boresightAxis[2], + strVecEarthZ, 3); + VectorOperations::add(strVecEarthX, strVecEarthZ, strVecEarth, 3); + VectorOperations::normalize(strVecEarth, strVecEarth, 3); + earthWeight = VectorOperations::dot(strVecEarth, earthDirI); + + if ((sunWeight == 0.0) and (earthWeight == 0.0)) { + // if this actually ever happens i will eat a broom + sunWeight = 0.5; + earthWeight = 0.5; + } + + // normalize weights for convenience + double normFactor = 1. / (std::abs(sunWeight) + std::abs(earthWeight)); + sunWeight *= normFactor; + earthWeight *= normFactor; + + // calculate z-axis for str blinding avoidance + double zAxisIX[3] = {0, 0, 0}; + VectorOperations::mulScalar(zAxisSun, sunWeight, zAxisSun, 3); + VectorOperations::mulScalar(zAxisEarth, earthWeight, zAxisEarth, 3); + VectorOperations::add(zAxisSun, zAxisEarth, zAxisIX, 3); + VectorOperations::mulScalar(zAxisIX, -1, zAxisIX, 3); + VectorOperations::normalize(zAxisIX, zAxisIX, 3); + + // calculate y-axis + double yAxisIX[3] = {0, 0, 0}; + VectorOperations::cross(zAxisIX, xAxisIX, yAxisIX); + VectorOperations::normalize(yAxisIX, yAxisIX, 3); + + // join transformation matrix + double dcmIX[3][3] = {{xAxisIX[0], yAxisIX[0], zAxisIX[0]}, + {xAxisIX[1], yAxisIX[1], zAxisIX[1]}, + {xAxisIX[2], yAxisIX[2], zAxisIX[2]}}; + QuaternionOperations::fromDcm(dcmIX, targetQuat); + + limitReferenceRotation(xAxisIX, targetQuat); + targetRotationRate(timeDelta, targetQuat, targetSatRotRate); + + std::memcpy(xAxisIXprev, xAxisIX, sizeof(xAxisIXprev)); +} + +void Guidance::targetQuatPtgNadir(timeval timeAbsolute, const double timeDelta, + const double posSatE[3], const double velSatE[3], + double targetQuat[4], double refSatRate[3]) { + //------------------------------------------------------------------------------------- + // Calculation of target quaternion for Nadir pointing + //------------------------------------------------------------------------------------- + + // satellite position in inertial reference frame + double posSatI[3] = {0, 0, 0}; + CoordinateTransformations::positionEcfToEci(posSatE, posSatI, &timeAbsolute); + + // negative x-axis aligned with position vector + // this aligns with the camera, E- and S-band antennas + double xAxisIX[3] = {0, 0, 0}; + VectorOperations::normalize(posSatI, xAxisIX, 3); + VectorOperations::mulScalar(xAxisIX, -1, xAxisIX, 3); + + // make z-Axis parallel to major part of camera resolution + double zAxisIX[3] = {0, 0, 0}; + double velSatI[3] = {0, 0, 0}; + CoordinateTransformations::velocityEcfToEci(velSatE, posSatE, velSatI, &timeAbsolute); + VectorOperations::cross(xAxisIX, velSatI, zAxisIX); + VectorOperations::normalize(zAxisIX, zAxisIX, 3); + + // y-Axis completes RHS + double yAxisIX[3] = {0, 0, 0}; + VectorOperations::cross(zAxisIX, xAxisIX, yAxisIX); + + // join transformation matrix + double dcmIX[3][3] = {{xAxisIX[0], yAxisIX[0], zAxisIX[0]}, + {xAxisIX[1], yAxisIX[1], zAxisIX[1]}, + {xAxisIX[2], yAxisIX[2], zAxisIX[2]}}; + QuaternionOperations::fromDcm(dcmIX, targetQuat); + + targetRotationRate(timeDelta, targetQuat, refSatRate); +} + +void Guidance::targetRotationRate(const double timeDelta, double quatIX[4], double *refSatRate) { + if (VectorOperations::norm(quatIXprev, 4) == 0) { + std::memcpy(quatIXprev, quatIX, sizeof(quatIXprev)); + } + if (timeDelta != 0.0) { + QuaternionOperations::rotationFromQuaternions(quatIX, quatIXprev, timeDelta, refSatRate); + VectorOperations::mulScalar(refSatRate, -1, refSatRate, 3); + } else { + std::memcpy(refSatRate, ZERO_VEC3, 3 * sizeof(double)); + } + std::memcpy(quatIXprev, quatIX, sizeof(quatIXprev)); +} + +void Guidance::limitReferenceRotation(const double xAxisIX[3], double quatIX[4]) { + if ((VectorOperations::norm(quatIXprev, 4) == 0) or + (VectorOperations::norm(xAxisIXprev, 3) == 0)) { + return; + } + + QuaternionOperations::preventSignJump(quatIX, quatIXprev); + + // check required rotation and return if below limit + double quatXprevX[4] = {0, 0, 0, 0}, quatXprevI[4] = {0, 0, 0, 0}; + QuaternionOperations::inverse(quatIXprev, quatXprevI); + QuaternionOperations::multiply(quatIX, quatXprevI, quatXprevX); + QuaternionOperations::normalize(quatXprevX); + double phiMax = acsParameters->gsTargetModeControllerParameters.rotRateLimit * + acsParameters->onBoardParams.sampleTime; + if (2 * std::acos(quatXprevX[3]) < phiMax) { + return; + } + + // x-axis always needs full rotation + double phiX = 0, phiXvec[3] = {0, 0, 0}; + phiX = std::acos(VectorOperations::dot(xAxisIXprev, xAxisIX)); + VectorOperations::cross(xAxisIXprev, xAxisIX, phiXvec); + VectorOperations::normalize(phiXvec, phiXvec, 3); + + double quatXprevXtilde[4] = {0, 0, 0, 0}, quatIXtilde[4] = {0, 0, 0, 0}; + VectorOperations::mulScalar(phiXvec, -std::sin(phiX / 2.), phiXvec, 3); + std::memcpy(quatXprevXtilde, phiXvec, sizeof(phiXvec)); + quatXprevXtilde[3] = cos(phiX / 2.); + QuaternionOperations::normalize(quatXprevXtilde); + QuaternionOperations::multiply(quatXprevXtilde, quatIXprev, quatIXtilde); + + // use the residual rotation up to the maximum + double quatXXtilde[4] = {0, 0, 0, 0}, quatXI[4] = {0, 0, 0, 0}; + QuaternionOperations::inverse(quatIX, quatXI); + QuaternionOperations::multiply(quatIXtilde, quatXI, quatXXtilde); + + double phiResidual = 0, phiResidualVec[3] = {0, 0, 0}; + if ((phiX * phiX) > (phiMax * phiMax)) { + phiResidual = 0; + } else { + phiResidual = std::sqrt((phiMax * phiMax) - (phiX * phiX)); + } + std::memcpy(phiResidualVec, quatXXtilde, sizeof(phiResidualVec)); + VectorOperations::normalize(phiResidualVec, phiResidualVec, 3); + + double quatXhatXTilde[4] = {0, 0, 0, 0}, quatXTildeXhat[4] = {0, 0, 0, 0}; + VectorOperations::mulScalar(phiResidualVec, std::sin(phiResidual / 2.), phiResidualVec, + 3); + std::memcpy(quatXhatXTilde, phiResidualVec, sizeof(phiResidualVec)); + quatXhatXTilde[3] = std::cos(phiResidual / 2.); + QuaternionOperations::normalize(quatXhatXTilde); + + // calculate final quaternion + QuaternionOperations::inverse(quatXhatXTilde, quatXTildeXhat); + QuaternionOperations::multiply(quatXTildeXhat, quatIXtilde, quatIX); + QuaternionOperations::normalize(quatIX); +} + +void Guidance::comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4], + double targetSatRotRate[3], double refQuat[4], double refSatRotRate[3], + double errorQuat[4], double errorSatRotRate[3], double &errorAngle) { + // First calculate error quaternion between current and target orientation without reference + // quaternion + double errorQuatWoRef[4] = {0, 0, 0, 0}; + QuaternionOperations::multiply(currentQuat, targetQuat, errorQuatWoRef); + // Then add rotation from reference quaternion + QuaternionOperations::multiply(refQuat, errorQuatWoRef, errorQuat); + // Keep scalar part of quaternion positive + if (errorQuat[3] < 0) { + VectorOperations::mulScalar(errorQuat, -1, errorQuat, 4); + } + // Calculate error angle + errorAngle = QuaternionOperations::getAngle(errorQuat, true); + + // Calculate error satellite rotational rate + // Convert target rotational rate into body RF + double targetSatRotRateB[3] = {0, 0, 0}; + QuaternionOperations::multiplyVector(currentQuat, targetSatRotRate, targetSatRotRateB); + VectorOperations::copy(targetSatRotRateB, targetSatRotRate, 3); + // Combine the target and reference satellite rotational rates + double combinedRefSatRotRate[3] = {0, 0, 0}; + VectorOperations::add(targetSatRotRate, refSatRotRate, combinedRefSatRotRate, 3); + // Then subtract the combined required satellite rotational rates from the actual rate + VectorOperations::subtract(currentSatRotRate, combinedRefSatRotRate, errorSatRotRate, 3); +} + +void Guidance::comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4], + double targetSatRotRate[3], double errorQuat[4], + double errorSatRotRate[3], double &errorAngle) { + double refQuat[4] = {0, 0, 0, 1}, refSatRotRate[3] = {0, 0, 0}; + comparePtg(currentQuat, currentSatRotRate, targetQuat, targetSatRotRate, refQuat, refSatRotRate, + errorQuat, errorSatRotRate, errorAngle); +} + +ReturnValue_t Guidance::getDistributionMatrixRw(ACS::SensorValues *sensorValues, + double *rwPseudoInv, acsctrl::RwAvail *rwAvail) { + rwAvail->rw1avail = (sensorValues->rw1Set.state.value and sensorValues->rw1Set.state.isValid()); + rwAvail->rw2avail = (sensorValues->rw2Set.state.value and sensorValues->rw2Set.state.isValid()); + rwAvail->rw3avail = (sensorValues->rw3Set.state.value and sensorValues->rw3Set.state.isValid()); + rwAvail->rw4avail = (sensorValues->rw4Set.state.value and sensorValues->rw4Set.state.isValid()); + + if (rwAvail->rw1avail and rwAvail->rw2avail and rwAvail->rw3avail and rwAvail->rw4avail) { + std::memcpy(rwPseudoInv, acsParameters->rwMatrices.pseudoInverse, 12 * sizeof(double)); + return returnvalue::OK; + } else if (not rwAvail->rw1avail and rwAvail->rw2avail and rwAvail->rw3avail and + rwAvail->rw4avail) { + std::memcpy(rwPseudoInv, acsParameters->rwMatrices.pseudoInverseWithoutRW1, + 12 * sizeof(double)); + return acsctrl::SINGLE_RW_UNAVAILABLE; + } else if (rwAvail->rw1avail and not rwAvail->rw2avail and rwAvail->rw3avail and + rwAvail->rw4avail) { + std::memcpy(rwPseudoInv, acsParameters->rwMatrices.pseudoInverseWithoutRW2, + 12 * sizeof(double)); + return acsctrl::SINGLE_RW_UNAVAILABLE; + } else if (rwAvail->rw1avail and rwAvail->rw2avail and not rwAvail->rw3avail and + rwAvail->rw4avail) { + std::memcpy(rwPseudoInv, acsParameters->rwMatrices.pseudoInverseWithoutRW3, + 12 * sizeof(double)); + return acsctrl::SINGLE_RW_UNAVAILABLE; + } else if (rwAvail->rw1avail and rwAvail->rw2avail and rwAvail->rw3avail and + not rwAvail->rw4avail) { + std::memcpy(rwPseudoInv, acsParameters->rwMatrices.pseudoInverseWithoutRW4, + 12 * sizeof(double)); + return acsctrl::SINGLE_RW_UNAVAILABLE; + } + return acsctrl::MULTIPLE_RW_UNAVAILABLE; +} + +void Guidance::resetValues() { + std::memcpy(quatIXprev, ZERO_VEC4, sizeof(quatIXprev)); + std::memcpy(xAxisIXprev, ZERO_VEC3, sizeof(xAxisIXprev)); +} + +void Guidance::getTargetParamsSafe(double sunTargetSafe[3]) { + std::error_code e; + if (not std::filesystem::exists(SD_0_SKEWED_PTG_FILE, e) or + not std::filesystem::exists(SD_1_SKEWED_PTG_FILE, e)) { + std::memcpy(sunTargetSafe, acsParameters->safeModeControllerParameters.sunTargetDir, + 3 * sizeof(double)); + } else { + std::memcpy(sunTargetSafe, acsParameters->safeModeControllerParameters.sunTargetDirLeop, + 3 * sizeof(double)); + } +} + +ReturnValue_t Guidance::solarArrayDeploymentComplete() { + std::error_code e; + if (std::filesystem::exists(SD_0_SKEWED_PTG_FILE, e)) { + std::remove(SD_0_SKEWED_PTG_FILE); + if (std::filesystem::exists(SD_0_SKEWED_PTG_FILE, e)) { + return returnvalue::FAILED; + } + } + if (std::filesystem::exists(SD_1_SKEWED_PTG_FILE, e)) { + std::remove(SD_1_SKEWED_PTG_FILE); + if (std::filesystem::exists(SD_1_SKEWED_PTG_FILE, e)) { + return returnvalue::FAILED; + } + } + return returnvalue::OK; +} diff --git a/mission/controller/acs/Guidance.h b/mission/controller/acs/Guidance.h new file mode 100644 index 0000000..aa771d8 --- /dev/null +++ b/mission/controller/acs/Guidance.h @@ -0,0 +1,64 @@ +#ifndef GUIDANCE_H_ +#define GUIDANCE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class Guidance { + public: + Guidance(AcsParameters *acsParameters_); + virtual ~Guidance(); + + void getTargetParamsSafe(double sunTargetSafe[3]); + ReturnValue_t solarArrayDeploymentComplete(); + void resetValues(); + + void targetQuatPtgIdle(timeval timeAbsolute, const double timeDelta, const double sunDirI[3], + const double posSatF[4], double targetQuat[4], double targetSatRotRate[3]); + void targetQuatPtgTarget(timeval timeAbsolute, const double timeDelta, const double posSatF[3], + const double velSatE[3], double quatIX[4], double targetSatRotRate[3]); + void targetQuatPtgGs(timeval timeAbsolute, const double timeDelta, const double posSatF[3], + const double sunDirI[3], double quatIX[4], double targetSatRotRate[3]); + void targetQuatPtgNadir(timeval timeAbsolute, const double timeDelta, const double posSatF[3], + const double velSatF[3], double targetQuat[4], double refSatRate[3]); + + void targetRotationRate(const double timeDelta, double quatInertialTarget[4], + double *targetSatRotRate); + + void limitReferenceRotation(const double xAxisIX[3], double quatIX[4]); + + void comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4], + double targetSatRotRate[3], double refQuat[4], double refSatRotRate[3], + double errorQuat[4], double errorSatRotRate[3], double &errorAngle); + void comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4], + double targetSatRotRate[3], double errorQuat[4], double errorSatRotRate[3], + double &errorAngle); + + ReturnValue_t getDistributionMatrixRw(ACS::SensorValues *sensorValues, double *rwPseudoInv, + acsctrl::RwAvail *rwAvail); + + private: + const AcsParameters *acsParameters; + + static constexpr double ZERO_VEC3[3] = {0, 0, 0}; + static constexpr double ZERO_VEC4[4] = {0, 0, 0, 0}; + + bool strBlindAvoidFlag = false; + double quatIXprev[4] = {0, 0, 0, 0}; + double xAxisIXprev[3] = {0, 0, 0}; + + static constexpr char SD_0_SKEWED_PTG_FILE[] = "/mnt/sd0/conf/acsDeploymentConfirm"; + static constexpr char SD_1_SKEWED_PTG_FILE[] = "/mnt/sd1/conf/acsDeploymentConfirm"; +}; + +#endif /* ACS_GUIDANCE_H_ */ diff --git a/mission/controller/acs/Igrf13Model.cpp b/mission/controller/acs/Igrf13Model.cpp new file mode 100644 index 0000000..b15edad --- /dev/null +++ b/mission/controller/acs/Igrf13Model.cpp @@ -0,0 +1,137 @@ +#include "Igrf13Model.h" + +Igrf13Model::Igrf13Model() {} +Igrf13Model::~Igrf13Model() {} + +void Igrf13Model::magFieldComp(const double longitude, const double gcLatitude, + const double altitude, timeval timeOfMagMeasurement, + double* magFieldModelInertial) { + double magFieldModel[3] = {0, 0, 0}; + double phi = longitude, theta = gcLatitude; // geocentric + /* Here is the co-latitude needed*/ + theta -= 90. * M_PI / 180.; + theta *= (-1); + + double rE = 6371200.0; // radius earth [m] + /* Predefine recursive associated Legendre polynomials */ + double P11 = 1; + double P10 = P11; // P10 = P(n-1,m-0) + double dP11 = 0; // derivative + double dP10 = dP11; // derivative + + double P2 = 0, dP2 = 0, P20 = 0, dP20 = 0, K = 0; + + for (int m = 0; m <= igrfOrder; m++) { + for (int n = 1; n <= igrfOrder; n++) { + if (m <= n) { + /* Calculation of Legendre Polynoms (normalised) */ + if (n == m) { + P2 = sin(theta) * P11; + dP2 = sin(theta) * dP11 + cos(theta) * P11; + P11 = P2; + P10 = P11; + P20 = 0; + dP11 = dP2; + dP10 = dP11; + dP20 = 0; + } else if (n == 1) { + P2 = cos(theta) * P10; + dP2 = cos(theta) * dP10 - sin(theta) * P10; + P20 = P10; + P10 = P2; + dP20 = dP10; + dP10 = dP2; + } else { + K = (pow((n - 1), 2) - pow(m, 2)) / ((2 * n - 1) * (2 * n - 3)); + P2 = cos(theta) * P10 - K * P20; + dP2 = cos(theta) * dP10 - sin(theta) * P10 - K * dP20; + P20 = P10; + P10 = P2; + dP20 = dP10; + dP10 = dP2; + } + /* gradient of scalar potential towards radius */ + magFieldModel[0] += + pow(rE / (altitude + rE), (n + 2)) * (n + 1) * + ((updatedG[m][n - 1] * cos(m * phi) + updatedH[m][n - 1] * sin(m * phi)) * P2); + /* gradient of scalar potential towards theta */ + magFieldModel[1] += + pow(rE / (altitude + rE), (n + 2)) * + ((updatedG[m][n - 1] * cos(m * phi) + updatedH[m][n - 1] * sin(m * phi)) * dP2); + /* gradient of scalar potential towards phi */ + magFieldModel[2] += + pow(rE / (altitude + rE), (n + 2)) * + ((-updatedG[m][n - 1] * sin(m * phi) + updatedH[m][n - 1] * cos(m * phi)) * P2 * m); + } + } + } + + magFieldModel[1] *= -1; + magFieldModel[2] *= (-1 / sin(theta)); + + double JD2000 = 0; + Clock::convertTimevalToJD2000(timeOfMagMeasurement, &JD2000); + double UT1 = JD2000 / 36525.; + + double gst = + 280.46061837 + 360.98564736629 * JD2000 + 0.0003875 * pow(UT1, 2) - 2.6e-8 * pow(UT1, 3); + gst = std::fmod(gst, 360.); + gst *= M_PI / 180.; + double lst = gst + longitude; // local sidereal time [rad] + + magFieldModelInertial[0] = + (magFieldModel[0] * cos(gcLatitude) + magFieldModel[1] * sin(gcLatitude)) * cos(lst) - + magFieldModel[2] * sin(lst); + magFieldModelInertial[1] = + (magFieldModel[0] * cos(gcLatitude) + magFieldModel[1] * sin(gcLatitude)) * sin(lst) + + magFieldModel[2] * cos(lst); + magFieldModelInertial[2] = + magFieldModel[0] * sin(gcLatitude) - magFieldModel[1] * cos(gcLatitude); + + // convert nT to uT + VectorOperations::mulScalar(magFieldModelInertial, 1e-3, magFieldModelInertial, 3); +} + +void Igrf13Model::updateCoeffGH(timeval timeOfMagMeasurement) { + double JD2000Igrf = (2458850.0 - 2451545); // Begin of IGRF-13 (2020-01-01,00:00:00) in JD2000 + double JD2000 = 0; + Clock::convertTimevalToJD2000(timeOfMagMeasurement, &JD2000); + double days = ceil(JD2000 - JD2000Igrf); + for (int i = 0; i <= igrfOrder; i++) { + for (int j = 0; j <= (igrfOrder - 1); j++) { + updatedG[i][j] = coeffG[i][j] + svG[i][j] * (days / 365); + updatedH[i][j] = coeffH[i][j] + svH[i][j] * (days / 365); + } + } +} + +void Igrf13Model::schmidtNormalization() { + double kronDelta = 0; + schmidtFactors[0][0] = 1; + for (int n = 1; n <= igrfOrder; n++) { + if (n == 1) { + schmidtFactors[0][n - 1] = 1; + } else { + schmidtFactors[0][n - 1] = schmidtFactors[0][n - 2] * (2 * n - 1) / n; + } + for (int m = 1; m <= igrfOrder; m++) { + if (m == 1) { + kronDelta = 1; + } else { + kronDelta = 0; + } + schmidtFactors[m][n - 1] = + schmidtFactors[m - 1][n - 1] * sqrt((n - m + 1) * (kronDelta + 1) / (n + m)); + } + } + + for (int i = 0; i <= igrfOrder; i++) { + for (int j = 0; j <= (igrfOrder - 1); j++) { + coeffG[i][j] = schmidtFactors[i][j] * coeffG[i][j]; + coeffH[i][j] = schmidtFactors[i][j] * coeffH[i][j]; + + svG[i][j] = schmidtFactors[i][j] * svG[i][j]; + svH[i][j] = schmidtFactors[i][j] * svH[i][j]; + } + } +} diff --git a/mission/controller/acs/Igrf13Model.h b/mission/controller/acs/Igrf13Model.h new file mode 100644 index 0000000..f0936b7 --- /dev/null +++ b/mission/controller/acs/Igrf13Model.h @@ -0,0 +1,133 @@ +/* + * Igrf13Model.h + * + * Created on: 10 Mar 2022 + * Author: Robin Marquardt + * Description: Calculates the magnetic field vector of earth with the IGRF Model. + * Sources: https://www.ngdc.noaa.gov/IAGA/vmod/igrf.html + * https://doi.org/10.1186/s40623-020-01288-x + * J. Davis, Mathematical Modeling of Earth's Magnetic Field, TN, 2004 + * + * [Conversion of ENU (geocentric) to IJK: Skript Bahnmechanik für Raumfahrzeuge, + * Prof. Dr.-Ing. Stefanos Fasoulas / Dr.-Ing. Frank Zimmermann] + * + */ + +#ifndef IGRF13MODEL_H_ +#define IGRF13MODEL_H_ + +#include +#include +#include +#include +#include + +#include + +// Output should be transformed to [T] instead of [nT] +// Updating Coefficients has to be implemented yet. Question, updating everyday ? +class Igrf13Model /*:public HasParametersIF*/ { + public: + Igrf13Model(); + virtual ~Igrf13Model(); + + // Main Function + void magFieldComp(const double longitude, const double gcLatitude, const double altitude, + timeval timeOfMagMeasurement, double* magFieldModelInertial); + // Right now the radius for igrf is simply with r0 + altitude calculated. In reality the radius is + // oriented from the satellite to earth COM Difference up to 25 km, which is 5 % of the total + // flight altitude + /* Inputs: + * - longitude: geocentric longitude [rad] + * - latitude: geocentric latitude [rad] + * - altitude: [m] + * - timeOfMagMeasurement: time of actual measurement [s] + * + * Outputs: + * - magFieldModelInertial: Magnetic Field Vector in IJK RF [nT]*/ + + // Coefficient wary over year, could be updated sometimes. + void updateCoeffGH(timeval timeOfMagMeasurement); // Secular variation (SV) + void schmidtNormalization(); + + private: + double coeffG[14][13] = { + {-29404.8, -2499.6, 1363.2, 903.0, -234.3, 66.0, 80.6, 23.7, 5.0, -1.9, 3.0, -2.0, 0.1}, + {-1450.9, 2982.0, -2381.2, 809.5, 363.2, 65.5, -76.7, 9.7, 8.4, -6.2, -1.4, -0.1, -0.9}, + {0.0, 1677.0, 1236.2, 86.3, 187.8, 72.9, -8.2, -17.6, 2.9, -0.1, -2.5, 0.5, 0.5}, + {0.0, 0.0, 525.7, -309.4, -140.7, -121.5, 56.5, -0.5, -1.5, 1.7, 2.3, 1.3, 0.7}, + {0.0, 0.0, 0.0, 48.0, -151.2, -36.2, 15.8, -21.1, -1.1, -0.9, -0.9, -1.2, -0.3}, + {0.0, 0.0, 0.0, 0.0, 13.5, 13.5, 6.4, 15.3, -13.2, 0.7, 0.3, 0.7, 0.8}, + {0.0, 0.0, 0.0, 0.0, 0.0, -64.7, -7.2, 13.7, 1.1, -0.9, -0.7, 0.3, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 9.8, -16.5, 8.8, 1.9, -0.1, 0.5, 0.8}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.3, -9.3, 1.4, 1.4, -0.3, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -11.9, -2.4, -0.6, -0.5, 0.4}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -3.8, 0.2, 0.1, 0.1}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.1, -1.1, 0.5}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.3, -0.5}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.4}}; // [m][n] in nT + + double coeffH[14][13] = { + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0}, + {4652.5, -2991.6, -82.1, 281.9, 47.7, -19.1, -51.5, 8.4, -23.4, 3.4, 0.0, -1.2, -0.9}, + {0.0, -734.6, 241.9, -158.4, 208.3, 25.1, -16.9, -15.3, 11.0, -0.2, 2.5, 0.5, 0.6}, + {0.0, 0.0, -543.4, 199.7, -121.2, 52.8, 2.2, 12.8, 9.8, 3.6, -0.6, 1.4, 1.4}, + {0.0, 0.0, 0.0, -349.7, 32.3, -64.5, 23.5, -11.7, -5.1, 4.8, -0.4, -1.8, -0.4}, + {0.0, 0.0, 0.0, 0.0, 98.9, 8.9, -2.2, 14.9, -6.3, -8.6, 0.6, 0.1, -1.3}, + {0.0, 0.0, 0.0, 0.0, 0.0, 68.1, -27.2, 3.6, 7.8, -0.1, -0.2, 0.8, -0.1}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.8, -6.9, 0.4, -4.3, -1.7, -0.2, 0.3}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.8, -1.4, -3.4, -1.6, 0.6, -0.1}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 9.6, -0.1, -3.0, 0.2, 0.5}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -8.8, -2.0, -0.9, 0.5}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.6, 0.0, -0.4}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, -0.4}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.6}}; // [m][n] in nT + + double svG[14][13] = { + {5.7, -11.0, 2.2, -1.2, -0.3, -0.5, -0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {7.4, -7.0, -5.9, -1.6, 0.5, -0.3, -0.2, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, -2.1, 3.1, -5.9, -0.6, 0.4, 0.0, -0.1, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, -12.0, 5.2, 0.2, 1.3, 0.7, 0.4, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, -5.1, 1.3, -1.4, 0.1, -0.1, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.9, 0.0, -0.5, 0.4, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.9, -0.8, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8, -0.1, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}}; // [m][n] in nT + + double svH[14][13] = { + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {-25.9, -30.2, 6.0, -0.1, 0.0, 0.0, 0.6, -0.2, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, -22.4, -1.1, 6.5, 2.5, -1.6, 0.6, 0.6, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.5, 3.6, -0.6, -1.3, -0.8, -0.2, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, -5.0, 3.0, 0.8, -0.2, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.3, 0.0, -1.1, -0.3, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.1, -0.4, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}}; // [m][n] in nT + + double schmidtFactors[14][13] = { + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; + ; + + double updatedG[14][13]; + double updatedH[14][13]; + static const int igrfOrder = 13; // degree of truncation +}; + +#endif /* IGRF13MODEL_H_ */ diff --git a/mission/controller/acs/MultiplicativeKalmanFilter.cpp b/mission/controller/acs/MultiplicativeKalmanFilter.cpp new file mode 100644 index 0000000..fc61ca0 --- /dev/null +++ b/mission/controller/acs/MultiplicativeKalmanFilter.cpp @@ -0,0 +1,637 @@ +#include "MultiplicativeKalmanFilter.h" + +MultiplicativeKalmanFilter::MultiplicativeKalmanFilter(AcsParameters *acsParameters) { + updateStandardDeviations(acsParameters); +} + +MultiplicativeKalmanFilter::~MultiplicativeKalmanFilter() {} + +void MultiplicativeKalmanFilter::updateStandardDeviations(const AcsParameters *acsParameters) { + sigmaSus = acsParameters->kalmanFilterParameters.sensorNoiseSus; + sigmaMgm = acsParameters->kalmanFilterParameters.sensorNoiseMgm; + sigmaStr = acsParameters->kalmanFilterParameters.sensorNoiseStr; + sigmaGyr = acsParameters->kalmanFilterParameters.sensorNoiseGyr; + sigmaGyrArw = acsParameters->kalmanFilterParameters.sensorNoiseGyrArw; + sigmaGyrBs = acsParameters->kalmanFilterParameters.sensorNoiseGyrBs; + + MatrixOperations::multiplyScalar(*EYE3, sigmaSus * sigmaSus, *covSus, 3, 3); + MatrixOperations::multiplyScalar(*EYE3, sigmaMgm * sigmaMgm, *covMgm, 3, 3); + MatrixOperations::multiplyScalar(*EYE3, sigmaStr * sigmaStr, *covStr, 3, 3); +} + +ReturnValue_t MultiplicativeKalmanFilter::reset( + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + std::memcpy(estimatedQuaternionBI, UNIT_QUAT, sizeof(estimatedQuaternionBI)); + std::memcpy(estimatedBiasGyr, ZERO_VEC3, sizeof(estimatedBiasGyr)); + std::memcpy(estimatedCovarianceMatrix, ZERO_MAT66, sizeof(estimatedCovarianceMatrix)); + mekfStatus = MekfStatus::UNINITIALIZED; + updateDataSetWithoutData(attitudeEstimationData); + return MEKF_UNINITIALIZED; +} + +ReturnValue_t MultiplicativeKalmanFilter::init( + const acsctrl::SusDataProcessed *susData, const acsctrl::MgmDataProcessed *mgmData, + const acsctrl::GyrDataProcessed *gyrData, + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + // check for valid QUEST quaternion + if (attitudeEstimationData->quatQuest.isValid()) { + // Initial Quaternion + std::memcpy(estimatedQuaternionBI, attitudeEstimationData->quatQuest.value, + sizeof(estimatedQuaternionBI)); + + // Initial Covariance + if (susData->susVecTot.isValid() and mgmData->mgmVecTot.isValid() and + gyrData->gyrVecTot.isValid()) { + // QUEST Covariance + double normSunI[3] = {0, 0, 0}, normMagI[3] = {0, 0, 0}, normSunEstB[3] = {0, 0, 0}, + normMagEstB[3] = {0, 0, 0}; + VectorOperations::normalize(susData->sunIjkModel.value, normSunI, 3); + VectorOperations::normalize(mgmData->magIgrfModel.value, normMagI, 3); + QuaternionOperations::multiplyVector(estimatedQuaternionBI, normSunI, normSunEstB); + QuaternionOperations::multiplyVector(estimatedQuaternionBI, normMagI, normMagEstB); + + double matrixSus[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + matrixMgm[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + matrixSusMgm[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + matrixMgmSus[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + MatrixOperations::multiply(normSunEstB, normSunEstB, *matrixSus, 3, 1, 3); + MatrixOperations::multiply(normMagEstB, normMagEstB, *matrixMgm, 3, 1, 3); + MatrixOperations::multiply(normSunEstB, normMagEstB, *matrixSusMgm, 3, 1, 3); + MatrixOperations::multiply(normMagEstB, normSunEstB, *matrixMgmSus, 3, 1, 3); + + double factorSus = 1. / (sigmaSus * sigmaSus); + double factorMgm = 1. / (sigmaMgm * sigmaMgm); + + double covQuest[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + MatrixOperations::multiplyScalar(*matrixSus, factorSus * factorSus, *matrixSus, 3, 3); + MatrixOperations::multiplyScalar(*matrixMgm, factorMgm * factorMgm, *matrixMgm, 3, 3); + MatrixOperations::add(*matrixSusMgm, *matrixMgmSus, *covQuest, 3, 3); + MatrixOperations::multiplyScalar( + *covQuest, + factorSus * factorMgm * VectorOperations::dot(normSunEstB, normMagEstB), + *covQuest, 3, 3); + MatrixOperations::add(*covQuest, *matrixSus, *covQuest, 3, 3); + MatrixOperations::add(*covQuest, *matrixMgm, *covQuest, 3, 3); + + double crossSunMag[3] = {0, 0, 0}; + VectorOperations::cross(normSunEstB, normMagEstB, crossSunMag); + double normCrossSunMag = VectorOperations::norm(crossSunMag, 3); + double factor = factorSus * factorMgm * normCrossSunMag * normCrossSunMag; + + MatrixOperations::multiplyScalar(*covQuest, 1. / factor, *covQuest, 3, 3); + MatrixOperations::add(*EYE3, *covQuest, *covQuest, 3, 3); + MatrixOperations::multiplyScalar(*covQuest, 1. / (factorSus + factorMgm), *covQuest, + 3, 3); + + // GYR Covariance + double covGyr[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + MatrixOperations::multiplyScalar(*EYE3, sigmaGyr, *covGyr, 3, 3); + + // Combine Covariances + MatrixOperations::writeSubmatrix(*estimatedCovarianceMatrix, *covQuest, 3, 3, 6, 6, 0, + 0); + MatrixOperations::writeSubmatrix(*estimatedCovarianceMatrix, *covGyr, 3, 3, 6, 6, 3, + 3); + + mekfStatus = MekfStatus::INITIALIZED; + updateDataSetWithoutData(attitudeEstimationData); + return MEKF_INITIALIZED; + } + } + // no initialisation possible, no valid measurements + mekfStatus = MekfStatus::UNINITIALIZED; + updateDataSetWithoutData(attitudeEstimationData); + return MEKF_UNINITIALIZED; +} + +ReturnValue_t MultiplicativeKalmanFilter::mekfEst( + const acsctrl::SusDataProcessed *susData, const acsctrl::MgmDataProcessed *mgmData, + const acsctrl::GyrDataProcessed *gyrData, const double timeDelta, + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + ReturnValue_t result = checkAvailableSensors(susData, mgmData, gyrData, attitudeEstimationData); + if (result != returnvalue::OK) { + reset(attitudeEstimationData); + return result; + } + + double measSensMatrix[matrixDimensionFactor][6] = {}, + measCovMatrix[matrixDimensionFactor][matrixDimensionFactor] = {}, + measVec[matrixDimensionFactor] = {}, estVec[matrixDimensionFactor] = {}; + kfUpdate(susData, mgmData, *measSensMatrix, *measCovMatrix, measVec, estVec); + + double kalmanGain[6][matrixDimensionFactor]; + std::memset(kalmanGain, 0, sizeof(kalmanGain)); + result = kfGain(*measSensMatrix, *measCovMatrix, *kalmanGain, attitudeEstimationData); + if (result != returnvalue::OK) { + reset(attitudeEstimationData); + return result; + } + + kfCovAposteriori(*kalmanGain, *measSensMatrix); + kfStateAposteriori(*kalmanGain, measVec, estVec); + kfPropagate(gyrData, timeDelta); + + result = kfFiniteCheck(attitudeEstimationData); + if (result != returnvalue::OK) { + return result; + } + mekfStatus = MekfStatus::RUNNING; + updateDataSet(attitudeEstimationData); + return MEKF_RUNNING; +} + +ReturnValue_t MultiplicativeKalmanFilter::checkAvailableSensors( + const acsctrl::SusDataProcessed *susData, const acsctrl::MgmDataProcessed *mgmData, + const acsctrl::GyrDataProcessed *gyrData, + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + // Check for GYR Measurements + if (not gyrData->gyrVecTot.isValid()) { + mekfStatus = MekfStatus::NO_GYR_DATA; + updateDataSetWithoutData(attitudeEstimationData); + return MEKF_NO_GYR_DATA; + } + // Check for Model Calculations + if (not susData->sunIjkModel.isValid() or not mgmData->magIgrfModel.isValid()) { + mekfStatus = MekfStatus::NO_MODEL_VECTORS; + updateDataSetWithoutData(attitudeEstimationData); + return MEKF_NO_MODEL_VECTORS; + } + // Check Measurements available from SUS, MGM, STR + if (not susData->susVecTot.isValid() and not mgmData->mgmVecTot.isValid() and + not strData.strQuat.valid) { + sensorsAvailable = SensorAvailability::NONE; + mekfStatus = MekfStatus::NO_SUS_MGM_STR_DATA; + updateDataSetWithoutData(attitudeEstimationData); + return MEKF_NO_SUS_MGM_STR_DATA; + } + if (susData->susVecTot.isValid() and mgmData->mgmVecTot.isValid() and strData.strQuat.valid) { + sensorsAvailable = SensorAvailability::SUS_MGM_STR; + matrixDimensionFactor = 9; + } else if (susData->susVecTot.isValid() and mgmData->mgmVecTot.isValid() and + not strData.strQuat.valid) { + sensorsAvailable = SensorAvailability::SUS_MGM; + matrixDimensionFactor = 6; + } else if (susData->susVecTot.isValid() and not mgmData->mgmVecTot.isValid() and + strData.strQuat.valid) { + sensorsAvailable = SensorAvailability::SUS_STR; + matrixDimensionFactor = 6; + } else if (not susData->susVecTot.isValid() and mgmData->mgmVecTot.isValid() and + strData.strQuat.valid) { + sensorsAvailable = SensorAvailability::MGM_STR; + matrixDimensionFactor = 6; + } else if (susData->susVecTot.isValid() and not mgmData->mgmVecTot.isValid() and + not strData.strQuat.valid) { + sensorsAvailable = SensorAvailability::SUS; + matrixDimensionFactor = 3; + } else if (not susData->susVecTot.isValid() and mgmData->mgmVecTot.isValid() and + not strData.strQuat.valid) { + sensorsAvailable = SensorAvailability::MGM; + matrixDimensionFactor = 3; + } else if (not susData->susVecTot.isValid() and not mgmData->mgmVecTot.isValid() and + strData.strQuat.valid) { + sensorsAvailable = SensorAvailability::STR; + matrixDimensionFactor = 3; + } + return returnvalue::OK; +} + +void MultiplicativeKalmanFilter::kfUpdate(const acsctrl::SusDataProcessed *susData, + const acsctrl::MgmDataProcessed *mgmData, + double *measSensMatrix, double *measCovMatrix, + double *measVec, double *estVec) { + double normMagB[3] = {0, 0, 0}, normSunB[3] = {0, 0, 0}, normMagI[3] = {0, 0, 0}, + normSunI[3] = {0, 0, 0}, normSunEstB[3] = {0, 0, 0}, normMagEstB[3] = {0, 0, 0}; + VectorOperations::normalize(mgmData->mgmVecTot.value, normMagB, 3); // b2 + VectorOperations::normalize(susData->susVecTot.value, normSunB, 3); // b1 + VectorOperations::normalize(mgmData->magIgrfModel.value, normMagI, 3); // r2 + VectorOperations::normalize(susData->sunIjkModel.value, normSunI, 3); // r1 + QuaternionOperations::multiplyVector(estimatedQuaternionBI, normSunI, normSunEstB); + QuaternionOperations::multiplyVector(estimatedQuaternionBI, normMagI, normMagEstB); + + // Measurement Sensitivity Matrix + double measSensMatrixSun[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; // H11 + double measSensMatrixMag[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; // H22 + double measSensMatrixStr[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; // H33 + MatrixOperations::skewMatrix(normSunEstB, *measSensMatrixSun); + MatrixOperations::skewMatrix(normMagEstB, *measSensMatrixMag); + + double measVecQuat[3] = {0, 0, 0}, quatEstErr[3] = {0, 0, 0}; + if (strData.strQuat.valid) { + double quatError[4] = {0, 0, 0, 0}; + double invPropagatedQuat[4] = {0, 0, 0, 0}; + QuaternionOperations::inverse(estimatedQuaternionBI, invPropagatedQuat); + QuaternionOperations::multiply(strData.strQuat.value, invPropagatedQuat, quatError); + for (uint8_t i = 0; i < 3; i++) { + measVecQuat[i] = 2. * quatError[i] / quatError[3]; + } + } + + switch (sensorsAvailable) { + case SensorAvailability::SUS_MGM_STR: + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixSun, 3, 3, + matrixDimensionFactor, 6, 0, 0); + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixMag, 3, 3, + matrixDimensionFactor, 6, 3, 0); + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixStr, 3, 3, + matrixDimensionFactor, 6, 6, 0); + + MatrixOperations::writeSubmatrix(measCovMatrix, *covSus, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 0, 0); + MatrixOperations::writeSubmatrix(measCovMatrix, *covMgm, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 3, 3); + MatrixOperations::writeSubmatrix(measCovMatrix, *covStr, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 6, 6); + + std::memcpy(measVec, normSunB, sizeof(normSunB)); + std::memcpy(measVec + 3, normMagB, sizeof(normMagB)); + std::memcpy(measVec + 6, measVecQuat, sizeof(measVecQuat)); + + std::memcpy(estVec, normSunEstB, sizeof(normSunEstB)); + std::memcpy(estVec + 3, normMagEstB, sizeof(normMagEstB)); + std::memcpy(estVec + 6, quatEstErr, sizeof(quatEstErr)); + return; + case SensorAvailability::SUS_MGM: + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixSun, 3, 3, + matrixDimensionFactor, 6, 0, 0); + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixMag, 3, 3, + matrixDimensionFactor, 6, 3, 0); + + MatrixOperations::writeSubmatrix(measCovMatrix, *covSus, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 0, 0); + MatrixOperations::writeSubmatrix(measCovMatrix, *covMgm, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 3, 3); + + std::memcpy(measVec, normSunB, sizeof(normSunB)); + std::memcpy(measVec + 3, normMagB, sizeof(normMagB)); + + std::memcpy(estVec, normSunEstB, sizeof(normSunEstB)); + std::memcpy(estVec + 3, normMagEstB, sizeof(normMagEstB)); + return; + case SensorAvailability::SUS_STR: + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixSun, 3, 3, + matrixDimensionFactor, 6, 0, 0); + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixStr, 3, 3, + matrixDimensionFactor, 6, 3, 0); + + MatrixOperations::writeSubmatrix(measCovMatrix, *covSus, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 0, 0); + MatrixOperations::writeSubmatrix(measCovMatrix, *covStr, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 3, 3); + + std::memcpy(measVec, normSunB, sizeof(normSunB)); + std::memcpy(measVec + 3, measVecQuat, sizeof(measVecQuat)); + + std::memcpy(estVec, normSunEstB, sizeof(normSunEstB)); + std::memcpy(estVec + 3, quatEstErr, sizeof(quatEstErr)); + return; + case SensorAvailability::MGM_STR: + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixMag, 3, 3, + matrixDimensionFactor, 6, 0, 0); + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixStr, 3, 3, + matrixDimensionFactor, 6, 3, 0); + + MatrixOperations::writeSubmatrix(measCovMatrix, *covMgm, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 0, 0); + MatrixOperations::writeSubmatrix(measCovMatrix, *covStr, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 3, 3); + + std::memcpy(measVec, normMagB, sizeof(normMagB)); + std::memcpy(measVec + 3, measVecQuat, sizeof(measVecQuat)); + + std::memcpy(estVec, normMagEstB, sizeof(normMagEstB)); + std::memcpy(estVec + 3, quatEstErr, sizeof(quatEstErr)); + return; + case SensorAvailability::SUS: + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixSun, 3, 3, + matrixDimensionFactor, 6, 0, 0); + + MatrixOperations::writeSubmatrix(measCovMatrix, *covSus, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 0, 0); + + std::memcpy(measVec, normSunB, sizeof(normSunB)); + + std::memcpy(estVec, normSunEstB, sizeof(normSunEstB)); + return; + case SensorAvailability::MGM: + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixMag, 3, 3, + matrixDimensionFactor, 6, 0, 0); + + MatrixOperations::writeSubmatrix(measCovMatrix, *covMgm, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 0, 0); + + std::memcpy(measVec, normMagB, sizeof(normMagB)); + + std::memcpy(estVec, normMagEstB, sizeof(normMagEstB)); + return; + case SensorAvailability::STR: + MatrixOperations::writeSubmatrix(measSensMatrix, *measSensMatrixStr, 3, 3, + matrixDimensionFactor, 6, 0, 0); + + MatrixOperations::writeSubmatrix(measCovMatrix, *covStr, 3, 3, matrixDimensionFactor, + matrixDimensionFactor, 0, 0); + + std::memcpy(measVec, measVecQuat, sizeof(measVecQuat)); + + std::memcpy(estVec, quatEstErr, sizeof(quatEstErr)); + return; + default: + sif::error << "MEKF::Invalid SensorAvailability Flag" << std::endl; + } +} + +ReturnValue_t MultiplicativeKalmanFilter::kfGain( + double *measSensMatrix, double *measCovMatrix, double *kalmanGain, + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + // Kalman Gain: K = P * H' / (H * P * H' + R) + double kalmanGainDen[matrixDimensionFactor][matrixDimensionFactor] = {}, + invKalmanGainDen[matrixDimensionFactor][matrixDimensionFactor] = {}; + double residualCov[6][matrixDimensionFactor], measSensMatrixTransposed[6][matrixDimensionFactor]; + std::memset(residualCov, 0, sizeof(residualCov)); + std::memset(measSensMatrixTransposed, 0, sizeof(measSensMatrixTransposed)); + + MatrixOperations::transpose(measSensMatrix, *measSensMatrixTransposed, + matrixDimensionFactor, 6); + MatrixOperations::multiply(*estimatedCovarianceMatrix, *measSensMatrixTransposed, + *residualCov, 6, 6, matrixDimensionFactor); + + MatrixOperations::multiply(measSensMatrix, *residualCov, *kalmanGainDen, + matrixDimensionFactor, 6, matrixDimensionFactor); + MatrixOperations::add(*kalmanGainDen, measCovMatrix, *kalmanGainDen, + matrixDimensionFactor, matrixDimensionFactor); + ReturnValue_t result = MatrixOperations::inverseMatrix(*kalmanGainDen, *invKalmanGainDen, + matrixDimensionFactor); + if (result != returnvalue::OK) { + mekfStatus = MekfStatus::COVARIANCE_INVERSION_FAILED; + updateDataSetWithoutData(attitudeEstimationData); + return MEKF_COVARIANCE_INVERSION_FAILED; + } + + MatrixOperations::multiply(*residualCov, *invKalmanGainDen, kalmanGain, 6, + matrixDimensionFactor, matrixDimensionFactor); + return returnvalue::OK; +} + +void MultiplicativeKalmanFilter::kfCovAposteriori(double *kalmanGain, double *measSensMatrix) { + // Covariance Matrix: P_plus [k] = (I-K*H)*P_min + double helperMat[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; + MatrixOperations::multiply(kalmanGain, measSensMatrix, *helperMat, 6, + matrixDimensionFactor, 6); + MatrixOperations::subtract(*EYE6, *helperMat, *helperMat, 6, 6); + MatrixOperations::multiply(*helperMat, *estimatedCovarianceMatrix, *covAposteriori, 6, 6, + 6); +} + +void MultiplicativeKalmanFilter::kfStateAposteriori(double *kalmanGain, double *measVec, + double *estVec) { + double stateVecErr[6] = {0, 0, 0, 0, 0, 0}, plantOutputDiff[matrixDimensionFactor] = {}; + VectorOperations::subtract(measVec, estVec, plantOutputDiff, matrixDimensionFactor); + MatrixOperations::multiply(kalmanGain, plantOutputDiff, stateVecErr, 6, + matrixDimensionFactor, 1); + + double quatAposteriori[3] = {stateVecErr[0], stateVecErr[1], stateVecErr[2]}; + double biasAposteriori[3] = {stateVecErr[3], stateVecErr[4], stateVecErr[5]}; + + double quaternionIdentity[4][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + double quaternionIdentityUpperMatrix[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + double quaternionIdentityUpperMatrixHelper[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + double negQuatVec[3] = {0, 0, 0}; + MatrixOperations::multiplyScalar(*EYE3, estimatedQuaternionBI[3], + *quaternionIdentityUpperMatrix, 3, 3); + MatrixOperations::skewMatrix(estimatedQuaternionBI, *quaternionIdentityUpperMatrixHelper); + MatrixOperations::add(*quaternionIdentityUpperMatrix, + *quaternionIdentityUpperMatrixHelper, + *quaternionIdentityUpperMatrix, 3, 3); + + VectorOperations::mulScalar(estimatedQuaternionBI, -1, negQuatVec, 3); + + MatrixOperations::writeSubmatrix(*quaternionIdentity, *quaternionIdentityUpperMatrix, 3, + 3, 4, 3, 0, 0); + MatrixOperations::writeSubmatrix(*quaternionIdentity, negQuatVec, 1, 3, 4, 3, 3, 0); + + double quatCorrection[4] = {0, 0, 0, 0}; + MatrixOperations::multiply(*quaternionIdentity, quatAposteriori, quatCorrection, 4, 3, 1); + VectorOperations::mulScalar(quatCorrection, 0.5, quatCorrection, 4); + VectorOperations::add(estimatedQuaternionBI, quatCorrection, estimatedQuaternionBI, 4); + QuaternionOperations::normalize(estimatedQuaternionBI); + + VectorOperations::add(estimatedBiasGyr, biasAposteriori, estimatedBiasGyr, 3); +} + +void MultiplicativeKalmanFilter::kfPropagate(const acsctrl::GyrDataProcessed *gyrData, + const double timeDelta) { + // Estimated Rotation Rate + VectorOperations::subtract(gyrData->gyrVecTot.value, estimatedBiasGyr, estimatedRotRate); + double estimatedRotRateNorm = VectorOperations::norm(estimatedRotRate, 3); + double estimatedRotRateSkewed[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + MatrixOperations::skewMatrix(estimatedRotRate, *estimatedRotRateSkewed); + + // Estimated Quaternion + double diffQuatMatrix[4][4] = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, + diffQuatMatrixHelper1[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + diffQuatMatrixHelper2[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, diffVector[3] = {0, 0, 0}, + helperQuat[4] = {0, 0, 0, 0}; + double diffScalar = std::cos(.5 * estimatedRotRateNorm * timeDelta); + VectorOperations::mulScalar( + estimatedRotRate, std::sin(.5 * estimatedRotRateNorm * timeDelta) / estimatedRotRateNorm, + diffVector, 3); + MatrixOperations::multiplyScalar(*EYE3, diffScalar, *diffQuatMatrixHelper1, 3, 3); + MatrixOperations::skewMatrix(diffVector, *diffQuatMatrixHelper2); + MatrixOperations::subtract(*diffQuatMatrixHelper1, *diffQuatMatrixHelper2, + *diffQuatMatrixHelper1, 3, 3); + + MatrixOperations::writeSubmatrix(*diffQuatMatrix, *diffQuatMatrixHelper1, 3, 3, 4, 4, 0, + 0); + MatrixOperations::writeSubmatrix(*diffQuatMatrix, diffVector, 3, 1, 4, 4, 0, 3); + VectorOperations::mulScalar(diffVector, -1, diffVector, 3); + MatrixOperations::writeSubmatrix(*diffQuatMatrix, diffVector, 1, 3, 4, 4, 3, 0); + diffQuatMatrix[3][3] = diffScalar; + + std::memcpy(helperQuat, estimatedQuaternionBI, sizeof(helperQuat)); + MatrixOperations::multiply(*diffQuatMatrix, helperQuat, estimatedQuaternionBI, 4, 4, 1); + QuaternionOperations::normalize(estimatedQuaternionBI); + + // Covariance Matrix: P_minus [k+1] = Phi*P_plus [k]*Phi'+G*Q*G' + // Phi [Discrete Error State Transition Matrix] + double errorStateTransition[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 1}}; + double phi11[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + phi12[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + phi11L[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + phi11R[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + phi12L[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + phi12R[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + + MatrixOperations::multiplyScalar( + *estimatedRotRateSkewed, std::sin(estimatedRotRateNorm * timeDelta) / estimatedRotRateNorm, + *phi11L, 3, 3); + MatrixOperations::multiply(*estimatedRotRateSkewed, *estimatedRotRateSkewed, *phi11R, 3, + 3, 3); + MatrixOperations::multiplyScalar(*phi11R, + (1 - std::cos(estimatedRotRateNorm * timeDelta)) / + (estimatedRotRateNorm * estimatedRotRateNorm), + *phi11R, 3, 3); + MatrixOperations::subtract(*EYE3, *phi11L, *phi11, 3, 3); + MatrixOperations::add(*phi11, *phi11R, *phi11, 3, 3); + + MatrixOperations::multiplyScalar(*EYE3, -timeDelta, *phi12, 3, 3); + MatrixOperations::multiplyScalar(*estimatedRotRateSkewed, + (1 - std::cos(estimatedRotRateNorm * timeDelta)) / + (estimatedRotRateNorm * estimatedRotRateNorm), + *phi12L, 3, 3); + MatrixOperations::multiply(*estimatedRotRateSkewed, *estimatedRotRateSkewed, *phi12R, 3, + 3, 3); + MatrixOperations::multiplyScalar( + *phi12R, + (estimatedRotRateNorm * timeDelta - std::sin(estimatedRotRateNorm * timeDelta)) / + (estimatedRotRateNorm * estimatedRotRateNorm * estimatedRotRateNorm), + *phi12R, 3, 3); + MatrixOperations::add(*phi12, *phi12L, *phi12, 3, 3); + MatrixOperations::subtract(*phi12, *phi12R, *phi12, 3, 3); + + MatrixOperations::writeSubmatrix(*errorStateTransition, *phi11, 3, 3, 6, 6, 0, 0); + MatrixOperations::writeSubmatrix(*errorStateTransition, *phi12, 3, 3, 6, 6, 0, 3); + + // G [Discrete Transfer Matrix] + double transferMatrix[6][6] = {{-1, 0, 0, 0, 0, 0}, {0, -1, 0, 0, 0, 0}, {0, 0, -1, 0, 0, 0}, + {0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 1}}; + + // Q [Discrete Time Covariance] + double timeCovariance[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; + double q11[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + q12[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + q21[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + q22[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + + if (estimatedRotRateNorm * timeDelta < M_PI / 10.) { + MatrixOperations::multiplyScalar( + *EYE3, + (sigmaGyrArw * sigmaGyrArw) * timeDelta + + ((sigmaGyrBs * sigmaGyrBs) * (timeDelta * timeDelta * timeDelta)) / 3., + *q11, 3, 3); + MatrixOperations::multiplyScalar( + *EYE3, -(sigmaGyrBs * sigmaGyrBs * timeDelta * timeDelta) / 2., *q12, 3, 3); + } else { + double q11h1[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + q11h2[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + q12h1[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + q12h2[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + + MatrixOperations::multiplyScalar(*EYE3, (sigmaGyrArw * sigmaGyrArw) * timeDelta, *q11, + 3, 3); + MatrixOperations::multiplyScalar(*EYE3, (timeDelta * timeDelta * timeDelta) / 3., + *q11h1, 3, 3); + MatrixOperations::multiply(*estimatedRotRateSkewed, *estimatedRotRateSkewed, *q11h2, 3, + 3, 3); + MatrixOperations::multiplyScalar( + *q11h2, + (2 * estimatedRotRateNorm * timeDelta - 2 * std::sin(estimatedRotRateNorm * timeDelta) - + ((estimatedRotRateNorm * estimatedRotRateNorm * estimatedRotRateNorm) * + (timeDelta * timeDelta * timeDelta) / .3)) / + (estimatedRotRateNorm * estimatedRotRateNorm * estimatedRotRateNorm * + estimatedRotRateNorm * estimatedRotRateNorm), + *q11h2, 3, 3); + MatrixOperations::add(*q11h1, *q11h2, *q11h1, 3, 3); + MatrixOperations::multiplyScalar(*q11h1, sigmaGyrBs * sigmaGyrBs, *q11h1, 3, 3); + MatrixOperations::add(*q11, *q11h1, *q11, 3, 3); + + MatrixOperations::multiplyScalar(*EYE3, -(timeDelta * timeDelta) / 2., *q12, 3, 3); + MatrixOperations::multiplyScalar( + *estimatedRotRateSkewed, + (estimatedRotRateNorm * timeDelta - std::sin(estimatedRotRateNorm * timeDelta)) / + (estimatedRotRateNorm * estimatedRotRateNorm * estimatedRotRateNorm), + *q12h1, 3, 3); + MatrixOperations::multiply(*estimatedRotRateSkewed, *estimatedRotRateSkewed, *q12h2, 3, + 3, 3); + MatrixOperations::multiplyScalar( + *q12h2, + (((estimatedRotRateNorm * estimatedRotRateNorm) * (timeDelta * timeDelta)) / 2. + + std::cos(estimatedRotRateNorm * timeDelta) - 1) / + (estimatedRotRateNorm * estimatedRotRateNorm * estimatedRotRateNorm * + estimatedRotRateNorm), + *q12h2, 3, 3); + MatrixOperations::add(*q12, *q12h1, *q12, 3, 3); + MatrixOperations::subtract(*q12, *q12h2, *q12, 3, 3); + MatrixOperations::multiplyScalar(*q12, sigmaGyrBs * sigmaGyrBs, *q12, 3, 3); + } + + MatrixOperations::transpose(*q12, *q21, 3); + MatrixOperations::multiplyScalar(*EYE3, sigmaGyrBs * sigmaGyrBs * timeDelta, *q22, 3, 3); + + MatrixOperations::writeSubmatrix(*timeCovariance, *q11, 3, 3, 6, 6, 0, 0); + MatrixOperations::writeSubmatrix(*timeCovariance, *q12, 3, 3, 6, 6, 0, 3); + MatrixOperations::writeSubmatrix(*timeCovariance, *q21, 3, 3, 6, 6, 3, 0); + MatrixOperations::writeSubmatrix(*timeCovariance, *q22, 3, 3, 6, 6, 3, 3); + + // Estimated Covariance + double errorStateTransitionTransposed[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}, + estCovarianceMatrixL[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}, + estCovarianceMatrixR[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}, + estCovarianceMatrixHelp[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; + + MatrixOperations::transpose(*errorStateTransition, *errorStateTransitionTransposed, 6); + MatrixOperations::multiply(*errorStateTransition, *covAposteriori, + *estCovarianceMatrixHelp, 6, 6, 6); + MatrixOperations::multiply(*estCovarianceMatrixHelp, *errorStateTransitionTransposed, + *estCovarianceMatrixL, 6, 6, 6); + MatrixOperations::multiply(*transferMatrix, *timeCovariance, *estCovarianceMatrixHelp, 6, + 6, 6); + MatrixOperations::multiply(*estCovarianceMatrixHelp, *transferMatrix, + *estCovarianceMatrixR, 6, 6, 6); + MatrixOperations::add(*estCovarianceMatrixL, *estCovarianceMatrixR, + *estimatedCovarianceMatrix, 6, 6); +} + +ReturnValue_t MultiplicativeKalmanFilter::kfFiniteCheck( + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + if (not(VectorOperations::isFinite(estimatedQuaternionBI, 4)) or + not(VectorOperations::isFinite(estimatedRotRate, 3)) or + not(MatrixOperations::isFinite(*estimatedCovarianceMatrix, 6, 6))) { + mekfStatus = MekfStatus::NOT_FINITE; + updateDataSetWithoutData(attitudeEstimationData); + return MEKF_NOT_FINITE; + } + return returnvalue::OK; +} + +void MultiplicativeKalmanFilter::updateDataSetWithoutData( + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + PoolReadGuard pg(attitudeEstimationData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(attitudeEstimationData->quatMekf.value, ZERO_VEC4, 4 * sizeof(double)); + attitudeEstimationData->quatMekf.setValid(false); + std::memcpy(attitudeEstimationData->satRotRateMekf.value, ZERO_VEC3, 3 * sizeof(double)); + attitudeEstimationData->satRotRateMekf.setValid(false); + attitudeEstimationData->mekfStatus.value = mekfStatus; + attitudeEstimationData->mekfStatus.setValid(true); + } +} + +void MultiplicativeKalmanFilter::updateDataSet( + acsctrl::AttitudeEstimationData *attitudeEstimationData) { + PoolReadGuard pg(attitudeEstimationData); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(attitudeEstimationData->quatMekf.value, estimatedQuaternionBI, 4 * sizeof(double)); + attitudeEstimationData->quatMekf.setValid(true); + std::memcpy(attitudeEstimationData->satRotRateMekf.value, estimatedRotRate, 3 * sizeof(double)); + attitudeEstimationData->satRotRateMekf.setValid(true); + attitudeEstimationData->mekfStatus.value = mekfStatus; + attitudeEstimationData->mekfStatus.setValid(true); + } +} + +void MultiplicativeKalmanFilter::setStrData(double qX, double qY, double qZ, double qW, bool valid, + bool allowStr) { + strData.strQuat.value[0] = qX; + strData.strQuat.value[1] = qY; + strData.strQuat.value[2] = qZ; + strData.strQuat.value[3] = qW; + strData.strQuat.valid = (valid and allowStr); +} diff --git a/mission/controller/acs/MultiplicativeKalmanFilter.h b/mission/controller/acs/MultiplicativeKalmanFilter.h new file mode 100644 index 0000000..d26b6dd --- /dev/null +++ b/mission/controller/acs/MultiplicativeKalmanFilter.h @@ -0,0 +1,146 @@ +#ifndef MULTIPLICATIVEKALMANFILTER_H_ +#define MULTIPLICATIVEKALMANFILTER_H_ + +#include +#include +#include +#include +#include +#include +#include + +class MultiplicativeKalmanFilter { + /* @brief: This class handles the calculation of an estimated quaternion and the gyroscope bias by + * means of the spacecraft attitude sensors + * + * @note: A description of the used algorithms can be found in the bachelor thesis of Robin + * Marquardt + * https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_Studenten/Marquardt_Robin&openfile=500811 + */ + public: + /* @brief: Constructor + */ + MultiplicativeKalmanFilter(AcsParameters *acsParameters); + virtual ~MultiplicativeKalmanFilter(); + + ReturnValue_t reset(acsctrl::AttitudeEstimationData *attitudeEstimationData); + + ReturnValue_t init(const acsctrl::SusDataProcessed *susData, + const acsctrl::MgmDataProcessed *mgmData, + const acsctrl::GyrDataProcessed *gyrData, + acsctrl::AttitudeEstimationData *attitudeEstimationData); + + ReturnValue_t mekfEst(const acsctrl::SusDataProcessed *susData, + const acsctrl::MgmDataProcessed *mgmData, + const acsctrl::GyrDataProcessed *gyrData, const double timeDelta, + acsctrl::AttitudeEstimationData *attitudeEstimationData); + + void updateStandardDeviations(const AcsParameters *acsParameters); + + void setStrData(const double qX, const double qY, const double qZ, const double qW, + const bool valid, const bool allowStr); + + static constexpr uint8_t IF_MEKF_ID = CLASS_ID::ACS_MEKF; + static constexpr ReturnValue_t MEKF_UNINITIALIZED = returnvalue::makeCode(IF_MEKF_ID, 2); + static constexpr ReturnValue_t MEKF_NO_GYR_DATA = returnvalue::makeCode(IF_MEKF_ID, 3); + static constexpr ReturnValue_t MEKF_NO_MODEL_VECTORS = returnvalue::makeCode(IF_MEKF_ID, 4); + static constexpr ReturnValue_t MEKF_NO_SUS_MGM_STR_DATA = returnvalue::makeCode(IF_MEKF_ID, 5); + static constexpr ReturnValue_t MEKF_COVARIANCE_INVERSION_FAILED = + returnvalue::makeCode(IF_MEKF_ID, 6); + static constexpr ReturnValue_t MEKF_NOT_FINITE = returnvalue::makeCode(IF_MEKF_ID, 7); + static constexpr ReturnValue_t MEKF_INITIALIZED = returnvalue::makeCode(IF_MEKF_ID, 8); + static constexpr ReturnValue_t MEKF_RUNNING = returnvalue::makeCode(IF_MEKF_ID, 9); + + private: + static constexpr double ZERO_VEC3[3] = {0, 0, 0}; + static constexpr double ZERO_VEC4[4] = {0, 0, 0, 0}; + static constexpr double ZERO_MAT66[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; + static constexpr double UNIT_QUAT[4] = {0, 0, 0, 1}; + static constexpr double EYE3[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + static constexpr double EYE6[6][6] = {{1, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0}, + {0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 1}}; + + enum MekfStatus : uint8_t { + UNINITIALIZED = 0, + NO_GYR_DATA = 1, + NO_MODEL_VECTORS = 2, + NO_SUS_MGM_STR_DATA = 3, + COVARIANCE_INVERSION_FAILED = 4, + NOT_FINITE = 5, + INITIALIZED = 10, + RUNNING = 11, + }; + + enum SensorAvailability : uint8_t { + NONE = 0, + SUS_MGM_STR = 1, + SUS_MGM = 2, + SUS_STR = 3, + MGM_STR = 4, + SUS = 5, + MGM = 6, + STR = 7, + }; + + MekfStatus mekfStatus = MekfStatus::UNINITIALIZED; + + struct StrData { + struct StrQuat { + double value[4] = {0, 0, 0, 0}; + bool valid = false; + } strQuat; + } strData; + + // Standard Deviations + double sigmaSus = 0; + double sigmaMgm = 0; + double sigmaStr = 0; + double sigmaGyr = 0; + // sigmaV + double sigmaGyrArw = 0; + // sigmaU + double sigmaGyrBs = 0; + + // Covariance Matrices + double covSus[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + double covMgm[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + double covStr[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + + double covAposteriori[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; + + // Sensor Availability + SensorAvailability sensorsAvailable = SensorAvailability::NONE; + uint8_t matrixDimensionFactor = 0; + + // Estimated States + double estimatedQuaternionBI[4] = {0, 0, 0, 1}; + double estimatedBiasGyr[3] = {0, 0, 0}; + double estimatedRotRate[3] = {0, 0, 0}; + double estimatedCovarianceMatrix[6][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; + + // Functions + ReturnValue_t checkAvailableSensors(const acsctrl::SusDataProcessed *susData, + const acsctrl::MgmDataProcessed *mgmData, + const acsctrl::GyrDataProcessed *gyrData, + acsctrl::AttitudeEstimationData *attitudeEstimationData); + + void kfUpdate(const acsctrl::SusDataProcessed *susData, const acsctrl::MgmDataProcessed *mgmData, + double *measSensMatrix, double *measCovMatrix, double *measVec, double *measEstVec); + ReturnValue_t kfGain(double *measSensMatrix, double *measCovMatrix, double *kalmanGain, + acsctrl::AttitudeEstimationData *attitudeEstimationData); + void kfCovAposteriori(double *kalmanGain, double *measSensMatrix); + void kfStateAposteriori(double *kalmanGain, double *measVec, double *estVec); + void kfPropagate(const acsctrl::GyrDataProcessed *gyrData, const double timeDiff); + + ReturnValue_t kfFiniteCheck(acsctrl::AttitudeEstimationData *attitudeEstimationData); + + void updateDataSetWithoutData(acsctrl::AttitudeEstimationData *attitudeEstimationData); + void updateDataSet(acsctrl::AttitudeEstimationData *attitudeEstimationData); +}; + +#endif /* ACS_MULTIPLICATIVEKALMANFILTER_H_ */ diff --git a/mission/controller/acs/Navigation.cpp b/mission/controller/acs/Navigation.cpp new file mode 100644 index 0000000..113a6cc --- /dev/null +++ b/mission/controller/acs/Navigation.cpp @@ -0,0 +1,73 @@ +#include "Navigation.h" + +Navigation::Navigation(AcsParameters *acsParameters) : multiplicativeKalmanFilter(acsParameters) {} + +Navigation::~Navigation() {} + +ReturnValue_t Navigation::useMekf(const ACS::SensorValues *sensorValues, + const acsctrl::GyrDataProcessed *gyrDataProcessed, + const acsctrl::MgmDataProcessed *mgmDataProcessed, + const acsctrl::SusDataProcessed *susDataProcessed, + const double timeDelta, + acsctrl::AttitudeEstimationData *attitudeEstimationData, + const bool allowStr) { + multiplicativeKalmanFilter.setStrData( + sensorValues->strSet.caliQx.value, sensorValues->strSet.caliQy.value, + sensorValues->strSet.caliQz.value, sensorValues->strSet.caliQw.value, + sensorValues->strSet.caliQx.isValid(), allowStr); + + if (mekfStatus == MultiplicativeKalmanFilter::MEKF_UNINITIALIZED) { + mekfStatus = multiplicativeKalmanFilter.init(susDataProcessed, mgmDataProcessed, + gyrDataProcessed, attitudeEstimationData); + return mekfStatus; + } else { + mekfStatus = multiplicativeKalmanFilter.mekfEst( + susDataProcessed, mgmDataProcessed, gyrDataProcessed, timeDelta, attitudeEstimationData); + return mekfStatus; + } +} + +void Navigation::resetMekf(acsctrl::AttitudeEstimationData *mekfData) { + mekfStatus = multiplicativeKalmanFilter.reset(mekfData); +} + +ReturnValue_t Navigation::useSpg4(timeval now, acsctrl::GpsDataProcessed *gpsDataProcessed) { + double position[3] = {0, 0, 0}; + double velocity[3] = {0, 0, 0}; + ReturnValue_t result = sgp4Propagator.propagate(position, velocity, now, 0); + + if (result == returnvalue::OK) { + { + PoolReadGuard pg(gpsDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + gpsDataProcessed->source = acs::gps::Source::SPG4; + gpsDataProcessed->source.setValid(true); + std::memcpy(gpsDataProcessed->gpsPosition.value, position, 3 * sizeof(double)); + gpsDataProcessed->gpsPosition.setValid(true); + std::memcpy(gpsDataProcessed->gpsVelocity.value, velocity, 3 * sizeof(double)); + gpsDataProcessed->gpsVelocity.setValid(true); + } + } + } else { + { + PoolReadGuard pg(gpsDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + gpsDataProcessed->source = acs::gps::Source::NONE; + gpsDataProcessed->source.setValid(true); + std::memcpy(gpsDataProcessed->gpsPosition.value, position, 3 * sizeof(double)); + gpsDataProcessed->gpsPosition.setValid(false); + std::memcpy(gpsDataProcessed->gpsVelocity.value, velocity, 3 * sizeof(double)); + gpsDataProcessed->gpsVelocity.setValid(false); + } + } + } + return result; +} + +ReturnValue_t Navigation::updateTle(const uint8_t *line1, const uint8_t *line2) { + return sgp4Propagator.initialize(line1, line2); +} + +void Navigation::updateMekfStandardDeviations(const AcsParameters *acsParameters) { + multiplicativeKalmanFilter.updateStandardDeviations(acsParameters); +} diff --git a/mission/controller/acs/Navigation.h b/mission/controller/acs/Navigation.h new file mode 100644 index 0000000..230ce79 --- /dev/null +++ b/mission/controller/acs/Navigation.h @@ -0,0 +1,35 @@ +#ifndef NAVIGATION_H_ +#define NAVIGATION_H_ + +#include +#include +#include +#include +#include +#include + +class Navigation { + public: + Navigation(AcsParameters *acsParameters); + virtual ~Navigation(); + + ReturnValue_t useMekf(const ACS::SensorValues *sensorValues, + const acsctrl::GyrDataProcessed *gyrDataProcessed, + const acsctrl::MgmDataProcessed *mgmDataProcessed, + const acsctrl::SusDataProcessed *susDataProcessed, const double timeDelta, + acsctrl::AttitudeEstimationData *attitudeEstimationData, + const bool allowStr); + void resetMekf(acsctrl::AttitudeEstimationData *mekfData); + + ReturnValue_t useSpg4(timeval now, acsctrl::GpsDataProcessed *gpsDataProcessed); + ReturnValue_t updateTle(const uint8_t *line1, const uint8_t *line2); + void updateMekfStandardDeviations(const AcsParameters *acsParameters); + + protected: + private: + MultiplicativeKalmanFilter multiplicativeKalmanFilter; + ReturnValue_t mekfStatus = MultiplicativeKalmanFilter::MEKF_UNINITIALIZED; + Sgp4Propagator sgp4Propagator; +}; + +#endif /* ACS_NAVIGATION_H_ */ diff --git a/mission/controller/acs/SensorProcessing.cpp b/mission/controller/acs/SensorProcessing.cpp new file mode 100644 index 0000000..46be5db --- /dev/null +++ b/mission/controller/acs/SensorProcessing.cpp @@ -0,0 +1,656 @@ +#include "SensorProcessing.h" + +SensorProcessing::SensorProcessing() {} + +SensorProcessing::~SensorProcessing() {} + +void SensorProcessing::processMgm(const float *mgm0Value, bool mgm0valid, const float *mgm1Value, + bool mgm1valid, const float *mgm2Value, bool mgm2valid, + const float *mgm3Value, bool mgm3valid, const float *mgm4Value, + bool mgm4valid, timeval timeAbsolute, double timeDelta, + const AcsParameters::MgmHandlingParameters *mgmParameters, + acsctrl::GpsDataProcessed *gpsDataProcessed, + acsctrl::MgmDataProcessed *mgmDataProcessed) { + // ---------------- IGRF- 13 Implementation here + // ------------------------------------------------ + double magIgrfModel[3] = {0.0, 0.0, 0.0}; + bool gpsValid = false; + if (gpsDataProcessed->source.value != acs::gps::Source::NONE) { + // There seems to be a bug here, which causes the model vector to drift until infinity, if the + // model class is not initialized new every time. Works for now, but should be investigated. + Igrf13Model igrf13; + igrf13.schmidtNormalization(); + igrf13.updateCoeffGH(timeAbsolute); + igrf13.magFieldComp(gpsDataProcessed->gdLongitude.value, gpsDataProcessed->gcLatitude.value, + gpsDataProcessed->altitude.value, timeAbsolute, magIgrfModel); + gpsValid = true; + } + if (not mgm0valid and not mgm1valid and not mgm2valid and not mgm3valid and + (not mgm4valid or not mgmParameters->useMgm4)) { + { + PoolReadGuard pg(mgmDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(mgmDataProcessed->mgm0vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(mgmDataProcessed->mgm1vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(mgmDataProcessed->mgm2vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(mgmDataProcessed->mgm3vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(mgmDataProcessed->mgm4vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(mgmDataProcessed->mgmVecTot.value, ZERO_VEC_D, 3 * sizeof(double)); + std::memcpy(mgmDataProcessed->mgmVecTotDerivative.value, ZERO_VEC_D, 3 * sizeof(double)); + mgmDataProcessed->setValidity(false, true); + std::memcpy(mgmDataProcessed->magIgrfModel.value, magIgrfModel, 3 * sizeof(double)); + mgmDataProcessed->magIgrfModel.setValid(gpsValid); + } + } + std::memcpy(savedMgmVecTot, ZERO_VEC_D, sizeof(savedMgmVecTot)); + return; + } + float mgm0ValueNoBias[3] = {0, 0, 0}, mgm1ValueNoBias[3] = {0, 0, 0}, + mgm2ValueNoBias[3] = {0, 0, 0}, mgm3ValueNoBias[3] = {0, 0, 0}, + mgm4ValueNoBias[3] = {0, 0, 0}; + float mgm0ValueBody[3] = {0, 0, 0}, mgm1ValueBody[3] = {0, 0, 0}, mgm2ValueBody[3] = {0, 0, 0}, + mgm3ValueBody[3] = {0, 0, 0}, mgm4ValueBody[3] = {0, 0, 0}; + float mgm0ValueCalib[3] = {0, 0, 0}, mgm1ValueCalib[3] = {0, 0, 0}, mgm2ValueCalib[3] = {0, 0, 0}, + mgm3ValueCalib[3] = {0, 0, 0}, mgm4ValueCalib[3] = {0, 0, 0}; + float sensorFusionNumerator[3] = {0, 0, 0}, sensorFusionDenominator[3] = {0, 0, 0}; + + if (mgm0valid) { + MatrixOperations::multiply(mgmParameters->mgm0orientationMatrix[0], mgm0Value, + mgm0ValueBody, 3, 3, 1); + VectorOperations::subtract(mgm0ValueBody, mgmParameters->mgm0hardIronOffset, + mgm0ValueNoBias, 3); + MatrixOperations::multiply(mgmParameters->mgm0softIronInverse[0], mgm0ValueNoBias, + mgm0ValueCalib, 3, 3, 1); + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += mgm0ValueCalib[i] / mgmParameters->mgm02variance[i]; + sensorFusionDenominator[i] += 1 / mgmParameters->mgm02variance[i]; + } + } + if (mgm1valid) { + MatrixOperations::multiply(mgmParameters->mgm1orientationMatrix[0], mgm1Value, + mgm1ValueBody, 3, 3, 1); + VectorOperations::subtract(mgm1ValueBody, mgmParameters->mgm1hardIronOffset, + mgm1ValueNoBias, 3); + MatrixOperations::multiply(mgmParameters->mgm1softIronInverse[0], mgm1ValueNoBias, + mgm1ValueCalib, 3, 3, 1); + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += mgm1ValueCalib[i] / mgmParameters->mgm13variance[i]; + sensorFusionDenominator[i] += 1 / mgmParameters->mgm13variance[i]; + } + } + if (mgm2valid) { + MatrixOperations::multiply(mgmParameters->mgm2orientationMatrix[0], mgm2Value, + mgm2ValueBody, 3, 3, 1); + VectorOperations::subtract(mgm2ValueBody, mgmParameters->mgm2hardIronOffset, + mgm2ValueNoBias, 3); + MatrixOperations::multiply(mgmParameters->mgm2softIronInverse[0], mgm2ValueNoBias, + mgm2ValueCalib, 3, 3, 1); + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += mgm2ValueCalib[i] / mgmParameters->mgm02variance[i]; + sensorFusionDenominator[i] += 1 / mgmParameters->mgm02variance[i]; + } + } + if (mgm3valid) { + MatrixOperations::multiply(mgmParameters->mgm3orientationMatrix[0], mgm3Value, + mgm3ValueBody, 3, 3, 1); + VectorOperations::subtract(mgm3ValueBody, mgmParameters->mgm3hardIronOffset, + mgm3ValueNoBias, 3); + MatrixOperations::multiply(mgmParameters->mgm3softIronInverse[0], mgm3ValueNoBias, + mgm3ValueCalib, 3, 3, 1); + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += mgm3ValueCalib[i] / mgmParameters->mgm13variance[i]; + sensorFusionDenominator[i] += 1 / mgmParameters->mgm13variance[i]; + } + } + if (mgm4valid and mgmParameters->useMgm4) { + float mgm4ValueUT[3]; + VectorOperations::mulScalar(mgm4Value, 1e-3, mgm4ValueUT, 3); // nT to uT + MatrixOperations::multiply(mgmParameters->mgm4orientationMatrix[0], mgm4ValueUT, + mgm4ValueBody, 3, 3, 1); + VectorOperations::subtract(mgm4ValueBody, mgmParameters->mgm4hardIronOffset, + mgm4ValueNoBias, 3); + MatrixOperations::multiply(mgmParameters->mgm4softIronInverse[0], mgm4ValueNoBias, + mgm4ValueCalib, 3, 3, 1); + + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += mgm4ValueCalib[i] / mgmParameters->mgm4variance[i]; + sensorFusionDenominator[i] += 1 / mgmParameters->mgm4variance[i]; + } + } + double mgmVecTot[3] = {0.0, 0.0, 0.0}; + for (uint8_t i = 0; i < 3; i++) { + mgmVecTot[i] = sensorFusionNumerator[i] / sensorFusionDenominator[i]; + } + if (VectorOperations::norm(mgmVecTot, 3) != 0 and mgmDataProcessed->mgmVecTot.isValid()) { + lowPassFilter(mgmVecTot, mgmDataProcessed->mgmVecTot.value, + mgmParameters->mgmVectorFilterWeight); + } + + //-----------------------Mgm Rate Computation --------------------------------------------------- + double mgmVecTotDerivative[3] = {0.0, 0.0, 0.0}; + bool mgmVecTotDerivativeValid = false; + if (timeDelta > 0 and VectorOperations::norm(savedMgmVecTot, 3) != 0) { + VectorOperations::subtract(mgmVecTot, savedMgmVecTot, mgmVecTotDerivative, 3); + VectorOperations::mulScalar(mgmVecTotDerivative, 1. / timeDelta, mgmVecTotDerivative, + 3); + mgmVecTotDerivativeValid = true; + } + std::memcpy(savedMgmVecTot, mgmVecTot, sizeof(savedMgmVecTot)); + + if (VectorOperations::norm(mgmVecTotDerivative, 3) != 0 and + mgmDataProcessed->mgmVecTotDerivative.isValid()) { + lowPassFilter(mgmVecTotDerivative, mgmDataProcessed->mgmVecTotDerivative.value, + mgmParameters->mgmDerivativeFilterWeight); + } + + { + PoolReadGuard pg(mgmDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(mgmDataProcessed->mgm0vec.value, mgm0ValueCalib, 3 * sizeof(float)); + mgmDataProcessed->mgm0vec.setValid(mgm0valid); + std::memcpy(mgmDataProcessed->mgm1vec.value, mgm1ValueCalib, 3 * sizeof(float)); + mgmDataProcessed->mgm1vec.setValid(mgm1valid); + std::memcpy(mgmDataProcessed->mgm2vec.value, mgm2ValueCalib, 3 * sizeof(float)); + mgmDataProcessed->mgm2vec.setValid(mgm2valid); + std::memcpy(mgmDataProcessed->mgm3vec.value, mgm3ValueCalib, 3 * sizeof(float)); + mgmDataProcessed->mgm3vec.setValid(mgm3valid); + std::memcpy(mgmDataProcessed->mgm4vec.value, mgm4ValueCalib, 3 * sizeof(float)); + mgmDataProcessed->mgm4vec.setValid(mgm4valid); + std::memcpy(mgmDataProcessed->mgmVecTot.value, mgmVecTot, 3 * sizeof(double)); + mgmDataProcessed->mgmVecTot.setValid(true); + std::memcpy(mgmDataProcessed->mgmVecTotDerivative.value, mgmVecTotDerivative, + 3 * sizeof(double)); + mgmDataProcessed->mgmVecTotDerivative.setValid(mgmVecTotDerivativeValid); + std::memcpy(mgmDataProcessed->magIgrfModel.value, magIgrfModel, 3 * sizeof(double)); + mgmDataProcessed->magIgrfModel.setValid(gpsValid); + mgmDataProcessed->setValidity(true, false); + } + } +} + +void SensorProcessing::processSus( + const uint16_t *sus0Value, bool sus0valid, const uint16_t *sus1Value, bool sus1valid, + const uint16_t *sus2Value, bool sus2valid, const uint16_t *sus3Value, bool sus3valid, + const uint16_t *sus4Value, bool sus4valid, const uint16_t *sus5Value, bool sus5valid, + const uint16_t *sus6Value, bool sus6valid, const uint16_t *sus7Value, bool sus7valid, + const uint16_t *sus8Value, bool sus8valid, const uint16_t *sus9Value, bool sus9valid, + const uint16_t *sus10Value, bool sus10valid, const uint16_t *sus11Value, bool sus11valid, + timeval timeAbsolute, double timeDelta, + const AcsParameters::SusHandlingParameters *susParameters, + const AcsParameters::SunModelParameters *sunModelParameters, + acsctrl::SusDataProcessed *susDataProcessed) { + /* -------- Sun Model Direction (IJK frame) ------- */ + double JD2000 = 0; + Clock::convertTimevalToJD2000(timeAbsolute, &JD2000); + + // Julean Centuries + double sunIjkModel[3] = {0.0, 0.0, 0.0}; + double JC2000 = JD2000 / 36525.; + + double meanLongitude = + sunModelParameters->omega_0 + (sunModelParameters->domega * JC2000) * M_PI / 180.; + double meanAnomaly = (sunModelParameters->m_0 + sunModelParameters->dm * JC2000) * M_PI / 180.; + + double eclipticLongitude = meanLongitude + sunModelParameters->p1 * sin(meanAnomaly) + + sunModelParameters->p2 * sin(2 * meanAnomaly); + + double epsilon = sunModelParameters->e - (sunModelParameters->e1) * JC2000; + + sunIjkModel[0] = cos(eclipticLongitude); + sunIjkModel[1] = sin(eclipticLongitude) * cos(epsilon); + sunIjkModel[2] = sin(eclipticLongitude) * sin(epsilon); + VectorOperations::normalize(sunIjkModel, sunIjkModel, 3); + + uint64_t susBrightness[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + if (sus0valid) { + susBrightness[0] = susConverter.checkSunSensorData(sus0Value); + } + if (sus1valid) { + susBrightness[1] = susConverter.checkSunSensorData(sus1Value); + } + if (sus2valid) { + susBrightness[2] = susConverter.checkSunSensorData(sus2Value); + } + if (sus3valid) { + susBrightness[3] = susConverter.checkSunSensorData(sus3Value); + } + if (sus4valid) { + susBrightness[4] = susConverter.checkSunSensorData(sus4Value); + } + if (sus5valid) { + susBrightness[5] = susConverter.checkSunSensorData(sus5Value); + } + if (sus6valid) { + susBrightness[6] = susConverter.checkSunSensorData(sus6Value); + } + if (sus7valid) { + susBrightness[7] = susConverter.checkSunSensorData(sus7Value); + } + if (sus8valid) { + susBrightness[8] = susConverter.checkSunSensorData(sus8Value); + } + if (sus9valid) { + susBrightness[9] = susConverter.checkSunSensorData(sus9Value); + } + if (sus10valid) { + susBrightness[10] = susConverter.checkSunSensorData(sus10Value); + } + if (sus11valid) { + susBrightness[11] = susConverter.checkSunSensorData(sus11Value); + } + + bool susValid[12] = {sus0valid, sus1valid, sus2valid, sus3valid, sus4valid, sus5valid, + sus6valid, sus7valid, sus8valid, sus9valid, sus10valid, sus11valid}; + bool allInvalid = + susConverter.checkValidity(susValid, susBrightness, susParameters->susBrightnessThreshold); + + if (allInvalid) { + { + PoolReadGuard pg(susDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataProcessed->sus0vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus1vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus2vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus3vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus4vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus5vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus6vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus7vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus8vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus9vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus10vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->sus11vec.value, ZERO_VEC_F, 3 * sizeof(float)); + std::memcpy(susDataProcessed->susVecTot.value, ZERO_VEC_D, 3 * sizeof(double)); + std::memcpy(susDataProcessed->susVecTotDerivative.value, ZERO_VEC_D, 3 * sizeof(double)); + susDataProcessed->setValidity(false, true); + std::memcpy(susDataProcessed->sunIjkModel.value, sunIjkModel, 3 * sizeof(double)); + susDataProcessed->sunIjkModel.setValid(true); + } + } + std::memcpy(savedSusVecTot, ZERO_VEC_D, sizeof(savedSusVecTot)); + return; + } + + float susVecSensor[12][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, + {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + float susVecBody[12][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, + {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + + if (susValid[0]) { + susConverter.calculateSunVector(susVecSensor[0], sus0Value); + MatrixOperations::multiply(susParameters->sus0orientationMatrix[0], susVecSensor[0], + susVecBody[0], 3, 3, 1); + } + if (susValid[1]) { + susConverter.calculateSunVector(susVecSensor[1], sus1Value); + MatrixOperations::multiply(susParameters->sus1orientationMatrix[0], susVecSensor[1], + susVecBody[1], 3, 3, 1); + } + if (susValid[2]) { + susConverter.calculateSunVector(susVecSensor[2], sus2Value); + MatrixOperations::multiply(susParameters->sus2orientationMatrix[0], susVecSensor[2], + susVecBody[2], 3, 3, 1); + } + if (susValid[3]) { + susConverter.calculateSunVector(susVecSensor[3], sus3Value); + MatrixOperations::multiply(susParameters->sus3orientationMatrix[0], susVecSensor[3], + susVecBody[3], 3, 3, 1); + } + if (susValid[4]) { + susConverter.calculateSunVector(susVecSensor[4], sus4Value); + MatrixOperations::multiply(susParameters->sus4orientationMatrix[0], susVecSensor[4], + susVecBody[4], 3, 3, 1); + } + if (susValid[5]) { + susConverter.calculateSunVector(susVecSensor[5], sus5Value); + MatrixOperations::multiply(susParameters->sus5orientationMatrix[0], susVecSensor[5], + susVecBody[5], 3, 3, 1); + } + if (susValid[6]) { + susConverter.calculateSunVector(susVecSensor[6], sus6Value); + MatrixOperations::multiply(susParameters->sus6orientationMatrix[0], susVecSensor[6], + susVecBody[6], 3, 3, 1); + } + if (susValid[7]) { + susConverter.calculateSunVector(susVecSensor[7], sus7Value); + MatrixOperations::multiply(susParameters->sus7orientationMatrix[0], susVecSensor[7], + susVecBody[7], 3, 3, 1); + } + if (susValid[8]) { + susConverter.calculateSunVector(susVecSensor[8], sus8Value); + MatrixOperations::multiply(susParameters->sus8orientationMatrix[0], susVecSensor[8], + susVecBody[8], 3, 3, 1); + } + if (susValid[9]) { + susConverter.calculateSunVector(susVecSensor[9], sus9Value); + MatrixOperations::multiply(susParameters->sus9orientationMatrix[0], susVecSensor[9], + susVecBody[9], 3, 3, 1); + } + if (susValid[10]) { + susConverter.calculateSunVector(susVecSensor[10], sus10Value); + MatrixOperations::multiply(susParameters->sus10orientationMatrix[0], susVecSensor[10], + susVecBody[10], 3, 3, 1); + } + if (susValid[11]) { + susConverter.calculateSunVector(susVecSensor[11], sus11Value); + MatrixOperations::multiply(susParameters->sus11orientationMatrix[0], susVecSensor[11], + susVecBody[11], 3, 3, 1); + } + + double susMeanValue[3] = {0, 0, 0}; + for (uint8_t i = 0; i < 12; i++) { + susMeanValue[0] += susVecBody[i][0]; + susMeanValue[1] += susVecBody[i][1]; + susMeanValue[2] += susVecBody[i][2]; + } + double susVecTot[3] = {0.0, 0.0, 0.0}; + VectorOperations::normalize(susMeanValue, susVecTot, 3); + + if (VectorOperations::norm(susVecTot, 3) != 0 and susDataProcessed->susVecTot.isValid()) { + lowPassFilter(susVecTot, susDataProcessed->susVecTot.value, + susParameters->susVectorFilterWeight); + } + + /* -------- Sun Derivatiative --------------------- */ + + double susVecTotDerivative[3] = {0.0, 0.0, 0.0}; + bool susVecTotDerivativeValid = false; + if (timeDelta > 0 and VectorOperations::norm(savedSusVecTot, 3) != 0) { + VectorOperations::subtract(susVecTot, savedSusVecTot, susVecTotDerivative, 3); + VectorOperations::mulScalar(susVecTotDerivative, 1. / timeDelta, susVecTotDerivative, + 3); + susVecTotDerivativeValid = true; + } + std::memcpy(savedSusVecTot, susVecTot, sizeof(savedSusVecTot)); + if (VectorOperations::norm(susVecTotDerivative, 3) != 0 and + susDataProcessed->susVecTotDerivative.isValid()) { + lowPassFilter(susVecTotDerivative, susDataProcessed->susVecTotDerivative.value, + susParameters->susRateFilterWeight); + } + { + PoolReadGuard pg(susDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(susDataProcessed->sus0vec.value, susVecBody[0], 3 * sizeof(float)); + susDataProcessed->sus0vec.setValid(sus0valid); + std::memcpy(susDataProcessed->sus1vec.value, susVecBody[1], 3 * sizeof(float)); + susDataProcessed->sus1vec.setValid(sus1valid); + std::memcpy(susDataProcessed->sus2vec.value, susVecBody[2], 3 * sizeof(float)); + susDataProcessed->sus2vec.setValid(sus2valid); + std::memcpy(susDataProcessed->sus3vec.value, susVecBody[3], 3 * sizeof(float)); + susDataProcessed->sus3vec.setValid(sus3valid); + std::memcpy(susDataProcessed->sus4vec.value, susVecBody[4], 3 * sizeof(float)); + susDataProcessed->sus4vec.setValid(sus4valid); + std::memcpy(susDataProcessed->sus5vec.value, susVecBody[5], 3 * sizeof(float)); + susDataProcessed->sus5vec.setValid(sus5valid); + std::memcpy(susDataProcessed->sus6vec.value, susVecBody[6], 3 * sizeof(float)); + susDataProcessed->sus6vec.setValid(sus6valid); + std::memcpy(susDataProcessed->sus7vec.value, susVecBody[7], 3 * sizeof(float)); + susDataProcessed->sus7vec.setValid(sus7valid); + std::memcpy(susDataProcessed->sus8vec.value, susVecBody[8], 3 * sizeof(float)); + susDataProcessed->sus8vec.setValid(sus8valid); + std::memcpy(susDataProcessed->sus9vec.value, susVecBody[9], 3 * sizeof(float)); + susDataProcessed->sus9vec.setValid(sus9valid); + std::memcpy(susDataProcessed->sus10vec.value, susVecBody[10], 3 * sizeof(float)); + susDataProcessed->sus10vec.setValid(sus10valid); + std::memcpy(susDataProcessed->sus11vec.value, susVecBody[11], 3 * sizeof(float)); + susDataProcessed->sus11vec.setValid(sus11valid); + std::memcpy(susDataProcessed->susVecTot.value, susVecTot, 3 * sizeof(double)); + susDataProcessed->susVecTot.setValid(true); + std::memcpy(susDataProcessed->susVecTotDerivative.value, susVecTotDerivative, + 3 * sizeof(double)); + susDataProcessed->susVecTotDerivative.setValid(susVecTotDerivativeValid); + std::memcpy(susDataProcessed->sunIjkModel.value, sunIjkModel, 3 * sizeof(double)); + susDataProcessed->sunIjkModel.setValid(true); + susDataProcessed->setValidity(true, false); + } + } +} + +void SensorProcessing::processGyr( + const double gyr0axXvalue, bool gyr0axXvalid, const double gyr0axYvalue, bool gyr0axYvalid, + const double gyr0axZvalue, bool gyr0axZvalid, const double gyr1axXvalue, bool gyr1axXvalid, + const double gyr1axYvalue, bool gyr1axYvalid, const double gyr1axZvalue, bool gyr1axZvalid, + const double gyr2axXvalue, bool gyr2axXvalid, const double gyr2axYvalue, bool gyr2axYvalid, + const double gyr2axZvalue, bool gyr2axZvalid, const double gyr3axXvalue, bool gyr3axXvalid, + const double gyr3axYvalue, bool gyr3axYvalid, const double gyr3axZvalue, bool gyr3axZvalid, + const AcsParameters::GyrHandlingParameters *gyrParameters, + acsctrl::GyrDataProcessed *gyrDataProcessed) { + bool gyr0valid = (gyr0axXvalid && gyr0axYvalid && gyr0axZvalid); + bool gyr1valid = (gyr1axXvalid && gyr1axYvalid && gyr1axZvalid); + bool gyr2valid = (gyr2axXvalid && gyr2axYvalid && gyr2axZvalid); + bool gyr3valid = (gyr3axXvalid && gyr3axYvalid && gyr3axZvalid); + if (!gyr0valid && !gyr1valid && !gyr2valid && !gyr3valid) { + { + PoolReadGuard pg(gyrDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(gyrDataProcessed->gyr0vec.value, ZERO_VEC_D, 3 * sizeof(double)); + std::memcpy(gyrDataProcessed->gyr1vec.value, ZERO_VEC_D, 3 * sizeof(double)); + std::memcpy(gyrDataProcessed->gyr2vec.value, ZERO_VEC_D, 3 * sizeof(double)); + std::memcpy(gyrDataProcessed->gyr3vec.value, ZERO_VEC_D, 3 * sizeof(double)); + std::memcpy(gyrDataProcessed->gyrVecTot.value, ZERO_VEC_D, 3 * sizeof(double)); + gyrDataProcessed->setValidity(false, true); + } + } + return; + } + // Transforming Values to the Body Frame (actually it is the geometry frame atm) + double gyr0ValueBody[3] = {0, 0, 0}, gyr1ValueBody[3] = {0, 0, 0}, gyr2ValueBody[3] = {0, 0, 0}, + gyr3ValueBody[3] = {0, 0, 0}; + float sensorFusionNumerator[3] = {0, 0, 0}, sensorFusionDenominator[3] = {0, 0, 0}; + + if (gyr0valid) { + double gyr0Value[3] = {gyr0axXvalue, gyr0axYvalue, gyr0axZvalue}; + VectorOperations::subtract(gyr0Value, gyrParameters->gyr0bias, gyr0Value, 3); + MatrixOperations::multiply(gyrParameters->gyr0orientationMatrix[0], gyr0Value, + gyr0ValueBody, 3, 3, 1); + VectorOperations::mulScalar(gyr0ValueBody, M_PI / 180, gyr0ValueBody, 3); + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += gyr0ValueBody[i] / gyrParameters->gyr02variance[i]; + sensorFusionDenominator[i] += 1 / gyrParameters->gyr02variance[i]; + } + } + if (gyr1valid) { + double gyr1Value[3] = {gyr1axXvalue, gyr1axYvalue, gyr1axZvalue}; + VectorOperations::subtract(gyr1Value, gyrParameters->gyr1bias, gyr1Value, 3); + MatrixOperations::multiply(gyrParameters->gyr1orientationMatrix[0], gyr1Value, + gyr1ValueBody, 3, 3, 1); + VectorOperations::mulScalar(gyr1ValueBody, M_PI / 180, gyr1ValueBody, 3); + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += gyr1ValueBody[i] / gyrParameters->gyr13variance[i]; + sensorFusionDenominator[i] += 1 / gyrParameters->gyr13variance[i]; + } + } + if (gyr2valid) { + double gyr2Value[3] = {gyr2axXvalue, gyr2axYvalue, gyr2axZvalue}; + VectorOperations::subtract(gyr2Value, gyrParameters->gyr2bias, gyr2Value, 3); + MatrixOperations::multiply(gyrParameters->gyr2orientationMatrix[0], gyr2Value, + gyr2ValueBody, 3, 3, 1); + VectorOperations::mulScalar(gyr2ValueBody, M_PI / 180, gyr2ValueBody, 3); + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += gyr2ValueBody[i] / gyrParameters->gyr02variance[i]; + sensorFusionDenominator[i] += 1 / gyrParameters->gyr02variance[i]; + } + } + if (gyr3valid) { + double gyr3Value[3] = {gyr3axXvalue, gyr3axYvalue, gyr3axZvalue}; + VectorOperations::subtract(gyr3Value, gyrParameters->gyr3bias, gyr3Value, 3); + MatrixOperations::multiply(gyrParameters->gyr3orientationMatrix[0], gyr3Value, + gyr3ValueBody, 3, 3, 1); + VectorOperations::mulScalar(gyr3ValueBody, M_PI / 180, gyr3ValueBody, 3); + for (uint8_t i = 0; i < 3; i++) { + sensorFusionNumerator[i] += gyr3ValueBody[i] / gyrParameters->gyr13variance[i]; + sensorFusionDenominator[i] += 1 / gyrParameters->gyr13variance[i]; + } + } + + /* -------- SatRateEst: Middle Value ------- */ + // take ADIS measurements, if both avail + // if just one ADIS measurement avail, perform sensor fusion + double gyrVecTot[3] = {0.0, 0.0, 0.0}; + if ((gyr0valid && gyr2valid) && gyrParameters->preferAdis == true) { + double gyr02ValuesSum[3]; + VectorOperations::add(gyr0ValueBody, gyr2ValueBody, gyr02ValuesSum, 3); + VectorOperations::mulScalar(gyr02ValuesSum, .5, gyrVecTot, 3); + } else { + for (uint8_t i = 0; i < 3; i++) { + gyrVecTot[i] = sensorFusionNumerator[i] / sensorFusionDenominator[i]; + } + } + + if (VectorOperations::norm(gyrVecTot, 3) != 0 and gyrDataProcessed->gyrVecTot.isValid()) { + lowPassFilter(gyrVecTot, gyrDataProcessed->gyrVecTot.value, gyrParameters->gyrFilterWeight); + } + + { + PoolReadGuard pg(gyrDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + std::memcpy(gyrDataProcessed->gyr0vec.value, gyr0ValueBody, 3 * sizeof(double)); + gyrDataProcessed->gyr0vec.setValid(gyr0valid); + std::memcpy(gyrDataProcessed->gyr1vec.value, gyr1ValueBody, 3 * sizeof(double)); + gyrDataProcessed->gyr1vec.setValid(gyr1valid); + std::memcpy(gyrDataProcessed->gyr2vec.value, gyr2ValueBody, 3 * sizeof(double)); + gyrDataProcessed->gyr2vec.setValid(gyr2valid); + std::memcpy(gyrDataProcessed->gyr3vec.value, gyr3ValueBody, 3 * sizeof(double)); + gyrDataProcessed->gyr3vec.setValid(gyr3valid); + std::memcpy(gyrDataProcessed->gyrVecTot.value, gyrVecTot, 3 * sizeof(double)); + gyrDataProcessed->gyrVecTot.setValid(true); + gyrDataProcessed->setValidity(true, false); + } + } +} + +void SensorProcessing::processGps(const double gpsLatitude, const double gpsLongitude, + const double gpsAltitude, const double timeDelta, + const bool validGps, + const AcsParameters::GpsParameters *gpsParameters, + acsctrl::GpsDataProcessed *gpsDataProcessed) { + // init variables + double gdLongitude = 0, gdLatitude = 0, gcLatitude = 0, altitude = 0, posSatE[3] = {0, 0, 0}, + gpsVelocityE[3] = {0, 0, 0}; + uint8_t gpsSource = acs::gps::Source::NONE; + // We do not trust the GPS and therefore it shall die here if SPG4 is running + if (gpsDataProcessed->source.value == acs::gps::Source::SPG4 and gpsParameters->useSpg4) { + CoordinateTransformations::latLongAltFromCartesian(gpsDataProcessed->gpsPosition.value, + gdLatitude, gdLongitude, altitude); + double factor = 1 - pow(ECCENTRICITY_WGS84, 2); + gcLatitude = atan(factor * tan(gdLatitude)); + { + PoolReadGuard pg(gpsDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + gpsDataProcessed->gdLongitude.value = gdLongitude; + gpsDataProcessed->gcLatitude.value = gcLatitude; + gpsDataProcessed->altitude.value = altitude; + gpsDataProcessed->setValidity(true, true); + } + } + return; + } else if (validGps) { + // Transforming from Degree to Radians and calculation geocentric latitude from geodetic + gdLongitude = gpsLongitude * M_PI / 180.; + double latitudeRad = gpsLatitude * M_PI / 180.; + double factor = 1 - pow(ECCENTRICITY_WGS84, 2); + gcLatitude = atan(factor * tan(latitudeRad)); + + // Altitude FDIR + if (gpsAltitude > gpsParameters->maximumFdirAltitude || + gpsAltitude < gpsParameters->minimumFdirAltitude) { + altitude = gpsParameters->fdirAltitude; + } else { + altitude = gpsAltitude; + } + + // Calculation of the satellite velocity in earth fixed frame + double deltaDistance[3] = {0, 0, 0}; + CoordinateTransformations::cartesianFromLatLongAlt(latitudeRad, gdLongitude, altitude, posSatE); + if (validSavedPosSatE and timeDelta < (gpsParameters->timeDiffVelocityMax) and timeDelta > 0) { + VectorOperations::subtract(posSatE, savedPosSatE, deltaDistance, 3); + VectorOperations::mulScalar(deltaDistance, 1. / timeDelta, gpsVelocityE, 3); + } + savedPosSatE[0] = posSatE[0]; + savedPosSatE[1] = posSatE[1]; + savedPosSatE[2] = posSatE[2]; + + validSavedPosSatE = true; + + gpsSource = acs::gps::Source::GPS; + } + { + PoolReadGuard pg(gpsDataProcessed); + if (pg.getReadResult() == returnvalue::OK) { + gpsDataProcessed->gdLongitude.value = gdLongitude; + gpsDataProcessed->gcLatitude.value = gcLatitude; + gpsDataProcessed->altitude.value = altitude; + std::memcpy(gpsDataProcessed->gpsPosition.value, posSatE, 3 * sizeof(double)); + std::memcpy(gpsDataProcessed->gpsVelocity.value, gpsVelocityE, 3 * sizeof(double)); + gpsDataProcessed->setValidity(validGps, true); + gpsDataProcessed->source.value = gpsSource; + gpsDataProcessed->source.setValid(true); + } + } +} + +void SensorProcessing::process(timeval timeAbsolute, double timeDelta, + ACS::SensorValues *sensorValues, + acsctrl::MgmDataProcessed *mgmDataProcessed, + acsctrl::SusDataProcessed *susDataProcessed, + acsctrl::GyrDataProcessed *gyrDataProcessed, + acsctrl::GpsDataProcessed *gpsDataProcessed, + const AcsParameters *acsParameters) { + sensorValues->update(); + + processGps( + sensorValues->gpsSet.latitude.value, sensorValues->gpsSet.longitude.value, + sensorValues->gpsSet.altitude.value, sensorValues->gpsSet.unixSeconds.value, + (sensorValues->gpsSet.latitude.isValid() && sensorValues->gpsSet.longitude.isValid() && + sensorValues->gpsSet.altitude.isValid() && sensorValues->gpsSet.unixSeconds.isValid()), + &acsParameters->gpsParameters, gpsDataProcessed); + + processMgm(sensorValues->mgm0Lis3Set.fieldStrengths.value, + sensorValues->mgm0Lis3Set.fieldStrengths.isValid(), + sensorValues->mgm1Rm3100Set.fieldStrengths.value, + sensorValues->mgm1Rm3100Set.fieldStrengths.isValid(), + sensorValues->mgm2Lis3Set.fieldStrengths.value, + sensorValues->mgm2Lis3Set.fieldStrengths.isValid(), + sensorValues->mgm3Rm3100Set.fieldStrengths.value, + sensorValues->mgm3Rm3100Set.fieldStrengths.isValid(), + sensorValues->imtqMgmSet.mtmRawNt.value, sensorValues->imtqMgmSet.mtmRawNt.isValid(), + timeAbsolute, timeDelta, &acsParameters->mgmHandlingParameters, gpsDataProcessed, + mgmDataProcessed); + + processSus(sensorValues->susSets[0].channels.value, sensorValues->susSets[0].channels.isValid(), + sensorValues->susSets[1].channels.value, sensorValues->susSets[1].channels.isValid(), + sensorValues->susSets[2].channels.value, sensorValues->susSets[2].channels.isValid(), + sensorValues->susSets[3].channels.value, sensorValues->susSets[3].channels.isValid(), + sensorValues->susSets[4].channels.value, sensorValues->susSets[4].channels.isValid(), + sensorValues->susSets[5].channels.value, sensorValues->susSets[5].channels.isValid(), + sensorValues->susSets[6].channels.value, sensorValues->susSets[6].channels.isValid(), + sensorValues->susSets[7].channels.value, sensorValues->susSets[7].channels.isValid(), + sensorValues->susSets[8].channels.value, sensorValues->susSets[8].channels.isValid(), + sensorValues->susSets[9].channels.value, sensorValues->susSets[9].channels.isValid(), + sensorValues->susSets[10].channels.value, sensorValues->susSets[10].channels.isValid(), + sensorValues->susSets[11].channels.value, sensorValues->susSets[11].channels.isValid(), + timeAbsolute, timeDelta, &acsParameters->susHandlingParameters, + &acsParameters->sunModelParameters, susDataProcessed); + + processGyr( + sensorValues->gyr0AdisSet.angVelocX.value, sensorValues->gyr0AdisSet.angVelocX.isValid(), + sensorValues->gyr0AdisSet.angVelocY.value, sensorValues->gyr0AdisSet.angVelocY.isValid(), + sensorValues->gyr0AdisSet.angVelocZ.value, sensorValues->gyr0AdisSet.angVelocZ.isValid(), + sensorValues->gyr1L3gSet.angVelocX.value, sensorValues->gyr1L3gSet.angVelocX.isValid(), + sensorValues->gyr1L3gSet.angVelocY.value, sensorValues->gyr1L3gSet.angVelocY.isValid(), + sensorValues->gyr1L3gSet.angVelocZ.value, sensorValues->gyr1L3gSet.angVelocZ.isValid(), + sensorValues->gyr2AdisSet.angVelocX.value, sensorValues->gyr2AdisSet.angVelocX.isValid(), + sensorValues->gyr2AdisSet.angVelocY.value, sensorValues->gyr2AdisSet.angVelocY.isValid(), + sensorValues->gyr2AdisSet.angVelocZ.value, sensorValues->gyr2AdisSet.angVelocZ.isValid(), + sensorValues->gyr3L3gSet.angVelocX.value, sensorValues->gyr3L3gSet.angVelocX.isValid(), + sensorValues->gyr3L3gSet.angVelocY.value, sensorValues->gyr3L3gSet.angVelocY.isValid(), + sensorValues->gyr3L3gSet.angVelocZ.value, sensorValues->gyr3L3gSet.angVelocZ.isValid(), + &acsParameters->gyrHandlingParameters, gyrDataProcessed); +} + +void SensorProcessing::lowPassFilter(double *newValue, double *oldValue, const double weight) { + double leftSide[3] = {0, 0, 0}, rightSide[3] = {0, 0, 0}; + VectorOperations::mulScalar(newValue, (1 - weight), leftSide, 3); + VectorOperations::mulScalar(oldValue, weight, rightSide, 3); + VectorOperations::add(leftSide, rightSide, newValue, 3); +} diff --git a/mission/controller/acs/SensorProcessing.h b/mission/controller/acs/SensorProcessing.h new file mode 100644 index 0000000..baa4311 --- /dev/null +++ b/mission/controller/acs/SensorProcessing.h @@ -0,0 +1,87 @@ +#ifndef SENSORPROCESSING_H_ +#define SENSORPROCESSING_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class SensorProcessing { + public: + SensorProcessing(); + virtual ~SensorProcessing(); + + void process(timeval timeAbsolute, double timeDelta, ACS::SensorValues *sensorValues, + acsctrl::MgmDataProcessed *mgmDataProcessed, + acsctrl::SusDataProcessed *susDataProcessed, + acsctrl::GyrDataProcessed *gyrDataProcessed, + acsctrl::GpsDataProcessed *gpsDataProcessed, const AcsParameters *acsParameters); + + private: + static constexpr float ZERO_VEC_F[3] = {0, 0, 0}; + static constexpr double ZERO_VEC_D[3] = {0, 0, 0}; + static constexpr double ECCENTRICITY_WGS84 = 0.0818195; + + protected: + void processMgm(const float *mgm0Value, bool mgm0valid, const float *mgm1Value, bool mgm1valid, + const float *mgm2Value, bool mgm2valid, const float *mgm3Value, bool mgm3valid, + const float *mgm4Value, bool mgm4valid, timeval timeAbsolute, double timeDelta, + const AcsParameters::MgmHandlingParameters *mgmParameters, + acsctrl::GpsDataProcessed *gpsDataProcessed, + acsctrl::MgmDataProcessed *mgmDataProcessed); + + void processSus(const uint16_t *sus0Value, bool sus0valid, const uint16_t *sus1Value, + bool sus1valid, const uint16_t *sus2Value, bool sus2valid, + const uint16_t *sus3Value, bool sus3valid, const uint16_t *sus4Value, + bool sus4valid, const uint16_t *sus5Value, bool sus5valid, + const uint16_t *sus6Value, bool sus6valid, const uint16_t *sus7Value, + bool sus7valid, const uint16_t *sus8Value, bool sus8valid, + const uint16_t *sus9Value, bool sus9valid, const uint16_t *sus10Value, + bool sus10valid, const uint16_t *sus11Value, bool sus11valid, + timeval timeAbsolute, double timeDelta, + const AcsParameters::SusHandlingParameters *susParameters, + const AcsParameters::SunModelParameters *sunModelParameters, + acsctrl::SusDataProcessed *susDataProcessed); + + void processGyr(const double gyr0axXvalue, bool gyr0axXvalid, const double gyr0axYvalue, + bool gyr0axYvalid, const double gyr0axZvalue, bool gyr0axZvalid, + const double gyr1axXvalue, bool gyr1axXvalid, const double gyr1axYvalue, + bool gyr1axYvalid, const double gyr1axZvalue, bool gyr1axZvalid, + const double gyr2axXvalue, bool gyr2axXvalid, const double gyr2axYvalue, + bool gyr2axYvalid, const double gyr2axZvalue, bool gyr2axZvalid, + const double gyr3axXvalue, bool gyr3axXvalid, const double gyr3axYvalue, + bool gyr3axYvalid, const double gyr3axZvalue, bool gyr3axZvalid, + const AcsParameters::GyrHandlingParameters *gyrParameters, + acsctrl::GyrDataProcessed *gyrDataProcessed); + + void processGps(const double gpsLatitude, const double gpsLongitude, const double gpsAltitude, + const double gpsUnixSeconds, const bool validGps, + const AcsParameters::GpsParameters *gpsParameters, + acsctrl::GpsDataProcessed *gpsDataProcessed); + + void lowPassFilter(double *newValue, double *oldValue, const double weight); + + double savedMgmVecTot[3] = {0.0, 0.0, 0.0}; + double savedSusVecTot[3] = {0.0, 0.0, 0.0}; + + double savedPosSatE[3] = {0.0, 0.0, 0.0}; + bool validSavedPosSatE = false; + + SusConverter susConverter; +}; + +#endif /*SENSORPROCESSING_H_*/ diff --git a/mission/controller/acs/SensorValues.cpp b/mission/controller/acs/SensorValues.cpp new file mode 100644 index 0000000..85815ae --- /dev/null +++ b/mission/controller/acs/SensorValues.cpp @@ -0,0 +1,140 @@ +#include "SensorValues.h" + +#include +#include +#include +#include + +#include + +namespace ACS { + +SensorValues::SensorValues() {} + +SensorValues::~SensorValues() {} + +ReturnValue_t SensorValues::updateMgm() { + std::vector results; + + { + PoolReadGuard pgMgm(&mgm0Lis3Set); + results.push_back(pgMgm.getReadResult()); + } + { + PoolReadGuard pgMgm(&mgm1Rm3100Set); + results.push_back(pgMgm.getReadResult()); + } + { + PoolReadGuard pgMgm(&mgm2Lis3Set); + results.push_back(pgMgm.getReadResult()); + } + { + PoolReadGuard pgMgm(&mgm3Rm3100Set); + results.push_back(pgMgm.getReadResult()); + } + { + PoolReadGuard pgMgm(&imtqMgmSet); + results.push_back(pgMgm.getReadResult()); + } + for (const auto& result : results) { + if (result != returnvalue::OK) { + return result; + } + } + return returnvalue::OK; +} + +ReturnValue_t SensorValues::updateSus() { + std::vector results; + for (auto& susSet : susSets) { + { + PoolReadGuard pgSus(&susSet); + results.push_back(pgSus.getReadResult()); + } + } + for (const auto& result : results) { + if (result != returnvalue::OK) { + return result; + } + } + return returnvalue::OK; +} + +ReturnValue_t SensorValues::updateGyr() { + std::vector results; + { + PoolReadGuard pgGyr(&gyr0AdisSet); + results.push_back(pgGyr.getReadResult()); + } + { + PoolReadGuard pgGyr(&gyr1L3gSet); + results.push_back(pgGyr.getReadResult()); + } + { + PoolReadGuard pgGyr(&gyr2AdisSet); + results.push_back(pgGyr.getReadResult()); + } + { + PoolReadGuard pgGyr(&gyr3L3gSet); + results.push_back(pgGyr.getReadResult()); + } + for (const auto& result : results) { + if (result != returnvalue::OK) { + return result; + } + } + return returnvalue::OK; +} + +ReturnValue_t SensorValues::updateStr() { + PoolReadGuard pgStr(&strSet); + return pgStr.getReadResult(); +} + +ReturnValue_t SensorValues::updateGps() { + PoolReadGuard pgGps(&gpsSet); + return pgGps.getReadResult(); +} + +ReturnValue_t SensorValues::updateRw() { + std::vector results; + { + PoolReadGuard pgRw(&rw1Set); + results.push_back(pgRw.getReadResult()); + } + { + PoolReadGuard pgRw(&rw2Set); + results.push_back(pgRw.getReadResult()); + } + { + PoolReadGuard pgRw(&rw3Set); + results.push_back(pgRw.getReadResult()); + } + { + PoolReadGuard pgRw(&rw4Set); + results.push_back(pgRw.getReadResult()); + } + for (const auto& result : results) { + if (result != returnvalue::OK) { + return result; + } + } + return returnvalue::OK; +} + +ReturnValue_t SensorValues::update() { + ReturnValue_t mgmUpdate = updateMgm(); + ReturnValue_t susUpdate = updateSus(); + ReturnValue_t gyrUpdate = updateGyr(); + ReturnValue_t strUpdate = updateStr(); + ReturnValue_t gpsUpdate = updateGps(); + ReturnValue_t rwUpdate = updateRw(); + + if ((mgmUpdate && susUpdate && gyrUpdate && strUpdate && gpsUpdate && rwUpdate) == + returnvalue::FAILED) { + return returnvalue::FAILED; + }; + return returnvalue::OK; +} + +} // namespace ACS diff --git a/mission/controller/acs/SensorValues.h b/mission/controller/acs/SensorValues.h new file mode 100644 index 0000000..6976166 --- /dev/null +++ b/mission/controller/acs/SensorValues.h @@ -0,0 +1,71 @@ +#ifndef SENSORVALUES_H_ +#define SENSORVALUES_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ACS { + +class SensorValues { + public: + SensorValues(); + virtual ~SensorValues(); + + ReturnValue_t update(); + ReturnValue_t updateMgm(); + ReturnValue_t updateSus(); + ReturnValue_t updateGyr(); + ReturnValue_t updateGps(); + ReturnValue_t updateStr(); + ReturnValue_t updateRw(); + + mgmLis3::MgmPrimaryDataset mgm0Lis3Set = mgmLis3::MgmPrimaryDataset(objects::MGM_0_LIS3_HANDLER); + mgmRm3100::Rm3100PrimaryDataset mgm1Rm3100Set = + mgmRm3100::Rm3100PrimaryDataset(objects::MGM_1_RM3100_HANDLER); + mgmLis3::MgmPrimaryDataset mgm2Lis3Set = mgmLis3::MgmPrimaryDataset(objects::MGM_2_LIS3_HANDLER); + mgmRm3100::Rm3100PrimaryDataset mgm3Rm3100Set = + mgmRm3100::Rm3100PrimaryDataset(objects::MGM_3_RM3100_HANDLER); + imtq::RawMtmMeasurementNoTorque imtqMgmSet = + imtq::RawMtmMeasurementNoTorque(objects::IMTQ_HANDLER); + + std::array susSets{ + susMax1227::SusDataset(objects::SUS_0_N_LOC_XFYFZM_PT_XF), + susMax1227::SusDataset(objects::SUS_1_N_LOC_XBYFZM_PT_XB), + susMax1227::SusDataset(objects::SUS_2_N_LOC_XFYBZB_PT_YB), + susMax1227::SusDataset(objects::SUS_3_N_LOC_XFYBZF_PT_YF), + susMax1227::SusDataset(objects::SUS_4_N_LOC_XMYFZF_PT_ZF), + susMax1227::SusDataset(objects::SUS_5_N_LOC_XFYMZB_PT_ZB), + susMax1227::SusDataset(objects::SUS_6_R_LOC_XFYBZM_PT_XF), + susMax1227::SusDataset(objects::SUS_7_R_LOC_XBYBZM_PT_XB), + susMax1227::SusDataset(objects::SUS_8_R_LOC_XBYBZB_PT_YB), + susMax1227::SusDataset(objects::SUS_9_R_LOC_XBYBZB_PT_YF), + susMax1227::SusDataset(objects::SUS_10_N_LOC_XMYBZF_PT_ZF), + susMax1227::SusDataset(objects::SUS_11_R_LOC_XBYMZB_PT_ZB), + }; + + AdisGyroPrimaryDataset gyr0AdisSet = AdisGyroPrimaryDataset(objects::GYRO_0_ADIS_HANDLER); + GyroPrimaryDataset gyr1L3gSet = GyroPrimaryDataset(objects::GYRO_1_L3G_HANDLER); + AdisGyroPrimaryDataset gyr2AdisSet = AdisGyroPrimaryDataset(objects::GYRO_2_ADIS_HANDLER); + GyroPrimaryDataset gyr3L3gSet = GyroPrimaryDataset(objects::GYRO_3_L3G_HANDLER); + + startracker::SolutionSet strSet = startracker::SolutionSet(objects::STAR_TRACKER); + + GpsPrimaryDataset gpsSet = GpsPrimaryDataset(objects::GPS_CONTROLLER); + + // bool mgt0valid; + + rws::StatusSet rw1Set = rws::StatusSet(objects::RW1); + rws::StatusSet rw2Set = rws::StatusSet(objects::RW2); + rws::StatusSet rw3Set = rws::StatusSet(objects::RW3); + rws::StatusSet rw4Set = rws::StatusSet(objects::RW4); +}; +} /* namespace ACS */ + +#endif /*ENSORVALUES_H_*/ diff --git a/mission/controller/acs/SusConverter.cpp b/mission/controller/acs/SusConverter.cpp new file mode 100644 index 0000000..31cc037 --- /dev/null +++ b/mission/controller/acs/SusConverter.cpp @@ -0,0 +1,64 @@ +#include "SusConverter.h" + +#include + +uint64_t SusConverter::checkSunSensorData(const uint16_t susChannel[6]) { + if (susChannel[0] <= SUS_CHANNEL_VALUE_LOW || susChannel[0] > SUS_CHANNEL_VALUE_HIGH || + susChannel[0] > susChannel[GNDREF]) { + return 0; + } + if (susChannel[1] <= SUS_CHANNEL_VALUE_LOW || susChannel[1] > SUS_CHANNEL_VALUE_HIGH || + susChannel[1] > susChannel[GNDREF]) { + return 0; + }; + if (susChannel[2] <= SUS_CHANNEL_VALUE_LOW || susChannel[2] > SUS_CHANNEL_VALUE_HIGH || + susChannel[2] > susChannel[GNDREF]) { + return 0; + }; + if (susChannel[3] <= SUS_CHANNEL_VALUE_LOW || susChannel[3] > SUS_CHANNEL_VALUE_HIGH || + susChannel[3] > susChannel[GNDREF]) { + return 0; + }; + + uint64_t susChannelValueSum = + 4 * susChannel[GNDREF] - (susChannel[0] + susChannel[1] + susChannel[2] + susChannel[3]); + if (susChannelValueSum < SUS_ALBEDO_CHECK) { + return 0; + }; + return susChannelValueSum; +} + +bool SusConverter::checkValidity(bool* susValid, const uint64_t brightness[12], + const float threshold) { + uint8_t maxBrightness = 0; + VectorOperations::maxValue(brightness, 12, &maxBrightness); + if (brightness[maxBrightness] == 0) { + return true; + } + for (uint8_t idx = 0; idx < 12; idx++) { + if ((idx != maxBrightness) and (brightness[idx] < threshold * brightness[maxBrightness])) { + susValid[idx] = false; + continue; + } + susValid[idx] = true; + } + return false; +} + +void SusConverter::calculateSunVector(float* sunVectorSensorFrame, const uint16_t susChannel[6]) { + // Substract measurement values from GNDREF zero current threshold + float ch0 = susChannel[GNDREF] - susChannel[0]; + float ch1 = susChannel[GNDREF] - susChannel[1]; + float ch2 = susChannel[GNDREF] - susChannel[2]; + float ch3 = susChannel[GNDREF] - susChannel[3]; + + // Calculation of x and y + float xout = ((D - S) / 2) * (ch2 - ch3 - ch0 + ch1) / (ch0 + ch1 + ch2 + ch3); //[mm] + float yout = ((D - S) / 2) * (ch2 + ch3 - ch0 - ch1) / (ch0 + ch1 + ch2 + ch3); //[mm] + + // Calculation of the angles + sunVectorSensorFrame[0] = -xout; + sunVectorSensorFrame[1] = -yout; + sunVectorSensorFrame[2] = H; + VectorOperations::normalize(sunVectorSensorFrame, sunVectorSensorFrame, 3); +} diff --git a/mission/controller/acs/SusConverter.h b/mission/controller/acs/SusConverter.h new file mode 100644 index 0000000..8a6c279 --- /dev/null +++ b/mission/controller/acs/SusConverter.h @@ -0,0 +1,31 @@ +#include + +#include "AcsParameters.h" + +class SusConverter { + public: + SusConverter() {} + + uint64_t checkSunSensorData(const uint16_t susChannel[6]); + bool checkValidity(bool* susValid, const uint64_t brightness[12], const float threshold); + void calculateSunVector(float* sunVectorSensorFrame, const uint16_t susChannel[6]); + + private: + static const uint8_t GNDREF = 4; + // =2^12[Bit]high borderline for the channel values of one sun sensor for validity Check + static constexpr uint16_t SUS_CHANNEL_VALUE_HIGH = 4096; + // [Bit]low borderline for the channel values of one sun sensor for validity Check + static constexpr uint8_t SUS_CHANNEL_VALUE_LOW = 0; + // 4096[Bit]high borderline for check if the sun sensor is illuminated by the sun or by the + // reflection of sunlight from the moon/earth + static constexpr uint16_t SUS_ALBEDO_CHECK = 1000; + // [Bit]low borderline for check if the sun sensor is illuminated by the sun or by the reflection + // of sunlight from the moon/earth + static constexpr uint8_t SUS_CHANNEL_SUM_LOW = 0; + + static constexpr float S = 0.03; // S=[mm] gap between diodes + static constexpr float D = 5; // D=[mm] edge length of the quadratic aperture + static constexpr float H = 2.5; // H=[mm] distance between diodes and aperture + + AcsParameters acsParameters; +}; diff --git a/mission/controller/acs/control/CMakeLists.txt b/mission/controller/acs/control/CMakeLists.txt new file mode 100644 index 0000000..2d40ecc --- /dev/null +++ b/mission/controller/acs/control/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(${LIB_EIVE_MISSION} PRIVATE Detumble.cpp PtgCtrl.cpp + SafeCtrl.cpp) diff --git a/mission/controller/acs/control/Detumble.cpp b/mission/controller/acs/control/Detumble.cpp new file mode 100644 index 0000000..408c30e --- /dev/null +++ b/mission/controller/acs/control/Detumble.cpp @@ -0,0 +1,47 @@ +#include "Detumble.h" + +#include +#include + +Detumble::Detumble() {} + +Detumble::~Detumble() {} + +acs::ControlModeStrategy Detumble::detumbleStrategy(const bool magFieldValid, + const bool satRotRateValid, + const bool magFieldRateValid, + const bool useFullDetumbleLaw) { + if (not magFieldValid) { + return acs::ControlModeStrategy::CTRL_NO_MAG_FIELD_FOR_CONTROL; + } else if (satRotRateValid and useFullDetumbleLaw) { + return acs::ControlModeStrategy::SAFECTRL_DETUMBLE_FULL; + } else if (magFieldRateValid) { + return acs::ControlModeStrategy::SAFECTRL_DETUMBLE_DETERIORATED; + } else { + return acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL; + } +} + +void Detumble::bDotLawFull(const double *satRotRateB, const double *magFieldB, double *magMomB, + double gain) { + // convert uT to T + double magFieldBT[3]; + VectorOperations::mulScalar(magFieldB, 1e-6, magFieldBT, 3); + // control law + double factor = gain / pow(VectorOperations::norm(magFieldBT, 3), 2); + double magFieldNormed[3] = {0, 0, 0}, crossProduct[3] = {0, 0, 0}; + VectorOperations::normalize(magFieldBT, magFieldNormed, 3); + VectorOperations::cross(satRotRateB, magFieldNormed, crossProduct); + VectorOperations::mulScalar(crossProduct, factor, magMomB, 3); +} + +void Detumble::bDotLaw(const double *magRateB, const double *magFieldB, double *magMomB, + double gain) { + // convert uT to T + double magFieldBT[3], magRateBT[3]; + VectorOperations::mulScalar(magFieldB, 1e-6, magFieldBT, 3); + VectorOperations::mulScalar(magRateB, 1e-6, magRateBT, 3); + // control law + double factor = -gain / pow(VectorOperations::norm(magFieldBT, 3), 2); + VectorOperations::mulScalar(magRateBT, factor, magMomB, 3); +} diff --git a/mission/controller/acs/control/Detumble.h b/mission/controller/acs/control/Detumble.h new file mode 100644 index 0000000..851e454 --- /dev/null +++ b/mission/controller/acs/control/Detumble.h @@ -0,0 +1,26 @@ +#ifndef ACS_CONTROL_DETUMBLE_H_ +#define ACS_CONTROL_DETUMBLE_H_ + +#include +#include +#include +#include + +class Detumble { + public: + Detumble(); + virtual ~Detumble(); + + acs::ControlModeStrategy detumbleStrategy(const bool magFieldValid, const bool satRotRateValid, + const bool magFieldRateValid, + const bool useFullDetumbleLaw); + + void bDotLawFull(const double *satRotRateB, const double *magFieldB, double *magMomB, + double gain); + + void bDotLaw(const double *magRateB, const double *magFieldB, double *magMomB, double gain); + + private: +}; + +#endif /*ACS_CONTROL_DETUMBLE_H_*/ diff --git a/mission/controller/acs/control/PtgCtrl.cpp b/mission/controller/acs/control/PtgCtrl.cpp new file mode 100644 index 0000000..a334b3a --- /dev/null +++ b/mission/controller/acs/control/PtgCtrl.cpp @@ -0,0 +1,231 @@ +#include "PtgCtrl.h" + +PtgCtrl::PtgCtrl(AcsParameters *acsParameters_) { acsParameters = acsParameters_; } + +PtgCtrl::~PtgCtrl() {} + +acs::ControlModeStrategy PtgCtrl::pointingCtrlStrategy( + const bool magFieldValid, const bool mekfValid, const bool strValid, const bool questValid, + const bool fusedRateValid, const uint8_t rotRateSource, const uint8_t mekfEnabled) { + if (not magFieldValid) { + return acs::ControlModeStrategy::CTRL_NO_MAG_FIELD_FOR_CONTROL; + } else if (strValid and fusedRateValid and rotRateSource > acs::rotrate::Source::SUSMGM) { + return acs::ControlModeStrategy::PTGCTRL_STR; + } else if (mekfEnabled and mekfValid) { + return acs::ControlModeStrategy::PTGCTRL_MEKF; + } else if (questValid and fusedRateValid and rotRateSource > acs::rotrate::Source::SUSMGM) { + return acs::ControlModeStrategy::PTGCTRL_QUEST; + } + return acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL; +} + +void PtgCtrl::ptgLaw(AcsParameters::PointingLawParameters *pointingLawParameters, + const double *errorQuat, const double *deltaRate, const double *rwPseudoInv, + double *torqueRws) { + //------------------------------------------------------------------------------------------------ + // Compute gain matrix K and P matrix + //------------------------------------------------------------------------------------------------ + double om = pointingLawParameters->om; + double zeta = pointingLawParameters->zeta; + double qErrorMin = pointingLawParameters->qiMin; + double omMax = pointingLawParameters->omMax; + + double qError[3] = {errorQuat[0], errorQuat[1], errorQuat[2]}; + + double cInt = 2 * om * zeta; + double kInt = 2 * om * om; + + double qErrorLaw[3] = {0, 0, 0}; + + for (int i = 0; i < 3; i++) { + if (std::abs(qError[i]) < qErrorMin) { + qErrorLaw[i] = qErrorMin; + } else { + qErrorLaw[i] = std::abs(qError[i]); + } + } + + double qErrorLawNorm = VectorOperations::norm(qErrorLaw, 3); + + double gain1 = cInt * omMax / qErrorLawNorm; + double gainVector[3] = {0, 0, 0}; + VectorOperations::mulScalar(qErrorLaw, gain1, gainVector, 3); + + double gainMatrixDiagonal[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + double gainMatrix[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + gainMatrixDiagonal[0][0] = gainVector[0]; + gainMatrixDiagonal[1][1] = gainVector[1]; + gainMatrixDiagonal[2][2] = gainVector[2]; + MatrixOperations::multiply(*gainMatrixDiagonal, + *(acsParameters->inertiaEIVE.inertiaMatrixDeployed), + *gainMatrix, 3, 3, 3); + + // Inverse of gainMatrix + double gainMatrixInverse[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + gainMatrixInverse[0][0] = 1. / gainMatrix[0][0]; + gainMatrixInverse[1][1] = 1. / gainMatrix[1][1]; + gainMatrixInverse[2][2] = 1. / gainMatrix[2][2]; + + double pMatrix[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + MatrixOperations::multiply( + *gainMatrixInverse, *(acsParameters->inertiaEIVE.inertiaMatrixDeployed), *pMatrix, 3, 3, 3); + MatrixOperations::multiplyScalar(*pMatrix, kInt, *pMatrix, 3, 3); + + //------------------------------------------------------------------------------------------------ + // Torque Calculations for the reaction wheels + //------------------------------------------------------------------------------------------------ + + double pError[3] = {0, 0, 0}; + MatrixOperations::multiply(*pMatrix, qError, pError, 3, 3, 1); + double pErrorSign[3] = {0, 0, 0}; + + for (int i = 0; i < 3; i++) { + if (std::abs(pError[i]) > 1) { + pErrorSign[i] = sign(pError[i]); + } else { + pErrorSign[i] = pError[i]; + } + } + // torque for quaternion error + double torqueQuat[3] = {0, 0, 0}; + MatrixOperations::multiply(*gainMatrix, pErrorSign, torqueQuat, 3, 3, 1); + VectorOperations::mulScalar(torqueQuat, -1, torqueQuat, 3); + + // torque for rate error + double torqueRate[3] = {0, 0, 0}; + MatrixOperations::multiply(*(acsParameters->inertiaEIVE.inertiaMatrixDeployed), deltaRate, + torqueRate, 3, 3, 1); + VectorOperations::mulScalar(torqueRate, cInt, torqueRate, 3); + VectorOperations::mulScalar(torqueRate, -1, torqueRate, 3); + + // final commanded Torque for every reaction wheel + double torque[3] = {0, 0, 0}; + VectorOperations::add(torqueRate, torqueQuat, torque, 3); + MatrixOperations::multiply(rwPseudoInv, torque, torqueRws, 4, 3, 1); + VectorOperations::mulScalar(torqueRws, -1, torqueRws, 4); +} + +void PtgCtrl::ptgNullspace(const bool allRwAvabilable, + AcsParameters::PointingLawParameters *pointingLawParameters, + const int32_t speedRw0, const int32_t speedRw1, const int32_t speedRw2, + const int32_t speedRw3, double *rwTrqNs) { + if (not allRwAvabilable) { + return; + } + // concentrate RW speeds as vector and convert to double + double speedRws[4] = {static_cast(speedRw0), static_cast(speedRw1), + static_cast(speedRw2), static_cast(speedRw3)}; + VectorOperations::mulScalar(speedRws, 1e-1, speedRws, 4); + VectorOperations::mulScalar(speedRws, RPM_TO_RAD_PER_SEC, speedRws, 4); + + // calculate RPM offset utilizing the nullspace + double rpmOffset[4] = {0, 0, 0, 0}; + double rpmOffsetSpeed = pointingLawParameters->nullspaceSpeed / 10 * RPM_TO_RAD_PER_SEC; + VectorOperations::mulScalar(acsParameters->rwMatrices.nullspaceVector, rpmOffsetSpeed, + rpmOffset, 4); + + // calculate resulting angular momentum + double rwAngMomentum[4] = {0, 0, 0, 0}, diffRwSpeed[4] = {0, 0, 0, 0}; + VectorOperations::subtract(speedRws, rpmOffset, diffRwSpeed, 4); + VectorOperations::mulScalar(diffRwSpeed, acsParameters->rwHandlingParameters.inertiaWheel, + rwAngMomentum, 4); + + // calculate resulting torque + double nullspaceMatrix[4][4] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + MatrixOperations::multiply(acsParameters->rwMatrices.nullspaceVector, + acsParameters->rwMatrices.nullspaceVector, *nullspaceMatrix, 4, + 1, 4); + MatrixOperations::multiply(*nullspaceMatrix, rwAngMomentum, rwTrqNs, 4, 4, 1); + VectorOperations::mulScalar(rwTrqNs, -1 * pointingLawParameters->gainNullspace, rwTrqNs, + 4); +} + +void PtgCtrl::ptgDesaturation(const bool allRwAvailable, const acsctrl::RwAvail *rwAvail, + AcsParameters::PointingLawParameters *pointingLawParameters, + const double *magFieldB, const bool magFieldBValid, + const int32_t speedRw0, const int32_t speedRw1, + const int32_t speedRw2, const int32_t speedRw3, double *mgtDpDes) { + if (not magFieldBValid or not pointingLawParameters->desatOn) { + return; + } + + // concentrate RW speeds as vector and convert to double + double speedRws[4] = {static_cast(speedRw0), static_cast(speedRw1), + static_cast(speedRw2), static_cast(speedRw3)}; + + // convert magFieldB from uT to T + double magFieldBT[3] = {0, 0, 0}; + VectorOperations::mulScalar(magFieldB, 1e-6, magFieldBT, 3); + + // calculate angular momentum of the reaction wheels with respect to the nullspace RW speed + // relocate RW speed zero to nullspace RW speed + double refSpeedRws[4] = {0, 0, 0, 0}; + VectorOperations::mulScalar(acsParameters->rwMatrices.nullspaceVector, + pointingLawParameters->nullspaceSpeed, refSpeedRws, 4); + if (not allRwAvailable) { + if (not rwAvail->rw1avail) { + refSpeedRws[0] = 0.0; + } else if (not rwAvail->rw2avail) { + refSpeedRws[1] = 0.0; + } else if (not rwAvail->rw3avail) { + refSpeedRws[2] = 0.0; + } else if (not rwAvail->rw4avail) { + refSpeedRws[3] = 0.0; + } + } + VectorOperations::subtract(speedRws, refSpeedRws, speedRws, 4); + + // convert speed from 10 RPM to 1 RPM + VectorOperations::mulScalar(speedRws, 1e-1, speedRws, 4); + // convert to rad/s + VectorOperations::mulScalar(speedRws, RPM_TO_RAD_PER_SEC, speedRws, 4); + // calculate angular momentum of each RW + double angMomentumRwU[4] = {0, 0, 0, 0}; + VectorOperations::mulScalar(speedRws, acsParameters->rwHandlingParameters.inertiaWheel, + angMomentumRwU, 4); + // convert RW angular momentum to body RF + double angMomentumRw[3] = {0, 0, 0}; + MatrixOperations::multiply(*(acsParameters->rwMatrices.alignmentMatrix), angMomentumRwU, + angMomentumRw, 3, 4, 1); + + // calculate total angular momentum + double angMomentumTotal[3] = {0, 0, 0}; + VectorOperations::subtract(angMomentumRw, pointingLawParameters->desatMomentumRef, + angMomentumTotal, 3); + + // resulting magnetic dipole command + double crossAngMomentumMagField[3] = {0, 0, 0}; + VectorOperations::cross(angMomentumTotal, magFieldBT, crossAngMomentumMagField); + double factor = + pointingLawParameters->deSatGainFactor / VectorOperations::norm(magFieldBT, 3); + VectorOperations::mulScalar(crossAngMomentumMagField, factor, mgtDpDes, 3); +} + +void PtgCtrl::rwAntistiction(ACS::SensorValues *sensorValues, int32_t *rwCmdSpeeds) { + bool rwAvailable[4] = { + (sensorValues->rw1Set.state.value && sensorValues->rw1Set.state.isValid()), + (sensorValues->rw2Set.state.value && sensorValues->rw2Set.state.isValid()), + (sensorValues->rw3Set.state.value && sensorValues->rw3Set.state.isValid()), + (sensorValues->rw4Set.state.value && sensorValues->rw4Set.state.isValid())}; + int32_t currRwSpeed[4] = { + sensorValues->rw1Set.currSpeed.value, sensorValues->rw2Set.currSpeed.value, + sensorValues->rw3Set.currSpeed.value, sensorValues->rw4Set.currSpeed.value}; + for (uint8_t i = 0; i < 4; i++) { + if (rwAvailable[i]) { + if (rwCmdSpeeds[i] != 0) { + if (rwCmdSpeeds[i] > -acsParameters->rwHandlingParameters.stictionSpeed && + rwCmdSpeeds[i] < acsParameters->rwHandlingParameters.stictionSpeed) { + if (rwCmdSpeeds[i] > currRwSpeed[i]) { + rwCmdSpeeds[i] = acsParameters->rwHandlingParameters.stictionSpeed; + } else if (rwCmdSpeeds[i] < currRwSpeed[i]) { + rwCmdSpeeds[i] = -acsParameters->rwHandlingParameters.stictionSpeed; + } else { + rwCmdSpeeds[i] = 0; + } + } + } + } else { + rwCmdSpeeds[i] = 0; + } + } +} diff --git a/mission/controller/acs/control/PtgCtrl.h b/mission/controller/acs/control/PtgCtrl.h new file mode 100644 index 0000000..daeff4a --- /dev/null +++ b/mission/controller/acs/control/PtgCtrl.h @@ -0,0 +1,62 @@ +#ifndef PTGCTRL_H_ +#define PTGCTRL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class PtgCtrl { + /* + * @brief: This class handles the pointing control mechanism. Calculation of an commanded + * torque for the reaction wheels, and magnetic Field strength for magnetorquer for desaturation + * + * @note: A description of the used algorithms can be found in + * https://eive-cloud.irs.uni-stuttgart.de/index.php/apps/files/?dir=/EIVE_Studenten/Marquardt_Robin&openfile=896110 + */ + public: + /* @brief: Constructor + * @param: acsParameters_ Pointer to object which defines the ACS configuration parameters + */ + PtgCtrl(AcsParameters *acsParameters_); + virtual ~PtgCtrl(); + + acs::ControlModeStrategy pointingCtrlStrategy(const bool magFieldValid, const bool mekfValid, + const bool strValid, const bool questValid, + const bool fusedRateValid, + const uint8_t rotRateSource, + const uint8_t mekfEnabled); + + /* @brief: Calculates the needed torque for the pointing control mechanism + */ + void ptgLaw(AcsParameters::PointingLawParameters *pointingLawParameters, const double *qError, + const double *deltaRate, const double *rwPseudoInv, double *torqueRws); + + void ptgNullspace(const bool allRwAvabilable, + AcsParameters::PointingLawParameters *pointingLawParameters, + const int32_t speedRw0, const int32_t speedRw1, const int32_t speedRw2, + const int32_t speedRw3, double *rwTrqNs); + + void ptgDesaturation(const bool allRwAvabilable, const acsctrl::RwAvail *rwAvail, + AcsParameters::PointingLawParameters *pointingLawParameters, + const double *magFieldB, const bool magFieldBValid, const int32_t speedRw0, + const int32_t speedRw1, const int32_t speedRw2, const int32_t speedRw3, + double *mgtDpDes); + + /* @brief: Commands the stiction torque in case wheel speed is to low + * torqueCommand modified torque after anti-stiction + */ + void rwAntistiction(ACS::SensorValues *sensorValues, int32_t *rwCmdSpeed); + + private: + const AcsParameters *acsParameters; + static constexpr double RPM_TO_RAD_PER_SEC = (2 * M_PI) / 60; +}; + +#endif /* ACS_CONTROL_PTGCTRL_H_ */ diff --git a/mission/controller/acs/control/SafeCtrl.cpp b/mission/controller/acs/control/SafeCtrl.cpp new file mode 100644 index 0000000..972b7b9 --- /dev/null +++ b/mission/controller/acs/control/SafeCtrl.cpp @@ -0,0 +1,198 @@ +#include "SafeCtrl.h" + +#include +#include +#include +#include + +SafeCtrl::SafeCtrl(AcsParameters *acsParameters_) { acsParameters = acsParameters_; } + +SafeCtrl::~SafeCtrl() {} + +acs::ControlModeStrategy SafeCtrl::safeCtrlStrategy( + const bool magFieldValid, const bool mekfValid, const bool satRotRateValid, + const bool sunDirValid, const bool fusedRateTotalValid, const uint8_t mekfEnabled, + const uint8_t gyrEnabled, const uint8_t dampingEnabled) { + if (not magFieldValid) { + return acs::ControlModeStrategy::CTRL_NO_MAG_FIELD_FOR_CONTROL; + } else if (mekfEnabled and mekfValid) { + return acs::ControlModeStrategy::SAFECTRL_MEKF; + } else if (sunDirValid) { + if (gyrEnabled and satRotRateValid) { + return acs::ControlModeStrategy::SAFECTRL_GYR; + } else if (not gyrEnabled and fusedRateTotalValid) { + return acs::ControlModeStrategy::SAFECTRL_SUSMGM; + } else { + return acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL; + } + } else if (not sunDirValid) { + if (dampingEnabled) { + if (gyrEnabled and satRotRateValid) { + return acs::ControlModeStrategy::SAFECTRL_ECLIPSE_DAMPING_GYR; + } else if (not gyrEnabled and satRotRateValid and fusedRateTotalValid) { + return acs::ControlModeStrategy::SAFECTRL_ECLIPSE_DAMPING_SUSMGM; + } else { + return acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL; + } + } else if (not dampingEnabled and satRotRateValid) { + return acs::ControlModeStrategy::SAFECTRL_ECLIPSE_IDELING; + } else { + return acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL; + } + } else { + return acs::ControlModeStrategy::CTRL_NO_SENSORS_FOR_CONTROL; + } +} + +void SafeCtrl::safeMekf(const double *magFieldB, const double *satRotRateB, + const double *sunDirModelI, const double *quatBI, const double *sunDirRefB, + double *magMomB, double &errorAngle) { + // convert magFieldB from uT to T + VectorOperations::mulScalar(magFieldB, 1e-6, magFieldBT, 3); + + // convert sunDirModel to body rf + double sunDirB[3] = {0, 0, 0}; + QuaternionOperations::multiplyVector(quatBI, sunDirModelI, sunDirB); + + // calculate angle alpha between sunDirRef and sunDir + double dotSun = VectorOperations::dot(sunDirRefB, sunDirB); + errorAngle = acos(dotSun); + + splitRotationalRate(satRotRateB, sunDirB); + calculateRotationalRateTorque(acsParameters->safeModeControllerParameters.k_parallelMekf, + acsParameters->safeModeControllerParameters.k_orthoMekf); + calculateAngleErrorTorque(sunDirB, sunDirRefB, + acsParameters->safeModeControllerParameters.k_alignMekf); + + // sum of all torques + for (uint8_t i = 0; i < 3; i++) { + cmdTorque[i] = cmdAlign[i] + cmdOrtho[i] + cmdParallel[i]; + } + + calculateMagneticMoment(magMomB); +} + +void SafeCtrl::safeGyr(const double *magFieldB, const double *satRotRateB, const double *sunDirB, + const double *sunDirRefB, double *magMomB, double &errorAngle) { + // convert magFieldB from uT to T + VectorOperations::mulScalar(magFieldB, 1e-6, magFieldBT, 3); + + // calculate error angle between sunDirRef and sunDir + double dotSun = VectorOperations::dot(sunDirRefB, sunDirB); + errorAngle = acos(dotSun); + + splitRotationalRate(satRotRateB, sunDirB); + calculateRotationalRateTorque(acsParameters->safeModeControllerParameters.k_parallelGyr, + acsParameters->safeModeControllerParameters.k_orthoGyr); + calculateAngleErrorTorque(sunDirB, sunDirRefB, + acsParameters->safeModeControllerParameters.k_alignGyr); + + // sum of all torques + for (uint8_t i = 0; i < 3; i++) { + cmdTorque[i] = cmdAlign[i] + cmdOrtho[i] + cmdParallel[i]; + } + + calculateMagneticMoment(magMomB); +} + +void SafeCtrl::safeSusMgm(const double *magFieldB, const double *rotRateTotalB, + const double *rotRateParallelB, const double *rotRateOrthogonalB, + const double *sunDirB, const double *sunDirRefB, double *magMomB, + double &errorAngle) { + // convert magFieldB from uT to T + VectorOperations::mulScalar(magFieldB, 1e-6, magFieldBT, 3); + + // calculate error angle between sunDirRef and sunDir + double dotSun = VectorOperations::dot(sunDirRefB, sunDirB); + errorAngle = acos(dotSun); + + if (VectorOperations::norm(rotRateParallelB, 3) != 0 and + VectorOperations::norm(rotRateOrthogonalB, 3) != 0) { + std::memcpy(satRotRateParallelB, rotRateParallelB, sizeof(satRotRateParallelB)); + std::memcpy(satRotRateOrthogonalB, rotRateOrthogonalB, sizeof(satRotRateOrthogonalB)); + } else { + splitRotationalRate(rotRateTotalB, sunDirB); + } + + calculateRotationalRateTorque(acsParameters->safeModeControllerParameters.k_parallelSusMgm, + acsParameters->safeModeControllerParameters.k_orthoSusMgm); + calculateAngleErrorTorque(sunDirB, sunDirRefB, + acsParameters->safeModeControllerParameters.k_alignSusMgm); + + // sum of all torques + for (uint8_t i = 0; i < 3; i++) { + cmdTorque[i] = cmdAlign[i] + cmdOrtho[i] + cmdParallel[i]; + } + + calculateMagneticMoment(magMomB); +} + +void SafeCtrl::safeRateDampingGyr(const double *magFieldB, const double *satRotRateB, + const double *sunDirRefB, double *magMomB, double &errorAngle) { + // convert magFieldB from uT to T + VectorOperations::mulScalar(magFieldB, 1e-6, magFieldBT, 3); + + // no error angle available for eclipse + errorAngle = NAN; + + splitRotationalRate(satRotRateB, sunDirRefB); + calculateRotationalRateTorque(acsParameters->safeModeControllerParameters.k_parallelGyr, + acsParameters->safeModeControllerParameters.k_orthoGyr); + + // sum of all torques + VectorOperations::add(cmdParallel, cmdOrtho, cmdTorque, 3); + + // calculate magnetic moment to command + calculateMagneticMoment(magMomB); +} + +void SafeCtrl::safeRateDampingSusMgm(const double *magFieldB, const double *satRotRateB, + const double *sunDirRefB, double *magMomB, + double &errorAngle) { + // convert magFieldB from uT to T + VectorOperations::mulScalar(magFieldB, 1e-6, magFieldBT, 3); + + // no error angle available for eclipse + errorAngle = NAN; + + splitRotationalRate(satRotRateB, sunDirRefB); + calculateRotationalRateTorque(acsParameters->safeModeControllerParameters.k_parallelSusMgm, + acsParameters->safeModeControllerParameters.k_orthoSusMgm); + + // sum of all torques + VectorOperations::add(cmdParallel, cmdOrtho, cmdTorque, 3); + + // calculate magnetic moment to command + calculateMagneticMoment(magMomB); +} + +void SafeCtrl::splitRotationalRate(const double *satRotRateB, const double *sunDirB) { + // split rotational rate into parallel and orthogonal parts + double parallelLength = VectorOperations::dot(satRotRateB, sunDirB) * + pow(VectorOperations::norm(sunDirB, 3), -2); + VectorOperations::mulScalar(sunDirB, parallelLength, satRotRateParallelB, 3); + VectorOperations::subtract(satRotRateB, satRotRateParallelB, satRotRateOrthogonalB, 3); +} + +void SafeCtrl::calculateRotationalRateTorque(const double gainParallel, const double gainOrtho) { + // calculate torque for parallel rotational rate + VectorOperations::mulScalar(satRotRateParallelB, -gainParallel, cmdParallel, 3); + + // calculate torque for orthogonal rotational rate + VectorOperations::mulScalar(satRotRateOrthogonalB, -gainOrtho, cmdOrtho, 3); +} + +void SafeCtrl::calculateAngleErrorTorque(const double *sunDirB, const double *sunDirRefB, + const double gainAlign) { + // calculate torque for alignment + double crossAlign[3] = {0, 0, 0}; + VectorOperations::cross(sunDirRefB, sunDirB, crossAlign); + VectorOperations::mulScalar(crossAlign, gainAlign, cmdAlign, 3); +} + +void SafeCtrl::calculateMagneticMoment(double *magMomB) { + double torqueMgt[3] = {0, 0, 0}; + VectorOperations::cross(magFieldBT, cmdTorque, torqueMgt); + double normMag = VectorOperations::norm(magFieldBT, 3); + VectorOperations::mulScalar(torqueMgt, pow(normMag, -2), magMomB, 3); +} diff --git a/mission/controller/acs/control/SafeCtrl.h b/mission/controller/acs/control/SafeCtrl.h new file mode 100644 index 0000000..e303f30 --- /dev/null +++ b/mission/controller/acs/control/SafeCtrl.h @@ -0,0 +1,64 @@ +#ifndef SAFECTRL_H_ +#define SAFECTRL_H_ + +#include +#include +#include +#include +#include + +class SafeCtrl { + public: + SafeCtrl(AcsParameters *acsParameters_); + virtual ~SafeCtrl(); + + acs::ControlModeStrategy safeCtrlStrategy(const bool magFieldValid, const bool mekfValid, + const bool satRotRateValid, const bool sunDirValid, + const bool fusedRateTotalValid, + const uint8_t mekfEnabled, const uint8_t gyrEnabled, + const uint8_t dampingEnabled); + + void safeMekf(const double *magFieldB, const double *satRotRateB, const double *sunDirModelI, + const double *quatBI, const double *sunDirRefB, double *magMomB, + double &errorAngle); + + void safeGyr(const double *magFieldB, const double *satRotRateB, const double *sunDirB, + const double *sunDirRefB, double *magMomB, double &errorAngle); + + void safeSusMgm(const double *magFieldB, const double *rotRateTotalB, + const double *rotRateParallelB, const double *rotRateOrthogonalB, + const double *sunDirB, const double *sunDirRefB, double *magMomB, + double &errorAngle); + + void safeRateDampingGyr(const double *magFieldB, const double *satRotRateB, + const double *sunDirRefB, double *magMomB, double &errorAngle); + + void safeRateDampingSusMgm(const double *magFieldB, const double *satRotRateB, + const double *sunDirRefB, double *magMomB, double &errorAngle); + + void splitRotationalRate(const double *satRotRateB, const double *sunDirB); + + void calculateRotationalRates(const double *magFieldB, const double *magRateB, + const double *sunDirB, const double *sunRateB, + double *fusedRotRate); + + void calculateRotationalRateTorque(const double gainParallel, const double gainOrtho); + + void calculateAngleErrorTorque(const double *sunDirB, const double *sunDirRefB, + const double gainAlign); + + void calculateMagneticMoment(double *magMomB); + + protected: + private: + AcsParameters *acsParameters; + double magFieldBT[3] = {0, 0, 0}; + double satRotRateParallelB[3] = {0, 0, 0}; + double satRotRateOrthogonalB[3] = {0, 0, 0}; + double cmdParallel[3] = {0, 0, 0}; + double cmdOrtho[3] = {0, 0, 0}; + double cmdAlign[3] = {0, 0, 0}; + double cmdTorque[3] = {0, 0, 0}; +}; + +#endif /* ACS_CONTROL_SAFECTRL_H_ */ diff --git a/mission/controller/controllerdefinitions/AcsCtrlDefinitions.h b/mission/controller/controllerdefinitions/AcsCtrlDefinitions.h new file mode 100644 index 0000000..ec99fe5 --- /dev/null +++ b/mission/controller/controllerdefinitions/AcsCtrlDefinitions.h @@ -0,0 +1,349 @@ +#ifndef MISSION_CONTROLLER_CONTROLLERDEFINITIONS_ACSCTRLDEFINITIONS_H_ +#define MISSION_CONTROLLER_CONTROLLERDEFINITIONS_ACSCTRLDEFINITIONS_H_ + +#include +#include +#include + +#include + +namespace acsctrl { + +static const uint8_t INTERFACE_ID = CLASS_ID::ACS_CTRL; +//! [EXPORT] : [COMMENT] File deletion failed and at least one file is still existent. +static constexpr ReturnValue_t FILE_DELETION_FAILED = MAKE_RETURN_CODE(0xA0); +//! [EXPORT] : [COMMENT] Writing the TLE to the file has failed. +static constexpr ReturnValue_t WRITE_FILE_FAILED = MAKE_RETURN_CODE(0xA1); +//! [EXPORT] : [COMMENT] Reading the TLE to the file has failed. +static constexpr ReturnValue_t READ_FILE_FAILED = MAKE_RETURN_CODE(0xA2); +//! [EXPORT] : [COMMENT] A single RW has failed. +static constexpr ReturnValue_t SINGLE_RW_UNAVAILABLE = MAKE_RETURN_CODE(0xA3); +//! [EXPORT] : [COMMENT] Multiple RWs have failed. +static constexpr ReturnValue_t MULTIPLE_RW_UNAVAILABLE = MAKE_RETURN_CODE(0xA4); + +struct RwAvail { + bool rw1avail = false; + bool rw2avail = false; + bool rw3avail = false; + bool rw4avail = false; +}; + +enum SetIds : uint32_t { + MGM_SENSOR_DATA, + MGM_PROCESSED_DATA, + SUS_SENSOR_DATA, + SUS_PROCESSED_DATA, + GYR_SENSOR_DATA, + GYR_PROCESSED_DATA, + GPS_PROCESSED_DATA, + MEKF_DATA, + CTRL_VAL_DATA, + ACTUATOR_CMD_DATA, + FUSED_ROTATION_RATE_DATA, + FUSED_ROTATION_RATE_SOURCES_DATA, + TLE_SET, +}; + +enum PoolIds : lp_id_t { + // MGM Raw + MGM_0_LIS3_UT, + MGM_1_RM3100_UT, + MGM_2_LIS3_UT, + MGM_3_RM3100_UT, + MGM_IMTQ_CAL_NT, + MGM_IMTQ_CAL_ACT_STATUS, + // MGM Processed + MGM_0_VEC, + MGM_1_VEC, + MGM_2_VEC, + MGM_3_VEC, + MGM_4_VEC, + MGM_VEC_TOT, + MGM_VEC_TOT_DERIVATIVE, + MAG_IGRF_MODEL, + // SUS Raw + SUS_0_N_LOC_XFYFZM_PT_XF, + SUS_6_R_LOC_XFYBZM_PT_XF, + + SUS_1_N_LOC_XBYFZM_PT_XB, + SUS_7_R_LOC_XBYBZM_PT_XB, + + SUS_2_N_LOC_XFYBZB_PT_YB, + SUS_8_R_LOC_XBYBZB_PT_YB, + + SUS_3_N_LOC_XFYBZF_PT_YF, + SUS_9_R_LOC_XBYBZB_PT_YF, + + SUS_4_N_LOC_XMYFZF_PT_ZF, + SUS_10_N_LOC_XMYBZF_PT_ZF, + + SUS_5_N_LOC_XFYMZB_PT_ZB, + SUS_11_R_LOC_XBYMZB_PT_ZB, + // SUS Processed + SUS_0_VEC, + SUS_1_VEC, + SUS_2_VEC, + SUS_3_VEC, + SUS_4_VEC, + SUS_5_VEC, + SUS_6_VEC, + SUS_7_VEC, + SUS_8_VEC, + SUS_9_VEC, + SUS_10_VEC, + SUS_11_VEC, + SUS_VEC_TOT, + SUS_VEC_TOT_DERIVATIVE, + SUN_IJK_MODEL, + // GYR Raw + GYR_0_ADIS, + GYR_1_L3, + GYR_2_ADIS, + GYR_3_L3, + // GYR Processed + GYR_0_VEC, + GYR_1_VEC, + GYR_2_VEC, + GYR_3_VEC, + GYR_VEC_TOT, + // GPS Processed + SOURCE, + GC_LATITUDE, + GD_LONGITUDE, + ALTITUDE, + GPS_POSITION, + GPS_VELOCITY, + // MEKF + SAT_ROT_RATE_MEKF, + QUAT_MEKF, + MEKF_STATUS, + QUAT_QUEST, + // Ctrl Values + SAFE_STRAT, + TGT_QUAT, + ERROR_QUAT, + ERROR_ANG, + TGT_ROT_RATE, + // Actuator Cmd + RW_TARGET_TORQUE, + RW_TARGET_SPEED, + MTQ_TARGET_DIPOLE, + // Fused Rotation Rate + ROT_RATE_TOT_SUSMGM, + ROT_RATE_TOT_SOURCE, + ROT_RATE_SOURCE, + // Fused Rotation Rate Sources + ROT_RATE_ORTHOGONAL_SUSMGM, + ROT_RATE_PARALLEL_SUSMGM, + ROT_RATE_TOTAL_SUSMGM, + ROT_RATE_TOTAL_QUEST, + ROT_RATE_TOTAL_STR, +}; + +static constexpr uint8_t MGM_SET_RAW_ENTRIES = 6; +static constexpr uint8_t MGM_SET_PROCESSED_ENTRIES = 8; +static constexpr uint8_t SUS_SET_RAW_ENTRIES = 12; +static constexpr uint8_t SUS_SET_PROCESSED_ENTRIES = 15; +static constexpr uint8_t GYR_SET_RAW_ENTRIES = 4; +static constexpr uint8_t GYR_SET_PROCESSED_ENTRIES = 5; +static constexpr uint8_t GPS_SET_PROCESSED_ENTRIES = 6; +static constexpr uint8_t ATTITUDE_ESTIMATION_SET_ENTRIES = 4; +static constexpr uint8_t CTRL_VAL_SET_ENTRIES = 5; +static constexpr uint8_t ACT_CMD_SET_ENTRIES = 3; +static constexpr uint8_t FUSED_ROT_RATE_SET_ENTRIES = 3; +static constexpr uint8_t FUSED_ROT_RATE_SOURCES_SET_ENTRIES = 5; + +/** + * @brief Raw MGM sensor data. Includes the IMTQ sensor data and actuator status. + */ +class MgmDataRaw : public StaticLocalDataSet { + public: + MgmDataRaw(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, MGM_SENSOR_DATA) {} + + // The ACS board measurement are in floating point uT + lp_vec_t mgm0Lis3 = lp_vec_t(sid.objectId, MGM_0_LIS3_UT, this); + lp_vec_t mgm1Rm3100 = lp_vec_t(sid.objectId, MGM_1_RM3100_UT, this); + lp_vec_t mgm2Lis3 = lp_vec_t(sid.objectId, MGM_2_LIS3_UT, this); + lp_vec_t mgm3Rm3100 = lp_vec_t(sid.objectId, MGM_3_RM3100_UT, this); + // The IMTQ measurements are in integer nT + lp_vec_t imtqRaw = lp_vec_t(sid.objectId, MGM_IMTQ_CAL_NT, this); + lp_var_t actuationCalStatus = + lp_var_t(sid.objectId, MGM_IMTQ_CAL_ACT_STATUS, this); + + private: +}; + +class MgmDataProcessed : public StaticLocalDataSet { + public: + MgmDataProcessed(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, MGM_PROCESSED_DATA) {} + + lp_vec_t mgm0vec = lp_vec_t(sid.objectId, MGM_0_VEC, this); + lp_vec_t mgm1vec = lp_vec_t(sid.objectId, MGM_1_VEC, this); + lp_vec_t mgm2vec = lp_vec_t(sid.objectId, MGM_2_VEC, this); + lp_vec_t mgm3vec = lp_vec_t(sid.objectId, MGM_3_VEC, this); + lp_vec_t mgm4vec = lp_vec_t(sid.objectId, MGM_4_VEC, this); + lp_vec_t mgmVecTot = lp_vec_t(sid.objectId, MGM_VEC_TOT, this); + lp_vec_t mgmVecTotDerivative = + lp_vec_t(sid.objectId, MGM_VEC_TOT_DERIVATIVE, this); + lp_vec_t magIgrfModel = lp_vec_t(sid.objectId, MAG_IGRF_MODEL, this); + + private: +}; + +class SusDataRaw : public StaticLocalDataSet { + public: + SusDataRaw(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, SUS_SENSOR_DATA) {} + + lp_vec_t sus0 = lp_vec_t(sid.objectId, SUS_0_N_LOC_XFYFZM_PT_XF, this); + lp_vec_t sus1 = lp_vec_t(sid.objectId, SUS_1_N_LOC_XBYFZM_PT_XB, this); + lp_vec_t sus2 = lp_vec_t(sid.objectId, SUS_2_N_LOC_XFYBZB_PT_YB, this); + lp_vec_t sus3 = lp_vec_t(sid.objectId, SUS_3_N_LOC_XFYBZF_PT_YF, this); + lp_vec_t sus4 = lp_vec_t(sid.objectId, SUS_4_N_LOC_XMYFZF_PT_ZF, this); + lp_vec_t sus5 = lp_vec_t(sid.objectId, SUS_5_N_LOC_XFYMZB_PT_ZB, this); + lp_vec_t sus6 = lp_vec_t(sid.objectId, SUS_6_R_LOC_XFYBZM_PT_XF, this); + lp_vec_t sus7 = lp_vec_t(sid.objectId, SUS_7_R_LOC_XBYBZM_PT_XB, this); + lp_vec_t sus8 = lp_vec_t(sid.objectId, SUS_8_R_LOC_XBYBZB_PT_YB, this); + lp_vec_t sus9 = lp_vec_t(sid.objectId, SUS_9_R_LOC_XBYBZB_PT_YF, this); + lp_vec_t sus10 = + lp_vec_t(sid.objectId, SUS_10_N_LOC_XMYBZF_PT_ZF, this); + lp_vec_t sus11 = + lp_vec_t(sid.objectId, SUS_11_R_LOC_XBYMZB_PT_ZB, this); + + private: +}; + +class SusDataProcessed : public StaticLocalDataSet { + public: + SusDataProcessed(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, SUS_PROCESSED_DATA) {} + + lp_vec_t sus0vec = lp_vec_t(sid.objectId, SUS_0_VEC, this); + lp_vec_t sus1vec = lp_vec_t(sid.objectId, SUS_1_VEC, this); + lp_vec_t sus2vec = lp_vec_t(sid.objectId, SUS_2_VEC, this); + lp_vec_t sus3vec = lp_vec_t(sid.objectId, SUS_3_VEC, this); + lp_vec_t sus4vec = lp_vec_t(sid.objectId, SUS_4_VEC, this); + lp_vec_t sus5vec = lp_vec_t(sid.objectId, SUS_5_VEC, this); + lp_vec_t sus6vec = lp_vec_t(sid.objectId, SUS_6_VEC, this); + lp_vec_t sus7vec = lp_vec_t(sid.objectId, SUS_7_VEC, this); + lp_vec_t sus8vec = lp_vec_t(sid.objectId, SUS_8_VEC, this); + lp_vec_t sus9vec = lp_vec_t(sid.objectId, SUS_9_VEC, this); + lp_vec_t sus10vec = lp_vec_t(sid.objectId, SUS_10_VEC, this); + lp_vec_t sus11vec = lp_vec_t(sid.objectId, SUS_11_VEC, this); + lp_vec_t susVecTot = lp_vec_t(sid.objectId, SUS_VEC_TOT, this); + lp_vec_t susVecTotDerivative = + lp_vec_t(sid.objectId, SUS_VEC_TOT_DERIVATIVE, this); + lp_vec_t sunIjkModel = lp_vec_t(sid.objectId, SUN_IJK_MODEL, this); + + private: +}; + +class GyrDataRaw : public StaticLocalDataSet { + public: + GyrDataRaw(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, GYR_SENSOR_DATA) {} + + lp_vec_t gyr0Adis = lp_vec_t(sid.objectId, GYR_0_ADIS, this); + lp_vec_t gyr1L3 = lp_vec_t(sid.objectId, GYR_1_L3, this); + lp_vec_t gyr2Adis = lp_vec_t(sid.objectId, GYR_2_ADIS, this); + lp_vec_t gyr3L3 = lp_vec_t(sid.objectId, GYR_3_L3, this); + + private: +}; + +class GyrDataProcessed : public StaticLocalDataSet { + public: + GyrDataProcessed(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, GYR_PROCESSED_DATA) {} + + lp_vec_t gyr0vec = lp_vec_t(sid.objectId, GYR_0_VEC, this); + lp_vec_t gyr1vec = lp_vec_t(sid.objectId, GYR_1_VEC, this); + lp_vec_t gyr2vec = lp_vec_t(sid.objectId, GYR_2_VEC, this); + lp_vec_t gyr3vec = lp_vec_t(sid.objectId, GYR_3_VEC, this); + lp_vec_t gyrVecTot = lp_vec_t(sid.objectId, GYR_VEC_TOT, this); + + private: +}; + +class GpsDataProcessed : public StaticLocalDataSet { + public: + GpsDataProcessed(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, GPS_PROCESSED_DATA) {} + + lp_var_t gcLatitude = lp_var_t(sid.objectId, GC_LATITUDE, this); + lp_var_t gdLongitude = lp_var_t(sid.objectId, GD_LONGITUDE, this); + lp_var_t altitude = lp_var_t(sid.objectId, ALTITUDE, this); + lp_vec_t gpsPosition = lp_vec_t(sid.objectId, GPS_POSITION, this); + lp_vec_t gpsVelocity = lp_vec_t(sid.objectId, GPS_VELOCITY, this); + lp_var_t source = lp_var_t(sid.objectId, SOURCE, this); + + private: +}; + +class AttitudeEstimationData : public StaticLocalDataSet { + public: + AttitudeEstimationData(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, MEKF_DATA) {} + + lp_vec_t quatMekf = lp_vec_t(sid.objectId, QUAT_MEKF, this); + lp_vec_t satRotRateMekf = lp_vec_t(sid.objectId, SAT_ROT_RATE_MEKF, this); + lp_var_t mekfStatus = lp_var_t(sid.objectId, MEKF_STATUS, this); + lp_vec_t quatQuest = lp_vec_t(sid.objectId, QUAT_MEKF, this); + + private: +}; + +class CtrlValData : public StaticLocalDataSet { + public: + CtrlValData(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, CTRL_VAL_DATA) {} + + lp_var_t safeStrat = lp_var_t(sid.objectId, SAFE_STRAT, this); + lp_vec_t tgtQuat = lp_vec_t(sid.objectId, TGT_QUAT, this); + lp_vec_t errQuat = lp_vec_t(sid.objectId, ERROR_QUAT, this); + lp_var_t errAng = lp_var_t(sid.objectId, ERROR_ANG, this); + lp_vec_t tgtRotRate = lp_vec_t(sid.objectId, TGT_ROT_RATE, this); + + private: +}; + +class ActuatorCmdData : public StaticLocalDataSet { + public: + ActuatorCmdData(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, ACTUATOR_CMD_DATA) {} + + lp_vec_t rwTargetTorque = lp_vec_t(sid.objectId, RW_TARGET_TORQUE, this); + lp_vec_t rwTargetSpeed = lp_vec_t(sid.objectId, RW_TARGET_SPEED, this); + lp_vec_t mtqTargetDipole = + lp_vec_t(sid.objectId, MTQ_TARGET_DIPOLE, this); + + private: +}; + +class FusedRotRateData : public StaticLocalDataSet { + public: + FusedRotRateData(HasLocalDataPoolIF* hkOwner) + : StaticLocalDataSet(hkOwner, FUSED_ROTATION_RATE_DATA) {} + + lp_vec_t rotRateTotalSusMgm = + lp_vec_t(sid.objectId, ROT_RATE_TOT_SUSMGM, this); + lp_vec_t rotRateTotalSource = + lp_vec_t(sid.objectId, ROT_RATE_TOT_SOURCE, this); + lp_var_t rotRateSource = lp_var_t(sid.objectId, ROT_RATE_SOURCE, this); + + private: +}; + +class FusedRotRateSourcesData : public StaticLocalDataSet { + public: + FusedRotRateSourcesData(HasLocalDataPoolIF* hkOwner) + : StaticLocalDataSet(hkOwner, FUSED_ROTATION_RATE_SOURCES_DATA) {} + + lp_vec_t rotRateOrthogonalSusMgm = + lp_vec_t(sid.objectId, ROT_RATE_ORTHOGONAL_SUSMGM, this); + lp_vec_t rotRateParallelSusMgm = + lp_vec_t(sid.objectId, ROT_RATE_PARALLEL_SUSMGM, this); + lp_vec_t rotRateTotalSusMgm = + lp_vec_t(sid.objectId, ROT_RATE_TOTAL_SUSMGM, this); + lp_vec_t rotRateTotalQuest = + lp_vec_t(sid.objectId, ROT_RATE_TOTAL_QUEST, this); + lp_vec_t rotRateTotalStr = lp_vec_t(sid.objectId, ROT_RATE_TOTAL_STR, this); + + private: +}; + +} // namespace acsctrl + +#endif /* MISSION_CONTROLLER_CONTROLLERDEFINITIONS_ACSCTRLDEFINITIONS_H_ */ diff --git a/mission/controller/controllerdefinitions/PowerCtrlDefinitions.h b/mission/controller/controllerdefinitions/PowerCtrlDefinitions.h new file mode 100644 index 0000000..1b378cd --- /dev/null +++ b/mission/controller/controllerdefinitions/PowerCtrlDefinitions.h @@ -0,0 +1,51 @@ +#ifndef MISSION_CONTROLLER_CONTROLLERDEFINITIONS_POWERCTRLDEFINITIONS_H_ +#define MISSION_CONTROLLER_CONTROLLERDEFINITIONS_POWERCTRLDEFINITIONS_H_ + +#include +#include +#include +#include + +#include + +namespace pwrctrl { + +enum SetIds : uint32_t { CORE_HK, ENABLE_PL }; + +enum PoolIds : lp_id_t { + TOTAL_BATTERY_CURRENT, + OPEN_CIRCUIT_VOLTAGE_CHARGE, + COULOMB_COUNTER_CHARGE, + PAYLOAD_FLAG +}; + +static constexpr uint8_t CORE_HK_ENTRIES = 3; +static constexpr uint8_t ENABLE_PL_ENTRIES = 1; + +class CoreHk : public StaticLocalDataSet { + public: + CoreHk(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, CORE_HK) {} + + lp_var_t totalBatteryCurrent = + lp_var_t(sid.objectId, TOTAL_BATTERY_CURRENT, this); + lp_var_t openCircuitVoltageCharge = + lp_var_t(sid.objectId, OPEN_CIRCUIT_VOLTAGE_CHARGE, this); + lp_var_t coulombCounterCharge = + lp_var_t(sid.objectId, COULOMB_COUNTER_CHARGE, this); + + private: +}; + +class EnablePl : public StaticLocalDataSet { + public: + EnablePl(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, ENABLE_PL) {} + EnablePl(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, ENABLE_PL)) {} + + lp_var_t plUseAllowed = lp_var_t(sid.objectId, PAYLOAD_FLAG, this); + + private: +}; + +} // namespace pwrctrl + +#endif /* MISSION_CONTROLLER_CONTROLLERDEFINITIONS_POWERCTRLDEFINITIONS_H_ */ diff --git a/mission/controller/tcsDefs.h b/mission/controller/tcsDefs.h new file mode 100644 index 0000000..f5d9d18 --- /dev/null +++ b/mission/controller/tcsDefs.h @@ -0,0 +1,352 @@ +#ifndef MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_ +#define MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_ + +#include +#include + +#include "eive/eventSubsystemIds.h" +#include "mission/tcs/defs.h" + +namespace tcsCtrl { + +/** + * NOP Limit: Hard limit for device, usually from datasheet. Device damage is possible lif NOP limit + * is exceeded. + * OP Limit: Soft limit. Device should be switched off or TCS controller should take action if the + * limit is exceeded to avoid reaching NOP limit + */ +struct TempLimits { + TempLimits(float nopLowerLimit, float opLowerLimit, float cutOffLimit, float opUpperLimit, + float nopUpperLimit) + : opLowerLimit(opLowerLimit), + opUpperLimit(opUpperLimit), + cutOffLimit(cutOffLimit), + nopLowerLimit(nopLowerLimit), + nopUpperLimit(nopUpperLimit) {} + float opLowerLimit; + float opUpperLimit; + float cutOffLimit; + float nopLowerLimit; + float nopUpperLimit; +}; + +/** + * Abstraction for the state of a single thermal component + */ +struct ThermalState { + uint8_t noSensorAvailableCounter; + // Which sensor is used for this component? + uint8_t sensorIndex = 0; + // Is heating on for that thermal module? + bool heating = false; + // Which switch is being used for heating the component + heater::Switch heaterSwitch = heater::Switch::HEATER_NONE; + // Heater start time and end times as UNIX seconds. Please note that these times will be updated + // when a switch command is sent, with no guarantess that the heater actually went on. + uint32_t heaterStartTime = 0; + uint32_t heaterEndTime = 0; +}; + +/** + * Abstraction for the state of a single heater. + */ +struct HeaterState { + bool switchTransition = false; + heater::SwitchState target = heater::SwitchState::OFF; + uint8_t heaterSwitchControlCycles = 0; + bool trackHeaterMaxBurnTime = false; + Countdown heaterOnMaxBurnTime; +}; + +using HeaterSwitchStates = std::array; + +enum ThermalComponents : uint8_t { + NONE = 0, + ACS_BOARD = 1, + MGT = 2, + RW = 3, + STR = 4, + IF_BOARD = 5, + TCS_BOARD = 6, + OBC = 7, + LEGACY_OBCIF_BOARD = 8, + SBAND_TRANSCEIVER = 9, + PCDUP60_BOARD = 10, + PCDUACU = 11, + PCDUPDU = 12, + PLPCDU_BOARD = 13, + PLOCMISSION_BOARD = 14, + PLOCPROCESSING_BOARD = 15, + DAC = 16, + CAMERA = 17, + DRO = 18, + X8 = 19, + HPA = 20, + TX = 21, + MPA = 22, + SCEX_BOARD = 23, + NUM_THERMAL_COMPONENTS +}; + +static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::TCS_CONTROLLER; +static constexpr Event NO_VALID_SENSOR_TEMPERATURE = MAKE_EVENT(0, severity::MEDIUM); +static constexpr Event NO_HEALTHY_HEATER_AVAILABLE = MAKE_EVENT(1, severity::MEDIUM); +static constexpr Event SYRLINKS_OVERHEATING = MAKE_EVENT(2, severity::HIGH); +static constexpr Event OBC_OVERHEATING = MAKE_EVENT(4, severity::HIGH); +static constexpr Event CAMERA_OVERHEATING = MAKE_EVENT(5, severity::HIGH); +static constexpr Event PCDU_SYSTEM_OVERHEATING = MAKE_EVENT(6, severity::HIGH); +static constexpr Event HEATER_NOT_OFF_FOR_OFF_MODE = MAKE_EVENT(7, severity::MEDIUM); +static constexpr Event MGT_OVERHEATING = MAKE_EVENT(8, severity::HIGH); +//! [EXPORT] : [COMMENT] P1: Module index. P2: Heater index +static constexpr Event TCS_SWITCHING_HEATER_ON = MAKE_EVENT(9, severity::INFO); +//! [EXPORT] : [COMMENT] P1: Module index. P2: Heater index +static constexpr Event TCS_SWITCHING_HEATER_OFF = MAKE_EVENT(10, severity::INFO); +//! [EXPORT] : [COMMENT] P1: Heater index. P2: Maximum burn time for heater. +static constexpr Event TCS_HEATER_MAX_BURN_TIME_REACHED = MAKE_EVENT(11, severity::MEDIUM); + +enum SetId : uint32_t { + SENSOR_TEMPERATURES = 0, + DEVICE_TEMPERATURES = 1, + SUS_TEMPERATURES = 2, + COMPONENT_TEMPERATURES = 3, + HEATER_SET = 4, + TCS_CTRL_INFO = 5 +}; + +enum PoolIds : lp_id_t { + SENSOR_PLOC_HEATSPREADER, + SENSOR_PLOC_MISSIONBOARD, + SENSOR_4K_CAMERA, + SENSOR_DAC_HEATSPREADER, + SENSOR_STARTRACKER, + SENSOR_RW1, + SENSOR_DRO, + SENSOR_SCEX, + SENSOR_X8, + SENSOR_HPA, + SENSOR_TX_MODUL, + SENSOR_MPA, + SENSOR_ACU, + SENSOR_PLPCDU_HEATSPREADER, + SENSOR_TCS_BOARD, + SENSOR_MAGNETTORQUER, + SENSOR_TMP1075_TCS_0, + SENSOR_TMP1075_TCS_1, + SENSOR_TMP1075_PLPCDU_0, + SENSOR_TMP1075_PLPCDU_1, + SENSOR_TMP1075_IF_BOARD, + + SUS_0_N_LOC_XFYFZM_PT_XF, + SUS_6_R_LOC_XFYBZM_PT_XF, + SUS_1_N_LOC_XBYFZM_PT_XB, + SUS_7_R_LOC_XBYBZM_PT_XB, + SUS_2_N_LOC_XFYBZB_PT_YB, + SUS_8_R_LOC_XBYBZB_PT_YB, + SUS_3_N_LOC_XFYBZF_PT_YF, + SUS_9_R_LOC_XBYBZB_PT_YF, + SUS_4_N_LOC_XMYFZF_PT_ZF, + SUS_10_N_LOC_XMYBZF_PT_ZF, + SUS_5_N_LOC_XFYMZB_PT_ZB, + SUS_11_R_LOC_XBYMZB_PT_ZB, + + COMPONENT_RW, + + TEMP_Q7S, + BATTERY_TEMP_1, + BATTERY_TEMP_2, + BATTERY_TEMP_3, + BATTERY_TEMP_4, + TEMP_RW1, + TEMP_RW2, + TEMP_RW3, + TEMP_RW4, + TEMP_STAR_TRACKER, + TEMP_SYRLINKS_POWER_AMPLIFIER, + TEMP_SYRLINKS_BASEBAND_BOARD, + TEMP_MGT, + TEMP_ACU, + TEMP_PDU1, + TEMP_PDU2, + TEMP_1_P60DOCK, + TEMP_2_P60DOCK, + TEMP_GYRO_0_SIDE_A, + TEMP_GYRO_1_SIDE_A, + TEMP_GYRO_2_SIDE_B, + TEMP_GYRO_3_SIDE_B, + TEMP_MGM_0_SIDE_A, + TEMP_MGM_2_SIDE_B, + TEMP_ADC_PAYLOAD_PCDU, + + HEATER_SWITCH_LIST, + HEATER_CURRENT, + + HEATER_ON_FOR_COMPONENT_VEC, + SENSOR_USED_FOR_TCS_CTRL, + HEATER_IDX_USED_FOR_TCS_CTRL, + HEATER_START_TIME, + HEATER_END_TIME +}; + +static const uint8_t ENTRIES_SENSOR_TEMPERATURE_SET = 25; +static const uint8_t ENTRIES_DEVICE_TEMPERATURE_SET = 25; +static const uint8_t ENTRIES_SUS_TEMPERATURE_SET = 12; + +/** + * @brief This dataset can be used to store the collected temperatures of all temperature sensors + */ +class SensorTemperatures : public StaticLocalDataSet { + public: + explicit SensorTemperatures(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, SENSOR_TEMPERATURES) {} + + explicit SensorTemperatures(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, SENSOR_TEMPERATURES)) {} + + lp_var_t plocHeatspreader = + lp_var_t(sid.objectId, PoolIds::SENSOR_PLOC_HEATSPREADER, this); + lp_var_t plocMissionboard = + lp_var_t(sid.objectId, PoolIds::SENSOR_PLOC_MISSIONBOARD, this); + lp_var_t payload4kCamera = lp_var_t(sid.objectId, PoolIds::SENSOR_4K_CAMERA, this); + lp_var_t dacHeatspreader = + lp_var_t(sid.objectId, PoolIds::SENSOR_DAC_HEATSPREADER, this); + lp_var_t startracker = lp_var_t(sid.objectId, PoolIds::SENSOR_STARTRACKER, this); + lp_var_t rw1 = lp_var_t(sid.objectId, PoolIds::SENSOR_RW1, this); + lp_var_t scex = lp_var_t(sid.objectId, PoolIds::SENSOR_SCEX, this); + lp_var_t eBandTx = lp_var_t(sid.objectId, PoolIds::SENSOR_TX_MODUL, this); + // E-Band module + lp_var_t dro = lp_var_t(sid.objectId, PoolIds::SENSOR_DRO, this); + lp_var_t mpa = lp_var_t(sid.objectId, PoolIds::SENSOR_MPA, this); + lp_var_t x8 = lp_var_t(sid.objectId, PoolIds::SENSOR_X8, this); + lp_var_t hpa = lp_var_t(sid.objectId, PoolIds::SENSOR_HPA, this); + lp_var_t acu = lp_var_t(sid.objectId, PoolIds::SENSOR_ACU, this); + lp_var_t plpcduHeatspreader = + lp_var_t(sid.objectId, PoolIds::SENSOR_PLPCDU_HEATSPREADER, this); + lp_var_t tcsBoard = lp_var_t(sid.objectId, PoolIds::SENSOR_TCS_BOARD, this); + lp_var_t mgt = lp_var_t(sid.objectId, PoolIds::SENSOR_MAGNETTORQUER, this); + lp_var_t tmp1075Tcs0 = lp_var_t(sid.objectId, PoolIds::SENSOR_TMP1075_TCS_0, this); + lp_var_t tmp1075Tcs1 = lp_var_t(sid.objectId, PoolIds::SENSOR_TMP1075_TCS_1, this); + lp_var_t tmp1075PlPcdu0 = + lp_var_t(sid.objectId, PoolIds::SENSOR_TMP1075_PLPCDU_0, this); + lp_var_t tmp1075PlPcdu1 = + lp_var_t(sid.objectId, PoolIds::SENSOR_TMP1075_PLPCDU_1, this); + lp_var_t tmp1075IfBrd = + lp_var_t(sid.objectId, PoolIds::SENSOR_TMP1075_IF_BOARD, this); +}; + +/** + * @brief This dataset can be used to store the collected temperatures of all device temperature + * sensors + */ +class DeviceTemperatures : public StaticLocalDataSet { + public: + explicit DeviceTemperatures(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, DEVICE_TEMPERATURES) {} + + explicit DeviceTemperatures(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, DEVICE_TEMPERATURES)) {} + + lp_var_t q7s = lp_var_t(sid.objectId, PoolIds::TEMP_Q7S, this); + lp_var_t batteryTemp1 = lp_var_t(sid.objectId, PoolIds::BATTERY_TEMP_1, this); + lp_var_t batteryTemp2 = lp_var_t(sid.objectId, PoolIds::BATTERY_TEMP_2, this); + lp_var_t batteryTemp3 = lp_var_t(sid.objectId, PoolIds::BATTERY_TEMP_3, this); + lp_var_t batteryTemp4 = lp_var_t(sid.objectId, PoolIds::BATTERY_TEMP_4, this); + lp_var_t rw1 = lp_var_t(sid.objectId, PoolIds::TEMP_RW1, this); + lp_var_t rw2 = lp_var_t(sid.objectId, PoolIds::TEMP_RW2, this); + lp_var_t rw3 = lp_var_t(sid.objectId, PoolIds::TEMP_RW3, this); + lp_var_t rw4 = lp_var_t(sid.objectId, PoolIds::TEMP_RW4, this); + lp_var_t startracker = lp_var_t(sid.objectId, PoolIds::TEMP_STAR_TRACKER, this); + lp_var_t syrlinksPowerAmplifier = + lp_var_t(sid.objectId, PoolIds::TEMP_SYRLINKS_POWER_AMPLIFIER, this); + lp_var_t syrlinksBasebandBoard = + lp_var_t(sid.objectId, PoolIds::TEMP_SYRLINKS_BASEBAND_BOARD, this); + lp_var_t mgt = lp_var_t(sid.objectId, PoolIds::TEMP_MGT, this); + lp_vec_t acu = lp_vec_t(sid.objectId, PoolIds::TEMP_ACU, this); + lp_var_t pdu1 = lp_var_t(sid.objectId, PoolIds::TEMP_PDU1, this); + lp_var_t pdu2 = lp_var_t(sid.objectId, PoolIds::TEMP_PDU2, this); + lp_var_t temp1P60dock = lp_var_t(sid.objectId, PoolIds::TEMP_1_P60DOCK, this); + lp_var_t temp2P60dock = lp_var_t(sid.objectId, PoolIds::TEMP_2_P60DOCK, this); + lp_var_t gyro0SideA = lp_var_t(sid.objectId, PoolIds::TEMP_GYRO_0_SIDE_A, this); + lp_var_t gyro1SideA = lp_var_t(sid.objectId, PoolIds::TEMP_GYRO_1_SIDE_A, this); + lp_var_t gyro2SideB = lp_var_t(sid.objectId, PoolIds::TEMP_GYRO_2_SIDE_B, this); + lp_var_t gyro3SideB = lp_var_t(sid.objectId, PoolIds::TEMP_GYRO_3_SIDE_B, this); + lp_var_t mgm0SideA = lp_var_t(sid.objectId, PoolIds::TEMP_MGM_0_SIDE_A, this); + lp_var_t mgm2SideB = lp_var_t(sid.objectId, PoolIds::TEMP_MGM_2_SIDE_B, this); + lp_var_t adcPayloadPcdu = + lp_var_t(sid.objectId, PoolIds::TEMP_ADC_PAYLOAD_PCDU, this); +}; + +/** + * @brief This dataset can be used to store the collected temperatures of all SUS temperature + * sensors + */ +class SusTemperatures : public StaticLocalDataSet { + public: + explicit SusTemperatures(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, SUS_TEMPERATURES) {} + + explicit SusTemperatures(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, SUS_TEMPERATURES)) {} + + lp_var_t sus_0_n_loc_xfyfzm_pt_xf = + lp_var_t(sid.objectId, PoolIds::SUS_0_N_LOC_XFYFZM_PT_XF, this); + lp_var_t sus_6_r_loc_xfybzm_pt_xf = + lp_var_t(sid.objectId, PoolIds::SUS_6_R_LOC_XFYBZM_PT_XF, this); + lp_var_t sus_1_n_loc_xbyfzm_pt_xb = + lp_var_t(sid.objectId, PoolIds::SUS_1_N_LOC_XBYFZM_PT_XB, this); + lp_var_t sus_7_r_loc_xbybzm_pt_xb = + lp_var_t(sid.objectId, PoolIds::SUS_7_R_LOC_XBYBZM_PT_XB, this); + lp_var_t sus_2_n_loc_xfybzb_pt_yb = + lp_var_t(sid.objectId, PoolIds::SUS_2_N_LOC_XFYBZB_PT_YB, this); + lp_var_t sus_8_r_loc_xbybzb_pt_yb = + lp_var_t(sid.objectId, PoolIds::SUS_8_R_LOC_XBYBZB_PT_YB, this); + lp_var_t sus_3_n_loc_xfybzf_pt_yf = + lp_var_t(sid.objectId, PoolIds::SUS_3_N_LOC_XFYBZF_PT_YF, this); + lp_var_t sus_9_r_loc_xbybzb_pt_yf = + lp_var_t(sid.objectId, PoolIds::SUS_9_R_LOC_XBYBZB_PT_YF, this); + lp_var_t sus_4_n_loc_xmyfzf_pt_zf = + lp_var_t(sid.objectId, PoolIds::SUS_4_N_LOC_XMYFZF_PT_ZF, this); + lp_var_t sus_10_n_loc_xmybzf_pt_zf = + lp_var_t(sid.objectId, PoolIds::SUS_10_N_LOC_XMYBZF_PT_ZF, this); + lp_var_t sus_5_n_loc_xfymzb_pt_zb = + lp_var_t(sid.objectId, PoolIds::SUS_5_N_LOC_XFYMZB_PT_ZB, this); + lp_var_t sus_11_r_loc_xbymzb_pt_zb = + lp_var_t(sid.objectId, PoolIds::SUS_11_R_LOC_XBYMZB_PT_ZB, this); +}; + +class HeaterInfo : public StaticLocalDataSet<3> { + public: + HeaterInfo(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, HEATER_SET) {} + HeaterInfo(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, HEATER_SET)) {} + + lp_vec_t heaterSwitchState = + lp_vec_t(sid.objectId, PoolIds::HEATER_SWITCH_LIST, + this); + lp_var_t heaterCurrent = lp_var_t(sid.objectId, PoolIds::HEATER_CURRENT, this); +}; + +class TcsCtrlInfo : public StaticLocalDataSet<6> { + public: + explicit TcsCtrlInfo(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, TCS_CTRL_INFO) {} + + explicit TcsCtrlInfo(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, TCS_CTRL_INFO)) {} + + lp_vec_t heatingOnVec = + lp_vec_t( + sid.objectId, PoolIds::HEATER_ON_FOR_COMPONENT_VEC, this); + lp_vec_t sensorIdxUsedForTcsCtrl = + lp_vec_t(sid.objectId, + PoolIds::SENSOR_USED_FOR_TCS_CTRL, this); + lp_vec_t heaterSwitchIdx = + lp_vec_t( + sid.objectId, PoolIds::HEATER_IDX_USED_FOR_TCS_CTRL, this); + lp_vec_t heaterStartTimes = + lp_vec_t(sid.objectId, PoolIds::HEATER_START_TIME, + this); + lp_vec_t heaterEndTimes = + lp_vec_t(sid.objectId, PoolIds::HEATER_END_TIME, + this); +}; + +} // namespace tcsCtrl + +#endif /* MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_ */ diff --git a/mission/genericFactory.cpp b/mission/genericFactory.cpp new file mode 100644 index 0000000..c5a69b2 --- /dev/null +++ b/mission/genericFactory.cpp @@ -0,0 +1,409 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OBSWConfig.h" +#include "devices/gpioIds.h" +#include "eive/definitions.h" +#include "fsfw/pus/Service11TelecommandScheduling.h" +#include "mission/system/acs/RwAssembly.h" +#include "mission/system/acs/acsModeTree.h" +#include "mission/system/tcs/tcsModeTree.h" +#include "mission/tcs/defs.h" +#include "mission/tmtc/Service15TmStorage.h" +#include "mission/tmtc/tmFilters.h" +#include "objects/systemObjectList.h" +#include "tmtc/pusIds.h" + +using persTmStore::PersistentTmStores; + +#if OBSW_ADD_TCPIP_SERVERS == 1 +#if OBSW_ADD_TMTC_UDP_SERVER == 1 +// UDP server includes +#include +#include +#endif +#if OBSW_ADD_TMTC_TCP_SERVER == 1 +// TCP server includes +#include +#include +#endif +#endif + +#if OBSW_ADD_TEST_CODE == 1 +#include +#endif + +#ifndef OBSW_TM_TO_PTME +#define OBSW_TM_TO_PTME 0 +#endif + +namespace cfdp { + +PacketInfoList<64> PACKET_LIST; +LostSegmentsList<128> LOST_SEGMENTS; +EntityId REMOTE_CFDP_ID(UnsignedByteField(config::EIVE_GROUND_CFDP_ENTITY_ID)); +RemoteEntityCfg GROUND_REMOTE_CFG(REMOTE_CFDP_ID); +OneRemoteConfigProvider REMOTE_CFG_PROVIDER(GROUND_REMOTE_CFG); +HostFilesystem HOST_FS; + +} // namespace cfdp + +std::atomic_bool tcs::TCS_BOARD_SHORTLY_UNAVAILABLE = false; +std::atomic_bool core::SAVE_PUS_SEQUENCE_COUNT = false; +std::atomic_bool core::SAVE_CFDP_SEQUENCE_COUNT = false; + +void ObjectFactory::produceGenericObjects(HealthTableIF** healthTable_, PusTmFunnel** pusFunnel, + CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan, + StorageManagerIF** ipcStore, StorageManagerIF** tmStore, + PersistentTmStores& stores, + uint32_t eventManagerQueueDepth, bool enableHkSets, + bool routeToPersistentStores) { + // Framework objects + new EventManager(objects::EVENT_MANAGER, eventManagerQueueDepth); + auto healthTable = new HealthTable(objects::HEALTH_TABLE); + if (healthTable_ != nullptr) { + *healthTable_ = healthTable; + } + new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER, 5, enableHkSets, 120); + new VerificationReporter(); + auto* timeStamper = new CdsShortTimeStamper(objects::TIME_STAMPER); + StorageManagerIF* tcStore; + { + PoolManager::LocalPoolConfig poolCfg = {{250, 16}, {250, 32}, {250, 64}, + {150, 128}, {120, 1200}, {120, 2048}}; + tcStore = new PoolManager(objects::TC_STORE, poolCfg); + } + + { + PoolManager::LocalPoolConfig poolCfg = {{600, 32}, {400, 64}, {400, 128}, + {400, 512}, {800, 1200}, {150, 2048}}; + *tmStore = new PoolManager(objects::TM_STORE, poolCfg); + } + + { + PoolManager::LocalPoolConfig poolCfg = {{300, 16}, {250, 32}, {150, 64}, {150, 128}, + {100, 256}, {50, 512}, {50, 1200}, {10, 2048}}; + *ipcStore = new PoolManager(objects::IPC_STORE, poolCfg); + } + PoolManager::LocalPoolConfig poolCfg = {{300, 32}, {400, 64}, {250, 128}, + {150, 512}, {400, 1200}, {150, 2048}}; + auto* ramToFileStore = new PoolManager(objects::DOWNLINK_RAM_STORE, poolCfg); + +#if OBSW_ADD_TCPIP_SERVERS == 1 +#if OBSW_ADD_TMTC_UDP_SERVER == 1 + auto udpBridge = new UdpTmTcBridge(objects::UDP_TMTC_SERVER, objects::CCSDS_PACKET_DISTRIBUTOR, + config::UDP_MSG_QUEUE_DEPTH); + new UdpTcPollingTask(objects::UDP_TMTC_POLLING_TASK, objects::UDP_TMTC_SERVER); + sif::info << "Created UDP server for TMTC commanding with listener port " + << udpBridge->getUdpPort() << std::endl; + udpBridge->setMaxNumberOfPacketsStored(config::UDP_MAX_STORED_CMDS); +#endif +#if OBSW_ADD_TMTC_TCP_SERVER == 1 + auto tcpBridge = new TcpTmTcBridge(objects::TCP_TMTC_SERVER, objects::CCSDS_PACKET_DISTRIBUTOR, + config::TCP_MSG_QUEUE_DEPTH); + TcpTmTcServer::TcpConfig cfg(true, true); + auto tcpServer = new TcpTmTcServer(objects::TCP_TMTC_POLLING_TASK, objects::TCP_TMTC_SERVER, cfg); + // TCP is stream based. Use packet ID as start marker when parsing for space packets + tcpServer->setSpacePacketParsingOptions({common::PUS_PACKET_ID, common::CFDP_PACKET_ID}); + sif::info << "Created TCP server for TMTC commanding with listener port " + << tcpServer->getTcpPort() << std::endl; + tcpBridge->setMaxNumberOfPacketsStored(config::TCP_MAX_STORED_CMDS); + tcpBridge->setNumberOfSentPacketsPerCycle(config::TCP_MAX_NUMBER_TMS_SENT_PER_CYCLE); +#endif /* OBSW_USE_TMTC_TCP_BRIDGE == 0 */ +#endif /* OBSW_ADD_TCPIP_BRIDGE == 1 */ + + auto* ccsdsDistrib = + new CcsdsDistributor(config::EIVE_PUS_APID, objects::CCSDS_PACKET_DISTRIBUTOR); + new PusDistributor(config::EIVE_PUS_APID, objects::PUS_PACKET_DISTRIBUTOR, ccsdsDistrib); + + PusTmFunnel::FunnelCfg pusFunnelCfg(objects::PUS_TM_FUNNEL, "PusTmFunnel", **tmStore, **ipcStore, + config::MAX_PUS_FUNNEL_QUEUE_DEPTH, sdcMan, + config::PUS_SEQUENCE_COUNT_FILE, + core::SAVE_PUS_SEQUENCE_COUNT); + // The PUS funnel routes all live TM to the live destinations and to the TM stores. + *pusFunnel = new PusTmFunnel(pusFunnelCfg, *ramToFileStore, *timeStamper); + + // MISC store and PUS funnel to MISC store routing + { + PersistentTmStoreArgs storeArgs(objects::MISC_TM_STORE, "tm", "misc", + + RolloverInterval::HOURLY, 2, *ramToFileStore, sdcMan); + stores.miscStore = + new PersistentTmStoreWithTmQueue(storeArgs, "MISC STORE", config::MISC_STORE_QUEUE_SIZE); + (*pusFunnel) + ->addPersistentTmStoreRouting(filters::miscFilter(), + stores.miscStore->getReportReceptionQueue(0)); + } + + // OK store and PUS Funnel to OK store routing + { + PersistentTmStoreArgs storeArgs(objects::OK_TM_STORE, "tm", "ok", RolloverInterval::MINUTELY, + 30, *ramToFileStore, sdcMan); + stores.okStore = + new PersistentTmStoreWithTmQueue(storeArgs, "OK STORE", config::OK_STORE_QUEUE_SIZE); + (*pusFunnel) + ->addPersistentTmStoreRouting(filters::okFilter(), + stores.okStore->getReportReceptionQueue(0)); + } + + // NOT OK store and PUS funnel to NOT OK store routing + { + PersistentTmStoreArgs storeArgs(objects::NOT_OK_TM_STORE, "tm", "nok", + RolloverInterval::MINUTELY, 30, *ramToFileStore, sdcMan); + stores.notOkStore = + new PersistentTmStoreWithTmQueue(storeArgs, "NOT OK STORE", config::NOK_STORE_QUEUE_SIZE); + (*pusFunnel) + ->addPersistentTmStoreRouting(filters::notOkFilter(), + stores.notOkStore->getReportReceptionQueue(0)); + } + + // HK store and PUS funnel to HK store routing + { + PersistentTmStoreArgs storeArgs(objects::HK_TM_STORE, "tm", "hk", RolloverInterval::MINUTELY, 2, + *ramToFileStore, sdcMan); + stores.hkStore = + new PersistentTmStoreWithTmQueue(storeArgs, "HK STORE", config::HK_STORE_QUEUE_SIZE); + (*pusFunnel) + ->addPersistentTmStoreRouting(filters::hkFilter(), + stores.hkStore->getReportReceptionQueue(0)); + } + + // CFDP store and PUS funnel to CFDP store routing + { + PersistentTmStoreArgs storeArgs(objects::CFDP_TM_STORE, "tm", "cfdp", + RolloverInterval::MINUTELY, 30, *ramToFileStore, sdcMan); + stores.cfdpStore = + new PersistentTmStoreWithTmQueue(storeArgs, "CFDP STORE", config::CFDP_STORE_QUEUE_SIZE); + + (*pusFunnel) + ->addPersistentTmStoreRouting(filters::cfdpFilter(), + stores.cfdpStore->getReportReceptionQueue(0)); + } + PusTmFunnel::FunnelCfg cfdpFunnelCfg(objects::CFDP_TM_FUNNEL, "CfdpTmFunnel", **tmStore, + **ipcStore, config::MAX_CFDP_FUNNEL_QUEUE_DEPTH, sdcMan, + config::CFDP_SEQUENCE_COUNT_FILE, + core::SAVE_CFDP_SEQUENCE_COUNT); + std::optional fileStoreDest{}; + if (routeToPersistentStores) { + fileStoreDest = stores.cfdpStore->getReportReceptionQueue(0); + } + *cfdpFunnel = + new CfdpTmFunnel(cfdpFunnelCfg, fileStoreDest, *ramToFileStore, config::EIVE_CFDP_APID); + +#if OBSW_ADD_TCPIP_SERVERS == 1 +#if OBSW_ADD_TMTC_UDP_SERVER == 1 + (*cfdpFunnel)->addLiveDestination("UDP Server", *udpBridge, 0); + (*pusFunnel)->addLiveDestination("UDP Server", *udpBridge, 0); +#endif +#if OBSW_ADD_TMTC_TCP_SERVER == 1 + (*cfdpFunnel)->addLiveDestination("TCP Server", *tcpBridge, 0); + (*pusFunnel)->addLiveDestination("TCP Server", *tcpBridge, 0); +#endif +#endif + + // PUS service stack + new Service1TelecommandVerification(objects::PUS_SERVICE_1_VERIFICATION, config::EIVE_PUS_APID, + pus::PUS_SERVICE_1, objects::PUS_TM_FUNNEL, + config::VERIFICATION_SERVICE_QUEUE_DEPTH); + new Service2DeviceAccess(objects::PUS_SERVICE_2_DEVICE_ACCESS, config::EIVE_PUS_APID, + pus::PUS_SERVICE_2, 3, 10); + new Service3Housekeeping(objects::PUS_SERVICE_3_HOUSEKEEPING, config::EIVE_PUS_APID, + pus::PUS_SERVICE_3, config::HK_SERVICE_QUEUE_DEPTH, 16); + + auto psbParamsService5 = + PsbParams(objects::PUS_SERVICE_5_EVENT_REPORTING, config::EIVE_PUS_APID, pus::PUS_SERVICE_5); + psbParamsService5.requestQueueDepth = 50; + psbParamsService5.maxPacketsPerCycle = 50; + new Service5EventReporting(psbParamsService5, 80, 160); + new Service8FunctionManagement(objects::PUS_SERVICE_8_FUNCTION_MGMT, config::EIVE_PUS_APID, + pus::PUS_SERVICE_8, config::ACTION_SERVICE_QUEUE_DEPTH, 16, 60); + new Service9TimeManagement( + PsbParams(objects::PUS_SERVICE_9_TIME_MGMT, config::EIVE_PUS_APID, pus::PUS_SERVICE_9)); + + auto psbParamsService11 = + PsbParams(objects::PUS_SERVICE_11_TC_SCHEDULER, config::EIVE_PUS_APID, pus::PUS_SERVICE_11); + psbParamsService11.requestQueueDepth = 100; + psbParamsService11.maxPacketsPerCycle = 100; + new Service11TelecommandScheduling(psbParamsService11, + ccsdsDistrib); + + new Service15TmStorage(objects::PUS_SERVICE_15_TM_STORAGE, config::EIVE_PUS_APID, 10); + auto psbParamsService17 = + PsbParams(objects::PUS_SERVICE_17_TEST, config::EIVE_PUS_APID, pus::PUS_SERVICE_17); + psbParamsService17.requestQueueDepth = 50; + psbParamsService17.maxPacketsPerCycle = 50; + new Service17Test(psbParamsService17); + new Service20ParameterManagement(objects::PUS_SERVICE_20_PARAMETERS, config::EIVE_PUS_APID, + pus::PUS_SERVICE_20); + new CService200ModeCommanding(objects::PUS_SERVICE_200_MODE_MGMT, config::EIVE_PUS_APID, + pus::PUS_SERVICE_200, 8); + HealthServiceCfg healthCfg(objects::PUS_SERVICE_201_HEALTH, config::EIVE_PUS_APID, + objects::HEALTH_TABLE, 20); + new CServiceHealthCommanding(healthCfg); + +#if OBSW_ADD_CFDP_COMPONENTS == 1 + using namespace cfdp; + + MessageQueueIF* cfdpMsgQueue = QueueFactory::instance()->createMessageQueue(32); + CfdpDistribCfg distribCfg(objects::CFDP_DISTRIBUTOR, *tcStore, cfdpMsgQueue); + new CfdpDistributor(distribCfg); + + auto* tmtcQueue = QueueFactory::instance()->createMessageQueue(32); + auto* cfdpQueue = QueueFactory::instance()->createMessageQueue(16); + auto eiveUserHandler = new cfdp::EiveUserHandler(HOST_FS, **ipcStore, cfdpQueue->getId()); + FsfwHandlerParams params(objects::CFDP_HANDLER, HOST_FS, **cfdpFunnel, *tcStore, **tmStore, + **ipcStore, *tmtcQueue, *cfdpQueue); + cfdp::IndicationCfg indicationCfg; + UnsignedByteField apid(config::EIVE_LOCAL_CFDP_ENTITY_ID); + cfdp::EntityId localId(apid); + GROUND_REMOTE_CFG.defaultChecksum = cfdp::ChecksumType::CRC_32; + GROUND_REMOTE_CFG.maxFileSegmentLen = config::CFDP_MAX_FILE_SEGMENT_LEN; + auto eiveFaultHandler = new EiveFaultHandler(objects::CFDP_FAULT_HANDLER); + CfdpHandlerCfg cfdpCfg(localId, indicationCfg, *eiveUserHandler, *eiveFaultHandler, PACKET_LIST, + LOST_SEGMENTS, REMOTE_CFG_PROVIDER); + auto* cfdpHandler = new CfdpHandler(params, cfdpCfg, signals::CFDP_CHANNEL_THROTTLE_SIGNAL); + // All CFDP packets arrive wrapped inside CCSDS space packets + CcsdsDistributorIF::DestInfo info("CFDP Destination", config::EIVE_CFDP_APID, + cfdpHandler->getRequestQueue(), true); + ccsdsDistrib->registerApplication(info); +#endif +} + +void ObjectFactory::createGenericHeaterComponents(GpioIF& gpioIF, PowerSwitchIF& pwrSwitcher, + HeaterHandler*& heaterHandler) { + HeaterHelper helper({{ + {new HeaterHealthDev(objects::HEATER_0_PLOC_PROC_BRD, MessageQueueIF::NO_QUEUE), + gpioIds::HEATER_0}, + {new HeaterHealthDev(objects::HEATER_1_PCDU_BRD, MessageQueueIF::NO_QUEUE), + gpioIds::HEATER_1}, + {new HeaterHealthDev(objects::HEATER_2_ACS_BRD, MessageQueueIF::NO_QUEUE), gpioIds::HEATER_2}, + {new HeaterHealthDev(objects::HEATER_3_OBC_BRD, MessageQueueIF::NO_QUEUE), gpioIds::HEATER_3}, + {new HeaterHealthDev(objects::HEATER_4_CAMERA, MessageQueueIF::NO_QUEUE), gpioIds::HEATER_4}, + {new HeaterHealthDev(objects::HEATER_5_STR, MessageQueueIF::NO_QUEUE), gpioIds::HEATER_5}, + {new HeaterHealthDev(objects::HEATER_6_DRO, MessageQueueIF::NO_QUEUE), gpioIds::HEATER_6}, + {new HeaterHealthDev(objects::HEATER_7_SYRLINKS, MessageQueueIF::NO_QUEUE), + gpioIds::HEATER_7}, + }}); + heaterHandler = new HeaterHandler(objects::HEATER_HANDLER, &gpioIF, helper, &pwrSwitcher, + power::Switches::PDU2_CH3_TCS_BOARD_HEATER_IN_8V); + heaterHandler->connectModeTreeParent(satsystem::tcs::SUBSYSTEM); +} + +void ObjectFactory::createThermalController(HeaterHandler& heaterHandler, bool pollPlPcduTmp1) { + auto* tcsCtrl = new ThermalController(objects::THERMAL_CONTROLLER, heaterHandler, + tcs::TCS_BOARD_SHORTLY_UNAVAILABLE, pollPlPcduTmp1); + tcsCtrl->connectModeTreeParent(satsystem::tcs::SUBSYSTEM); +} +void ObjectFactory::createRwAssy(PowerSwitchIF& pwrSwitcher, power::Switch_t theSwitch, + std::array rws, + std::array rwIds) { + RwHelper rwHelper(rwIds); + auto* rwAss = new RwAssembly(objects::RW_ASSY, &pwrSwitcher, theSwitch, rwHelper); + for (size_t idx = 0; idx < rwIds.size(); idx++) { + ReturnValue_t result = rws[idx]->connectModeTreeParent(*rwAss); + if (result != returnvalue::OK) { + sif::error << "Connecting RW " << static_cast(idx) << " to RW assembly failed" + << std::endl; + } + } + rwAss->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); +} + +void ObjectFactory::createSusAssy(PowerSwitchIF& pwrSwitcher, + std::array suses) { + std::array susIds = { + objects::SUS_0_N_LOC_XFYFZM_PT_XF, objects::SUS_1_N_LOC_XBYFZM_PT_XB, + objects::SUS_2_N_LOC_XFYBZB_PT_YB, objects::SUS_3_N_LOC_XFYBZF_PT_YF, + objects::SUS_4_N_LOC_XMYFZF_PT_ZF, objects::SUS_5_N_LOC_XFYMZB_PT_ZB, + objects::SUS_6_R_LOC_XFYBZM_PT_XF, objects::SUS_7_R_LOC_XBYBZM_PT_XB, + objects::SUS_8_R_LOC_XBYBZB_PT_YB, objects::SUS_9_R_LOC_XBYBZB_PT_YF, + objects::SUS_10_N_LOC_XMYBZF_PT_ZF, objects::SUS_11_R_LOC_XBYMZB_PT_ZB}; + auto susAssHelper = SusAssHelper(susIds); + auto susAss = new SusAssembly(objects::SUS_BOARD_ASS, &pwrSwitcher, susAssHelper); + for (auto& sus : suses) { + if (sus != nullptr) { + ReturnValue_t result = sus->connectModeTreeParent(*susAss); + if (result != returnvalue::OK) { + sif::error << "Connecting SUS " << sus->getObjectId() << " to SUS assembly failed" + << std::endl; + } + } + } + susAss->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); +} + +void ObjectFactory::createAcsBoardAssy(PowerSwitchIF& pwrSwitcher, + std::array assemblyDhbs, + ExtendedControllerBase* gpsCtrl, GpioIF* gpioComIF) { + AcsBoardHelper acsBoardHelper = AcsBoardHelper( + objects::MGM_0_LIS3_HANDLER, objects::MGM_1_RM3100_HANDLER, objects::MGM_2_LIS3_HANDLER, + objects::MGM_3_RM3100_HANDLER, objects::GYRO_0_ADIS_HANDLER, objects::GYRO_1_L3G_HANDLER, + objects::GYRO_2_ADIS_HANDLER, objects::GYRO_3_L3G_HANDLER, objects::GPS_CONTROLLER, + objects::GPS_0_HEALTH_DEV, objects::GPS_1_HEALTH_DEV); + auto acsAss = + new AcsBoardAssembly(objects::ACS_BOARD_ASS, &pwrSwitcher, acsBoardHelper, gpioComIF); + for (auto& assChild : assemblyDhbs) { + ReturnValue_t result = assChild->connectModeTreeParent(*acsAss); + if (result != returnvalue::OK) { + sif::error << "Connecting assembly for ACS board component " << assChild->getObjectId() + << " failed" << std::endl; + } + } + gpsCtrl->connectModeTreeParent(*acsAss); + auto* gps0HealthDev = new HealthDevice(objects::GPS_0_HEALTH_DEV, acsAss->getCommandQueue()); + auto* gps1HealthDev = new HealthDevice(objects::GPS_1_HEALTH_DEV, acsAss->getCommandQueue()); + acsAss->registerChild(objects::GPS_0_HEALTH_DEV, gps0HealthDev->getCommandQueue()); + acsAss->registerChild(objects::GPS_1_HEALTH_DEV, gps1HealthDev->getCommandQueue()); + acsAss->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); +} + +TcsBoardAssembly* ObjectFactory::createTcsBoardAssy(PowerSwitchIF& pwrSwitcher, + std::atomic_bool& tcsShortlyUnavailable) { + TcsBoardHelper helper(RTD_INFOS); + auto* tcsBoardAss = + new TcsBoardAssembly(objects::TCS_BOARD_ASS, &pwrSwitcher, + power::Switches::PDU1_CH0_TCS_BOARD_3V3, helper, tcsShortlyUnavailable); + tcsBoardAss->connectModeTreeParent(satsystem::tcs::SUBSYSTEM); + return tcsBoardAss; +} diff --git a/mission/genericFactory.h b/mission/genericFactory.h new file mode 100644 index 0000000..f6c2bf1 --- /dev/null +++ b/mission/genericFactory.h @@ -0,0 +1,64 @@ +#ifndef MISSION_CORE_GENERICFACTORY_H_ +#define MISSION_CORE_GENERICFACTORY_H_ + +#include +#include +#include +#include +#include +#include + +#include "fsfw/objectmanager/SystemObjectIF.h" +#include "fsfw/power/PowerSwitchIF.h" +#include "fsfw_hal/common/gpio/GpioIF.h" + +using persTmStore::PersistentTmStores; + +class HeaterHandler; +class HealthTableIF; +class PusTmFunnel; +class CfdpTmFunnel; +class ExtendedControllerBase; +class TcsBoardAssembly; + +const std::array, EiveMax31855::NUM_RTDS> RTD_INFOS = {{ + {objects::RTD_0_IC3_PLOC_HEATSPREADER, "RTD_0_PLOC_HSPD"}, + {objects::RTD_1_IC4_PLOC_MISSIONBOARD, "RTD_1_PLOC_MISSIONBRD"}, + {objects::RTD_2_IC5_4K_CAMERA, "RTD_2_4K_CAMERA"}, + {objects::RTD_3_IC6_DAC_HEATSPREADER, "RTD_3_DAC_HSPD"}, + {objects::RTD_4_IC7_STARTRACKER, "RTD_4_STARTRACKER"}, + {objects::RTD_5_IC8_RW1_MX_MY, "RTD_5_RW1_MX_MY"}, + {objects::RTD_6_IC9_DRO, "RTD_6_DRO"}, + {objects::RTD_7_IC10_SCEX, "RTD_7_SCEX"}, + {objects::RTD_8_IC11_X8, "RTD_8_X8"}, + {objects::RTD_9_IC12_HPA, "RTD_9_HPA"}, + {objects::RTD_10_IC13_PL_TX, "RTD_10_PL_TX,"}, + {objects::RTD_11_IC14_MPA, "RTD_11_MPA"}, + {objects::RTD_12_IC15_ACU, "RTD_12_ACU"}, + {objects::RTD_13_IC16_PLPCDU_HEATSPREADER, "RTD_13_PLPCDU_HSPD"}, + {objects::RTD_14_IC17_TCS_BOARD, "RTD_14_TCS_BOARD"}, + {objects::RTD_15_IC18_IMTQ, "RTD_15_IMTQ"}, +}}; + +namespace ObjectFactory { + +void produceGenericObjects(HealthTableIF** healthTable, PusTmFunnel** pusFunnel, + CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan, + StorageManagerIF** ipcStore, StorageManagerIF** tmStore, + PersistentTmStores& stores, uint32_t eventManagerQueueDepth, + bool enableHkSets, bool routeToPersistentStores); +void createGenericHeaterComponents(GpioIF& gpioIF, PowerSwitchIF& pwrSwitcher, + HeaterHandler*& heaterHandler); + +void createThermalController(HeaterHandler& heaterHandler, bool pollPlPcduTmp1); +void createRwAssy(PowerSwitchIF& pwrSwitcher, power::Switch_t theSwitch, + std::array rws, std::array rwIds); +void createSusAssy(PowerSwitchIF& pwrSwitcher, std::array suses); +void createAcsBoardAssy(PowerSwitchIF& pwrSwitcher, std::array assemblyDhbs, + ExtendedControllerBase* gpsCtrl, GpioIF* gpioComIF); +TcsBoardAssembly* createTcsBoardAssy(PowerSwitchIF& pwrSwitcher, + std::atomic_bool& tcsShortlyUnavailable); + +} // namespace ObjectFactory + +#endif /* MISSION_CORE_GENERICFACTORY_H_ */ diff --git a/mission/memory/CMakeLists.txt b/mission/memory/CMakeLists.txt new file mode 100644 index 0000000..8c2da88 --- /dev/null +++ b/mission/memory/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${LIB_EIVE_MISSION} PRIVATE NvmParameterBase.cpp) diff --git a/mission/memory/NvmParameterBase.cpp b/mission/memory/NvmParameterBase.cpp new file mode 100644 index 0000000..9b89440 --- /dev/null +++ b/mission/memory/NvmParameterBase.cpp @@ -0,0 +1,60 @@ +#include + +#include + +#include "fsfw/filesystem/HasFileSystemIF.h" +#include "fsfw/serviceinterface/ServiceInterface.h" + +NVMParameterBase::NVMParameterBase(std::string fullName) : fullName(fullName) {} + +NVMParameterBase::NVMParameterBase() {} + +ReturnValue_t NVMParameterBase::readJsonFile() { + std::error_code e; + if (std::filesystem::exists(fullName, e)) { + // Read JSON file content into object + std::ifstream i(fullName); + try { + i >> json; + } catch (nlohmann::json::exception& nlohmannE) { + sif::warning << "Reading JSON file failed with error " << nlohmannE.what() << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; + } + return HasFileSystemIF::FILE_DOES_NOT_EXIST; +} + +ReturnValue_t NVMParameterBase::writeJsonFile() { + std::ofstream o(fullName); + try { + o << std::setw(4) << json << std::endl; + } catch (nlohmann::json::exception& e) { + sif::warning << "Writing JSON file failed with error " << e.what() << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void NVMParameterBase::setFullName(std::string fullName) { this->fullName = fullName; } + +std::string NVMParameterBase::getFullName() const { return fullName; } + +bool NVMParameterBase::getJsonFileExists() { + std::error_code e; + return std::filesystem::exists(fullName, e); +} + +void NVMParameterBase::printKeys() const { + sif::info << "Printing keys for JSON file " << fullName << std::endl; + for (const auto& key : keys) { + sif::info << key << std::endl; + } +} + +void NVMParameterBase::print() const { + sif::info << "Printing JSON file " << fullName << std::endl; + for (const auto& key : keys) { + sif::info << key << ": " << json[key] << std::endl; + } +} diff --git a/mission/memory/NvmParameterBase.h b/mission/memory/NvmParameterBase.h new file mode 100644 index 0000000..8487c99 --- /dev/null +++ b/mission/memory/NvmParameterBase.h @@ -0,0 +1,85 @@ +#ifndef BSP_Q7S_CORE_NVMPARAMS_NVMPARAMIF_H_ +#define BSP_Q7S_CORE_NVMPARAMS_NVMPARAMIF_H_ + +#include +#include +#include + +#include "fsfw/returnvalues/returnvalue.h" +#include "returnvalues/classIds.h" + +class NVMParameterBase { + public: + virtual ~NVMParameterBase() {} + + NVMParameterBase(std::string fullName); + + /** + * @brief Use this constructor when name of json file shall be set on an later + * point + */ + NVMParameterBase(); + + bool getJsonFileExists(); + + /** + * Returns returnvalue::OK on successfull read and HasFileSystemIF::FILE_DOES_NOT_EXIST if + * file does not exist yet. + * @return + */ + virtual ReturnValue_t readJsonFile(); + + virtual ReturnValue_t writeJsonFile(); + + virtual void setFullName(std::string fullName); + std::string getFullName() const; + + template + ReturnValue_t insertValue(std::string key, T value); + + template + ReturnValue_t setValue(std::string key, T value); + + template + ReturnValue_t getValue(std::string key, T& value) const; + + void printKeys() const; + void print() const; + + private: + static const uint8_t INTERFACE_ID = CLASS_ID::NVM_PARAM_BASE; + + //! [EXPORT] : [COMMENT] Specified key does not exist in json file + static const ReturnValue_t KEY_NOT_EXISTS = MAKE_RETURN_CODE(0xA0); + + nlohmann::json json; + std::vector keys; + std::string fullName = ""; +}; + +template +inline ReturnValue_t NVMParameterBase::insertValue(std::string key, T value) { + // Check whether key already exists. If it does not, insert it + if (std::find(keys.begin(), keys.end(), key) == keys.end()) { + keys.push_back(key); + } + json[key] = value; + return returnvalue::OK; +} + +template +inline ReturnValue_t NVMParameterBase::setValue(std::string key, T value) { + json[key] = value; + return returnvalue::OK; +} + +template +inline ReturnValue_t NVMParameterBase::getValue(std::string key, T& value) const { + if (!json.contains(key)) { + return KEY_NOT_EXISTS; + } + value = json[key]; + return returnvalue::OK; +} + +#endif /* BSP_Q7S_CORE_NVMPARAMS_NVMPARAMIF_H_ */ diff --git a/mission/memory/SdCardMountedIF.h b/mission/memory/SdCardMountedIF.h new file mode 100644 index 0000000..e66ab87 --- /dev/null +++ b/mission/memory/SdCardMountedIF.h @@ -0,0 +1,21 @@ +#ifndef MISSION_MEMORY_SDCARDMOUNTERIF_H_ +#define MISSION_MEMORY_SDCARDMOUNTERIF_H_ + +#include +#include + +#include "definitions.h" + +class SdCardMountedIF { + public: + virtual ~SdCardMountedIF() {}; + virtual const char* getCurrentMountPrefix() const = 0; + virtual bool isSdCardUsable(std::optional sdCard) = 0; + virtual std::optional getPreferredSdCard() const = 0; + virtual void setActiveSdCard(sd::SdCard sdCard) = 0; + virtual std::optional getActiveSdCard() const = 0; + + private: +}; + +#endif /* MISSION_MEMORY_SDCARDMOUNTERIF_H_ */ diff --git a/mission/memory/definitions.h b/mission/memory/definitions.h new file mode 100644 index 0000000..53184e4 --- /dev/null +++ b/mission/memory/definitions.h @@ -0,0 +1,19 @@ +#ifndef BSP_Q7S_MEMORY_DEFINITIONS_H_ +#define BSP_Q7S_MEMORY_DEFINITIONS_H_ + +#include + +namespace sd { + +enum SdState : uint8_t { + OFF = 0, + ON = 1, + // A mounted SD card is on as well + MOUNTED = 2 +}; + +enum SdCard : uint8_t { SLOT_0 = 0, SLOT_1 = 1, BOTH, NONE }; + +} // namespace sd + +#endif /* BSP_Q7S_MEMORY_DEFINITIONS_H_ */ diff --git a/mission/payload/CMakeLists.txt b/mission/payload/CMakeLists.txt new file mode 100644 index 0000000..f48a594 --- /dev/null +++ b/mission/payload/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources( + ${LIB_EIVE_MISSION} PRIVATE PayloadPcduHandler.cpp RadiationSensorHandler.cpp + ScexDeviceHandler.cpp scexHelpers.cpp defs.cpp) diff --git a/mission/payload/PayloadPcduHandler.cpp b/mission/payload/PayloadPcduHandler.cpp new file mode 100644 index 0000000..1fc3fa9 --- /dev/null +++ b/mission/payload/PayloadPcduHandler.cpp @@ -0,0 +1,904 @@ +#include +#include +#include + +#include "OBSWConfig.h" +#include "fsfw/thermal/tcsDefinitions.h" +#include "mission/payload/payloadPcduDefinitions.h" + +#ifdef XIPHOS_Q7S +#include +#include +#include +#include +#include +#endif + +#include "devices/gpioIds.h" + +PayloadPcduHandler::PayloadPcduHandler(object_id_t objectId, object_id_t comIF, CookieIF* cookie, + GpioIF* gpioIF, SdCardMountedIF* sdcMan, + Stack5VHandler& stackHandler, bool periodicPrintout) + : DeviceHandlerBase(objectId, comIF, cookie), + adcSet(this), + stackHandler(stackHandler), + periodicPrintout(periodicPrintout), + gpioIF(gpioIF), + sdcMan(sdcMan) {} + +void PayloadPcduHandler::doStartUp() { + if (state > States::STACK_5V_CORRECT) { + // Config error + sif::error << "PayloadPcduHandler::doStartUp: Invalid state" << std::endl; + } + clearSetOnOffFlag = true; + if (state == States::PL_PCDU_OFF) { + state = States::STACK_5V_SWITCHING; + } + if (state == States::STACK_5V_SWITCHING) { + ReturnValue_t retval = stackHandler.deviceToOn(StackCommander::PL_PCDU, true); + if (retval == BUSY) { + return; + } + state = States::STACK_5V_PENDING; + } + if (state == States::STACK_5V_PENDING) { + if (stackHandler.isSwitchOn()) { + state = States::STACK_5V_CORRECT; + } + } + if (state == States::STACK_5V_CORRECT) { + quickTransitionAlreadyCalled = false; + setMode(_MODE_TO_ON); + } +} + +void PayloadPcduHandler::doShutDown() { + if (not quickTransitionAlreadyCalled) { + quickTransitionBackToOff(false, false); + quickTransitionAlreadyCalled = true; + } + if (clearSetOnOffFlag) { + std::memset(adcSet.processed.value, 0, adcSet.processed.getSerializedSize()); + clearSetOnOffFlag = false; + } + ReturnValue_t retval = stackHandler.deviceToOff(StackCommander::PL_PCDU, true); + if (retval == BUSY) { + return; + } + state = States::PL_PCDU_OFF; + quickTransitionAlreadyCalled = false; + { + PoolReadGuard pg(&adcSet); + adcSet.setReportingEnabled(false); + adcSet.tempC = thermal::INVALID_TEMPERATURE; + + std::memset(adcSet.channels.value, 0, sizeof(adcSet.channels.value)); + std::memset(adcSet.processed.value, 0, sizeof(adcSet.processed.value)); + adcSet.setValidity(false, true); + } + // No need to set mode _MODE_POWER_DOWN, power switching was already handled + setMode(MODE_OFF); +} + +void PayloadPcduHandler::doTransition(Mode_t modeFrom, Submode_t subModeFrom) { + if (getMode() == _MODE_TO_NORMAL) { + stateMachineToNormal(modeFrom, subModeFrom); + return; + } else if (getMode() == _MODE_TO_ON and modeFrom == MODE_NORMAL) { + pullAllGpiosLow(200); + state = States::STACK_5V_CORRECT; + } + DeviceHandlerBase::doTransition(modeFrom, subModeFrom); +} + +ReturnValue_t PayloadPcduHandler::stateMachineToNormal(Mode_t modeFrom, Submode_t subModeFrom) { + using namespace plpcdu; + bool doFinish = true; + if (toNormalOneShot) { + PoolReadGuard pg(&adcSet); + adcSet.setReportingEnabled(true); + toNormalOneShot = false; + } + if (((getSubmode() >> SOLID_STATE_RELAYS_ADC_ON) & 0b1) == 1) { + if (state == States::PL_PCDU_OFF) { + sif::error << "PayloadPcduHandler::stateMachineToNormal: Unexpected state PL_PCDU_OFF" + << "detected" << std::endl; + setMode(MODE_OFF); + return returnvalue::FAILED; + } + if (state == States::STACK_5V_CORRECT) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Switching on SSR VBAT0 & VBAT1 GPIOs" << std::endl; +#endif + // Switch on relays here + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_VBAT0); + gpioIF->pullHigh(gpioIds::PLPCDU_ENB_VBAT1); + state = States::ON_TRANS_SSR; + transitionOk = true; + doFinish = false; + } + if (state == States::ON_TRANS_SSR) { + // If necessary, check whether a certain amount of time has elapsed + if (transitionOk) { + transitionOk = false; + state = States::ON_TRANS_ADC_CLOSE_ZERO; + adcCountdown.setTimeout(50); + adcCountdown.resetTimer(); + adcState = AdcState::BOOT_DELAY; + doFinish = false; + // If the values are not close to zero, we should not allow transition + monMode = MonitoringMode::CLOSE_TO_ZERO; + } + } + if (state == States::ON_TRANS_ADC_CLOSE_ZERO) { + if (adcState == AdcState::BOOT_DELAY) { + doFinish = false; + if (adcCountdown.hasTimedOut()) { + adcState = AdcState::SEND_SETUP; + adcCmdExecuted = false; + } + } + if (adcState == AdcState::SEND_SETUP) { + if (adcCmdExecuted) { + adcState = AdcState::NORMAL; + doFinish = true; + adcCountdown.setTimeout(100); + adcCountdown.resetTimer(); + adcCmdExecuted = false; + } + } + } + } + + auto switchHandler = [&](NormalSubmodeBits bit, gpioId_t id, std::string info) { + if (((diffMask >> bit) & 1) == 1) { + if (((getSubmode() >> bit) & 1) == 1) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Enabling PL PCDU " << info << " module" << std::endl; +#endif + // Switch on DRO and start monitoring for negative voltages + updateSwitchGpio(id, gpio::Levels::HIGH); + } else { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Disabling PL PCDU " << info << " module" << std::endl; +#endif + updateSwitchGpio(id, gpio::Levels::LOW); + } + } + }; + + // sif::debug << "DIFF MASK: " << (int)diffMask << std::endl; + + // No handling for the SSRs: If those are pulled low, the ADC is off + // and normal mode does not really make sense anyway + switchHandler(DRO_ON, gpioIds::PLPCDU_ENB_DRO, "DRO"); + switchHandler(X8_ON, gpioIds::PLPCDU_ENB_X8, "X8"); + switchHandler(TX_ON, gpioIds::PLPCDU_ENB_TX, "TX"); + switchHandler(MPA_ON, gpioIds::PLPCDU_ENB_MPA, "MPA"); + switchHandler(HPA_ON, gpioIds::PLPCDU_ENB_HPA, "HPA"); + if (doFinish) { + toNormalOneShot = true; + setMode(MODE_NORMAL); + } + return returnvalue::OK; +} + +ReturnValue_t PayloadPcduHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + switch (adcState) { + case (AdcState::SEND_SETUP): { + *id = plpcdu::SETUP_CMD; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (AdcState::NORMAL): { + *id = plpcdu::READ_WITH_TEMP_EXT; + return buildCommandFromCommand(*id, nullptr, 0); + } + default: { + break; + } + } + return NOTHING_TO_SEND; +} + +ReturnValue_t PayloadPcduHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + if (adcState == AdcState::SEND_SETUP) { + *id = plpcdu::SETUP_CMD; + return buildCommandFromCommand(*id, nullptr, 0); + } + if (getMode() == _MODE_TO_NORMAL) { + return buildNormalDeviceCommand(id); + } + return NOTHING_TO_SEND; +} + +void PayloadPcduHandler::updateSwitchGpio(gpioId_t id, gpio::Levels level) { + if (level == gpio::Levels::HIGH) { + gpioIF->pullHigh(id); + } else { + gpioIF->pullLow(id); + } + adcCountdown.setTimeout(100); + adcCountdown.resetTimer(); +} + +void PayloadPcduHandler::fillCommandAndReplyMap() { + insertInCommandAndReplyMap(plpcdu::READ_CMD, 2); + insertInCommandAndReplyMap(plpcdu::READ_TEMP_EXT, 1); + insertInCommandAndReplyMap(plpcdu::READ_WITH_TEMP_EXT, 1); + insertInCommandAndReplyMap(plpcdu::SETUP_CMD, 1); +} + +ReturnValue_t PayloadPcduHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + switch (deviceCommand) { + case (plpcdu::SETUP_CMD): { + cmdBuf[0] = plpcdu::SETUP_BYTE; + rawPacket = cmdBuf.data(); + rawPacketLen = 1; + break; + } + case (plpcdu::READ_CMD): { + max1227::prepareExternallyClockedRead0ToN(cmdBuf.data(), plpcdu::CHANNEL_N, rawPacketLen); + rawPacket = cmdBuf.data(); + break; + } + case (plpcdu::READ_TEMP_EXT): { + max1227::prepareExternallyClockedTemperatureRead(cmdBuf.data(), rawPacketLen); + rawPacket = cmdBuf.data(); + break; + } + case (plpcdu::READ_WITH_TEMP_EXT): { + size_t sz = 0; + max1227::prepareExternallyClockedRead0ToN(cmdBuf.data(), plpcdu::CHANNEL_N, sz); + max1227::prepareExternallyClockedTemperatureRead(cmdBuf.data() + sz, sz); + rawPacketLen = sz; + rawPacket = cmdBuf.data(); + break; + } + default: { + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + } + return returnvalue::OK; +} + +ReturnValue_t PayloadPcduHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + // SPI is full duplex + *foundId = getPendingCommand(); + *foundLen = remainingSize; + return returnvalue::OK; +} + +ReturnValue_t PayloadPcduHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t* packet) { + using namespace plpcdu; + switch (id) { + case (SETUP_CMD): { + if (getMode() == _MODE_TO_NORMAL) { + adcCmdExecuted = true; + } + break; + } + case (READ_TEMP_EXT): { + uint8_t tempStartIdx = TEMP_REPLY_SIZE - 2; + adcSet.tempC.value = + max1227::getTemperature(packet[tempStartIdx] << 8 | packet[tempStartIdx + 1]); + break; + } + case (READ_CMD): { + { + PoolReadGuard pg(&adcSet); + if (pg.getReadResult() != returnvalue::OK) { + return pg.getReadResult(); + } + handleExtConvRead(packet); + checkAdcValues(); + adcSet.setValidity(true, true); + } + handlePrintout(); + break; + } + case (READ_WITH_TEMP_EXT): { + { + PoolReadGuard pg(&adcSet); + if (pg.getReadResult() != returnvalue::OK) { + return pg.getReadResult(); + } + handleExtConvRead(packet); + uint8_t tempStartIdx = ADC_REPLY_SIZE + TEMP_REPLY_SIZE - 2; + adcSet.tempC.value = + max1227::getTemperature(packet[tempStartIdx] << 8 | packet[tempStartIdx + 1]); + checkAdcValues(); + adcSet.setValidity(true, true); + } + handlePrintout(); + break; + } + default: { + break; + } + } + return returnvalue::OK; +} + +uint32_t PayloadPcduHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { + // 20 minutes transition delay is allowed + return 20 * 60 * 1000; +} + +ReturnValue_t PayloadPcduHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(plpcdu::PlPcduPoolIds::CHANNEL_VEC, &channelValues); + localDataPoolMap.emplace(plpcdu::PlPcduPoolIds::PROCESSED_VEC, &processedValues); + localDataPoolMap.emplace(plpcdu::PlPcduPoolIds::TEMP, &tempC); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(adcSet.getSid(), false, 5.0)); + return returnvalue::OK; +} + +void PayloadPcduHandler::setToGoToNormalModeImmediately(bool enable) { + this->goToNormalMode = enable; +} + +void PayloadPcduHandler::handleExtConvRead(const uint8_t* bufStart) { + for (uint8_t idx = 0; idx < 12; idx++) { + adcSet.channels[idx] = bufStart[idx * 2 + 1] << 8 | bufStart[idx * 2 + 2]; + } +} + +void PayloadPcduHandler::handlePrintout() { + using namespace plpcdu; + if (periodicPrintout) { + if (opDivider.checkAndIncrement()) { + sif::info << "PL PCDU ADC hex [" << std::setfill('0') << std::hex; + for (uint8_t idx = 0; idx < 12; idx++) { + sif::info << std::setw(3) << adcSet.channels[idx]; + if (idx < 11) { + sif::info << ","; + } + } + sif::info << "] | T[C] " << std::dec << adcSet.tempC.value << std::endl; + sif::info << "Neg V: " << adcSet.processed[U_NEG_V_FB] << std::endl; + sif::info << "I HPA [mA]: " << adcSet.processed[I_HPA] << std::endl; + sif::info << "U HPA [V]: " << adcSet.processed[U_HPA_DIV_6] << std::endl; + sif::info << "I MPA [mA]: " << adcSet.processed[I_MPA] << std::endl; + sif::info << "U MPA [V]: " << adcSet.processed[U_MPA_DIV_6] << std::endl; + sif::info << "I TX [mA]: " << adcSet.processed[I_TX] << std::endl; + sif::info << "U TX [V]: " << adcSet.processed[U_TX_DIV_6] << std::endl; + sif::info << "I X8 [mA]: " << adcSet.processed[I_X8] << std::endl; + sif::info << "U X8 [V]: " << adcSet.processed[U_X8_DIV_6] << std::endl; + sif::info << "I DRO [mA]: " << adcSet.processed[I_DRO] << std::endl; + sif::info << "U DRO [V]: " << adcSet.processed[U_DRO_DIV_6] << std::endl; + } + } +} + +void PayloadPcduHandler::enablePeriodicPrintout(bool enable, uint8_t divider) { + this->periodicPrintout = enable; + opDivider.setDivider(divider); +} + +void PayloadPcduHandler::quickTransitionBackToOff(bool startTransitionToOff, bool notifyFdir) { + States currentState = state; + pullAllGpiosLow(200); + state = States::STACK_5V_SWITCHING; + adcState = AdcState::OFF; + if (startTransitionToOff) { + startTransition(MODE_OFF, 0); + } + if (notifyFdir) { + triggerEvent(TRANSITION_BACK_TO_OFF, static_cast(currentState)); + } +} + +void PayloadPcduHandler::checkAdcValues() { + using namespace plpcdu; + adcSet.processed[U_BAT_DIV_6] = + static_cast(adcSet.channels[0]) * VOLTAGE_DIV / MAX122X_BIT * MAX122X_VREF; + adcSet.processed[U_NEG_V_FB] = + V_POS - VOLTAGE_DIV_U_NEG * + (V_POS - static_cast(adcSet.channels[1]) / MAX122X_BIT * MAX122X_VREF); + adcSet.processed[I_HPA] = static_cast(adcSet.channels[2]) * SCALE_CURRENT_HPA * 1000.0; + adcSet.processed[U_HPA_DIV_6] = static_cast(adcSet.channels[3]) * SCALE_VOLTAGE; + adcSet.processed[I_MPA] = static_cast(adcSet.channels[4]) * SCALE_CURRENT_MPA * 1000.0; + adcSet.processed[U_MPA_DIV_6] = static_cast(adcSet.channels[5]) * SCALE_VOLTAGE; + adcSet.processed[I_TX] = static_cast(adcSet.channels[6]) * SCALE_CURRENT_TX * 1000.0; + adcSet.processed[U_TX_DIV_6] = static_cast(adcSet.channels[7]) * SCALE_VOLTAGE; + adcSet.processed[I_X8] = static_cast(adcSet.channels[8]) * SCALE_CURRENT_X8 * 1000.0; + adcSet.processed[U_X8_DIV_6] = static_cast(adcSet.channels[9]) * SCALE_VOLTAGE; + adcSet.processed[I_DRO] = static_cast(adcSet.channels[10]) * SCALE_CURRENT_DRO * 1000.0; + adcSet.processed[U_DRO_DIV_6] = static_cast(adcSet.channels[11]) * SCALE_VOLTAGE; + float lowerBound = 0.0; + float upperBound = 0.0; + bool adcTransition = adcState == AdcState::NORMAL and adcCountdown.isBusy(); + if (NO_ADC_CHECKS or adcTransition) { + return; + } + // Now check against voltage and current limits. + uint8_t submode = getSubmode(); + if (((submode >> NormalSubmodeBits::DRO_ON) & 0b1) == 0b1) { + if (ssrToDroInjectionRequested) { + handleFailureInjection("SSR to DRO", NEG_V_OUT_OF_BOUNDS); + ssrToDroInjectionRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[NEG_V_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[NEG_V_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_NEG_V_FB], lowerBound, upperBound, + NEG_V_OUT_OF_BOUNDS)) { + sif::warning << "Negative voltage was out of bounds, went back to OFF" << std::endl; + return; + } + params.getValue(PARAM_KEY_MAP[DRO_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[DRO_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_DRO_DIV_6], lowerBound, upperBound, + U_DRO_OUT_OF_BOUNDS)) { + sif::warning << "DRO voltage was out of bounds, went back to OFF" << std::endl; + return; + } + params.getValue(PARAM_KEY_MAP[DRO_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_DRO], upperBound, I_DRO_OUT_OF_BOUNDS)) { + sif::warning << "Detected out of bounds current for DRO: " << adcSet.processed[I_DRO] + << ", Raw: " << adcSet.channels[I_DRO] << std::endl; + return; + } + } + if (((submode >> NormalSubmodeBits::X8_ON) & 0b1) == 0b1) { + if (droToX8InjectionRequested) { + handleFailureInjection("X8 to TX", U_X8_OUT_OF_BOUNDS); + droToX8InjectionRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[X8_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[X8_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_X8_DIV_6], lowerBound, upperBound, + U_X8_OUT_OF_BOUNDS)) { + sif::warning << "X8 voltage was out of bounds, went back to OFF" << std::endl; + return; + } + params.getValue(PARAM_KEY_MAP[X8_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_X8], upperBound, I_X8_OUT_OF_BOUNDS)) { + sif::warning << "X8 current was out of bounds, went back to OFF" << std::endl; + return; + } + } + if (((submode >> NormalSubmodeBits::TX_ON) & 0b1) == 0b1) { + if (txToMpaInjectionRequested) { + handleFailureInjection("TX to MPA", U_TX_OUT_OF_BOUNDS); + txToMpaInjectionRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[TX_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[TX_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_TX_DIV_6], lowerBound, upperBound, + U_TX_OUT_OF_BOUNDS)) { + sif::warning << "TX voltage was out of bounds, went back to OFF" << std::endl; + return; + } + params.getValue(PARAM_KEY_MAP[TX_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_TX], upperBound, I_TX_OUT_OF_BOUNDS)) { + sif::warning << "TX current was out of bounds, went back to OFF" << std::endl; + return; + } + } + if (((submode >> NormalSubmodeBits::MPA_ON) & 0b1) == 0b1) { + if (mpaToHpaInjectionRequested) { + handleFailureInjection("MPA to HPA", U_HPA_OUT_OF_BOUNDS); + mpaToHpaInjectionRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[MPA_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[MPA_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_MPA_DIV_6], lowerBound, upperBound, + U_MPA_OUT_OF_BOUNDS)) { + sif::warning << "MPA voltage was out of bounds, went back to OFF" << std::endl; + return; + } + params.getValue(PARAM_KEY_MAP[MPA_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_MPA], upperBound, I_MPA_OUT_OF_BOUNDS)) { + sif::warning << "MPA current was out of bounds, went back to OFF" << std::endl; + return; + } + } + if (((submode >> NormalSubmodeBits::HPA_ON) & 0b1) == 0b1) { + if (allOnInjectRequested) { + handleFailureInjection("All On", U_HPA_OUT_OF_BOUNDS); + allOnInjectRequested = false; + return; + } + params.getValue(PARAM_KEY_MAP[HPA_U_LOWER_BOUND], lowerBound); + params.getValue(PARAM_KEY_MAP[HPA_U_UPPER_BOUND], upperBound); + if (not checkVoltage(adcSet.processed[U_HPA_DIV_6], lowerBound, upperBound, + U_HPA_OUT_OF_BOUNDS)) { + sif::warning << "HPA voltage was out of bounds, went back to OFF" << std::endl; + return; + } + params.getValue(PARAM_KEY_MAP[HPA_I_UPPER_BOUND], upperBound); + if (not checkCurrent(adcSet.processed[I_HPA], upperBound, I_HPA_OUT_OF_BOUNDS)) { + sif::warning << "PayloadPcduHandler::checkCurrent: I HPA exceeded limit: Measured " + << adcSet.processed[I_HPA] << " mA" << std::endl; + return; + } + } + transitionOk = true; +} + +void PayloadPcduHandler::checkJsonFileInit() { + if (not jsonFileInitComplete) { + auto activeSd = sdcMan->getActiveSdCard(); + if (activeSd and sdcMan->isSdCardUsable(activeSd.value())) { + if (sdcMan->getCurrentMountPrefix() == nullptr) { + return; + } + params.initialize(sdcMan->getCurrentMountPrefix()); + jsonFileInitComplete = true; + } + } else { + if (not sdcMan->isSdCardUsable(std::nullopt)) { + jsonFileInitComplete = false; + } + } +} + +bool PayloadPcduHandler::checkVoltage(float val, float lowerBound, float upperBound, Event event) { + bool tooLarge = false; + if (val < lowerBound or val > upperBound) { + if (val > upperBound) { + tooLarge = true; + } else { + tooLarge = false; + } + uint32_t p2 = 0; + serializeFloat(p2, val); + triggerEvent(event, tooLarge, p2); + transitionOk = false; + quickTransitionBackToOff(true, true); + quickTransitionAlreadyCalled = true; + return false; + } + return true; +} + +bool PayloadPcduHandler::checkCurrent(float val, float upperBound, Event event) { + if (val > upperBound) { + uint32_t p2 = 0; + serializeFloat(p2, val); + triggerEvent(event, true, p2); + transitionOk = false; + quickTransitionBackToOff(true, true); + quickTransitionAlreadyCalled = true; + return false; + } + return true; +} + +ReturnValue_t PayloadPcduHandler::initialize() { + checkJsonFileInit(); + return DeviceHandlerBase::initialize(); +} + +void PayloadPcduHandler::performOperationHook() { checkJsonFileInit(); } + +ReturnValue_t PayloadPcduHandler::checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t* msToReachTheMode) { + using namespace plpcdu; + if (commandedMode != MODE_OFF) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) { + return NON_OP_STATE_OF_CHARGE; + } + } + } + if (commandedMode == MODE_NORMAL) { + uint8_t dhbSubmode = getSubmode(); + diffMask = commandedSubmode ^ dhbSubmode; + // For all higher level modes, SSR needs to be on. This is to ensure we have valid ADC + // measurements + if ((droOnForSubmode(commandedSubmode) or x8OnForSubmode(commandedSubmode) or + txOnForSubmode(commandedSubmode) or mpaOnForSubmode(commandedSubmode) or + hpaOnForSubmode(commandedSubmode)) and + not ssrOnForSubmode(dhbSubmode)) { + return TRANS_NOT_ALLOWED; + } + if (disableChannelOrderCheck) { + return returnvalue::OK; + } + if (x8OnForSubmode(commandedSubmode) and not droOnForSubmode(dhbSubmode)) { + return TRANS_NOT_ALLOWED; + } + if (txOnForSubmode(commandedSubmode) and + (not droOnForSubmode(dhbSubmode) or not x8OnForSubmode(dhbSubmode))) { + return TRANS_NOT_ALLOWED; + } + if (mpaOnForSubmode(commandedSubmode) and + (not droOnForSubmode(dhbSubmode) or not x8OnForSubmode(dhbSubmode) or + not txOnForSubmode(dhbSubmode))) { + return TRANS_NOT_ALLOWED; + } + if (hpaOnForSubmode(commandedSubmode) and + (not droOnForSubmode(dhbSubmode) or not x8OnForSubmode(dhbSubmode) or + not txOnForSubmode(dhbSubmode) or not mpaOnForSubmode(dhbSubmode))) { + return TRANS_NOT_ALLOWED; + } + return returnvalue::OK; + } + return DeviceHandlerBase::checkModeCommand(commandedMode, commandedSubmode, msToReachTheMode); +} + +bool PayloadPcduHandler::ssrOnForSubmode(uint8_t submode) { + return submode & (1 << plpcdu::SOLID_STATE_RELAYS_ADC_ON); +} +bool PayloadPcduHandler::droOnForSubmode(uint8_t submode) { + return submode & (1 << plpcdu::DRO_ON); +} + +bool PayloadPcduHandler::x8OnForSubmode(uint8_t submode) { return submode & (1 << plpcdu::X8_ON); } + +bool PayloadPcduHandler::txOnForSubmode(uint8_t submode) { return submode & (1 << plpcdu::TX_ON); } + +bool PayloadPcduHandler::mpaOnForSubmode(uint8_t submode) { + return submode & (1 << plpcdu::MPA_ON); +} + +bool PayloadPcduHandler::hpaOnForSubmode(uint8_t submode) { + return submode & (1 << plpcdu::HPA_ON); +} + +ReturnValue_t PayloadPcduHandler::serializeFloat(uint32_t& param, float val) { + size_t dummy = 0; + return SerializeAdapter::serialize(&val, reinterpret_cast(¶m), &dummy, 4, + SerializeIF::Endianness::NETWORK); +} + +ReturnValue_t PayloadPcduHandler::getParameter(uint8_t domainId, uint8_t uniqueId, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, + uint16_t startAtIndex) { + using namespace plpcdu; + switch (uniqueId) { + case (PlPcduParamId::NEG_V_LOWER_BOUND): + case (PlPcduParamId::NEG_V_UPPER_BOUND): + case (PlPcduParamId::DRO_U_LOWER_BOUND): + case (PlPcduParamId::DRO_U_UPPER_BOUND): + case (PlPcduParamId::DRO_I_UPPER_BOUND): + case (PlPcduParamId::X8_U_LOWER_BOUND): + case (PlPcduParamId::X8_U_UPPER_BOUND): + case (PlPcduParamId::X8_I_UPPER_BOUND): + case (PlPcduParamId::TX_U_LOWER_BOUND): + case (PlPcduParamId::TX_U_UPPER_BOUND): + case (PlPcduParamId::TX_I_UPPER_BOUND): + case (PlPcduParamId::MPA_U_LOWER_BOUND): + case (PlPcduParamId::MPA_U_UPPER_BOUND): + case (PlPcduParamId::MPA_I_UPPER_BOUND): + case (PlPcduParamId::HPA_U_LOWER_BOUND): + case (PlPcduParamId::HPA_U_UPPER_BOUND): + case (PlPcduParamId::HPA_I_UPPER_BOUND): + case (PlPcduParamId::SSR_TO_DRO_WAIT_TIME): + case (PlPcduParamId::DRO_TO_X8_WAIT_TIME): + case (PlPcduParamId::X8_TO_TX_WAIT_TIME): + case (PlPcduParamId::TX_TO_MPA_WAIT_TIME): + case (PlPcduParamId::MPA_TO_HPA_WAIT_TIME): { + handleDoubleParamUpdate(PARAM_KEY_MAP[static_cast(uniqueId)], parameterWrapper, + newValues); + break; + } + case (PlPcduParamId::INJECT_SSR_TO_DRO_FAILURE): { + ssrToDroInjectionRequested = true; + break; + } + case (PlPcduParamId::INJECT_DRO_TO_X8_FAILURE): { + droToX8InjectionRequested = true; + break; + } + case (PlPcduParamId::INJECT_X8_TO_TX_FAILURE): { + x8ToTxInjectionRequested = true; + break; + } + case (PlPcduParamId::INJECT_TX_TO_MPA_FAILURE): { + txToMpaInjectionRequested = true; + break; + } + case (PlPcduParamId::INJECT_MPA_TO_HPA_FAILURE): { + mpaToHpaInjectionRequested = true; + break; + } + case (PlPcduParamId::INJECT_ALL_ON_FAILURE): { + allOnInjectRequested = true; + break; + } + case (PlPcduParamId::DISABLE_ORDER_CHECK_CHANNELS): { + uint8_t newValue = 0; + ReturnValue_t result = newValues->getElement(&newValue); + if (result != returnvalue::OK) { + return result; + } + if (newValue > 1) { + return HasParametersIF::INVALID_VALUE; + } + parameterWrapper->set(disableChannelOrderCheck); + break; + } + default: { + return DeviceHandlerBase::getParameter(domainId, uniqueId, parameterWrapper, newValues, + startAtIndex); + } + } + return returnvalue::OK; +} + +void PayloadPcduHandler::handleFailureInjection(std::string output, Event event) { + sif::info << "PayloadPcduHandler::handleFailureInjection: " << output + << " failure injection. " + "Transitioning back to off" + << std::endl; + triggerEvent(event, 0, 0); + transitionOk = false; + quickTransitionBackToOff(true, true); + quickTransitionAlreadyCalled = true; + droToX8InjectionRequested = false; +} + +void PayloadPcduHandler::pullAllGpiosLow(uint32_t delayBeforeSwitchingOffDro) { + sif::info << "Pulling all PL PCDU GPIOs to low" << std::endl; + gpioIF->pullLow(gpioIds::PLPCDU_ENB_HPA); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_MPA); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_TX); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_X8); + TaskFactory::delayTask(delayBeforeSwitchingOffDro); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_DRO); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_VBAT0); + gpioIF->pullLow(gpioIds::PLPCDU_ENB_VBAT1); +} + +ReturnValue_t PayloadPcduHandler::handleDoubleParamUpdate(std::string key, + ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues) { + double newValue = 0.0; + ReturnValue_t result = newValues->getElement(&newValue, 0, 0); + if (result != returnvalue::OK) { + return result; + } + params.setValue(key, newValue); + // Do this so the dumping and loading with the framework works as well + doubleDummy = newValue; + parameterWrapper->set(doubleDummy); + return params.writeJsonFile(); +} + +LocalPoolDataSetBase* PayloadPcduHandler::getDataSetHandle(sid_t sid) { return &adcSet; } + +#ifdef XIPHOS_Q7S +ReturnValue_t PayloadPcduHandler::extConvAsTwoCallback(SpiComIF* comIf, SpiCookie* cookie, + const uint8_t* sendData, size_t sendLen, + void* args) { + auto handler = reinterpret_cast(args); + if (handler == nullptr) { + sif::error << "GyroADIS16507Handler::spiSendCallback: Passed handler pointer is invalid!" + << std::endl; + return returnvalue::FAILED; + } + DeviceCommandId_t currentCommand = handler->getPendingCommand(); + switch (currentCommand) { + case (plpcdu::READ_WITH_TEMP_EXT): { + return transferAsTwo(comIf, cookie, sendData, sendLen, false); + } + case (plpcdu::READ_TEMP_EXT): { + return transferAsTwo(comIf, cookie, sendData, sendLen, true); + } + default: { + return comIf->performRegularSendOperation(cookie, sendData, sendLen); + } + } + return returnvalue::OK; +} + +ReturnValue_t PayloadPcduHandler::transferAsTwo(SpiComIF* comIf, SpiCookie* cookie, + const uint8_t* sendData, size_t sendLen, + bool tempOnly) { + ReturnValue_t result = returnvalue::OK; + int retval = 0; + // Prepare transfer + int fileDescriptor = 0; + UnixFileGuard fileHelper(comIf->getSpiDev(), fileDescriptor, O_RDWR, "SpiComIF::sendMessage"); + if (fileHelper.getOpenResult() != returnvalue::OK) { + return spi::OPENING_FILE_FAILED; + } + spi::SpiModes spiMode = spi::SpiModes::MODE_0; + uint32_t spiSpeed = 0; + cookie->getSpiParameters(spiMode, spiSpeed, nullptr); + comIf->setSpiSpeedAndMode(fileDescriptor, spiMode, spiSpeed); + cookie->assignWriteBuffer(sendData); + size_t transferLen = plpcdu::TEMP_REPLY_SIZE; + if (not tempOnly) { + transferLen += plpcdu::ADC_REPLY_SIZE; + } + cookie->setTransferSize(transferLen); + + gpioId_t gpioId = cookie->getChipSelectPin(); + GpioIF& gpioIF = comIf->getGpioInterface(); + MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING; + uint32_t timeoutMs = 0; + MutexIF* mutex = comIf->getCsMutex(); + if (mutex == nullptr) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::warning << "GyroADIS16507Handler::spiSendCallback: " + "Mutex or GPIO interface invalid" + << std::endl; + return returnvalue::FAILED; +#endif + } + + if (gpioId != gpio::NO_GPIO) { + cookie->getMutexParams(timeoutType, timeoutMs); + result = mutex->lockMutex(timeoutType, timeoutMs); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "SpiComIF::sendMessage: Failed to lock mutex" << std::endl; +#endif + return result; + } + } + spi_ioc_transfer* transferStruct = cookie->getTransferStructHandle(); + uint64_t origTx = transferStruct->tx_buf; + uint64_t origRx = transferStruct->rx_buf; + if (tempOnly) { + transferLen = 1; + } else { + transferLen = plpcdu::ADC_REPLY_SIZE + 1; + } + transferStruct->len = transferLen; + // Pull SPI CS low. For now, no support for active high given + if (gpioId != gpio::NO_GPIO) { + gpioIF.pullLow(gpioId); + } + + // Execute transfer + // Initiate a full duplex SPI transfer. + retval = ioctl(fileDescriptor, SPI_IOC_MESSAGE(1), cookie->getTransferStructHandle()); + if (retval < 0) { + utility::handleIoctlError("SpiComIF::sendMessage: ioctl error."); + result = spi::FULL_DUPLEX_TRANSFER_FAILED; + } +#if FSFW_HAL_SPI_WIRETAPPING == 1 + comIf->performSpiWiretapping(cookie); +#endif /* FSFW_LINUX_SPI_WIRETAPPING == 1 */ + + if (gpioId != gpio::NO_GPIO) { + gpioIF.pullHigh(gpioId); + } + + transferStruct->tx_buf += transferLen; + transferStruct->rx_buf += transferLen; + transferStruct->len = plpcdu::TEMP_REPLY_SIZE - 1; + if (gpioId != gpio::NO_GPIO) { + gpioIF.pullLow(gpioId); + } + + // Execute transfer + // Initiate a full duplex SPI transfer. + retval = ioctl(fileDescriptor, SPI_IOC_MESSAGE(1), cookie->getTransferStructHandle()); + if (retval < 0) { + utility::handleIoctlError("SpiComIF::sendMessage: ioctl error."); + result = spi::FULL_DUPLEX_TRANSFER_FAILED; + } +#if FSFW_HAL_SPI_WIRETAPPING == 1 + comIf->performSpiWiretapping(cookie); +#endif /* FSFW_LINUX_SPI_WIRETAPPING == 1 */ + + if (gpioId != gpio::NO_GPIO) { + gpioIF.pullHigh(gpioId); + } + + transferStruct->tx_buf = origTx; + transferStruct->rx_buf = origRx; + if (gpioId != gpio::NO_GPIO) { + mutex->unlockMutex(); + } + return returnvalue::OK; +} + +#endif diff --git a/mission/payload/PayloadPcduHandler.h b/mission/payload/PayloadPcduHandler.h new file mode 100644 index 0000000..da9da1e --- /dev/null +++ b/mission/payload/PayloadPcduHandler.h @@ -0,0 +1,193 @@ +#ifndef LINUX_DEVICES_PLPCDUHANDLER_H_ +#define LINUX_DEVICES_PLPCDUHANDLER_H_ + +#include +#include +#include +#include +#include +#include + +#include "events/subsystemIdRanges.h" +#include "fsfw/FSFW.h" +#include "fsfw_hal/common/gpio/GpioIF.h" +#include "mission/memory/SdCardMountedIF.h" +#include "mission/power/defs.h" +#include "mission/system/DualLanePowerStateMachine.h" + +#ifdef FSFW_OSAL_LINUX +class SpiComIF; +class SpiCookie; +#endif + +/** + * @brief Device handler for the EIVE Payload PCDU + * @details + * Documentation: + * https://egit.irs.uni-stuttgart.de/eive/eive_dokumente/src/branch/master/400_Raumsegment/412_PayloaPCDUDocumentation/release/EIVE-D-421-001_PLPCDU_Documentation.pdf + * + * Important components: + * - SSR - Solid State Relay: Decouples voltages from battery + * - DRO - Dielectric Resonsant Oscillator: Generates modulation signal + * - X8: Frequency X8 Multiplicator + * - TX: Transmitter/Sender module. Modulates data onto carrier signal + * - MPA - Medium Power Amplifier + * - HPA - High Power Amplifier + */ +class PayloadPcduHandler : public DeviceHandlerBase { + public: + static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PL_PCDU_HANDLER; + //! [EXPORT] : [COMMENT] Could not transition properly and went back to ALL OFF + static constexpr Event TRANSITION_BACK_TO_OFF = + event::makeEvent(SUBSYSTEM_ID, 0, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event NEG_V_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 1, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_DRO_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 2, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_DRO_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 3, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_X8_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 4, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_X8_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 5, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_TX_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 6, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_TX_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 7, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_MPA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 8, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_MPA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 9, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event U_HPA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 10, severity::MEDIUM); + //! [EXPORT] : [COMMENT] P1: 0 -> too low, 1 -> too high P2: Float value + static constexpr Event I_HPA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 11, severity::MEDIUM); + + PayloadPcduHandler(object_id_t objectId, object_id_t comIF, CookieIF* cookie, GpioIF* gpioIF, + SdCardMountedIF* sdcMan, Stack5VHandler& stackHandler, bool periodicPrintout); + + void setToGoToNormalModeImmediately(bool enable); + void performOperationHook() override; + void enablePeriodicPrintout(bool enable, uint8_t divider); + ReturnValue_t initialize() override; + +#ifdef XIPHOS_Q7S + static ReturnValue_t extConvAsTwoCallback(SpiComIF* comIf, SpiCookie* cookie, + const uint8_t* sendData, size_t sendLen, void* args); + static ReturnValue_t transferAsTwo(SpiComIF* comIf, SpiCookie* cookie, const uint8_t* sendData, + size_t sendLen, bool tempOnly); +#endif + + private: + static constexpr bool NO_ADC_CHECKS = false; + + enum class States : uint8_t { + PL_PCDU_OFF, + STACK_5V_SWITCHING, + STACK_5V_PENDING, + STACK_5V_CORRECT, + // Solid State Relay, enable battery voltages VBAT0 and VBAT1. This will also switch on + // the ADC + ON_TRANS_SSR, + ON_TRANS_ADC_CLOSE_ZERO + } state = States::PL_PCDU_OFF; + + duallane::Submodes pwrSubmode = duallane::Submodes::A_SIDE; + + enum class AdcMode { EXT_CONV, INT_CONV } adcMode = AdcMode::INT_CONV; + + enum class MonitoringMode { NONE, CLOSE_TO_ZERO, NEGATIVE } monMode = MonitoringMode::NONE; + + enum class AdcState { OFF, BOOT_DELAY, SEND_SETUP, NORMAL } adcState = AdcState::OFF; + + bool goToNormalMode = false; + plpcdu::PlPcduAdcSet adcSet; + Stack5VHandler& stackHandler; + std::array cmdBuf = {}; + // This variable is tied to DRO +6 V voltage. Voltages, currents are monitored and the experiment + // is shut down immediately if there is a negative voltage. + bool transitionOk = false; + bool commandExecuted = false; + bool adcCmdExecuted = false; + bool periodicPrintout = false; + bool jsonFileInitComplete = false; + double doubleDummy = 0.0; + + bool ssrToDroInjectionRequested = false; + bool droToX8InjectionRequested = false; + bool x8ToTxInjectionRequested = false; + bool txToMpaInjectionRequested = false; + bool mpaToHpaInjectionRequested = false; + bool allOnInjectRequested = false; + bool clearSetOnOffFlag = true; + bool toNormalOneShot = true; + + PeriodicOperationDivider opDivider = PeriodicOperationDivider(5); + uint8_t tempReadDivisor = 1; + Countdown countdown = Countdown(5000); + Countdown adcCountdown = Countdown(50); + GpioIF* gpioIF; + SdCardMountedIF* sdcMan; + plpcdu::PlPcduParameter params; + bool quickTransitionAlreadyCalled = true; + uint8_t diffMask = 0; + + PoolEntry channelValues = PoolEntry({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + PoolEntry processedValues = + PoolEntry({0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}); + PoolEntry tempC = PoolEntry({0.0}); + + /** + * This parameter disables all checks for the channels except the SSR on check. The SSR on check + * is kept to ensure that there is a common start point where the ADC is enabled. + */ + uint8_t disableChannelOrderCheck = false; + + void updateSwitchGpio(gpioId_t id, gpio::Levels level); + + void doTransition(Mode_t modeFrom, Submode_t subModeFrom) override; + void doStartUp() override; + void doShutDown() override; + // Main FDIR function which goes from any PL PCDU state back to all off + void quickTransitionBackToOff(bool startTransitionToOff, bool notifyFdir); + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueId, ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues, uint16_t startAtIndex) override; + + void handleExtConvRead(const uint8_t* bufStart); + void handlePrintout(); + void pullAllGpiosLow(uint32_t delayBeforeSwitchingOffDro); + void checkAdcValues(); + void handleOutOfBoundsPrintout(); + void checkJsonFileInit(); + ReturnValue_t stateMachineToNormal(Mode_t modeFrom, Submode_t subModeFrom); + bool checkVoltage(float val, float lowerBound, float upperBound, Event event); + bool checkCurrent(float val, float upperBound, Event event); + void handleFailureInjection(std::string output, Event event); + ReturnValue_t serializeFloat(uint32_t& param, float val); + ReturnValue_t handleDoubleParamUpdate(std::string key, ParameterWrapper* parameterWrapper, + const ParameterWrapper* newValues); + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER); + ReturnValue_t checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t* msToReachTheMode) override; + static bool ssrOnForSubmode(uint8_t submode); + static bool droOnForSubmode(uint8_t submode); + static bool x8OnForSubmode(uint8_t submode); + static bool txOnForSubmode(uint8_t submode); + static bool mpaOnForSubmode(uint8_t submode); + static bool hpaOnForSubmode(uint8_t submode); +}; + +#endif /* LINUX_DEVICES_PLPCDUHANDLER_H_ */ diff --git a/mission/payload/RadiationSensorHandler.cpp b/mission/payload/RadiationSensorHandler.cpp new file mode 100644 index 0000000..07363fb --- /dev/null +++ b/mission/payload/RadiationSensorHandler.cpp @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include +#include +#include + +RadiationSensorHandler::RadiationSensorHandler(object_id_t objectId, object_id_t comIF, + CookieIF *comCookie, GpioIF *gpioIF, + Stack5VHandler &stackHandler) + : DeviceHandlerBase(objectId, comIF, comCookie), + dataset(this), + gpioIF(gpioIF), + stackHandler(stackHandler) { + if (comCookie == nullptr) { + sif::error << "RadiationSensorHandler: Invalid com cookie" << std::endl; + } + // Time out immediately so we get an immediate measurement at device startup. + measurementCd.timeOut(); +} + +RadiationSensorHandler::~RadiationSensorHandler() {} + +void RadiationSensorHandler::doStartUp() { + if (internalState == InternalState::OFF) { + ReturnValue_t retval = stackHandler.deviceToOn(StackCommander::RAD_SENSOR, true); + if (retval == BUSY) { + return; + } + internalState = InternalState::POWER_SWITCHING; + } + if (internalState == InternalState::POWER_SWITCHING) { + if (stackHandler.isSwitchOn()) { + internalState = InternalState::SETUP; + } + } + if (internalState == InternalState::CONFIGURED) { + if (goToNormalMode) { + setMode(MODE_NORMAL); + } else { + setMode(_MODE_TO_ON); + } + } +} + +void RadiationSensorHandler::doShutDown() { + ReturnValue_t retval = stackHandler.deviceToOff(StackCommander::RAD_SENSOR, true); + if (retval == BUSY) { + return; + } + internalState = InternalState::OFF; + setMode(_MODE_POWER_DOWN); +} + +ReturnValue_t RadiationSensorHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + if (measurementCd.isBusy()) { + return NOTHING_TO_SEND; + } + switch (communicationStep) { + case CommunicationStep::START_CONVERSION: { + *id = radSens::START_CONVERSION; + communicationStep = CommunicationStep::READ_CONVERSIONS; + break; + } + case CommunicationStep::READ_CONVERSIONS: { + *id = radSens::READ_CONVERSIONS; + communicationStep = CommunicationStep::START_CONVERSION; + break; + } + default: { + sif::debug << "RadiationSensorHandler::buildNormalDeviceCommand: Unknown communication " + << "step" << std::endl; + return returnvalue::OK; + } + } + return buildCommandFromCommand(*id, nullptr, 0); +} + +ReturnValue_t RadiationSensorHandler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + if (internalState == InternalState::SETUP) { + *id = radSens::WRITE_SETUP; + } else { + return NOTHING_TO_SEND; + } + return buildCommandFromCommand(*id, nullptr, 0); +} + +ReturnValue_t RadiationSensorHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + switch (deviceCommand) { + case (radSens::WRITE_SETUP): { + cmdBuffer[0] = radSens::SETUP_DEFINITION; + rawPacket = cmdBuffer; + rawPacketLen = 1; + internalState = InternalState::CONFIGURED; + return returnvalue::OK; + } + case (radSens::START_CONVERSION): { + ReturnValue_t result = gpioIF->pullHigh(gpioIds::ENABLE_RADFET); + // Test a small delay between pulling the RADFET high and reading the sensor. As long as this + // delay remains small enough, this should not cause scheduling issues. Do not make this + // delay large, this device might be scheduled inside the ACS PST! + TaskFactory::delayTask(5); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::warning + << "RadiationSensorHandler::buildCommandFromCommand: Pulling RADFET Enable pin " + "high failed" + << std::endl; +#endif + } + /* First the fifo will be reset here */ + cmdBuffer[0] = radSens::RESET_DEFINITION; + cmdBuffer[1] = radSens::CONVERSION_DEFINITION; + rawPacket = cmdBuffer; + rawPacketLen = 2; + return returnvalue::OK; + } + case (radSens::READ_CONVERSIONS): { + cmdBuffer[0] = radSens::DUMMY_BYTE; + std::memset(cmdBuffer, radSens::DUMMY_BYTE, radSens::READ_SIZE); + rawPacket = cmdBuffer; + rawPacketLen = radSens::READ_SIZE; + return returnvalue::OK; + } + case radSens::ENABLE_DEBUG_OUTPUT: { + printPeriodicData = true; + rawPacketLen = 0; + return returnvalue::OK; + } + case radSens::DISABLE_DEBUG_OUTPUT: { + rawPacketLen = 0; + printPeriodicData = false; + return returnvalue::OK; + } + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::FAILED; +} + +void RadiationSensorHandler::fillCommandAndReplyMap() { + this->insertInCommandMap(radSens::WRITE_SETUP); + this->insertInCommandMap(radSens::START_CONVERSION); + this->insertInCommandMap(radSens::ENABLE_DEBUG_OUTPUT); + this->insertInCommandMap(radSens::DISABLE_DEBUG_OUTPUT); + this->insertInCommandAndReplyMap(radSens::READ_CONVERSIONS, 1, &dataset, radSens::READ_SIZE); +} + +ReturnValue_t RadiationSensorHandler::scanForReply(const uint8_t *start, size_t remainingSize, + DeviceCommandId_t *foundId, size_t *foundLen) { + *foundId = this->getPendingCommand(); + + switch (*foundId) { + case radSens::START_CONVERSION: + case radSens::WRITE_SETUP: + *foundLen = remainingSize; + return IGNORE_REPLY_DATA; + case radSens::READ_CONVERSIONS: { + ReturnValue_t result = gpioIF->pullLow(gpioIds::ENABLE_RADFET); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::warning << "RadiationSensorHandler::scanForReply; Pulling RADFET Enale pin " + "low failed" + << std::endl; +#endif + } + break; + } + case radSens::ENABLE_DEBUG_OUTPUT: + case radSens::DISABLE_DEBUG_OUTPUT: + sif::info << "RadiationSensorHandler::scanForReply: " << remainingSize << std::endl; + break; + default: + break; + } + + *foundLen = remainingSize; + + return returnvalue::OK; +} + +ReturnValue_t RadiationSensorHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + switch (id) { + case radSens::READ_CONVERSIONS: { + uint8_t offset = 0; + measurementCd.resetTimer(); + { + PoolReadGuard readSet(&dataset); + uint16_t tempRaw = ((packet[offset] & 0x0f) << 8) | packet[offset + 1]; + dataset.temperatureCelcius = max1227::getTemperature(tempRaw); + offset += 2; + dataset.ain0 = (*(packet + offset) << 8) | *(packet + offset + 1); + offset += 2; + dataset.ain1 = (*(packet + offset) << 8) | *(packet + offset + 1); + offset += 6; + dataset.ain4 = (*(packet + offset) << 8) | *(packet + offset + 1); + offset += 2; + dataset.ain5 = (*(packet + offset) << 8) | *(packet + offset + 1); + offset += 2; + dataset.ain6 = (*(packet + offset) << 8) | *(packet + offset + 1); + offset += 2; + dataset.ain7 = (*(packet + offset) << 8) | *(packet + offset + 1); + dataset.setValidity(true, true); + } + if (printPeriodicData) { + sif::info << "Radiation sensor temperature: " << dataset.temperatureCelcius << " °C" + << std::dec << std::endl; + sif::info << "Radiation sensor ADC value channel 0: " << dataset.ain0 << std::endl; + sif::info << "Radiation sensor ADC value channel 1: " << dataset.ain1 << std::endl; + sif::info << "Radiation sensor ADC value channel 4: " << dataset.ain4 << std::endl; + sif::info << "Radiation sensor ADC value channel 5: " << dataset.ain5 << std::endl; + sif::info << "Radiation sensor ADC value channel 6: " << dataset.ain6 << std::endl; + sif::info << "Radiation sensor ADC value channel 7: " << dataset.ain7 << std::endl; + } + ReturnValue_t result = + getHkManagerHandle()->generateHousekeepingPacket(dataset.getSid(), &dataset, true); + if (result != returnvalue::OK) { + // TODO: Maybe add event? + sif::error << "Generating HK set for radiation sensor failed" << std::endl; + } + break; + } + default: { + sif::debug << "RadiationSensorHandler::interpretDeviceReply: Unknown reply id" << std::endl; + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + return returnvalue::OK; +} + +uint32_t RadiationSensorHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { + return 5000; +} + +ReturnValue_t RadiationSensorHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(radSens::TEMPERATURE_C, new PoolEntry({0.0})); + localDataPoolMap.emplace(radSens::AIN0, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN1, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN4, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN5, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN6, new PoolEntry({0})); + localDataPoolMap.emplace(radSens::AIN7, new PoolEntry({0})); + // It should normally not be necessary to enable this set, as a sample TM will be generated + // after a measurement. If this is still enabled, sample with double the measurement frequency + // to ensure we get all measurements. + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(dataset.getSid(), false, DEFAULT_MEASUREMENT_CD_MS / 2)); + return returnvalue::OK; +} + +void RadiationSensorHandler::setToGoToNormalModeImmediately() { this->goToNormalMode = true; } + +void RadiationSensorHandler::enablePeriodicDataPrint(bool enable) { + this->printPeriodicData = enable; +} diff --git a/mission/payload/RadiationSensorHandler.h b/mission/payload/RadiationSensorHandler.h new file mode 100644 index 0000000..429cda7 --- /dev/null +++ b/mission/payload/RadiationSensorHandler.h @@ -0,0 +1,60 @@ +#ifndef MISSION_PAYLOAD_RADIATIONSENSORHANDLER_H_ +#define MISSION_PAYLOAD_RADIATIONSENSORHANDLER_H_ + +#include +#include +#include +#include + +/** + * @brief This is the device handler class for radiation sensor on the OBC IF Board. The + * sensor is based on the MAX1227 ADC converter. + * + * @details Datasheet of MAX1227: https://datasheets.maximintegrated.com/en/ds/MAX1227-MAX1231.pdf + * + * @author J. Meier + */ +class RadiationSensorHandler : public DeviceHandlerBase { + public: + RadiationSensorHandler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie, + GpioIF *gpioIF, Stack5VHandler &handler); + virtual ~RadiationSensorHandler(); + void setToGoToNormalModeImmediately(); + void enablePeriodicDataPrint(bool enable); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t remainingSize, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + + private: + static constexpr uint32_t DEFAULT_MEASUREMENT_CD_MS = 30 * 60 * 1000; + + enum class CommunicationStep { START_CONVERSION, READ_CONVERSIONS }; + + enum class InternalState { OFF, POWER_SWITCHING, SETUP, CONFIGURED }; + + bool printPeriodicData = false; + radSens::RadSensorDataset dataset; + Countdown measurementCd = Countdown(DEFAULT_MEASUREMENT_CD_MS); + static const uint8_t MAX_CMD_LEN = radSens::READ_SIZE; + GpioIF *gpioIF = nullptr; + Stack5VHandler &stackHandler; + + bool goToNormalMode = false; + uint8_t cmdBuffer[MAX_CMD_LEN]; + InternalState internalState = InternalState::OFF; + CommunicationStep communicationStep = CommunicationStep::START_CONVERSION; +}; + +#endif /* MISSION_PAYLOAD_RADIATIONSENSORHANDLER_H_ */ diff --git a/mission/payload/ScexDeviceHandler.cpp b/mission/payload/ScexDeviceHandler.cpp new file mode 100644 index 0000000..0d15c82 --- /dev/null +++ b/mission/payload/ScexDeviceHandler.cpp @@ -0,0 +1,395 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "eive/definitions.h" +#include "fsfw/globalfunctions/CRC.h" + +using std::ofstream; +using namespace returnvalue; + +ScexDeviceHandler::ScexDeviceHandler(object_id_t objectId, ScexUartReader& reader, CookieIF* cookie, + SdCardMountedIF& sdcMan) + : DeviceHandlerBase(objectId, reader.getObjectId(), cookie), sdcMan(sdcMan), reader(reader) { + fsUnusableEventCd.timeOut(); +} + +ScexDeviceHandler::~ScexDeviceHandler() {} + +void ScexDeviceHandler::doStartUp() { + filesystemChecks(); + setMode(MODE_ON); +} + +void ScexDeviceHandler::doShutDown() { + reader.reset(); + commandActive = false; + fileNameSet = false; + multiFileFinishOutstanding = false; + setMode(_MODE_POWER_DOWN); +} + +ReturnValue_t ScexDeviceHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t ScexDeviceHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t ScexDeviceHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + using namespace scex; + + auto cmdTyped = static_cast(deviceCommand); + if (std::find(VALID_CMDS.begin(), VALID_CMDS.end(), deviceCommand) == VALID_CMDS.end()) { + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + bool tempCheck = false; + if (commandDataLen >= 1) { + tempCheck = commandData[0]; + } + if (commandActive) { + return DeviceHandlerIF::BUSY; + } + + switch (deviceCommand) { + case (PING): { + finishCountdown.setTimeout(SHORT_CD); + // countdown starten + finishCountdown.resetTimer(); + prepareScexCmd(cmdTyped, {cmdBuf.data(), cmdBuf.size()}, rawPacketLen, {nullptr, 0}, + tempCheck); + break; + } + case (EXP_STATUS_CMD): { + finishCountdown.setTimeout(SHORT_CD); + // countdown starten + finishCountdown.resetTimer(); + prepareScexCmd(cmdTyped, {cmdBuf.data(), cmdBuf.size()}, rawPacketLen, {nullptr, 0}, + tempCheck); + break; + } + case (ION_CMD): { + finishCountdown.setTimeout(SHORT_CD); + // countdown starten + finishCountdown.resetTimer(); + prepareScexCmd(cmdTyped, {cmdBuf.data(), cmdBuf.size()}, rawPacketLen, {nullptr, 0}, + tempCheck); + break; + } + case (TEMP_CMD): { + finishCountdown.setTimeout(SHORT_CD); + // countdown starten + finishCountdown.resetTimer(); + prepareScexCmd(cmdTyped, {cmdBuf.data(), cmdBuf.size()}, rawPacketLen, {nullptr, 0}, + tempCheck); + break; + } + case (FRAM): { + finishCountdown.setTimeout(LONG_CD); + // countdown starten + finishCountdown.resetTimer(); + if (debugMode) { + uint32_t remainingMillis = finishCountdown.getRemainingMillis(); + + sif::info << "ScexDeviceHandler::buildCommandFromCommand: RemainingMillis: " + << remainingMillis << std::endl; + } + + multiFileFinishOutstanding = true; + prepareScexCmd(cmdTyped, {cmdBuf.data(), cmdBuf.size()}, rawPacketLen, + {commandData + 1, commandDataLen - 1}, tempCheck); + updatePeriodicReply(true, deviceCommand); + break; + } + case (ONE_CELL): { + finishCountdown.setTimeout(LONG_CD); + // countdown starts + finishCountdown.resetTimer(); + multiFileFinishOutstanding = true; + prepareScexCmd(cmdTyped, {cmdBuf.data(), cmdBuf.size()}, rawPacketLen, + {commandData + 1, commandDataLen - 1}, tempCheck); + updatePeriodicReply(true, deviceCommand); + break; + } + case (ALL_CELLS_CMD): { + finishCountdown.setTimeout(LONG_CD); + // countdown starts + finishCountdown.resetTimer(); + multiFileFinishOutstanding = true; + prepareScexCmd(cmdTyped, {cmdBuf.data(), cmdBuf.size()}, rawPacketLen, + {commandData + 1, commandDataLen - 1}, tempCheck); + updatePeriodicReply(true, deviceCommand); + break; + } + default: { + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + } + commandActive = true; + rawPacket = cmdBuf.data(); + return OK; +} + +void ScexDeviceHandler::fillCommandAndReplyMap() { + insertInCommandAndReplyMap(scex::Cmds::PING, 5, nullptr, 0, false, false, 0, &finishCountdown); + insertInCommandAndReplyMap(scex::Cmds::ION_CMD, 3, nullptr, 0, false, false, 0, &finishCountdown); + insertInCommandAndReplyMap(scex::Cmds::TEMP_CMD, 3, nullptr, 0, false, false, 0, + &finishCountdown); + insertInCommandAndReplyMap(scex::Cmds::EXP_STATUS_CMD, 3, nullptr, 0, false, false, 0, + &finishCountdown); + + insertInCommandAndReplyMap(scex::Cmds::ALL_CELLS_CMD, 0, nullptr, 0, true, false, + scex::Cmds::ALL_CELLS_CMD, &finishCountdown); + insertInCommandAndReplyMap(scex::Cmds::ONE_CELL, 0, nullptr, 0, true, false, scex::Cmds::ONE_CELL, + &finishCountdown); + insertInCommandAndReplyMap(scex::Cmds::FRAM, 0, nullptr, 0, true, false, scex::Cmds::FRAM, + &finishCountdown); + + insertInReplyMap(scex::Cmds::ERROR_REPLY, 3); +} + +ReturnValue_t ScexDeviceHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + size_t len = remainingSize; + ReturnValue_t result = helper.deSerialize(&start, &len); + + if (result == ScexHelper::INVALID_CRC) { + sif::warning << "ScexDeviceHandler::scanForReply: CRC invalid" << std::endl; + *foundLen = remainingSize; + } else { + result = handleValidReply(remainingSize, foundId, foundLen); + } + return result; +} + +ReturnValue_t ScexDeviceHandler::handleValidReply(size_t remSize, DeviceCommandId_t* foundId, + size_t* foundLen) { + using namespace scex; + ReturnValue_t result = OK; + + switch (helper.getCmd()) { + case (FRAM): { + if (debugMode) { + uint32_t remainingMillis = finishCountdown.getRemainingMillis(); + + sif::info << "ScexDeviceHandler::handleValidReply: RemMillis: " << remainingMillis + << std::endl; + } + result = APERIODIC_REPLY; + break; + } + case (ONE_CELL): { + result = APERIODIC_REPLY; + break; + } + case (ALL_CELLS_CMD): { + result = APERIODIC_REPLY; + break; + } + default: { + break; + } + } + if (result == APERIODIC_REPLY and multiFileFinishOutstanding) { + finishAction(true, helper.getCmd(), OK); + multiFileFinishOutstanding = false; + } + + *foundId = helper.getCmd(); + *foundLen = remSize; + return result; +} + +ReturnValue_t ScexDeviceHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) { + using namespace scex; + + ReturnValue_t status = OK; + auto multiFileHandler = [&](const char* cmdName) { + if ((helper.getPacketCounter() == 1) or (not fileNameSet)) { + status = generateNewScexFile(cmdName); + if (status != returnvalue::OK) { + return status; + } + fileNameSet = true; + } else { + if (!sdcMan.isSdCardUsable(std::nullopt)) { + fsUnsableEvent(); + return returnvalue::FAILED; + } + // Append to existing file. + ofstream out(fileName, ofstream::binary | ofstream::app); + if (out.bad()) { + sif::error << "ScexDeviceHandler::handleValidReply: Could not open file " << fileName + << std::endl; + return FAILED; + } + out << helper; + } + return OK; + }; + id = helper.getCmd(); + switch (id) { + case (PING): { + status = generateNewScexFile(PING_IDLE_BASE_NAME); + break; + } + case (ION_CMD): { + status = generateNewScexFile(ION_BASE_NAME); + break; + } + case (TEMP_CMD): { + status = generateNewScexFile(TEMPERATURE_BASE_NAME); + break; + } + case (EXP_STATUS_CMD): { + status = generateNewScexFile(EXP_STATUS_BASE_NAME); + break; + } + case (FRAM): { + status = multiFileHandler(FRAM_BASE_NAME); + break; + } + case (ONE_CELL): { + status = multiFileHandler(ONE_CELL_BASE_NAME); + break; + } + case (ALL_CELLS_CMD): { + status = multiFileHandler(ALL_CELLS_BASE_NAME); + break; + } + default: + // Unknown DeviceCommand + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + if (helper.getPacketCounter() >= helper.getTotalPacketCounter()) { + reader.finish(); + commandActive = false; + if (id != PING) { + fileNameSet = false; + } + if (id == FRAM or id == ALL_CELLS_CMD or id == ONE_CELL) { + triggerEvent(MULTI_PACKET_COMMAND_DONE, id); + updatePeriodicReply(false, id); + } + } + if (debugMode) { + uint32_t remainingMillis = finishCountdown.getRemainingMillis(); + sif::info << __FILE__ << __func__ << "(" << __LINE__ << ") RemMillis: " << remainingMillis + << std::endl; + } + return status; +} + +void ScexDeviceHandler::performOperationHook() { + if (getMode() != MODE_OFF) { + filesystemChecks(); + } + uint32_t remainingMillis = finishCountdown.getRemainingMillis(); + if (commandActive and finishCountdown.hasTimedOut()) { + triggerEvent(scex::EXPERIMENT_TIMEDOUT, currCmd, 0); + reader.finish(); + sif::warning << "ScexDeviceHandler::scanForReply: Reader timeout; RemMillis: " + << remainingMillis << std::endl; + fileNameSet = false; + commandActive = false; + } +} + +uint32_t ScexDeviceHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 5000; } + +ReturnValue_t ScexDeviceHandler::getSwitches(const uint8_t** switches, uint8_t* numberOfSwitches) { + if (switchId) { + *numberOfSwitches = 1; + *switches = &switchId.value(); + } + return OK; +} + +ReturnValue_t ScexDeviceHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + return OK; +} + +void ScexDeviceHandler::filesystemChecks() { + auto mntPrefix = sdcMan.getCurrentMountPrefix(); + if (mntPrefix == nullptr or !sdcMan.isSdCardUsable(std::nullopt)) { + sif::warning << "SCEX: Filesystem currently unavailable" << std::endl; + fsUnsableEvent(); + } else { + std::filesystem::path fullFilePath = mntPrefix; + std::error_code e; + fullFilePath /= "scex"; + bool fileExists = std::filesystem::exists(fullFilePath, e); + if (not fileExists) { + bool created = std::filesystem::create_directory(fullFilePath, e); + if (not created) { + sif::error << "Could not create SCEX directory: " << e << std::endl; + } + } + } +} + +void ScexDeviceHandler::fsUnsableEvent() { + if (fsUnusableEventCd.isBusy()) { + return; + } + triggerEvent(scex::FS_UNUSABLE); + fsUnusableEventCd.resetTimer(); +} + +ReturnValue_t ScexDeviceHandler::generateNewScexFile(const char* cmdName) { + char timeString[64]{}; + auto activeSd = sdcMan.getActiveSdCard(); + if (not activeSd) { + return HasFileSystemIF::FILESYSTEM_INACTIVE; + } + + std::ostringstream oss; + auto prefix = sdcMan.getCurrentMountPrefix(); + if (prefix == nullptr or !sdcMan.isSdCardUsable(std::nullopt)) { + fsUnsableEvent(); + return returnvalue::FAILED; + } + timeval tv; + Clock::getClock_timeval(&tv); + time_t epoch = tv.tv_sec; + struct tm* time = gmtime(&epoch); + size_t writtenBytes = strftime(reinterpret_cast(timeString), sizeof(timeString), + config::FILE_DATE_FORMAT, time); + if (writtenBytes == 0) { + sif::error << "PersistentTmStore::createMostRecentFile: Could not create file timestamp" + << std::endl; + return returnvalue::FAILED; + } + oss << prefix << "/scex/scex-" << cmdName << "-" << timeString << ".bin"; + fileName = oss.str(); + ofstream out(fileName, ofstream::binary); + if (out.bad()) { + sif::error << "ScexDeviceHandler::interpretDeviceReply: Could not open file " << fileName + << std::endl; + return FAILED; + } + out << helper; + return OK; +} + +void ScexDeviceHandler::setPowerSwitcher(PowerSwitchIF& powerSwitcher, power::Switch_t switchId) { + DeviceHandlerBase::setPowerSwitcher(&powerSwitcher); + this->switchId = switchId; +} + +ReturnValue_t ScexDeviceHandler::initializeAfterTaskCreation() { + return DeviceHandlerBase::initializeAfterTaskCreation(); +} diff --git a/mission/payload/ScexDeviceHandler.h b/mission/payload/ScexDeviceHandler.h new file mode 100644 index 0000000..ecde12a --- /dev/null +++ b/mission/payload/ScexDeviceHandler.h @@ -0,0 +1,76 @@ +#ifndef MISSION_PAYLOAD_SCEXDEVICEHANDLER_H_ +#define MISSION_PAYLOAD_SCEXDEVICEHANDLER_H_ + +#include +#include +#include + +#include + +#include "eive/eventSubsystemIds.h" + +class SdCardMountedIF; + +class ScexDeviceHandler : public DeviceHandlerBase { + public: + static constexpr char FRAM_BASE_NAME[] = "framContent"; + static constexpr char ION_BASE_NAME[] = "ion"; + static constexpr char TEMPERATURE_BASE_NAME[] = "temperature"; + static constexpr char EXP_STATUS_BASE_NAME[] = "expStatus"; + static constexpr char ONE_CELL_BASE_NAME[] = "oneCell"; + static constexpr char ALL_CELLS_BASE_NAME[] = "allCells"; + static constexpr char PING_IDLE_BASE_NAME[] = "pingIdle"; + + ScexDeviceHandler(object_id_t objectId, ScexUartReader &reader, CookieIF *cookie, + SdCardMountedIF &sdcMan); + void setPowerSwitcher(PowerSwitchIF &powerSwitcher, power::Switch_t switchId); + virtual ~ScexDeviceHandler(); + + private: + static constexpr uint32_t LONG_CD = 180 * 1000; + static constexpr uint32_t SHORT_CD = 12000; + std::array cmdBuf = {}; + + std::optional switchId; + std::string fileId = ""; + std::string fileName = ""; + bool fileNameSet = false; + bool commandActive = false; + bool multiFileFinishOutstanding = false; + bool debugMode = false; + + scex::Cmds currCmd = scex::Cmds::PING; + SdCardMountedIF &sdcMan; + Countdown finishCountdown = Countdown(LONG_CD); + Countdown fsUnusableEventCd = Countdown(10000); + + // DeviceHandlerBase private function implementation + void doStartUp() override; + void doShutDown() override; + ScexHelper helper; + ScexUartReader &reader; + + void fsUnsableEvent(); + + void performOperationHook() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + void fillCommandAndReplyMap() override; + void filesystemChecks(); + ReturnValue_t scanForReply(const uint8_t *start, size_t remainingSize, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + ReturnValue_t handleValidReply(size_t remSize, DeviceCommandId_t *foundId, size_t *foundLen); + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t getSwitches(const uint8_t **switches, uint8_t *numberOfSwitches) override; + + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + ReturnValue_t initializeAfterTaskCreation() override; + + ReturnValue_t generateNewScexFile(const char *cmdName); +}; + +#endif /* MISSION_PAYLOAD_SCEXDEVICEHANDLER_H_ */ diff --git a/mission/payload/defs.cpp b/mission/payload/defs.cpp new file mode 100644 index 0000000..d5d4912 --- /dev/null +++ b/mission/payload/defs.cpp @@ -0,0 +1,32 @@ +#include + +const char* payload::getModeStr(Mode mode) { + static const char* modeStr = "UNKNOWN"; + switch (mode) { + case (Mode::CAM_STREAM): { + modeStr = "CAM STREAM"; + break; + } + case (Mode::EARTH_OBSV): { + modeStr = "EARTH OBSV"; + break; + } + case (Mode::MPSOC_STREAM): { + modeStr = "MPSOC STREAM"; + break; + } + case (Mode::OFF): { + modeStr = "OFF"; + break; + } + case (Mode::SUPV_ONLY): { + modeStr = "SUPV ONLY"; + break; + } + case (Mode::SCEX): { + modeStr = "SCEX"; + break; + } + } + return modeStr; +} diff --git a/mission/payload/defs.h b/mission/payload/defs.h new file mode 100644 index 0000000..297ed87 --- /dev/null +++ b/mission/payload/defs.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace payload { + +enum Mode { + OFF = 0, + SUPV_ONLY = 10, + MPSOC_STREAM = 11, + CAM_STREAM = 12, + EARTH_OBSV = 13, + SCEX = 14 +}; + +extern const char* getModeStr(Mode mode); + +namespace ploc { + +enum Modes { OFF = 0, SUPV_ONLY = 1, MPSOC_ON = 2 }; + +} + +} // namespace payload diff --git a/mission/payload/payloadPcduDefinitions.h b/mission/payload/payloadPcduDefinitions.h new file mode 100644 index 0000000..dbd38a9 --- /dev/null +++ b/mission/payload/payloadPcduDefinitions.h @@ -0,0 +1,244 @@ +#ifndef LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_ +#define LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_ + +#include +#include +#include +#include + +#include +#include +#include + +#include "OBSWConfig.h" + +namespace plpcdu { + +using namespace max1227; + +enum PlPcduAdcChannels : uint8_t { + U_BAT_DIV_6 = 0, + // According to schematic, will be 2.2158V for Vneg = +0V and 0.2446V for Vneg = -6V + // Full Forumula: V_neg = V_post - (R1 + R2) / R1 * (V_pos - V_out) with R1 being 27.4k + // and R2 being 49.9k. FB = Feedback + U_NEG_V_FB = 1, + I_HPA = 2, + U_HPA_DIV_6 = 3, + I_MPA = 4, + U_MPA_DIV_6 = 5, + I_TX = 6, + U_TX_DIV_6 = 7, + I_X8 = 8, + U_X8_DIV_6 = 9, + I_DRO = 10, + U_DRO_DIV_6 = 11, + NUM_CHANNELS = 12 +}; + +enum PlPcduParamId : uint8_t { + NEG_V_LOWER_BOUND = 0, + NEG_V_UPPER_BOUND = 1, + DRO_U_LOWER_BOUND = 2, + DRO_U_UPPER_BOUND = 3, + DRO_I_UPPER_BOUND = 4, + X8_U_LOWER_BOUND = 5, + X8_U_UPPER_BOUND = 6, + X8_I_UPPER_BOUND = 7, + TX_U_LOWER_BOUND = 8, + TX_U_UPPER_BOUND = 9, + TX_I_UPPER_BOUND = 10, + MPA_U_LOWER_BOUND = 11, + MPA_U_UPPER_BOUND = 12, + MPA_I_UPPER_BOUND = 13, + HPA_U_LOWER_BOUND = 14, + HPA_U_UPPER_BOUND = 15, + HPA_I_UPPER_BOUND = 16, + + SSR_TO_DRO_WAIT_TIME = 17, + DRO_TO_X8_WAIT_TIME = 18, + X8_TO_TX_WAIT_TIME = 19, + TX_TO_MPA_WAIT_TIME = 20, + MPA_TO_HPA_WAIT_TIME = 21, + + INJECT_SSR_TO_DRO_FAILURE = 30, + INJECT_DRO_TO_X8_FAILURE = 31, + INJECT_X8_TO_TX_FAILURE = 32, + INJECT_TX_TO_MPA_FAILURE = 33, + INJECT_MPA_TO_HPA_FAILURE = 34, + INJECT_ALL_ON_FAILURE = 35, + + DISABLE_ORDER_CHECK_CHANNELS = 40 +}; + +static std::map PARAM_KEY_MAP = { + {NEG_V_LOWER_BOUND, "negVoltLowerBound"}, {NEG_V_UPPER_BOUND, "negVoltUpperBound"}, + {DRO_U_LOWER_BOUND, "droVoltLowerBound"}, {DRO_U_UPPER_BOUND, "droVoltUpperBound"}, + {DRO_I_UPPER_BOUND, "droCurrUpperBound"}, {X8_U_LOWER_BOUND, "x8VoltLowerBound"}, + {X8_U_UPPER_BOUND, "x8VoltUpperBound"}, {X8_I_UPPER_BOUND, "x8CurrUpperBound"}, + {TX_U_LOWER_BOUND, "txVoltLowerBound"}, {TX_U_UPPER_BOUND, "txVoltUpperBound"}, + {TX_I_UPPER_BOUND, "txCurrUpperBound"}, {MPA_U_LOWER_BOUND, "mpaVoltLowerBound"}, + {MPA_U_UPPER_BOUND, "mpaVoltUpperBound"}, {MPA_I_UPPER_BOUND, "mpaCurrUpperBound"}, + {HPA_U_LOWER_BOUND, "hpaVoltLowerBound"}, {HPA_U_UPPER_BOUND, "hpaVoltUpperBound"}, + {HPA_I_UPPER_BOUND, "hpaCurrUpperBound"}, {SSR_TO_DRO_WAIT_TIME, "ssrToDroWait"}, + {DRO_TO_X8_WAIT_TIME, "droToX8Wait"}, {X8_TO_TX_WAIT_TIME, "x8ToTxWait"}, + {TX_TO_MPA_WAIT_TIME, "txToMpaWait"}, {MPA_TO_HPA_WAIT_TIME, "mpaToHpaWait"}, +}; + +enum PlPcduPoolIds : uint32_t { CHANNEL_VEC = 0, PROCESSED_VEC = 1, TEMP = 2 }; + +static constexpr size_t MAX_ADC_REPLY_SIZE = 64; + +static constexpr DeviceCommandId_t READ_CMD = 0; +static constexpr DeviceCommandId_t SETUP_CMD = 1; +static constexpr DeviceCommandId_t READ_TEMP_EXT = 2; +static constexpr DeviceCommandId_t READ_WITH_TEMP_EXT = 3; + +enum NormalSubmodeBits { + SOLID_STATE_RELAYS_ADC_ON = 0, + DRO_ON = 1, + X8_ON = 2, + TX_ON = 3, + MPA_ON = 4, + HPA_ON = 5 +}; + +static constexpr Submode_t ALL_OFF_SUBMODE = 0; +static constexpr Submode_t ALL_ON_SUBMODE = (1 << HPA_ON) | (1 << MPA_ON) | (1 << TX_ON) | + (1 << X8_ON) | (1 << DRO_ON) | + (1 << SOLID_STATE_RELAYS_ADC_ON); + +// 12 ADC values * 2 + trailing zero +static constexpr size_t ADC_REPLY_SIZE = 25; +// Conversion byte + 24 * zero +static constexpr size_t TEMP_REPLY_SIZE = 25; + +static constexpr uint8_t SETUP_BYTE = + max1227::buildSetupByte(ClkSel::EXT_CONV_EXT_TIMED, RefSel::INT_REF_NO_WAKEUP, DiffSel::NONE_0); + +static constexpr uint32_t ADC_SET_ID = READ_CMD; +static constexpr uint8_t CHANNELS_NUM = 12; +static constexpr uint8_t CHANNEL_N = CHANNELS_NUM - 1; +// Store temperature as well +static constexpr size_t DATASET_ENTRIES = CHANNELS_NUM + 1; + +static constexpr uint8_t VOLTAGE_DIV = 6; +// 12-bit ADC: 2 to the power of 12 minus 1 +static constexpr uint16_t MAX122X_BIT = 4095; +static constexpr float MAX122X_VREF = 2.5; +static constexpr float GAIN_INA169 = 100.0; +static constexpr float R_SHUNT_HPA = 0.008; +static constexpr float R_SHUNT_MPA = 0.015; +static constexpr float R_SHUNT_TX = 0.05; +static constexpr float R_SHUNT_X8 = 0.015; +static constexpr float R_SHUNT_DRO = 0.22; +static constexpr float V_POS = 3.3; +static constexpr float VOLTAGE_DIV_U_NEG = (49.9 + 27.4) / 27.4; +static constexpr float MAX122X_SCALE = MAX122X_VREF / MAX122X_BIT; +static constexpr float SCALE_VOLTAGE = MAX122X_SCALE * VOLTAGE_DIV; +static constexpr float SCALE_CURRENT_HPA = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_HPA); +static constexpr float SCALE_CURRENT_MPA = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_MPA); +static constexpr float SCALE_CURRENT_TX = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_TX); +static constexpr float SCALE_CURRENT_X8 = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_X8); +static constexpr float SCALE_CURRENT_DRO = MAX122X_SCALE / (GAIN_INA169 * R_SHUNT_DRO); + +// TODO: Make these configurable parameters using a JSON file +// Upper bound of currents in milliamperes [mA] +static constexpr double DFT_NEG_V_LOWER_BOUND = -6.5; +static constexpr double DFT_NEG_V_UPPER_BOUND = -2.7; + +static constexpr double DFT_DRO_U_LOWER_BOUND = 5.0; +static constexpr double DFT_DRO_U_UPPER_BOUND = 7.0; +// Max Current DRO + Max Current Neg V | 40 + 15 +static constexpr double DFT_DRO_I_UPPER_BOUND = 55.0; + +static constexpr double DFT_X8_U_LOWER_BOUND = 2.6; +static constexpr double DFT_X8_U_UPPER_BOUND = 4.0; +static constexpr double DFT_X8_I_UPPER_BOUND = 100.0; + +static constexpr double DFT_TX_U_LOWER_BOUND = 2.6; +static constexpr double DFT_TX_U_UPPER_BOUND = 4.0; +static constexpr double DFT_TX_I_UPPER_BOUND = 250.0; + +static constexpr double DFT_MPA_U_LOWER_BOUND = 2.6; +static constexpr double DFT_MPA_U_UPPER_BOUND = 4.0; +static constexpr double DFT_MPA_I_UPPER_BOUND = 650.0; + +static constexpr double DFT_HPA_U_LOWER_BOUND = 9.4; +static constexpr double DFT_HPA_U_UPPER_BOUND = 11.0; +static constexpr double DFT_HPA_I_UPPER_BOUND = 3800.0; + +// Wait time in floating point seconds +static constexpr double DFT_SSR_TO_DRO_WAIT_TIME = 5.0; +static constexpr double DFT_DRO_TO_X8_WAIT_TIME = 905.0; +static constexpr double DFT_X8_TO_TX_WAIT_TIME = 5.0; +static constexpr double DFT_TX_TO_MPA_WAIT_TIME = 5.0; +static constexpr double DFT_MPA_TO_HPA_WAIT_TIME = 5.0; + +/** + * The current of the processed values is calculated and stored as a milliamperes [mA]. + * The voltages are stored as Volt values. + */ +class PlPcduAdcSet : public StaticLocalDataSet { + public: + PlPcduAdcSet(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, ADC_SET_ID) {} + + PlPcduAdcSet(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, ADC_SET_ID)) {} + + lp_vec_t channels = lp_vec_t(sid.objectId, CHANNEL_VEC, this); + lp_vec_t processed = lp_vec_t(sid.objectId, PROCESSED_VEC, this); + lp_var_t tempC = lp_var_t(sid.objectId, TEMP, this); +}; + +class PlPcduParameter : public NVMParameterBase { + public: + PlPcduParameter() : NVMParameterBase(""), mountPrefix("") { + using namespace plpcdu; + // Initialize with default values + resetValues(); + } + + ReturnValue_t initialize(std::string mountPrefix) { + setFullName(mountPrefix + "/conf/plpcdu.json"); + ReturnValue_t result = readJsonFile(); + if (result != returnvalue::OK) { + // File does not exist or reading JSON failed for various reason. Rewrite the JSON file +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Creating PL PCDU JSON file at " << getFullName() << std::endl; +#endif + resetValues(); + writeJsonFile(); + } + return returnvalue::OK; + } + void resetValues() { + insertValue(PARAM_KEY_MAP[SSR_TO_DRO_WAIT_TIME], DFT_SSR_TO_DRO_WAIT_TIME); + insertValue(PARAM_KEY_MAP[DRO_TO_X8_WAIT_TIME], DFT_DRO_TO_X8_WAIT_TIME); + insertValue(PARAM_KEY_MAP[X8_TO_TX_WAIT_TIME], DFT_X8_TO_TX_WAIT_TIME); + insertValue(PARAM_KEY_MAP[TX_TO_MPA_WAIT_TIME], DFT_TX_TO_MPA_WAIT_TIME); + insertValue(PARAM_KEY_MAP[MPA_TO_HPA_WAIT_TIME], DFT_MPA_TO_HPA_WAIT_TIME); + insertValue(PARAM_KEY_MAP[NEG_V_LOWER_BOUND], DFT_NEG_V_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[NEG_V_UPPER_BOUND], DFT_NEG_V_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[DRO_U_LOWER_BOUND], DFT_DRO_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[DRO_U_UPPER_BOUND], DFT_DRO_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[DRO_I_UPPER_BOUND], DFT_DRO_I_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[X8_U_LOWER_BOUND], DFT_X8_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[X8_U_UPPER_BOUND], DFT_X8_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[X8_I_UPPER_BOUND], DFT_X8_I_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[TX_U_LOWER_BOUND], DFT_TX_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[TX_U_UPPER_BOUND], DFT_TX_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[TX_I_UPPER_BOUND], DFT_TX_I_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[MPA_U_LOWER_BOUND], DFT_MPA_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[MPA_U_UPPER_BOUND], DFT_MPA_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[MPA_I_UPPER_BOUND], DFT_MPA_I_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[HPA_U_LOWER_BOUND], DFT_HPA_U_LOWER_BOUND); + insertValue(PARAM_KEY_MAP[HPA_U_UPPER_BOUND], DFT_HPA_U_UPPER_BOUND); + insertValue(PARAM_KEY_MAP[HPA_I_UPPER_BOUND], DFT_HPA_I_UPPER_BOUND); + } + + private: + std::string mountPrefix; +}; + +} // namespace plpcdu + +#endif /* LINUX_DEVICES_DEVICEDEFINITIONS_PAYLOADPCDUDEFINITIONS_H_ */ diff --git a/mission/payload/plocSpBase.h b/mission/payload/plocSpBase.h new file mode 100644 index 0000000..96de052 --- /dev/null +++ b/mission/payload/plocSpBase.h @@ -0,0 +1,117 @@ +#ifndef MISSION_PAYLOAD_PLOCSPBASE_H_ +#define MISSION_PAYLOAD_PLOCSPBASE_H_ + +#include +#include +#include + +#include + +namespace ploc { + +struct SpTcParams { + SpTcParams(SpacePacketCreator& creator) : creator(creator) {} + + SpTcParams(SpacePacketCreator& creator, uint8_t* buf, size_t maxSize) + : creator(creator), buf(buf), maxSize(maxSize) {} + + // Set full payload length. Must also consider the two CRC bytes + void setFullPayloadLen(size_t fullPayloadLen_) { fullPayloadLen = fullPayloadLen_; } + + SpacePacketCreator& creator; + uint8_t* buf = nullptr; + size_t maxSize = 0; + size_t fullPayloadLen = 0; +}; + +class SpTcBase { + public: + SpTcBase(SpTcParams params) : SpTcBase(params, 0x00, 1, 0) {} + + SpTcBase(SpTcParams params, uint16_t apid, size_t payloadLen) + : SpTcBase(params, apid, payloadLen, 0) {} + + SpTcBase(SpTcParams params, uint16_t apid, size_t payloadLen, uint16_t seqCount) + : spParams(params) { + spParams.creator.setApid(apid); + spParams.creator.setSeqCount(seqCount); + payloadStart = spParams.buf + ccsds::HEADER_LEN; + spParams.fullPayloadLen = payloadLen; + updateSpFields(); + } + + void updateSpFields() { + updateLenFromParams(); + spParams.creator.setPacketType(ccsds::PacketType::TC); + } + + void updateLenFromParams() { spParams.creator.setDataLenField(spParams.fullPayloadLen - 1); } + const uint8_t* getFullPacket() const { return spParams.buf; } + + const uint8_t* getPacketData() const { return spParams.buf + ccsds::HEADER_LEN; } + + size_t getFullPacketLen() const { return spParams.creator.getFullPacketLen(); } + + uint16_t getApid() const { return spParams.creator.getApid(); } + + uint16_t getSeqCount() const { return spParams.creator.getSequenceCount(); } + + ReturnValue_t checkPayloadLen() { + if (ccsds::HEADER_LEN + spParams.fullPayloadLen > spParams.maxSize) { + return SerializeIF::BUFFER_TOO_SHORT; + } + + return returnvalue::OK; + } + + ReturnValue_t serializeHeader() { + updateSpFields(); + size_t serLen = 0; + return spParams.creator.serializeBe(spParams.buf, serLen, spParams.maxSize); + } + + ReturnValue_t checkSizeAndSerializeHeader() { + ReturnValue_t result = checkPayloadLen(); + if (result != returnvalue::OK) { + return result; + } + return serializeHeader(); + } + + ReturnValue_t calcAndSetCrc() { + /* Calculate crc */ + uint16_t crc = CRC::crc16ccitt(spParams.buf, getFullPacketLen() - 2); + + /* Add crc to packet data field of space packet */ + size_t serializedSize = 0; + return SerializeAdapter::serialize(&crc, spParams.buf + getFullPacketLen() - 2, &serializedSize, + spParams.maxSize, SerializeIF::Endianness::BIG); + } + + protected: + ploc::SpTcParams spParams; + uint8_t* payloadStart; +}; + +/** + * @brief Class for handling tm replies of the supervisor. + */ +class SpTmReader : public SpacePacketReader { + public: + SpTmReader() = default; + /** + * @brief Constructor creates idle packet and sets length field to maximum allowed size. + */ + SpTmReader(const uint8_t* buf, size_t maxSize) : SpacePacketReader(buf, maxSize) {} + + ReturnValue_t checkCrc() const { + if (CRC::crc16ccitt(getFullData(), getFullPacketLen()) != 0) { + return returnvalue::FAILED; + } + return returnvalue::OK; + } +}; + +} // namespace ploc + +#endif /* MISSION_PAYLOAD_PLOCSPBASE_H_ */ diff --git a/mission/payload/radSensorDefinitions.h b/mission/payload/radSensorDefinitions.h new file mode 100644 index 0000000..3262c23 --- /dev/null +++ b/mission/payload/radSensorDefinitions.h @@ -0,0 +1,83 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_RADSENSOR_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_RADSENSOR_H_ + +namespace radSens { + +static const DeviceCommandId_t NONE = 0x0; // Set when no command is pending + +/** + * This command initiates the ADC conversion for all channels including the internal + * temperature sensor. + */ +static const DeviceCommandId_t WRITE_SETUP = 1; +static const DeviceCommandId_t START_CONVERSION = 2; +static const DeviceCommandId_t READ_CONVERSIONS = 3; +static const DeviceCommandId_t ENABLE_DEBUG_OUTPUT = 4; +static const DeviceCommandId_t DISABLE_DEBUG_OUTPUT = 5; + +/** + * @brief This is the configuration byte which will be written to the setup register after + * power on. + * + * @note Bit1 (DIFFSEL1) - Bit0 (DIFFSEL0): 0b00, no data follows the setup byte + * Bit3 (REFSEL1) - Bit2 (REFSEL0): 0b10, internal reference, no wake-up delay + * Bit5 (CLKSEL1) - Bit4 (CLKSEL0): 0b10, MAX1227 uses internal oscillator for timing + * Bit7 - Bit6: 0b01, tells MAX1227 that this is the setup register + * + */ +static const uint8_t SETUP_DEFINITION = 0b01101000; + +/** + * @brief This value will always be written to the ADC conversion register to specify the + * conversions to perform. + * @details Bit0: 1 - Enables temperature conversion + * Bit2 (SCAN1) and Bit1 (SCAN0): 0b00 (channel conversion from 0 to N) + * Bit6 - Bit3 defines N: 0b0111 (N = 7) + * Bit7: Always 1. Tells the ADC that this is the conversion register. + */ +static const uint8_t CONVERSION_DEFINITION = 0b10111001; +// static const uint8_t CONVERSION_DEFINITION = 0b10111111; + +/** + * @brief Writing this value resets the fifo of the MAX1227. + */ +static const uint8_t RESET_DEFINITION = 0b00011000; + +static const uint8_t DUMMY_BYTE = 0xFF; + +static const uint8_t RAD_SENSOR_DATA_SET_ID = READ_CONVERSIONS; + +static const uint8_t DATASET_ENTRIES = 7; +/** + * One temperature value and conversions for AIN0 - AIN7 + */ +static const uint8_t READ_SIZE = 18; + +enum Max1227PoolIds : lp_id_t { + TEMPERATURE_C, + AIN0, + AIN1, + AIN4, + AIN5, + AIN6, + AIN7, +}; + +class RadSensorDataset : public StaticLocalDataSet { + public: + RadSensorDataset(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, RAD_SENSOR_DATA_SET_ID) {} + + RadSensorDataset(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, RAD_SENSOR_DATA_SET_ID)) {} + + lp_var_t temperatureCelcius = lp_var_t(sid.objectId, TEMPERATURE_C, this); + lp_var_t ain0 = lp_var_t(sid.objectId, AIN0, this); + lp_var_t ain1 = lp_var_t(sid.objectId, AIN1, this); + lp_var_t ain4 = lp_var_t(sid.objectId, AIN4, this); + lp_var_t ain5 = lp_var_t(sid.objectId, AIN5, this); + lp_var_t ain6 = lp_var_t(sid.objectId, AIN6, this); + lp_var_t ain7 = lp_var_t(sid.objectId, AIN7, this); +}; +} // namespace radSens + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_RADSENSOR_H_ */ diff --git a/mission/payload/scexHelpers.cpp b/mission/payload/scexHelpers.cpp new file mode 100644 index 0000000..26f6a13 --- /dev/null +++ b/mission/payload/scexHelpers.cpp @@ -0,0 +1,34 @@ +#include +#include + +#include + +uint8_t scex::createCmdByte(Cmds cmd, bool tempCheck) { + return (IDLE_BIT_0_DEF_STATE << 7) | (IDLE_BIT_1_DEF_STATE << 6) | (cmd << 1) | tempCheck; +} + +ReturnValue_t scex::prepareScexCmd(Cmds cmd, std::pair cmdBufPair, size_t& cmdLen, + std::pair usrDataPair, bool tempCheck) { + using namespace scex; + uint8_t* cmdBuf = cmdBufPair.first; + const uint8_t* userData = usrDataPair.first; + // Send command + if (cmdBuf == nullptr or (cmdBufPair.second < usrDataPair.second + HEADER_LEN + CRC_LEN) or + (usrDataPair.second > 0 and userData == nullptr)) { + cmdLen = 0; + return returnvalue::FAILED; + } + cmdBuf[0] = createCmdByte(cmd, tempCheck); + // These two fields are the packet counter and the total packet count. Those are 1 and 1 for each + // telecommand so far + cmdBuf[1] = 1; + cmdBuf[2] = 1; + cmdBuf[3] = (usrDataPair.second >> 8) & 0xff; + cmdBuf[4] = usrDataPair.second & 0xff; + std::memcpy(cmdBuf + HEADER_LEN, userData, usrDataPair.second); + uint16_t crc = CRC::crc16ccitt(cmdBuf, usrDataPair.second + HEADER_LEN); + cmdBuf[usrDataPair.second + HEADER_LEN] = (crc >> 8) & 0xff; + cmdBuf[usrDataPair.second + HEADER_LEN + 1] = crc & 0xff; + cmdLen = usrDataPair.second + HEADER_LEN + CRC_LEN; + return returnvalue::OK; +} diff --git a/mission/payload/scexHelpers.h b/mission/payload/scexHelpers.h new file mode 100644 index 0000000..06a8bb0 --- /dev/null +++ b/mission/payload/scexHelpers.h @@ -0,0 +1,53 @@ +#ifndef MISSION_PAYLOAD_SCEXHELPERS_H_ +#define MISSION_PAYLOAD_SCEXHELPERS_H_ + +#include +#include + +#include +#include + +#include "eive/eventSubsystemIds.h" +#include "eive/objects.h" + +// Definitions for the Solar Cell Experiment +namespace scex { + +static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::SCEX_HANDLER; + +static constexpr Event MISSING_PACKET = event::makeEvent(SUBSYSTEM_ID, 0, severity::LOW); +static constexpr Event EXPERIMENT_TIMEDOUT = event::makeEvent(SUBSYSTEM_ID, 1, severity::LOW); +//! FRAM, One Cell or All cells command finished. P1: Command ID +static constexpr Event MULTI_PACKET_COMMAND_DONE = + event::makeEvent(SUBSYSTEM_ID, 2, severity::INFO); +static constexpr Event FS_UNUSABLE = event::makeEvent(SUBSYSTEM_ID, 3, severity::LOW); + +enum Cmds : DeviceCommandId_t { + PING = 0b00111, + ALL_CELLS_CMD = 0b00101, + ONE_CELL = 0b00110, + FRAM = 0b00001, + EXP_STATUS_CMD = 0b00010, + TEMP_CMD = 0b00011, + ION_CMD = 0b00100, + ERROR_REPLY = 0b01000, + INVALID = 255 +}; + +static const std::vector VALID_CMDS = { + PING, ALL_CELLS_CMD, ONE_CELL, FRAM, EXP_STATUS_CMD, TEMP_CMD, ION_CMD}; + +static constexpr uint8_t HEADER_LEN = 5; +static constexpr uint8_t CRC_LEN = 2; + +static constexpr uint8_t IDLE_BIT_0_DEF_STATE = 0; +static constexpr uint8_t IDLE_BIT_1_DEF_STATE = 1; + +uint8_t createCmdByte(Cmds cmd, bool tempCheck = false); + +ReturnValue_t prepareScexCmd(Cmds cmd, std::pair cmdBufPair, size_t& cmdLen, + std::pair usrDataPair, bool tempCheck = false); + +} // namespace scex + +#endif /* MISSION_PAYLOAD_SCEXHELPERS_H_ */ diff --git a/mission/persistentTmStoreDefs.h b/mission/persistentTmStoreDefs.h new file mode 100644 index 0000000..8b8873d --- /dev/null +++ b/mission/persistentTmStoreDefs.h @@ -0,0 +1,52 @@ +#ifndef MISSION_PERSISTENTTMSTOREDEFS_H_ +#define MISSION_PERSISTENTTMSTOREDEFS_H_ + +#include + +#include "eive/eventSubsystemIds.h" + +namespace persTmStore { + +struct PersistentTmStores { + PersistentTmStoreWithTmQueue* okStore; + PersistentTmStoreWithTmQueue* notOkStore; + PersistentTmStoreWithTmQueue* miscStore; + PersistentTmStoreWithTmQueue* hkStore; + PersistentTmStoreWithTmQueue* cfdpStore; +}; + +static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PERSISTENT_TM_STORE; + +//! [EXPORT] : [COMMENT] +//! P1: Result code of TM packet parser. +//! P2: Timestamp of possibly corrupt file as a unix timestamp. +static constexpr Event POSSIBLE_FILE_CORRUPTION = event::makeEvent(SUBSYSTEM_ID, 0, severity::LOW); +//! [EXPORT] : [COMMENT] File in store too large. P1: Detected file size +//! P2: Allowed file size +static constexpr Event FILE_TOO_LARGE = event::makeEvent(SUBSYSTEM_ID, 1, severity::LOW); +static constexpr Event BUSY_DUMPING_EVENT = event::makeEvent(SUBSYSTEM_ID, 2, severity::INFO); + +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_OK_STORE_DONE = event::makeEvent(SUBSYSTEM_ID, 5, severity::INFO); +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_NOK_STORE_DONE = event::makeEvent(SUBSYSTEM_ID, 6, severity::INFO); +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_MISC_STORE_DONE = event::makeEvent(SUBSYSTEM_ID, 7, severity::INFO); +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_HK_STORE_DONE = event::makeEvent(SUBSYSTEM_ID, 8, severity::INFO); +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_CFDP_STORE_DONE = event::makeEvent(SUBSYSTEM_ID, 9, severity::INFO); + +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_OK_CANCELLED = event::makeEvent(SUBSYSTEM_ID, 10, severity::LOW); +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_NOK_CANCELLED = event::makeEvent(SUBSYSTEM_ID, 11, severity::LOW); +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_MISC_CANCELLED = event::makeEvent(SUBSYSTEM_ID, 12, severity::LOW); +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_HK_CANCELLED = event::makeEvent(SUBSYSTEM_ID, 13, severity::LOW); +//! [EXPORT] : [COMMENT] P1: Number of dumped packets. P2: Total dumped bytes. +static constexpr Event DUMP_CFDP_CANCELLED = event::makeEvent(SUBSYSTEM_ID, 14, severity::LOW); +}; // namespace persTmStore + +#endif /* MISSION_PERSISTENTTMSTOREDEFS_H_ */ diff --git a/mission/pollingSeqTables.cpp b/mission/pollingSeqTables.cpp new file mode 100644 index 0000000..9a1a132 --- /dev/null +++ b/mission/pollingSeqTables.cpp @@ -0,0 +1,645 @@ +#include "pollingSeqTables.h" + +#include +#include +#include +#include +#include +#include + +#include "OBSWConfig.h" +#include "eive/definitions.h" +#include "eive/objects.h" +#include "linux/payload/FreshMpsocHandler.h" +#include "linux/payload/FreshSupvHandler.h" + +#ifndef RPI_TEST_ADIS16507 +#define RPI_TEST_ADIS16507 0 +#endif + +#ifndef RPI_TEST_GPS_HANDLER +#define RPI_TEST_GPS_HANDLER 0 +#endif + +ReturnValue_t pst::pstSyrlinks(FixedTimeslotTaskIF *thisSequence) { + uint32_t length = thisSequence->getPeriodMs(); + + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0.25, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0.25, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0.4, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0.4, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0.75, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SYRLINKS_HANDLER, length * 0.75, DeviceHandlerIF::GET_READ); + + static_cast(length); + + return thisSequence->checkSequence(); +} + +// I don't think this needs to be in a PST because linux takes care of bus serialization, but +// keep it like this for now, it works +ReturnValue_t pst::pstI2c(TmpSchedConfig schedConf, FixedTimeslotTaskIF *thisSequence) { + // Length of a communication cycle + uint32_t length = thisSequence->getPeriodMs(); + static_cast(length); + + if (schedConf.scheduleTmpDev0) { + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_0, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_0, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_0, length * 0.1, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_0, length * 0.1, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_0, length * 0.1, DeviceHandlerIF::GET_READ); + } + + if (schedConf.scheduleTmpDev1) { + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_1, length * 0.2, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_1, length * 0.2, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_1, length * 0.2, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_1, length * 0.3, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::TMP1075_HANDLER_TCS_1, length * 0.3, DeviceHandlerIF::GET_READ); + } + + if (schedConf.schedulePlPcduDev0) { + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_0, length * 0.4, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_0, length * 0.4, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_0, length * 0.4, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_0, length * 0.5, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_0, length * 0.5, + DeviceHandlerIF::GET_READ); + } + if (schedConf.schedulePlPcduDev1) { + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_1, length * 0.6, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_1, length * 0.6, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_1, length * 0.6, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_1, length * 0.7, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::TMP1075_HANDLER_PLPCDU_1, length * 0.7, + DeviceHandlerIF::GET_READ); + } + + if (schedConf.scheduleIfBoardDev) { + thisSequence->addSlot(objects::TMP1075_HANDLER_IF_BOARD, length * 0.8, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::TMP1075_HANDLER_IF_BOARD, length * 0.8, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_IF_BOARD, length * 0.8, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::TMP1075_HANDLER_IF_BOARD, length * 0.9, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::TMP1075_HANDLER_IF_BOARD, length * 0.9, + DeviceHandlerIF::GET_READ); + } + static_cast(length); + return thisSequence->checkSequence(); +} + +ReturnValue_t pst::pstGompaceCan(FixedTimeslotTaskIF *thisSequence) { + uint32_t length = thisSequence->getPeriodMs(); + // PCDU handlers receives two messages and both must be handled + thisSequence->addSlot(objects::PCDU_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0, DeviceHandlerIF::GET_WRITE); + + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0.5, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0.5, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0.5, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0.5, DeviceHandlerIF::SEND_READ); + + thisSequence->addSlot(objects::P60DOCK_HANDLER, length * 0.5, DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::PDU1_HANDLER, length * 0.5, DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::PDU2_HANDLER, length * 0.5, DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::ACU_HANDLER, length * 0.5, DeviceHandlerIF::GET_READ); + + if (thisSequence->checkSequence() != returnvalue::OK) { + sif::error << "GomSpace PST initialization failed" << std::endl; + return returnvalue::FAILED; + } + static_cast(length); + return returnvalue::OK; +} + +ReturnValue_t pst::pstTest(FixedTimeslotTaskIF *thisSequence) { + /* Length of a communication cycle */ + uint32_t length = thisSequence->getPeriodMs(); + bool notEmpty = false; + +#if RPI_TEST_ADIS16507 == 1 + notEmpty = true; + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0.2, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0.4, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0.6, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0.8, DeviceHandlerIF::GET_READ); +#endif + +#if RPI_TEST_GPS_HANDLER == 1 + notEmpty = true; + thisSequence->addSlot(objects::GPS0_HANDLER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + + thisSequence->addSlot(objects::GPS0_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GPS0_HANDLER, length * 0, DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::GPS0_HANDLER, length * 0.5, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GPS0_HANDLER, length * 0.5, DeviceHandlerIF::GET_READ); +#endif + static_cast(length); + if (not notEmpty) { + return returnvalue::FAILED; + } + if (thisSequence->checkSequence() != returnvalue::OK) { + sif::error << "Test PST initialization failed" << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t pst::pstTcsAndAcs(FixedTimeslotTaskIF *thisSequence, AcsPstCfg cfg) { + /* Length of a communication cycle */ + uint32_t length = thisSequence->getPeriodMs(); + + // SUS: 16 ms + if (cfg.scheduleSus) { + /* Write setup */ + thisSequence->addSlot(objects::SUS_0_N_LOC_XFYFZM_PT_XF, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_0_N_LOC_XFYFZM_PT_XF, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_0_N_LOC_XFYFZM_PT_XF, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_1_N_LOC_XBYFZM_PT_XB, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_1_N_LOC_XBYFZM_PT_XB, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_1_N_LOC_XBYFZM_PT_XB, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_2_N_LOC_XFYBZB_PT_YB, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_2_N_LOC_XFYBZB_PT_YB, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_2_N_LOC_XFYBZB_PT_YB, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_3_N_LOC_XFYBZF_PT_YF, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_3_N_LOC_XFYBZF_PT_YF, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_3_N_LOC_XFYBZF_PT_YF, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_4_N_LOC_XMYFZF_PT_ZF, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_4_N_LOC_XMYFZF_PT_ZF, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_4_N_LOC_XMYFZF_PT_ZF, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_5_N_LOC_XFYMZB_PT_ZB, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_5_N_LOC_XFYMZB_PT_ZB, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_5_N_LOC_XFYMZB_PT_ZB, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_6_R_LOC_XFYBZM_PT_XF, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_6_R_LOC_XFYBZM_PT_XF, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_6_R_LOC_XFYBZM_PT_XF, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_7_R_LOC_XBYBZM_PT_XB, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_7_R_LOC_XBYBZM_PT_XB, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_7_R_LOC_XBYBZM_PT_XB, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_8_R_LOC_XBYBZB_PT_YB, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_8_R_LOC_XBYBZB_PT_YB, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_8_R_LOC_XBYBZB_PT_YB, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_9_R_LOC_XBYBZB_PT_YF, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_9_R_LOC_XBYBZB_PT_YF, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_9_R_LOC_XBYBZB_PT_YF, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_10_N_LOC_XMYBZF_PT_ZF, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_10_N_LOC_XMYBZF_PT_ZF, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_10_N_LOC_XMYBZF_PT_ZF, length * 0, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SUS_11_R_LOC_XBYMZB_PT_ZB, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SUS_11_R_LOC_XBYMZB_PT_ZB, length * 0, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SUS_11_R_LOC_XBYMZB_PT_ZB, length * 0, + DeviceHandlerIF::GET_WRITE); + + thisSequence->addSlot(objects::SUS_0_N_LOC_XFYFZM_PT_XF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_0_N_LOC_XFYFZM_PT_XF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_1_N_LOC_XBYFZM_PT_XB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_1_N_LOC_XBYFZM_PT_XB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_2_N_LOC_XFYBZB_PT_YB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_2_N_LOC_XFYBZB_PT_YB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_3_N_LOC_XFYBZF_PT_YF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_3_N_LOC_XFYBZF_PT_YF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_4_N_LOC_XMYFZF_PT_ZF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_4_N_LOC_XMYFZF_PT_ZF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_5_N_LOC_XFYMZB_PT_ZB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_5_N_LOC_XFYMZB_PT_ZB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_6_R_LOC_XFYBZM_PT_XF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_6_R_LOC_XFYBZM_PT_XF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_7_R_LOC_XBYBZM_PT_XB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_7_R_LOC_XBYBZM_PT_XB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_8_R_LOC_XBYBZB_PT_YB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_8_R_LOC_XBYBZB_PT_YB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_9_R_LOC_XBYBZB_PT_YF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_9_R_LOC_XBYBZB_PT_YF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_10_N_LOC_XMYBZF_PT_ZF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_10_N_LOC_XMYBZF_PT_ZF, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SUS_11_R_LOC_XBYMZB_PT_ZB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SUS_11_R_LOC_XBYMZB_PT_ZB, + length * config::spiSched::SCHED_BLOCK_1_PERIOD, + DeviceHandlerIF::GET_READ); + } + + if (cfg.scheduleStr) { + // 2 COM cycles for full PST for STR. The STR requests 2 packets types in NORMAL mode, this + // ensures we always get an updated STR dataset for a regular normal mode cycle. + thisSequence->addSlot(objects::STAR_TRACKER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::STAR_TRACKER, length * 0, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::STAR_TRACKER, length * 0, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::STAR_TRACKER, length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::STAR_TRACKER, length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::STAR_TRACKER, length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::STAR_TRACKER, length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::STAR_TRACKER, length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::STAR_TRACKER, length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + } + + bool enableAside = true; + bool enableBside = true; + if (cfg.scheduleAcsBoard) { + if (enableAside) { + // A side + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::MGM_1_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::MGM_1_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::MGM_1_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::MGM_1_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::MGM_1_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + } + if (enableBside) { + // B side + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::MGM_2_LIS3_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::MGM_3_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::MGM_3_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::MGM_3_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::MGM_3_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::MGM_3_RM3100_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + } + if (enableAside) { + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_1_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + } + if (enableBside) { + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_2_ADIS_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_2_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::GYRO_3_L3G_HANDLER, + length * config::spiSched::SCHED_BLOCK_3_PERIOD, + DeviceHandlerIF::GET_READ); + } + } + + if (cfg.scheduleImtq) { + // This is the MTM measurement cycle + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_1_PERIOD, + imtq::ComStep::DHB_OP); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_1_PERIOD, + imtq::ComStep::START_MEASURE_SEND); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_1_PERIOD, + imtq::ComStep::START_MEASURE_GET); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_3_PERIOD, + imtq::ComStep::READ_MEASURE_SEND); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_3_PERIOD, + imtq::ComStep::READ_MEASURE_GET); + } + + thisSequence->addSlot(objects::ACS_CONTROLLER, length * config::spiSched::SCHED_BLOCK_4_PERIOD, + 0); + + if (cfg.scheduleImtq) { + // This is the torquing cycle. + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + imtq::ComStep::START_ACTUATE_SEND); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + imtq::ComStep::START_ACTUATE_GET); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_6_PERIOD, + imtq::ComStep::READ_ACTUATE_SEND); + thisSequence->addSlot(objects::IMTQ_HANDLER, length * config::spiSched::SCHED_BLOCK_6_PERIOD, + imtq::ComStep::READ_ACTUATE_GET); + } + + if (cfg.scheduleRws) { + // this is the torquing cycle + thisSequence->addSlot(objects::RW1, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::RW2, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::RW3, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::RW4, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + + thisSequence->addSlot(objects::RW1, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::RW2, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::RW3, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::RW4, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::SEND_WRITE); + + thisSequence->addSlot(objects::RW1, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::RW2, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::RW3, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::RW4, length * config::spiSched::SCHED_BLOCK_5_PERIOD, + DeviceHandlerIF::GET_WRITE); + + thisSequence->addSlot(objects::RW1, length * config::spiSched::SCHED_BLOCK_7_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::RW2, length * config::spiSched::SCHED_BLOCK_7_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::RW3, length * config::spiSched::SCHED_BLOCK_7_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::RW4, length * config::spiSched::SCHED_BLOCK_7_PERIOD, + DeviceHandlerIF::SEND_READ); + + thisSequence->addSlot(objects::RW1, length * config::spiSched::SCHED_BLOCK_7_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::RW2, length * config::spiSched::SCHED_BLOCK_7_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::RW3, length * config::spiSched::SCHED_BLOCK_7_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::RW4, length * config::spiSched::SCHED_BLOCK_7_PERIOD, + DeviceHandlerIF::GET_READ); + } + + thisSequence->addSlot(objects::SPI_RTD_COM_IF, length * config::spiSched::SCHED_BLOCK_RTD_PERIOD, + 0); + + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * config::spiSched::SCHED_BLOCK_8_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * config::spiSched::SCHED_BLOCK_8_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * config::spiSched::SCHED_BLOCK_8_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * config::spiSched::SCHED_BLOCK_8_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::PLPCDU_HANDLER, length * config::spiSched::SCHED_BLOCK_8_PERIOD, + DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * config::spiSched::SCHED_BLOCK_8_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * config::spiSched::SCHED_BLOCK_8_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * config::spiSched::SCHED_BLOCK_8_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * config::spiSched::SCHED_BLOCK_9_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::BPX_BATT_HANDLER, length * config::spiSched::SCHED_BLOCK_9_PERIOD, + DeviceHandlerIF::GET_READ); + + /* Radiation sensor */ + thisSequence->addSlot(objects::RAD_SENSOR, length * config::spiSched::SCHED_BLOCK_9_PERIOD, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::RAD_SENSOR, length * config::spiSched::SCHED_BLOCK_9_PERIOD, + DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::RAD_SENSOR, length * config::spiSched::SCHED_BLOCK_9_PERIOD, + DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::RAD_SENSOR, length * config::spiSched::SCHED_BLOCK_9_PERIOD, + DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::RAD_SENSOR, length * config::spiSched::SCHED_BLOCK_9_PERIOD, + DeviceHandlerIF::GET_READ); + + thisSequence->addSlot(objects::POWER_CONTROLLER, length * config::spiSched::SCHED_BLOCK_10_PERIOD, + 0); + return returnvalue::OK; +} +ReturnValue_t pst::pstPayload(FixedTimeslotTaskIF *thisSequence) { + uint32_t length = thisSequence->getPeriodMs(); + + thisSequence->addSlot(objects::CAM_SWITCHER, length * 0, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PLOC_MPSOC_HANDLER, length * 0, + DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::PLOC_MPSOC_HANDLER, length * 0, DeviceHandlerIF::SEND_WRITE); + + thisSequence->addSlot(objects::PLOC_SUPERVISOR_HANDLER, length * 0, + FreshSupvHandler::OpCode::DEFAULT_OPERATION); + // Parse TM with a bit of delay. Two COM steps which might cover telemetry wehich takes a bit + // longer to send + thisSequence->addSlot(objects::PLOC_SUPERVISOR_HANDLER, length * 0.1, + FreshSupvHandler::OpCode::PARSE_TM); + thisSequence->addSlot(objects::PLOC_SUPERVISOR_HANDLER, length * 0.2, + FreshSupvHandler::OpCode::PARSE_TM); + // Parse TM with a bit of delay. Two COM steps which might cover telemetry wehich takes a bit + // longer to send + thisSequence->addSlot(objects::PLOC_MPSOC_HANDLER, length * 0.2, + FreshMpsocHandler::OpCode::PARSE_TM); + thisSequence->addSlot(objects::PLOC_MPSOC_HANDLER, length * 0.4, + FreshMpsocHandler::OpCode::PARSE_TM); + + thisSequence->addSlot(objects::SCEX, length * 0.6, DeviceHandlerIF::PERFORM_OPERATION); + thisSequence->addSlot(objects::SCEX, length * 0.6, DeviceHandlerIF::SEND_WRITE); + thisSequence->addSlot(objects::SCEX, length * 0.6, DeviceHandlerIF::GET_WRITE); + thisSequence->addSlot(objects::SCEX, length * 0.6, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SCEX, length * 0.6, DeviceHandlerIF::GET_READ); + thisSequence->addSlot(objects::SCEX, length * 0.8, DeviceHandlerIF::SEND_READ); + thisSequence->addSlot(objects::SCEX, length * 0.8, DeviceHandlerIF::GET_READ); + return thisSequence->checkSequence(); +} diff --git a/mission/pollingSeqTables.h b/mission/pollingSeqTables.h new file mode 100644 index 0000000..db75de8 --- /dev/null +++ b/mission/pollingSeqTables.h @@ -0,0 +1,76 @@ +#ifndef POLLINGSEQUENCEFACTORY_H_ +#define POLLINGSEQUENCEFACTORY_H_ + +#include "OBSWConfig.h" +#if defined(RASPBERRY_PI) +#include "rpiConfig.h" +#elif defined(XIPHOS_Q7S) +#include "q7sConfig.h" +#endif + +#include "fsfw/returnvalues/returnvalue.h" + +class FixedTimeslotTaskIF; + +/** + * All device handlers are scheduled by adding them into + * Polling Sequence Tables (PST) to satisfy stricter timing requirements of + * device communication, a device handler has four different communication steps: + * 1. DeviceHandlerIF::SEND_WRITE -> Send write via interface + * 2. DeviceHandlerIF::GET_WRITE -> Get confirmation for write + * 3. DeviceHandlerIF::SEND_READ -> Send read request + * 4. DeviceHandlerIF::GET_READ -> Read from interface + * The PST specifies precisely when the respective ComIF functions are called + * during the communication cycle time. + * The task is created using the FixedTimeslotTaskIF, + * which utilises the underlying Operating System Abstraction Layer (OSAL) + * + * @param thisSequence FixedTimeslotTaskIF * object is passed inside the + * Factory class when creating the PST + * @return + */ +namespace pst { + +struct AcsPstCfg { + bool scheduleAcsBoard = true; + bool scheduleImtq = true; + bool scheduleRws = true; + bool scheduleSus = true; + bool scheduleStr = true; +}; + +// Default config is for FM. +struct TmpSchedConfig { + bool scheduleTmpDev0 = true; + bool scheduleTmpDev1 = true; + bool schedulePlPcduDev0 = true; + // damaged on FM + bool schedulePlPcduDev1 = false; + bool scheduleIfBoardDev = true; +}; + +/** + * @brief This function creates the PST for all gomspace devices. + * @details + * Scheduled in a separate PST because the gomspace library uses blocking calls when requesting + * data from devices. + */ +ReturnValue_t pstGompaceCan(FixedTimeslotTaskIF* thisSequence); + +ReturnValue_t pstSyrlinks(FixedTimeslotTaskIF* thisSequence); + +ReturnValue_t pstTcsAndAcs(FixedTimeslotTaskIF* thisSequence, AcsPstCfg cfg); + +ReturnValue_t pstI2c(TmpSchedConfig schedConf, FixedTimeslotTaskIF* thisSequence); + +ReturnValue_t pstPayload(FixedTimeslotTaskIF* thisSequence); + +/** + * Generic test PST + * @param thisSequence + * @return + */ +ReturnValue_t pstTest(FixedTimeslotTaskIF* thisSequence); +} // namespace pst + +#endif /* POLLINGSEQUENCEINIT_H_ */ diff --git a/mission/power/AcuHandler.cpp b/mission/power/AcuHandler.cpp new file mode 100644 index 0000000..24499c0 --- /dev/null +++ b/mission/power/AcuHandler.cpp @@ -0,0 +1,190 @@ +#include + +#include "OBSWConfig.h" + +ACUHandler::ACUHandler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie, + FailureIsolationBase *customFdir, bool enableHkSets) + : GomspaceDeviceHandler(objectId, comIF, comCookie, cfg, customFdir, enableHkSets), + coreHk(this), + auxHk(this) { + cfg.maxConfigTableAddress = ACU::MAX_CONFIGTABLE_ADDRESS; + cfg.maxHkTableAddress = ACU::MAX_HKTABLE_ADDRESS; + cfg.hkTableSize = ACU::HK_TABLE_SIZE; + cfg.cfgTableSize = ACU::CONFIG_TABLE_SIZE; +} + +ACUHandler::~ACUHandler() {} + +ReturnValue_t ACUHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = GOMSPACE::REQUEST_HK_TABLE; + return buildCommandFromCommand(*id, nullptr, 0); +} + +void ACUHandler::fillCommandAndReplyMap() { GomspaceDeviceHandler::fillCommandAndReplyMap(); } + +void ACUHandler::letChildHandleHkReply(DeviceCommandId_t id, const uint8_t *packet) { + parseHkTableReply(packet); + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + PoolReadGuard pg0(&auxHk); + PoolReadGuard pg1(&coreHk); + if (pg0.getReadResult() != returnvalue::OK or pg1.getReadResult() != returnvalue::OK) { + return; + } + for (size_t idx = 0; idx < 3; idx++) { + float tempC = coreHk.temperatures[idx] * 0.1; + sif::info << "ACU: Temperature " << idx << ": " << tempC << " °C" << std::endl; + } + sif::info << "ACU: Ground Watchdog Timer Count: " << auxHk.wdtCntGnd.value << std::endl; + sif::info << "ACU: Ground watchdog timer, seconds left before reboot: " + << auxHk.wdtGndLeft.value << std::endl; +#endif + } +} + +void ACUHandler::letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t *packet) { + handleDeviceTm(packet, ACU::CONFIG_TABLE_SIZE, id); +} + +LocalPoolDataSetBase *ACUHandler::getDataSetHandle(sid_t sid) { + if (sid == coreHk.getSid()) { + return &coreHk; + } else if (sid == auxHk.getSid()) { + return &auxHk; + } + return nullptr; +} + +ReturnValue_t ACUHandler::parseHkTableReply(const uint8_t *packet) { + PoolReadGuard pg0(&coreHk); + PoolReadGuard pg1(&auxHk); + auto res0 = pg0.getReadResult(); + auto res1 = pg1.getReadResult(); + if (res0 != returnvalue::OK) { + return res0; + } + if (res1 != returnvalue::OK) { + return res1; + } + for (size_t idx = 0; idx < 6; idx++) { + coreHk.currentInChannels[idx] = as(packet + (idx * 2)); + } + for (size_t idx = 0; idx < 6; idx++) { + coreHk.voltageInChannels[idx] = as(packet + 0xc + (idx * 2)); + } + + coreHk.vcc = as(packet + 0x1a); + coreHk.vbat = as(packet + 0x18); + + for (size_t idx = 0; idx < 3; idx++) { + coreHk.temperatures[idx] = as(packet + 0x1c + (idx * 2)) * 0.1; + } + + coreHk.mpptMode = packet[0x22]; + + for (size_t idx = 0; idx < 6; idx++) { + coreHk.vboostInChannels[idx] = as(packet + 0x24 + (idx * 2)); + } + for (size_t idx = 0; idx < 6; idx++) { + coreHk.powerInChannels[idx] = as(packet + 0x30 + (idx * 2)); + } + for (size_t idx = 0; idx < 3; idx++) { + auxHk.dacEnables[idx] = *(packet + 0x3c + idx); + } + for (size_t idx = 0; idx < 6; idx++) { + auxHk.dacRawChannelVals[idx] = as(packet + 0x40 + (idx * 2)); + } + + auxHk.bootCause = as(packet + 0x50); + coreHk.bootcnt = as(packet + 0x54); + coreHk.uptime = as(packet + 0x58); + auxHk.resetCause = as(packet + 0x5c); + coreHk.mpptTime = as(packet + 0x5e); + coreHk.mpptPeriod = as(packet + 0x60); + + for (size_t idx = 0; idx < 8; idx++) { + auxHk.deviceTypes[idx] = *(packet + 0x64 + idx); + } + for (size_t idx = 0; idx < 8; idx++) { + auxHk.devicesStatus[idx] = *(packet + 0x6c + idx); + } + + auxHk.wdtCntGnd = as(packet + 0x74); + auxHk.wdtGndLeft = as(packet + 0x78); + coreHk.setValidity(true, true); + auxHk.setValidity(true, true); + return returnvalue::OK; +} + +ReturnValue_t ACUHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + using namespace ACU; + localDataPoolMap.emplace(pool::ACU_CURRENT_IN_CHANNELS, new PoolEntry(6)); + localDataPoolMap.emplace(pool::ACU_VOLTAGE_IN_CHANNELS, new PoolEntry(6)); + + localDataPoolMap.emplace(pool::ACU_VCC, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_VBAT, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::ACU_TEMPERATURES, new PoolEntry(3)); + + localDataPoolMap.emplace(pool::ACU_MPPT_MODE, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::ACU_VBOOST_IN_CHANNELS, new PoolEntry(6)); + localDataPoolMap.emplace(pool::ACU_POWER_IN_CHANNELS, new PoolEntry(6)); + + localDataPoolMap.emplace(pool::ACU_DAC_ENABLES, new PoolEntry(3)); + localDataPoolMap.emplace(pool::ACU_DAC_RAW_CHANNELS, new PoolEntry(6)); + + localDataPoolMap.emplace(pool::ACU_BOOTCAUSE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_BOOTCNT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_UPTIME, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_RESET_CAUSE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_MPPT_TIME, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_MPPT_PERIOD, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::ACU_DEVICES, new PoolEntry(8)); + localDataPoolMap.emplace(pool::ACU_DEVICES_STATUS, new PoolEntry(8)); + + localDataPoolMap.emplace(pool::ACU_WDT_CNT_GND, new PoolEntry({0})); + localDataPoolMap.emplace(pool::ACU_WDT_GND_LEFT, new PoolEntry({0})); + + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(coreHk.getSid(), enableHkSets, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(auxHk.getSid(), enableHkSets, 6000.0)); + return returnvalue::OK; +} + +void ACUHandler::printChannelStats() { + PoolReadGuard pg(&coreHk); + sif::info << "ACU Info: Current [mA], Voltage [mV]" << std::endl; + for (size_t idx = 0; idx < 6; idx++) { + sif::info << std::setw(8) << std::left << "Channel " << idx << std::dec << "| " + << static_cast(coreHk.currentInChannels[idx]) << std::setw(15) + << std::right << coreHk.voltageInChannels[idx] << std::endl; + } +} + +void ACUHandler::setDebugMode(bool enable) { this->debugMode = enable; } + +ReturnValue_t ACUHandler::printStatus(DeviceCommandId_t cmd) { + ReturnValue_t result = returnvalue::OK; + switch (cmd) { + case (GOMSPACE::PRINT_SWITCH_V_I): { + PoolReadGuard pg(&coreHk); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + break; + } + printChannelStats(); + break; + } + default: { + return DeviceHandlerIF::COMMAND_NOT_SUPPORTED; + } + } + if (result != returnvalue::OK) { + sif::warning << "Reading PDU1 HK table failed!" << std::endl; + } + return result; +} diff --git a/mission/power/AcuHandler.h b/mission/power/AcuHandler.h new file mode 100644 index 0000000..5d895c9 --- /dev/null +++ b/mission/power/AcuHandler.h @@ -0,0 +1,56 @@ +#ifndef MISSION_POWER_ACUHANDLER_H_ +#define MISSION_POWER_ACUHANDLER_H_ + +#include +#include + +#include "fsfw/datapool/PoolReadGuard.h" + +/** + * @brief Handler for the ACU from Gomspace. Monitors and controls the battery charging via + * the solar panels. + */ +class ACUHandler : public GomspaceDeviceHandler { + public: + ACUHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + FailureIsolationBase* customFdir, bool enableHkSets); + virtual ~ACUHandler(); + + void setDebugMode(bool enable); + + virtual ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + protected: + void letChildHandleHkReply(DeviceCommandId_t id, const uint8_t* packet) override; + void letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t* packet) override; + /** + * @brief As soon as the device is in MODE_NORMAL, this function is executed periodically. + */ + virtual ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + + ReturnValue_t printStatus(DeviceCommandId_t cmd) override; + + virtual void fillCommandAndReplyMap() override; + + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + private: + ACU::CoreHk coreHk; + ACU::AuxHk auxHk; + TableConfig cfg; + bool debugMode = false; + + /** + * @brief Function extracts the hk table information from the received csp packet and stores + * the values in the acuHkTableDataset. + */ + ReturnValue_t parseHkTableReply(const uint8_t* packet); + + /** + * @brief Prints channel statistics (current and voltage) to console + */ + void printChannelStats(); +}; + +#endif /* MISSION_POWER_ACUHANDLER_H_ */ diff --git a/mission/power/BpxBatteryHandler.cpp b/mission/power/BpxBatteryHandler.cpp new file mode 100644 index 0000000..9625370 --- /dev/null +++ b/mission/power/BpxBatteryHandler.cpp @@ -0,0 +1,298 @@ +#include +#include +#include + +BpxBatteryHandler::BpxBatteryHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + bool enableHkSets) + : DeviceHandlerBase(objectId, comIF, comCookie), + enableHkSets(enableHkSets), + hkSet(this), + cfgSet(this) {} + +BpxBatteryHandler::~BpxBatteryHandler() {} + +void BpxBatteryHandler::doStartUp() { + if (state == States::CHECK_COM) { + if (commandExecuted) { + state = States::IDLE; + commandExecuted = false; + if (goToNormalModeImmediately) { + setMode(MODE_NORMAL); + } else { + setMode(_MODE_TO_ON); + } + } + } +} + +void BpxBatteryHandler::doShutDown() { + // Perform a COM check on reboot + state = States::CHECK_COM; + setMode(MODE_OFF); +} + +ReturnValue_t BpxBatteryHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + *id = bpxBat::GET_HK; + return buildCommandFromCommand(*id, nullptr, 0); +} + +ReturnValue_t BpxBatteryHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + if (state == States::CHECK_COM) { + *id = bpxBat::PING; + return buildCommandFromCommand(*id, nullptr, 0); + } + return returnvalue::OK; +} + +void BpxBatteryHandler::fillCommandAndReplyMap() { + using namespace bpxBat; + insertInCommandAndReplyMap(GET_HK, 1, &hkSet, GET_HK_REPLY_LEN); + insertInCommandAndReplyMap(bpxBat::PING, 1, nullptr, PING_REPLY_LEN); + insertInCommandAndReplyMap(bpxBat::REBOOT, 1, nullptr, 0); + insertInCommandAndReplyMap(bpxBat::RESET_COUNTERS, 1, nullptr, EMPTY_REPLY_LEN); + insertInCommandAndReplyMap(bpxBat::CONFIG_CMD, 1, nullptr, EMPTY_REPLY_LEN); + insertInCommandAndReplyMap(bpxBat::CONFIG_GET, 1, &cfgSet, CONFIG_GET_REPLY_LEN); + insertInCommandAndReplyMap(bpxBat::CONFIG_SET, 1, nullptr, EMPTY_REPLY_LEN); + insertInCommandAndReplyMap(bpxBat::MAN_HEAT_ON, 1, nullptr, MAN_HEAT_REPLY_LEN); + insertInCommandAndReplyMap(bpxBat::MAN_HEAT_OFF, 1, nullptr, MAN_HEAT_REPLY_LEN); +} + +ReturnValue_t BpxBatteryHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + switch (deviceCommand) { + case (bpxBat::GET_HK): { + cmdBuf[0] = bpxBat::PORT_GET_HK; + this->rawPacketLen = 1; + break; + } + case (bpxBat::PING): { + if (commandDataLen == 1 and commandData != nullptr) { + sentPingByte = commandData[0]; + } else { + sentPingByte = bpxBat::DEFAULT_PING_SENT_BYTE; + } + + cmdBuf[0] = bpxBat::PORT_PING; + cmdBuf[1] = sentPingByte; + this->rawPacketLen = 2; + break; + } + case (bpxBat::REBOOT): { + sif::info << "BPX BATT: Executing reboot command" << std::endl; + cmdBuf[0] = bpxBat::PORT_REBOOT; + cmdBuf[1] = 0x80; + cmdBuf[2] = 0x07; + cmdBuf[3] = 0x80; + cmdBuf[4] = 0x07; + this->rawPacketLen = 5; + // This instructs the FDIR to set the device mode off and on again + // to ensure the I2C communication is also verified + triggerEvent(DeviceHandlerIF::DEVICE_WANTS_HARD_REBOOT); + break; + } + case (bpxBat::RESET_COUNTERS): { + cmdBuf[0] = bpxBat::PORT_RESET_COUNTERS; + cmdBuf[1] = bpxBat::RESET_COUNTERS_MAGIC_VALUE; + this->rawPacketLen = 2; + break; + } + case (bpxBat::CONFIG_CMD): { + cmdBuf[0] = bpxBat::PORT_CONFIG_CMD; + // Needs to be set to 0x01 according to datasheet + cmdBuf[1] = 0x01; + this->rawPacketLen = 2; + break; + } + case (bpxBat::CONFIG_GET): { + cmdBuf[0] = bpxBat::PORT_CONFIG_GET; + this->rawPacketLen = 1; + break; + } + case (bpxBat::CONFIG_SET): { + cmdBuf[0] = bpxBat::PORT_CONFIG_SET; + if (commandDataLen != 3) { + return DeviceHandlerIF::INVALID_NUMBER_OR_LENGTH_OF_PARAMETERS; + } + for (uint8_t idx = 0; idx < 3; idx++) { + cmdBuf[idx + 1] = commandData[idx]; + } + this->rawPacketLen = 4; + break; + } + case (bpxBat::MAN_HEAT_ON): { + cmdBuf[0] = bpxBat::PORT_MAN_HEAT_ON; + if (commandDataLen != 2) { + return DeviceHandlerIF::INVALID_NUMBER_OR_LENGTH_OF_PARAMETERS; + } + for (uint8_t idx = 0; idx < 2; idx++) { + cmdBuf[idx + 1] = commandData[idx]; + } + this->rawPacketLen = 3; + break; + } + case (bpxBat::MAN_HEAT_OFF): { + cmdBuf[0] = bpxBat::PORT_MAN_HEAT_OFF; + this->rawPacketLen = 1; + break; + } + default: { + return DeviceHandlerIF::COMMAND_NOT_SUPPORTED; + } + } + this->rawPacket = cmdBuf.data(); + + lastCmd = deviceCommand; + return returnvalue::OK; +} + +ReturnValue_t BpxBatteryHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + using namespace bpxBat; + switch (lastCmd) { + case (bpxBat::GET_HK): { + if (remainingSize != GET_HK_REPLY_LEN) { + return DeviceHandlerIF::LENGTH_MISSMATCH; + } + break; + } + case (bpxBat::PING): + case (bpxBat::MAN_HEAT_ON): + case (bpxBat::MAN_HEAT_OFF): { + if (remainingSize != MAN_HEAT_REPLY_LEN) { + return DeviceHandlerIF::LENGTH_MISSMATCH; + } + break; + } + case (bpxBat::REBOOT): { + // Ignore + break; + } + case (bpxBat::RESET_COUNTERS): + case (bpxBat::CONFIG_CMD): + case (bpxBat::CONFIG_SET): { + if (remainingSize != EMPTY_REPLY_LEN) { + return DeviceHandlerIF::LENGTH_MISSMATCH; + } + break; + } + case (bpxBat::CONFIG_GET): { + if (remainingSize != CONFIG_GET_REPLY_LEN) { + return DeviceHandlerIF::LENGTH_MISSMATCH; + } + + break; + } + default: { + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + *foundLen = remainingSize; + *foundId = lastCmd; + return returnvalue::OK; +} + +ReturnValue_t BpxBatteryHandler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) { + if (id != bpxBat::REBOOT and packet[1] != 0) { + return DeviceHandlerIF::DEVICE_REPORTED_ERROR; + } + switch (id) { + case (bpxBat::GET_HK): { + PoolReadGuard rg(&hkSet); + ReturnValue_t result = hkSet.parseRawHk(packet + 2, 21); + hkSet.setValidity(true, true); + if (result != returnvalue::OK) { + return result; + } + if (debugMode) { + sif::info << "BPX Battery HK output:" << std::endl; + sif::info << "Charge current [mA]: " << hkSet.chargeCurrent << std::endl; + sif::info << "Discharge current [mA]: " << hkSet.dischargeCurrent << std::endl; + sif::info << "Heater current [mA]: " << hkSet.heaterCurrent << std::endl; + sif::info << "Battery voltage [mV]: " << hkSet.battVoltage << std::endl; + sif::info << "Battery Temperature 1 [C]: " << hkSet.battTemp1 << std::endl; + sif::info << "Battery Temperature 2 [C]: " << hkSet.battTemp2 << std::endl; + sif::info << "Battery Temperature 3 [C]: " << hkSet.battTemp3 << std::endl; + sif::info << "Battery Temperature 4 [C]: " << hkSet.battTemp4 << std::endl; + sif::info << "Battery Reboot Counter: " << hkSet.rebootCounter << std::endl; + sif::info << "Battery Boot Cause: " << static_cast(hkSet.bootcause.value) << std::endl; + } + break; + } + case (bpxBat::PING): { + if (packet[2] != sentPingByte) { + return DeviceHandlerIF::INVALID_DATA; + } + if (getMode() == _MODE_START_UP) { + commandExecuted = true; + } + break; + } + case (bpxBat::RESET_COUNTERS): + case (bpxBat::CONFIG_CMD): + case (bpxBat::CONFIG_SET): { + break; + } + case (bpxBat::MAN_HEAT_ON): + case (bpxBat::MAN_HEAT_OFF): { + if (packet[2] != 0x01) { + return DeviceHandlerIF::DEVICE_DID_NOT_EXECUTE; + } + break; + } + case (bpxBat::CONFIG_GET): { + PoolReadGuard rg(&cfgSet); + ReturnValue_t result = cfgSet.parseRawHk(packet + 2, 3); + if (result != returnvalue::OK) { + return result; + } + cfgSet.setValidity(true, true); + break; + } + case (bpxBat::REBOOT): { + break; + } + default: { + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + return returnvalue::OK; +} + +uint32_t BpxBatteryHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 10000; } + +ReturnValue_t BpxBatteryHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(bpxBat::BATT_TEMP_1, &battTemp1); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_2, &battTemp2); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_3, &battTemp3); + localDataPoolMap.emplace(bpxBat::BATT_TEMP_4, &battTemp4); + localDataPoolMap.emplace(bpxBat::CHARGE_CURRENT, &chargeCurrent); + localDataPoolMap.emplace(bpxBat::DISCHARGE_CURRENT, &dischargeCurrent); + localDataPoolMap.emplace(bpxBat::HEATER_CURRENT, &heaterCurrent); + localDataPoolMap.emplace(bpxBat::BATT_VOLTAGE, &battVolt); + localDataPoolMap.emplace(bpxBat::REBOOT_COUNTER, &rebootCounter); + localDataPoolMap.emplace(bpxBat::BOOTCAUSE, &bootCause); + + localDataPoolMap.emplace(bpxBat::BATTERY_HEATER_MODE, &battheatMode); + localDataPoolMap.emplace(bpxBat::BATTHEAT_LOW_LIMIT, &battheatLow); + localDataPoolMap.emplace(bpxBat::BATTHEAT_HIGH_LIMIT, &battheatHigh); + + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(hkSet.getSid(), enableHkSets, 20.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(cfgSet.getSid(), false, 30.0)); + return returnvalue::OK; +} + +void BpxBatteryHandler::setToGoToNormalMode(bool enable) { + this->goToNormalModeImmediately = enable; +} + +void BpxBatteryHandler::setDebugMode(bool enable) { this->debugMode = enable; } + +void BpxBatteryHandler::performOperationHook() { +#if OBSW_THREAD_TRACING == 1 + trace::threadTrace(opCounter, "BPX BATT"); +#endif +} diff --git a/mission/power/BpxBatteryHandler.h b/mission/power/BpxBatteryHandler.h new file mode 100644 index 0000000..ef8390e --- /dev/null +++ b/mission/power/BpxBatteryHandler.h @@ -0,0 +1,67 @@ +#ifndef MISSION_POWER_BPXBATTERYHANDLER_H_ +#define MISSION_POWER_BPXBATTERYHANDLER_H_ + +#include +#include +#include + +class BpxBatteryHandler : public DeviceHandlerBase { + public: + BpxBatteryHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + bool enableHkSets); + virtual ~BpxBatteryHandler(); + + void setToGoToNormalMode(bool enable); + void setDebugMode(bool enable); + + protected: + enum class States { + CHECK_COM = 0, + IDLE = 1, + }; + + bool enableHkSets = false; + States state = States::CHECK_COM; + bool commandExecuted = false; + bool debugMode = false; + bool goToNormalModeImmediately = false; + uint8_t sentPingByte = bpxBat::DEFAULT_PING_SENT_BYTE; +#if OBSW_THREAD_TRACING == 1 + uint32_t opCounter = 0; +#endif + + BpxBatteryHk hkSet; + DeviceCommandId_t lastCmd = DeviceHandlerIF::NO_COMMAND_ID; + std::array cmdBuf = {}; + BpxBatteryCfg cfgSet; + PoolEntry chargeCurrent = PoolEntry({0}); + PoolEntry dischargeCurrent = PoolEntry({0}); + PoolEntry heaterCurrent = PoolEntry({0}); + PoolEntry battVolt = PoolEntry({0}); + PoolEntry battTemp1 = PoolEntry({0}); + PoolEntry battTemp2 = PoolEntry({0}); + PoolEntry battTemp3 = PoolEntry({0}); + PoolEntry battTemp4 = PoolEntry({0}); + PoolEntry rebootCounter = PoolEntry({0}); + PoolEntry bootCause = PoolEntry({0}); + PoolEntry battheatMode = PoolEntry({0}); + PoolEntry battheatLow = PoolEntry({0}); + PoolEntry battheatHigh = PoolEntry({0}); + + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + void performOperationHook() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; +}; + +#endif /* MISSION_POWER_BPXBATTERYHANDLER_H_ */ diff --git a/mission/power/CMakeLists.txt b/mission/power/CMakeLists.txt new file mode 100644 index 0000000..05232e2 --- /dev/null +++ b/mission/power/CMakeLists.txt @@ -0,0 +1,10 @@ +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE CspCookie.cpp + Pdu1Handler.cpp + Pdu2Handler.cpp + AcuHandler.cpp + P60DockHandler.cpp + PcduHandler.cpp + GomspaceDeviceHandler.cpp + BpxBatteryHandler.cpp) diff --git a/mission/power/CspCookie.cpp b/mission/power/CspCookie.cpp new file mode 100644 index 0000000..2bb85fd --- /dev/null +++ b/mission/power/CspCookie.cpp @@ -0,0 +1,29 @@ +#include + +using namespace GOMSPACE; +CspCookie::CspCookie(uint16_t maxReplyLength_, uint8_t cspAddress_, uint32_t timeoutMs) + : maxReplyLength(maxReplyLength_), + cspAddress(cspAddress_), + timeoutMs(timeoutMs), + reqType(SpecialRequestTypes::DEFAULT_COM_IF) {} + +CspCookie::~CspCookie() {} + +uint16_t CspCookie::getMaxReplyLength() { return maxReplyLength; } + +uint8_t CspCookie::getCspAddress() { return cspAddress; } + +GOMSPACE::SpecialRequestTypes CspCookie::getRequest() const { return reqType; } + +void CspCookie::setRequest(GOMSPACE::SpecialRequestTypes request, size_t replyLen_) { + reqType = request; + replyLen = replyLen_; +} + +uint8_t CspCookie::getCspPort() const { return cspPort; } + +uint32_t CspCookie::getTimeout() const { return timeoutMs; } + +void CspCookie::setCspPort(uint8_t port) { cspPort = port; } + +size_t CspCookie::getReplyLen() const { return replyLen; } diff --git a/mission/power/CspCookie.h b/mission/power/CspCookie.h new file mode 100644 index 0000000..a77de5d --- /dev/null +++ b/mission/power/CspCookie.h @@ -0,0 +1,38 @@ +#ifndef LINUX_CSP_CSPCOOKIE_H_ +#define LINUX_CSP_CSPCOOKIE_H_ + +#include +#include + +#include +#include + +/** + * @brief This is the cookie for devices supporting the CSP (CubeSat Space + * Protocol). + * @author J. Meier + */ +class CspCookie : public CookieIF { + public: + CspCookie(uint16_t maxReplyLength_, uint8_t cspAddress_, uint32_t timeoutMs); + virtual ~CspCookie(); + + void setCspPort(uint8_t port); + uint8_t getCspPort() const; + uint16_t getMaxReplyLength(); + GOMSPACE::SpecialRequestTypes getRequest() const; + void setRequest(GOMSPACE::SpecialRequestTypes request, size_t replyLen); + size_t getReplyLen() const; + uint8_t getCspAddress(); + uint32_t getTimeout() const; + + private: + uint8_t cspPort = 0; + uint16_t maxReplyLength; + uint8_t cspAddress; + size_t replyLen = 0; + uint32_t timeoutMs; + GOMSPACE::SpecialRequestTypes reqType; +}; + +#endif /* LINUX_CSP_CSPCOOKIE_H_ */ diff --git a/mission/power/GomSpacePackets.h b/mission/power/GomSpacePackets.h new file mode 100644 index 0000000..ad90eca --- /dev/null +++ b/mission/power/GomSpacePackets.h @@ -0,0 +1,384 @@ +#ifndef MISSION_POWER_GOMSPACEPACKETS_H_ +#define MISSION_POWER_GOMSPACEPACKETS_H_ + +#include +#include +#include +#include +#include + +class CspParamRequestBase : public SerialLinkedListAdapter { + public: + CspParamRequestBase(uint16_t querySize, uint8_t tableId) + : querySize(querySize), tableId(tableId) { + setLinks(); + } + + protected: + void setLinks() { + setStart(&cspPort); + cspPort.setNext(&querySize); + querySize.setNext(&action); + action.setNext(&tableId); + tableId.setNext(&payloadlength); + payloadlength.setNext(&checksum); + checksum.setNext(&seq); + seq.setNext(&total); + } + SerializeElement cspPort = GOMSPACE::PARAM_PORT; + SerializeElement querySize; + SerializeElement action = GOMSPACE::ParamRequestIds::GET; + SerializeElement tableId; + // Default value 0: Fetch whole table. + SerializeElement payloadlength = 0; + SerializeElement checksum = GOMSPACE::IGNORE_CHECKSUM; + SerializeElement seq = 0; + SerializeElement total = 0; +}; + +/** + * @brief This class can be used to generated the command for the CspComIF + * to reset the watchdog in a gomspace device. + */ +class WatchdogResetCommand : public SerialLinkedListAdapter { + public: + WatchdogResetCommand() { setLinks(); } + + private: + WatchdogResetCommand(const WatchdogResetCommand &command); + void setLinks() { + setStart(&cspPort); + cspPort.setNext(&querySize); + querySize.setNext(&magic); + } + SerializeElement cspPort = GOMSPACE::P60_PORT_GNDWDT_RESET; + SerializeElement querySize = 1; + /* Sending 0x78 to port 9 of a gomspace device resets the ground watchdog */ + SerializeElement magic = 0x78; +}; + +/** + * @brief A serial linked list adapter implementation to generate ping + * commands for devices supporting the CSP protocol. This command can + * be sent to the CspComIF which will send out the ping request. + * + * @details A ping request simply sends back the received data provided by the + * data buffer. cspPort and querySize are only informations required + * by the CspComI and other than the data array not physically + * transmitted to the target device. + */ +class CspPingCommand : public SerialLinkedListAdapter { + public: + /** + * @brief Constructor + * + * @param querySize_ The size of bytes replied by the ping request. + * Amounts to the number of bytes send. + * @param data_ Pointer to data which should be sent to the device. + * All data will be sent back by the ping target. + */ + CspPingCommand(const uint8_t *data_, uint16_t querySize_) + : querySize(querySize_), data(data_, querySize_) { + setLinks(); + } + + private: + CspPingCommand(const CspPingCommand &command); + void setLinks() { + setStart(&cspPort); + cspPort.setNext(&querySize); + querySize.setNext(&data); + } + SerializeElement cspPort = GOMSPACE::PING_PORT; + SerializeElement querySize; + SerializeElement> data; +}; + +/** + * @brief A serial linked list adapter implementation of the gs_rparam_query_t + * struct defined in rparam.h. Can be used to build the message to set + * a parameter in gomspace devices. + * + * @note cspPort and querySize will not be sent with the CSP packet to the + * gomspace device but are required for the CspComIF to get the port + * and the size to query. + */ +class CspSetParamCommand : public CspParamRequestBase { + public: + CspSetParamCommand(uint16_t querySize_, uint16_t payloadlength_, uint16_t addr_, + const uint8_t *parameter_, uint8_t parameterCount_, uint8_t tableId = 1) + : CspParamRequestBase(querySize_, tableId), + addr(addr_), + parameter(parameter_, parameterCount_) { + total.setNext(&addr); + addr.setNext(¶meter); + CspParamRequestBase::payloadlength = payloadlength_; + CspParamRequestBase::action = GOMSPACE::ParamRequestIds::SET; + } + CspSetParamCommand(const CspSetParamCommand &command) = delete; + + private: + SerializeElement addr; + SerializeElement> parameter; +}; + +/** + * @brief This class can be used to generate a get param command for the + * gomspace devices which will be sent to the device communication + * interface object. + * + * @note cspPort and querySize only serve as information for the CspComIF + * and will not be transmitted physically to the target device. + */ +class CspGetParamCommand : public CspParamRequestBase { + public: + /* The size of the header of a gomspace CSP packet. */ + static const uint8_t GS_HDR_LENGTH = 12; + + CspGetParamCommand(uint16_t querySize_, uint8_t tableId_, uint16_t addresslength_, uint16_t addr_) + : CspParamRequestBase(querySize_, tableId_), addr(addr_) { + total.setNext(&addr); + CspParamRequestBase::tableId = tableId_; + CspParamRequestBase::payloadlength = addresslength_; + } + CspGetParamCommand(const CspGetParamCommand &command) = delete; + + private: + SerializeElement addr; +}; + +/** + * @brief This class can be used to generate a get param command for the + * gomspace devices which will be sent to the device communication + * interface object. + * + * @note cspPort and querySize only serve as information for the CspComIF + * and will not be transmitted physically to the target device. + */ +class RequestFullTableCommand : public CspParamRequestBase { + public: + RequestFullTableCommand(uint16_t querySize_, uint8_t tableId_) + : CspParamRequestBase(querySize_, tableId_) {} + + RequestFullTableCommand(const RequestFullTableCommand &command) = delete; + + private: +}; + +/** + * @brief This class can be used to deserialize replies from gomspace devices + * and extract the relevant data. + */ +class CspGetParamReply : public SerialLinkedListAdapter { + public: + /** + * @brief Constructor + * + * @param payloadBuffer Pointer to a buffer to store the payload data of + * the CSP packet. + * @param payloadBufferSz The size of the payload buffer where the payload + * data will be stored. + */ + CspGetParamReply(uint8_t *payloadBuffer_, uint8_t payloadBufferSz_) + : payload(payloadBuffer_, payloadBufferSz_) { + setLinks(); + } + + uint8_t getAction() { return action; } + + uint8_t getTableId() { return tableId; } + + uint16_t getLength() { return length; } + + uint16_t getAddress() { return addr; } + + private: + CspGetParamReply(const CspGetParamReply &reply); + void setLinks() { + setStart(&action); + action.setNext(&tableId); + tableId.setNext(&length); + length.setNext(&checksum); + checksum.setNext(&seq); + seq.setNext(&total); + total.setNext(&addr); + addr.setNext(&payload); + } + + SerializeElement action; + SerializeElement tableId; + SerializeElement length; // length of address field + payload data + SerializeElement checksum; + SerializeElement seq; + SerializeElement total; + SerializeElement addr; + SerializeElement> payload; +}; + +/** + * @brief This class generates telemetry packets containing data from + * CSP get-parameter-replies. + */ +class ParamReply : public SerialLinkedListAdapter { + public: + /** + * @brief Constructor + * + * @param payloadBuffer Pointer to a buffer to store the payload data of + * the CSP packet. + * @param payloadBufferSz The size of the payload buffer where the payload + * data will be stored. + */ + ParamReply(uint8_t action_, uint8_t tableId_, uint16_t addr_, uint16_t payloadLength_, + uint8_t *payloadBuffer_) + : action(action_), + tableId(tableId_), + addr(addr_), + payloadLength(payloadLength_), + payload(payloadBuffer_, payloadLength) { + setLinks(); + } + + private: + ParamReply(const CspGetParamReply &reply); + void setLinks() { + setStart(&action); + action.setNext(&tableId); + tableId.setNext(&addr); + addr.setNext(&payloadLength); + payloadLength.setNext(&payload); + } + SerializeElement action; + SerializeElement tableId; + SerializeElement addr; + SerializeElement payloadLength; + SerializeElement> payload; +}; + +/** + * @brief This class generates the reply containing data from a full housekeeping table + * request of the PDU2. + */ +class Pdu2FullTableReply : public SerialLinkedListAdapter { + public: + /** + * @brief Constructor + * + * @param action_ The command which triggered the full table request. + * @param tableId_ The id of the requested table. + * @param tableDataset_ The dataset holding the table data. + */ + Pdu2FullTableReply(uint8_t action_, uint8_t tableId_, SerializeIF *tableDataset_) + : action(action_), tableId(tableId_), dataset(tableDataset_) { + setLinks(); + } + + private: + Pdu2FullTableReply(const Pdu2FullTableReply &reply); + void setLinks() { + setStart(&action); + action.setNext(&tableId); + tableId.setNext(&dataset); + } + SerializeElement action; + SerializeElement tableId; + LinkedElement dataset; +}; + +/** + * @brief This class helps to unpack information from an action message + * to set a parameter in gomspace devices. The action message can be + * for example received from the PUS Service 8. + */ +class SetParamMessageUnpacker : public SerialLinkedListAdapter { + public: + /* Largest parameter is a uint32_t */ + static const uint32_t MAX_SIZE = 4; + + SetParamMessageUnpacker() { setLinks(); } + + uint16_t getAddress() { return address; } + + uint8_t *getParameter() { return parameter->front(); } + + uint8_t getParameterSize() { return parameter->size; } + + private: + void setLinks() { + setStart(&address); + address.setNext(¶meter); + } + SetParamMessageUnpacker(const SetParamMessageUnpacker &message); + SerializeElement address; + SerializeElement> parameter; +}; + +/** + * @brief This class generates a message which can be sent to the GomspaceDeviceHandler to + * command a parameter change. + * + * @details Structure of set parameter command: + * | memory address | size of parameter value | parameter value | + */ +class GomspaceSetParamMessage : public SerialLinkedListAdapter { + public: + /* The size of the largest parameter */ + static const uint8_t MAX_SIZE = 4; + + /** + * @brief Constructor + * + * @param memoryAddress The address of the parameter to change in the configuration table. + * @param parameterValue Pointer to the parameter value to set. + * @param parameterSize The size of the parameter. + * + */ + GomspaceSetParamMessage(uint16_t memoryAddress, const uint8_t *parameterValue, + uint8_t parameterSize) + : memoryAddress(memoryAddress), parameterValueBuffer(parameterValue, parameterSize, true) { + setLinks(); + } + + private: + GomspaceSetParamMessage(const GomspaceSetParamMessage &reply); + void setLinks() { + setStart(&memoryAddress); + memoryAddress.setNext(¶meterValueBuffer); + } + SerializeElement memoryAddress; + /** + * Parameter can be uint8_t, uint16_t or uint32_t. Thus max size of parameterValueBuffer is + * four bytes. + */ + SerializeElement> parameterValueBuffer; +}; + +/** + * @brief This class helps to unpack information from an action message + * to get a parameter from gomspace devices. The action message can be + * for example received from the PUS Service 8. + */ +class GetParamMessageUnpacker : public SerialLinkedListAdapter { + public: + GetParamMessageUnpacker() { setLinks(); } + + uint8_t getTableId() { return tableId; } + + uint16_t getAddress() { return address; } + + uint8_t getParameterSize() { return parameterSize; } + + private: + GetParamMessageUnpacker(const GetParamMessageUnpacker &message); + void setLinks() { + setStart(&tableId); + tableId.setNext(&address); + address.setNext(¶meterSize); + } + SerializeElement tableId; + SerializeElement address; // The memory address offset within the table + /* The size of the requested value (e.g. temperature is a uint16_t value) */ + SerializeElement parameterSize; +}; + +#endif /* MISSION_POWER_GOMSPACEPACKETS_H_ */ diff --git a/mission/power/GomspaceDeviceHandler.cpp b/mission/power/GomspaceDeviceHandler.cpp new file mode 100644 index 0000000..c20ac4e --- /dev/null +++ b/mission/power/GomspaceDeviceHandler.cpp @@ -0,0 +1,652 @@ +#include +#include +#include +#include +#include + +#include + +#include "eive/objects.h" + +using namespace GOMSPACE; + +GomspaceDeviceHandler::GomspaceDeviceHandler(object_id_t objectId, object_id_t comIF, + CookieIF* comCookie, TableConfig& tableConfig, + FailureIsolationBase* customFdir, bool enableHkSets) + : DeviceHandlerBase(objectId, comIF, comCookie, customFdir), + enableHkSets(enableHkSets), + tableCfg(tableConfig) { + if (comCookie == nullptr) { + sif::error << "GomspaceDeviceHandler::GomspaceDeviceHandler: Invalid com cookie" << std::endl; + } +} + +void GomspaceDeviceHandler::initPduConfigTable() { + tableCfg.maxConfigTableAddress = PDU::MAX_CONFIGTABLE_ADDRESS; + tableCfg.maxHkTableAddress = PDU::MAX_HKTABLE_ADDRESS; + tableCfg.hkTableSize = PDU::HK_TABLE_SIZE; + tableCfg.cfgTableSize = PDU::CONFIG_TABLE_SIZE; +} + +GomspaceDeviceHandler::~GomspaceDeviceHandler() {} + +void GomspaceDeviceHandler::doStartUp() {} + +void GomspaceDeviceHandler::doShutDown() {} + +ReturnValue_t GomspaceDeviceHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + auto* cspCookie = dynamic_cast(comCookie); + cspCookie->setRequest(SpecialRequestTypes::DEFAULT_COM_IF, 0); + ReturnValue_t result = childCommandHook(deviceCommand, commandData, commandDataLen); + switch (deviceCommand) { + case (GOMSPACE::PING): { + result = generatePingCommand(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + break; + } + case (GOMSPACE::REBOOT): { + generateRebootCommand(); + break; + } + case (GOMSPACE::PARAM_SET): { + result = generateSetParamCommand(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + break; + } + case (GOMSPACE::PARAM_GET): { + result = generateGetParamCommand(commandData, commandDataLen); + if (result != returnvalue::OK) { + return result; + } + break; + } + case (GOMSPACE::GNDWDT_RESET): { + result = generateResetWatchdogCmd(); + if (result != returnvalue::OK) { + return result; + } + break; + } + case (GOMSPACE::PRINT_SWITCH_V_I): + case (GOMSPACE::PRINT_LATCHUPS): { + result = printStatus(deviceCommand); + break; + } + case (GOMSPACE::REQUEST_HK_TABLE): { + DeviceType devType; + if (getDevType(devType) != returnvalue::OK) { + return returnvalue::FAILED; + } + result = + generateRequestFullHkTableCmd(devType, tableCfg.hkTableSize, deviceCommand, cspCookie); + if (result != returnvalue::OK) { + return result; + } + break; + } + case (GOMSPACE::REQUEST_CONFIG_TABLE): { + DeviceType devType; + if (getDevType(devType) != returnvalue::OK) { + return returnvalue::FAILED; + } + result = + generateRequestFullCfgTableCmd(devType, tableCfg.cfgTableSize, deviceCommand, cspCookie); + if (result != returnvalue::OK) { + return result; + } + break; + } + case (GOMSPACE::LOAD_TABLE): { + if (commandDataLen != 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + auto* info = reinterpret_cast(cspPacket); + info->sourceTable = commandData[0]; + info->targetTable = commandData[1]; + rawPacket = cspPacket; + rawPacketLen = sizeof(GOMSPACE::TableInfo); + cspCookie->setCspPort(CspPorts::P60_PORT_RPARAM_ENUM); + cspCookie->setRequest(SpecialRequestTypes::LOAD_TABLE, 0); + rememberCommandId = deviceCommand; + break; + } + case (GOMSPACE::SAVE_TABLE_FILE): { + if (commandDataLen > 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + auto* info = reinterpret_cast(cspPacket); + if (commandData[0] != 0 and commandData[0] != 1 and commandData[0] != 2 and + commandData[0] != 4) { + return HasActionsIF::INVALID_PARAMETERS; + } + info->sourceTable = commandData[0]; + if (info->sourceTable != 4) { + if (commandDataLen == 2) { + info->targetTable = commandData[1]; + } else { + info->targetTable = info->sourceTable; + } + } else { + // Will be ignored, still set the value which is always used + info->targetTable = 4; + } + rawPacket = cspPacket; + rawPacketLen = sizeof(GOMSPACE::TableInfo); + cspCookie->setCspPort(CspPorts::P60_PORT_RPARAM_ENUM); + cspCookie->setRequest(SpecialRequestTypes::SAVE_TABLE, 0); + rememberCommandId = deviceCommand; + break; + } + case (GOMSPACE::SAVE_TABLE_DEFAULT): { + if (commandDataLen != 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + auto* info = reinterpret_cast(cspPacket); + if (commandData[0] != 0 and commandData[0] != 1 and commandData[0] != 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + info->sourceTable = commandData[0]; + info->targetTable = info->sourceTable + 4; + rawPacket = cspPacket; + rawPacketLen = sizeof(GOMSPACE::TableInfo); + cspCookie->setCspPort(CspPorts::P60_PORT_RPARAM_ENUM); + cspCookie->setRequest(SpecialRequestTypes::SAVE_TABLE, 0); + rememberCommandId = deviceCommand; + break; + } + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::OK; +} + +void GomspaceDeviceHandler::fillCommandAndReplyMap() { + this->insertInCommandAndReplyMap(GOMSPACE::PING, 3); + this->insertInCommandMap(GOMSPACE::REBOOT); + this->insertInCommandAndReplyMap(GOMSPACE::PARAM_SET, 3); + this->insertInCommandAndReplyMap(GOMSPACE::PARAM_GET, 3); + this->insertInCommandAndReplyMap(GOMSPACE::REQUEST_HK_TABLE, 3); + this->insertInCommandAndReplyMap(GOMSPACE::REQUEST_CONFIG_TABLE, 3); + this->insertInCommandMap(GOMSPACE::GNDWDT_RESET); + this->insertInCommandMap(GOMSPACE::PRINT_SWITCH_V_I); + this->insertInCommandMap(GOMSPACE::PRINT_LATCHUPS); + insertInCommandMap(GOMSPACE::SAVE_TABLE_FILE); + insertInCommandMap(GOMSPACE::SAVE_TABLE_DEFAULT); + insertInCommandMap(GOMSPACE::LOAD_TABLE); +} + +ReturnValue_t GomspaceDeviceHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + switch (rememberCommandId) { + case (GOMSPACE::PING): + *foundId = GOMSPACE::PING; + *foundLen = PING_REPLY_SIZE; + rememberCommandId = GOMSPACE::NONE; + break; + case (GOMSPACE::PARAM_GET): { + *foundId = GOMSPACE::PARAM_GET; + *foundLen = rememberRequestedSize + GOMSPACE::GS_HDR_LENGTH; + rememberCommandId = GOMSPACE::NONE; + break; + } + case (GOMSPACE::PARAM_SET): { + *foundId = GOMSPACE::PARAM_SET; + *foundLen = rememberRequestedSize; + rememberCommandId = GOMSPACE::NONE; + break; + } + case (GOMSPACE::REQUEST_HK_TABLE): + case (GOMSPACE::REQUEST_CONFIG_TABLE): { + if (remainingSize < rememberRequestedSize) { + sif::error << "GomspaceDeviceHandler::scanForReply: Table reply, received data smaller " + "than expected " + << rememberRequestedSize << std::endl; + return returnvalue::FAILED; + } + *foundId = rememberCommandId; + *foundLen = rememberRequestedSize; + rememberCommandId = GOMSPACE::NONE; + break; + } + default: + return IGNORE_REPLY_DATA; + } + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t* packet) { + switch (id) { + case (GOMSPACE::PING): { + SerializeElement replyTime = *packet; + handleDeviceTm(replyTime, true); + break; + } + case (GOMSPACE::PARAM_GET): { + // -2 to subtract address size from gomspace parameter reply packet + uint16_t payloadLength = (*(packet + 2) << 8 | *(packet + 3)) - 2; + if (payloadLength > sizeof(uint32_t)) { + sif::error << "GomspaceDeviceHandler: PARAM_GET: Invalid payload " + << "size in reply" << std::endl; + return INVALID_PAYLOAD_SIZE; + } + uint8_t tempPayloadBuffer[payloadLength]; + /* Extract information from received data */ + CspGetParamReply cspGetParamReply(tempPayloadBuffer, payloadLength); + size_t size = GOMSPACE::GS_HDR_LENGTH + payloadLength; + ReturnValue_t result = + cspGetParamReply.deSerialize(&packet, &size, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::error << "GomspaceDeviceHandler: Failed to deserialize get parameter" + << "reply" << std::endl; + return result; + } + uint8_t action = cspGetParamReply.getAction(); + uint8_t tableId = cspGetParamReply.getTableId(); + uint16_t address = cspGetParamReply.getAddress(); + /* Pack relevant information into a tm packet */ + ParamReply paramReply(action, tableId, address, payloadLength, tempPayloadBuffer); + handleDeviceTm(paramReply, id, true); + break; + } + case (GOMSPACE::PARAM_SET): { + /* When setting a parameter, the p60dock sends back the state of the + * operation */ + if (*packet != PARAM_SET_OK) { + return returnvalue::FAILED; + } + setParamCallback(setParamCacher, true); + break; + } + case (GOMSPACE::REQUEST_HK_TABLE): { + letChildHandleHkReply(id, packet); + break; + } + case (GOMSPACE::REQUEST_CONFIG_TABLE): { + letChildHandleConfigReply(id, packet); + break; + } + default: + break; + } + return returnvalue::OK; +} + +void GomspaceDeviceHandler::setNormalDatapoolEntriesInvalid() {} + +ReturnValue_t GomspaceDeviceHandler::generateSetParamCommand(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result = + setParamCacher.deSerialize(&commandData, &commandDataLen, SerializeIF::Endianness::BIG); + // This breaks layering but I really don't want to accept this command.. + if (setParamCacher.getAddress() == PDU2::CONFIG_ADDRESS_OUT_EN_Q7S and + this->getObjectId() == objects::PDU2_HANDLER) { + triggerEvent(power::SWITCHING_Q7S_DENIED, 0, 0); + return returnvalue::FAILED; + } + if (result != returnvalue::OK) { + sif::error << "GomspaceDeviceHandler: Failed to deserialize set parameter " + "message" + << std::endl; + return result; + } + result = setParamCallback(setParamCacher, false); + if (result != returnvalue::OK) { + return result; + } + /* Get and check address */ + uint16_t address = setParamCacher.getAddress(); + if (address > tableCfg.maxConfigTableAddress) { + sif::error << "GomspaceDeviceHandler: Invalid address for set parameter " + << "action" << std::endl; + return INVALID_ADDRESS; + } + /* CSP reply only contains the transaction state */ + uint16_t querySize = 1; + const uint8_t* parameterPtr = setParamCacher.getParameter(); + uint8_t parameterSize = setParamCacher.getParameterSize(); + uint16_t payloadlength = sizeof(address) + parameterSize; + + /* Generate command for CspComIF */ + CspSetParamCommand setParamCmd(querySize, payloadlength, address, parameterPtr, parameterSize); + size_t cspPacketLen = 0; + uint8_t* buffer = cspPacket; + result = setParamCmd.serialize(&buffer, &cspPacketLen, sizeof(cspPacket), + SerializeIF::Endianness::BIG); + + if (result != returnvalue::OK) { + sif::error << "GomspaceDeviceHandler: Failed to serialize command for " + << "CspComIF" << std::endl; + return result; + } + if (cspPacketLen > MAX_PACKET_LEN) { + sif::error << "GomspaceDeviceHandler: Invalid length of set parameter " + "command" + << std::endl; + return PACKET_TOO_LONG; + } + rawPacket = cspPacket; + rawPacketLen = cspPacketLen; + rememberRequestedSize = querySize; + rememberCommandId = GOMSPACE::PARAM_SET; + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::generateGetParamCommand(const uint8_t* commandData, + size_t commandDataLen) { + ReturnValue_t result; + /* Unpack the received action message */ + GetParamMessageUnpacker getParamMessage; + result = getParamMessage.deSerialize(&commandData, &commandDataLen, SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::error << "Failed to deserialize message to extract information " + "from get parameter message" + << std::endl; + return result; + } + /* Get an check table id to read from */ + uint8_t tableId = getParamMessage.getTableId(); + if (not validTableId(tableId)) { + sif::error << "GomspaceDeviceHandler: Invalid table id in get parameter" + " message" + << std::endl; + return INVALID_TABLE_ID; + } + /* Get and check address */ + uint16_t address = getParamMessage.getAddress(); + if (address > tableCfg.maxHkTableAddress and tableId == TableIds::HK) { + sif::error << "GomspaceDeviceHandler: Invalid address to get parameter from " + << "housekeeping table" << std::endl; + return INVALID_ADDRESS; + } + if (address > tableCfg.maxConfigTableAddress and tableId == TableIds::CONFIG) { + sif::error << "GomspaceDeviceHandler: Invalid address to get parameter from " + << "configuration table" << std::endl; + return INVALID_ADDRESS; + } + uint16_t length = sizeof(address); + uint8_t parameterSize = getParamMessage.getParameterSize(); + if (parameterSize > sizeof(uint32_t)) { + sif::error << "GomspaceDeviceHandler: GET_PARAM: Invalid parameter " + << "size" << std::endl; + return INVALID_PARAM_SIZE; + } + uint16_t querySize = parameterSize + GOMSPACE::GS_HDR_LENGTH; + + /* Generate the CSP command to send to the P60 Dock */ + CspGetParamCommand getParamCmd(querySize, tableId, length, address); + size_t cspPacketLen = 0; + uint8_t* buffer = cspPacket; + result = getParamCmd.serialize(&buffer, &cspPacketLen, sizeof(cspPacket), + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::error << "GomspaceDeviceHandler: Failed to serialize command to " + << "get parameter" << std::endl; + } + if (cspPacketLen > MAX_PACKET_LEN) { + sif::error << "GomspaceDeviceHandler: Received invalid get parameter " + "command" + << std::endl; + return PACKET_TOO_LONG; + } + rawPacket = cspPacket; + rawPacketLen = cspPacketLen; + rememberRequestedSize = querySize; + rememberCommandId = GOMSPACE::PARAM_GET; + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::generatePingCommand(const uint8_t* commandData, + size_t commandDataLen) { + CspPingCommand cspPingCommand(commandData, commandDataLen); + size_t cspPacketLen = 0; + uint8_t* buffer = cspPacket; + ReturnValue_t result = cspPingCommand.serialize(&buffer, &cspPacketLen, sizeof(cspPacket), + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::error << "GomspaceDeviceHandler: Failed to serialize ping command" << std::endl; + return result; + } + if (cspPacketLen > MAX_PACKET_LEN) { + sif::error << "GomspaceDeviceHandler: Received invalid ping message" << std::endl; + return PACKET_TOO_LONG; + } + rawPacket = cspPacket; + rawPacketLen = cspPacketLen; + rememberCommandId = GOMSPACE::PING; + return returnvalue::OK; +} + +void GomspaceDeviceHandler::generateRebootCommand() { + uint8_t cspPort = GOMSPACE::REBOOT_PORT; + uint16_t querySize = 0; + *cspPacket = GOMSPACE::REBOOT_PORT; + *(cspPacket + 1) = querySize; + size_t cspPacketLen = sizeof(cspPort) + sizeof(cspPacketLen); + rawPacket = cspPacket; + rawPacketLen = cspPacketLen; +} + +ReturnValue_t GomspaceDeviceHandler::childCommandHook(DeviceCommandId_t cmd, + const uint8_t* commandData, + size_t commandDataLen) { + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; +} + +ReturnValue_t GomspaceDeviceHandler::setParamCallback(SetParamMessageUnpacker& unpacker, + bool afterExecution) { + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::initializePduPool( + localpool::DataPool& localDataPoolMap, LocalDataPoolManager& poolManager, + std::array initOutEnb) { + using namespace PDU; + localDataPoolMap.emplace(pool::PDU_CURRENTS, new PoolEntry(9)); + localDataPoolMap.emplace(pool::PDU_VOLTAGES, new PoolEntry(9)); + + localDataPoolMap.emplace(pool::PDU_VCC, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_VBAT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_TEMPERATURE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_CONV_EN, new PoolEntry(3)); + + localDataPoolMap.emplace(pool::PDU_OUT_ENABLE, + new PoolEntry(initOutEnb.data(), initOutEnb.size())); + + localDataPoolMap.emplace(pool::PDU_BOOTCAUSE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_BOOTCNT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_UPTIME, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_RESETCAUSE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_BATT_MODE, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::PDU_LATCHUPS, new PoolEntry(9)); + + localDataPoolMap.emplace(pool::PDU_DEVICES, new PoolEntry(8)); + localDataPoolMap.emplace(pool::PDU_STATUSES, new PoolEntry(8)); + + localDataPoolMap.emplace(pool::PDU_WDT_CNT_GND, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_CNT_I2C, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_CNT_CAN, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_CNT_CSP1, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_CNT_CSP2, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_GND_LEFT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_I2C_LEFT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_CAN_LEFT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_CSP_LEFT1, new PoolEntry({0})); + localDataPoolMap.emplace(pool::PDU_WDT_CSP_LEFT2, new PoolEntry({0})); + return returnvalue::OK; +} + +bool GomspaceDeviceHandler::validTableId(uint8_t id) { + if (id == TableIds::CONFIG or id == TableIds::BOARD_PARAMS or id == TableIds::CALIBRATION or + id == TableIds::HK) { + return true; + } + return false; +} + +ReturnValue_t GomspaceDeviceHandler::getDevType(GOMSPACE::DeviceType& type) const { + if (getObjectId() == objects::PDU1_HANDLER or getObjectId() == objects::PDU2_HANDLER) { + type = DeviceType::PDU; + } else if (getObjectId() == objects::ACU_HANDLER) { + type = DeviceType::ACU; + } else if (getObjectId() == objects::P60DOCK_HANDLER) { + type = DeviceType::P60DOCK; + } else { + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::generateResetWatchdogCmd() { + WatchdogResetCommand watchdogResetCommand; + size_t cspPacketLen = 0; + uint8_t* buffer = cspPacket; + ReturnValue_t result = watchdogResetCommand.serialize(&buffer, &cspPacketLen, sizeof(cspPacket), + SerializeIF::Endianness::BIG); + if (result != returnvalue::OK) { + sif::error << "GomspaceDeviceHandler: Failed to serialize watchdog reset " + << "command" << std::endl; + return result; + } + rawPacket = cspPacket; + rawPacketLen = cspPacketLen; + rememberRequestedSize = 0; // No bytes will be queried with the ground + // watchdog command. + rememberCommandId = GOMSPACE::GNDWDT_RESET; + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::generateRequestFullHkTableCmd(DeviceType dev, + uint16_t tableReplySize, + DeviceCommandId_t id, + CspCookie* cspCookie) { + if (dev == DeviceType::ACU) { + cspCookie->setRequest(SpecialRequestTypes::GET_ACU_HK, tableReplySize); + } else if (dev == DeviceType::P60DOCK) { + cspCookie->setRequest(SpecialRequestTypes::GET_P60DOCK_HK, tableReplySize); + } else if (dev == DeviceType::PDU) { + cspCookie->setRequest(SpecialRequestTypes::GET_PDU_HK, tableReplySize); + } + cspCookie->setCspPort(CspPorts::P60_PORT_RPARAM_ENUM); + rememberRequestedSize = tableReplySize; + rememberCommandId = id; + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::generateRequestFullCfgTableCmd(DeviceType dev, + uint16_t tableReplySize, + DeviceCommandId_t id, + CspCookie* cspCookie) { + if (dev == DeviceType::ACU) { + cspCookie->setRequest(SpecialRequestTypes::GET_ACU_CONFIG, tableReplySize); + } else if (dev == DeviceType::P60DOCK) { + cspCookie->setRequest(SpecialRequestTypes::GET_P60DOCK_CONFIG, tableReplySize); + } else if (dev == DeviceType::PDU) { + cspCookie->setRequest(SpecialRequestTypes::GET_PDU_CONFIG, tableReplySize); + } + cspCookie->setCspPort(CspPorts::P60_PORT_RPARAM_ENUM); + // Unfortunately, this does not work.. + // cspPacket[0] = defaultTable; + // rawPacket = cspPacket; + // rawPacketLen = 1; + rememberRequestedSize = tableReplySize; + rememberCommandId = id; + return returnvalue::OK; +} + +uint32_t GomspaceDeviceHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 0; } + +void GomspaceDeviceHandler::setModeNormal() { setMode(_MODE_TO_NORMAL); } + +ReturnValue_t GomspaceDeviceHandler::printStatus(DeviceCommandId_t cmd) { + sif::info << "No printHkTable implementation given.." << std::endl; + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::parsePduHkTable(PDU::PduCoreHk& coreHk, PDU::PduAuxHk& auxHk, + const uint8_t* packet) { + PoolReadGuard pg0(&coreHk); + PoolReadGuard pg1(&auxHk); + if (pg0.getReadResult() != returnvalue::OK or pg1.getReadResult() != returnvalue::OK) { + sif::warning << "Reading PDU1 datasets failed!" << std::endl; + return returnvalue::FAILED; + } + /* Fist 10 bytes contain the gomspace header. Each variable is preceded by the 16-bit table + * address. */ + // dataOffset += 12; + for (uint8_t idx = 0; idx < PDU::CHANNELS_LEN; idx++) { + coreHk.currents[idx] = as(packet + (idx * 2)); + } + for (uint8_t idx = 0; idx < PDU::CHANNELS_LEN; idx++) { + coreHk.voltages[idx] = as(packet + 0x12 + (idx * 2)); + } + coreHk.vcc.value = as(packet + 0x24); + coreHk.vbat.value = as(packet + 0x26); + coreHk.temperature = as(packet + 0x28) * 0.1; + + for (uint8_t idx = 0; idx < 3; idx++) { + auxHk.converterEnable[idx] = packet[0x2a + idx]; + } + + for (uint8_t idx = 0; idx < PDU::CHANNELS_LEN; idx++) { + coreHk.outputEnables[idx] = packet[0x2e + idx]; + } + auxHk.bootcause = as(packet + 0x38); + coreHk.bootcount = as(packet + 0x3c); + auxHk.uptime = as(packet + 0x40); + auxHk.resetcause = as(packet + 0x44); + coreHk.battMode = *(packet + 0x46); + + for (uint8_t idx = 0; idx < PDU::CHANNELS_LEN; idx++) { + auxHk.latchups[idx] = as(packet + 0x48 + (idx * 2)); + } + + for (uint8_t idx = 0; idx < PDU::DEVICES_NUM; idx++) { + auxHk.deviceTypes[idx] = *(packet + 0x5a + idx); + } + for (uint8_t idx = 0; idx < PDU::DEVICES_NUM; idx++) { + auxHk.devicesStatus[idx] = *(packet + 0x62 + idx); + } + + auxHk.gndWdtReboots = as(packet + 0x6c); + auxHk.i2cWdtReboots = as(packet + 0x70); + auxHk.canWdtReboots = as(packet + 0x74); + auxHk.csp1WdtReboots = as(packet + 0x78); + auxHk.csp2WdtReboots = as(packet + 0x78 + 4); + auxHk.groundWatchdogSecondsLeft = as(packet + 0x80); + auxHk.i2cWatchdogSecondsLeft = as(packet + 0x84); + auxHk.canWatchdogSecondsLeft = as(packet + 0x88); + auxHk.csp1WatchdogPingsLeft = *(packet + 0x8c); + auxHk.csp2WatchdogPingsLeft = *(packet + 0x8c + 1); + coreHk.setChanged(true); + if (not coreHk.isValid()) { + coreHk.setValidity(true, true); + } + if (not auxHk.isValid()) { + auxHk.setValidity(true, true); + } + return returnvalue::OK; +} + +ReturnValue_t GomspaceDeviceHandler::setHealth(HealthState health) { + if (health != HealthState::HEALTHY and health != HealthState::EXTERNAL_CONTROL and + health != HealthState::NEEDS_RECOVERY) { + return returnvalue::FAILED; + } + return returnvalue::OK; +} diff --git a/mission/power/GomspaceDeviceHandler.h b/mission/power/GomspaceDeviceHandler.h new file mode 100644 index 0000000..758cc5a --- /dev/null +++ b/mission/power/GomspaceDeviceHandler.h @@ -0,0 +1,188 @@ +#ifndef MISSION_POWER_GOMSPACEDEVICEHANDLER_H_ +#define MISSION_POWER_GOMSPACEDEVICEHANDLER_H_ + +#include +#include +#include + +#include "fsfw/devicehandlers/DeviceHandlerBase.h" +#include "returnvalues/classIds.h" + +struct TableConfig { + uint16_t maxConfigTableAddress; + uint16_t maxHkTableAddress; + uint16_t hkTableSize; + uint16_t cfgTableSize; +}; +/** + * @brief This is the device handler class for all gomspace devices. + * + * @details + * All gomspace devices are similar with respect to commanding. Thusmost of the functionality to + * command a gomspace device can be accommodated in one class. For device specific functions, a new + * class could be created by inheriting from the GomspaceDeviceHandler. + * + * Flight manual: + * https://egit.irs.uni-stuttgart.de/redmine/projects/eive-flight-manual/wiki/Gomspace_PCDU_P60_System + */ +class GomspaceDeviceHandler : public DeviceHandlerBase { + public: + static constexpr uint8_t CLASS_ID = CLASS_ID::GOM_SPACE_HANDLER; + static const ReturnValue_t PACKET_TOO_LONG = returnvalue::makeCode(CLASS_ID, 0); + static const ReturnValue_t INVALID_TABLE_ID = returnvalue::makeCode(CLASS_ID, 1); + static const ReturnValue_t INVALID_ADDRESS = returnvalue::makeCode(CLASS_ID, 2); + static const ReturnValue_t INVALID_PARAM_SIZE = returnvalue::makeCode(CLASS_ID, 3); + static const ReturnValue_t INVALID_PAYLOAD_SIZE = returnvalue::makeCode(CLASS_ID, 4); + static const ReturnValue_t UNKNOWN_REPLY_ID = returnvalue::makeCode(CLASS_ID, 5); + + /** + * @brief Constructor + * + * @param maxConfigTableAddress The maximum memory address of the configu- + * ration table of a gomspace device. + * @param maxHkTableAddress The maximum memory address of a value in the + * houskeeping (telemetry) table of a gomspace + * device. + */ + GomspaceDeviceHandler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie, + TableConfig &tableConfig, FailureIsolationBase *customFdir, + bool enableHkSets); + virtual ~GomspaceDeviceHandler(); + + /** + * @brief This function can be used to set a gomspace device to normal mode immediately after + * object creation. + */ + void setModeNormal(); + + protected: + static const uint8_t MAX_PACKET_LEN = 36; + static const uint8_t PARAM_SET_OK = 1; + static const uint8_t PING_REPLY_SIZE = 2; + + bool enableHkSets = false; + uint8_t rememberRequestedSize = 0; + uint8_t rememberCommandId = GOMSPACE::NONE; + uint8_t cspPacket[MAX_PACKET_LEN]; + + LocalPoolDataSetBase *hkTableDataset = nullptr; + + void initPduConfigTable(); + void doStartUp() override; + void doShutDown() override; + virtual ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + virtual void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t remainingSize, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void setNormalDatapoolEntriesInvalid() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + + ReturnValue_t setHealth(HealthState health) override; + + /** + * @brief The command to generate a request to receive the full housekeeping table is device + * specific. Thus the child has to build this command. + */ + ReturnValue_t generateRequestFullHkTableCmd(GOMSPACE::DeviceType devType, uint16_t tableSize, + DeviceCommandId_t id, CspCookie *cspCookie); + /** + * Unfortunately, it was not possible to specify the table ID (e.g. request table from + * default store) + * @param devType + * @param tableSize + * @param id + * @param cspCookie + * @return + */ + ReturnValue_t generateRequestFullCfgTableCmd(GOMSPACE::DeviceType devType, uint16_t tableSize, + DeviceCommandId_t id, CspCookie *cspCookie); + ReturnValue_t getDevType(GOMSPACE::DeviceType &type) const; + /** + * This command handles printing the HK table to the console. This is useful for debugging + * purposes + * @return + */ + virtual ReturnValue_t printStatus(DeviceCommandId_t cmd); + + /** + * @brief Because housekeeping tables are device specific the handling of the reply is + * given to the child class. + * @param id The id of the command which initiates the full table request. + * @param packet Pointer to the reply containing the hk table. + */ + virtual void letChildHandleHkReply(DeviceCommandId_t id, const uint8_t *packet) = 0; + virtual void letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t *packet) = 0; + virtual LocalPoolDataSetBase *getDataSetHandle(sid_t sid) = 0; + + /** + * @brief Can be overriden by child classes to implement device specific commands. + * @return Return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED to let this handler handle + * the command or returnvalue::OK if the child handles the command + */ + virtual ReturnValue_t childCommandHook(DeviceCommandId_t cmd, const uint8_t *commandData, + size_t commandDataLen); + + ReturnValue_t parsePduHkTable(PDU::PduCoreHk &coreHk, PDU::PduAuxHk &auxHk, + const uint8_t *packet); + ReturnValue_t initializePduPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager, + std::array initOutEnb); + + template + T as(const uint8_t *); + static bool validTableId(uint8_t id); + + private: + SetParamMessageUnpacker setParamCacher; + TableConfig &tableCfg; + /** + * @brief Function to generate the command to set a parameter. Command + * will be sent to the ComIF over the rawPacket buffer. + */ + ReturnValue_t generateSetParamCommand(const uint8_t *commandData, size_t commandDataLen); + + /** + * Callback is called on a parameter set command. It is called before executing it and after + * after successful execution + * @param unpacker Passed before + * @param beforeSet False for callback before execution, true if called after successful + * execution + * @return + */ + virtual ReturnValue_t setParamCallback(SetParamMessageUnpacker &unpacker, bool afterExecution); + + /** + * @brief Function to generate the command to get a parameter from a + * gomspace device. Command will be sent to the ComIF over the + * rawPacket buffer. + */ + ReturnValue_t generateGetParamCommand(const uint8_t *commandData, size_t commandDataLen); + + /** + * @brief Function to generate the ping command for the ComIF. + */ + ReturnValue_t generatePingCommand(const uint8_t *commandData, size_t commandDataLen); + + /** + * @brief Function to generate the command to reboot a gomspace device + * via the ComIF. + */ + void generateRebootCommand(); + + /** + * @brief Function to generate the command to force a ground watchdog + * reset in a gomspace device. + */ + ReturnValue_t generateResetWatchdogCmd(); +}; + +template +inline T GomspaceDeviceHandler::as(const uint8_t *ptr) { + return *(reinterpret_cast(ptr)); +} + +#endif /* MISSION_POWER_GOMSPACEDEVICEHANDLER_H_ */ diff --git a/mission/power/P60DockHandler.cpp b/mission/power/P60DockHandler.cpp new file mode 100644 index 0000000..77fd9aa --- /dev/null +++ b/mission/power/P60DockHandler.cpp @@ -0,0 +1,273 @@ +#include +#include + +#include "OBSWConfig.h" + +P60DockHandler::P60DockHandler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie, + FailureIsolationBase *customFdir, bool enableHkSets) + : GomspaceDeviceHandler(objectId, comIF, comCookie, cfg, customFdir, enableHkSets), + coreHk(this), + auxHk(this) { + cfg.maxConfigTableAddress = P60Dock::MAX_CONFIGTABLE_ADDRESS; + cfg.maxHkTableAddress = P60Dock::MAX_HKTABLE_ADDRESS; + cfg.hkTableSize = P60Dock::HK_TABLE_SIZE; + cfg.cfgTableSize = P60Dock::CONFIG_TABLE_SIZE; +} + +P60DockHandler::~P60DockHandler() {} + +ReturnValue_t P60DockHandler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = GOMSPACE::REQUEST_HK_TABLE; + return buildCommandFromCommand(*id, NULL, 0); +} + +void P60DockHandler::letChildHandleHkReply(DeviceCommandId_t id, const uint8_t *packet) { + parseHkTableReply(packet); +} + +void P60DockHandler::parseHkTableReply(const uint8_t *packet) { + using namespace P60Dock; + PoolReadGuard pg0(&coreHk); + PoolReadGuard pg1(&auxHk); + if (pg0.getReadResult() != returnvalue::OK or pg1.getReadResult() != returnvalue::OK) { + coreHk.setValidity(false, true); + auxHk.setValidity(false, true); + return; + } + /** + * Fist 10 bytes contain the gomspace header. Each variable is preceded by the 16-bit table + * address. + */ + for (uint8_t idx = 0; idx < hk::CHNLS_LEN; idx++) { + coreHk.currents[idx] = as(packet + (idx * 2)); + } + for (uint8_t idx = 0; idx < hk::CHNLS_LEN; idx++) { + coreHk.voltages[idx] = as(packet + 0x1a + (idx * 2)); + } + for (uint8_t idx = 0; idx < hk::CHNLS_LEN; idx++) { + coreHk.outputEnables[idx] = *(packet + 0x34 + idx); + } + + coreHk.temperature1 = as(packet + 0x44) * 0.1; + coreHk.temperature2 = as(packet + 0x44 + 2) * 0.1; + + auxHk.bootcause = as(packet + 0x48); + coreHk.bootCount = as(packet + 0x4c); + if (firstHk) { + triggerEvent(P60_BOOT_COUNT, coreHk.bootCount.value); + } + auxHk.uptime = as(packet + 0x50); + auxHk.resetcause = as(packet + 0x54); + uint8_t newBattMode = packet[0x56]; + if (firstHk) { + triggerEvent(BATT_MODE, newBattMode); + } else if (newBattMode != coreHk.battMode.value) { + triggerEvent(BATT_MODE_CHANGED, coreHk.battMode.value, newBattMode); + } + coreHk.battMode = newBattMode; + + auxHk.heaterForBp4PackOn = *(packet + 0x57); + auxHk.converter5VStatus = *(packet + 0x58); + + for (uint8_t idx = 0; idx < hk::CHNLS_LEN; idx++) { + auxHk.latchups[idx] = as(packet + 0x5a + (idx * 2)); + } + + auxHk.dockVbatVoltageValue = as(packet + 0x74); + auxHk.dockVccCurrent = as(packet + 0x76); + coreHk.batteryCurrent = as(packet + 0x78); + coreHk.batteryVoltage = as(packet + 0x7a); + + auxHk.batteryTemperature1 = as(packet + 0x7c); + auxHk.batteryTemperature2 = as(packet + 0x7c + 2); + + for (uint8_t idx = 0; idx < NUM_DEVS; idx++) { + auxHk.devicesType[idx] = *(packet + 0x80 + idx); + } + for (uint8_t idx = 0; idx < NUM_DEVS; idx++) { + auxHk.devicesStatus[idx] = *(packet + 0x88 + idx); + } + + auxHk.dearmStatus = *(packet + 0x90); + + auxHk.wdtCntGnd = as(packet + 0x94); + auxHk.wdtCntI2c = as(packet + 0x98); + auxHk.wdtCntCan = as(packet + 0x9c); + auxHk.wdtCntCsp1 = as(packet + 0xa0); + auxHk.wdtCntCsp2 = as(packet + 0xa0 + 4); + auxHk.wdtGndLeft = as(packet + 0xa8); + auxHk.wdtI2cLeft = as(packet + 0xac); + auxHk.wdtCanLeft = as(packet + 0xb0); + + auxHk.wdtCspLeft1 = *(packet + 0xb4); + auxHk.wdtCspLeft2 = *(packet + 0xb4 + 1); + + auxHk.batteryChargeCurrent = as(packet + 0xb6); + auxHk.batteryDischargeCurrent = as(packet + 0xb8); + auxHk.ant6Depl = *(packet + 0xba); + auxHk.ar6Depl = *(packet + 0xbb); + if (firstHk) { + firstHk = false; + } + coreHk.setValidity(true, true); + auxHk.setValidity(true, true); + // No BP4 pack, no this is always invalid. + auxHk.heaterForBp4PackOn.setValid(false); +} + +ReturnValue_t P60DockHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + using namespace P60Dock; + localDataPoolMap.emplace(pool::P60_CURRENTS, &hkCurrents); + + localDataPoolMap.emplace(pool::P60_VOLTAGES, &hkVoltages); + + localDataPoolMap.emplace(pool::P60_OUTPUT_ENABLE, &outputEnables); + + localDataPoolMap.emplace(pool::P60DOCK_TEMPERATURE_1, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_TEMPERATURE_2, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::P60DOCK_BOOT_CAUSE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_BOOT_CNT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_UPTIME, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_RESETCAUSE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_BATT_MODE, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_HEATER_ON, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_CONV_5V_ENABLE_STATUS, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::LATCHUPS, &latchups); + + localDataPoolMap.emplace(pool::P60DOCK_DOCK_VBAT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_DOCK_VCC_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_BATTERY_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_BATTERY_VOLTAGE, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::P60DOCK_BATTERY_TEMPERATURE_1, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_BATTERY_TEMPERATURE_2, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::DEVICES_TYPE, &devicesType); + localDataPoolMap.emplace(pool::DEVICES_STATUS, &devicesStatus); + + localDataPoolMap.emplace(pool::P60DOCK_DEARM_STATUS, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::P60DOCK_WDT_CNT_GND, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_WDT_CNT_I2C, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_WDT_CNT_CAN, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_WDT_CNT_CSP_1, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_WDT_CNT_CSP_2, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::P60DOCK_WDT_GND_LEFT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_WDT_I2C_LEFT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_WDT_CAN_LEFT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_WDT_CSP_LEFT_1, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_WDT_CSP_LEFT_2, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::P60DOCK_BATT_CHARGE_CURRENT, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_BATT_DISCHARGE_CURRENT, new PoolEntry({0})); + + localDataPoolMap.emplace(pool::P60DOCK_ANT6_DEPL, new PoolEntry({0})); + localDataPoolMap.emplace(pool::P60DOCK_AR6_DEPL, new PoolEntry({0})); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(coreHk.getSid(), enableHkSets, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(auxHk.getSid(), enableHkSets, 6000.0)); + return returnvalue::OK; +} + +ReturnValue_t P60DockHandler::printStatus(DeviceCommandId_t cmd) { + ReturnValue_t result = returnvalue::OK; + switch (cmd) { + case (GOMSPACE::PRINT_SWITCH_V_I): { + PoolReadGuard pg0(&coreHk); + PoolReadGuard pg1(&auxHk); + if (pg0.getReadResult() != returnvalue::OK or pg1.getReadResult() != returnvalue::OK) { + break; + } + printHkTableSwitchIV(); + return returnvalue::OK; + } + case (GOMSPACE::PRINT_LATCHUPS): { + PoolReadGuard pg(&auxHk); + result = pg.getReadResult(); + printHkTableLatchups(); + if (result != returnvalue::OK) { + break; + } + return returnvalue::OK; + } + default: { + return DeviceHandlerIF::COMMAND_NOT_SUPPORTED; + } + } + sif::warning << "Reading P60 Dock HK table failed" << std::endl; + return returnvalue::FAILED; +} + +void P60DockHandler::printHkTableSwitchIV() { + using namespace P60Dock; + sif::info << "P60 Dock Info:" << std::endl; + sif::info << "Boot Cause: " << auxHk.bootcause << " | Boot Count: " << std::setw(4) << std::right + << coreHk.bootCount << std::endl; + sif::info << "Reset Cause: " << auxHk.resetcause + << " | Battery Mode: " << static_cast(coreHk.battMode.value) << std::endl; + sif::info << "SwitchState, Currents [mA], Voltages [mV]:" << std::endl; + sif::info << std::setw(MAX_CHANNEL_STR_WIDTH) << std::left << "Dock VBAT VCC" << std::dec + << "| -, " << std::setw(4) << std::right << auxHk.dockVccCurrent << ", " << std::setw(5) + << auxHk.dockVbatVoltageValue << std::endl; + sif::info << std::setw(MAX_CHANNEL_STR_WIDTH) << std::left << "BATT" << std::dec << "| -, " + << std::setw(4) << std::right << coreHk.batteryCurrent.value << ", " << std::setw(5) + << coreHk.batteryVoltage.value << std::endl; + + auto genericPrintoutHandler = [&](std::string name, uint8_t idx) { + sif::info << std::setw(MAX_CHANNEL_STR_WIDTH) << std::left << name << std::dec << "| " + << unsigned(coreHk.outputEnables[idx]) << ", " << std::setw(4) << std::right + << coreHk.currents[idx] << ", " << std::setw(5) << coreHk.voltages[idx] << std::endl; + }; + + genericPrintoutHandler("ACU VCC", hk::ACU_VCC); + genericPrintoutHandler("ACU VBAT", hk::ACU_VBAT); + genericPrintoutHandler("PDU1 VCC", hk::PDU1_VCC); + genericPrintoutHandler("PDU1 VBAT", hk::PDU1_VBAT); + genericPrintoutHandler("PDU2 VCC", hk::PDU2_VCC); + genericPrintoutHandler("PDU2 VBAT", hk::PDU2_VBAT); + genericPrintoutHandler("Stack VBAT", hk::STACK_VBAT); + genericPrintoutHandler("Stack 3V3", hk::STACK_3V3); + genericPrintoutHandler("Stack 5V", hk::STACK_5V); +} + +LocalPoolDataSetBase *P60DockHandler::getDataSetHandle(sid_t sid) { + if (sid == coreHk.getSid()) { + return &coreHk; + } else if (sid == auxHk.getSid()) { + return &auxHk; + } + return nullptr; +} + +void P60DockHandler::printHkTableLatchups() { + using namespace P60Dock; + sif::info << "P60 Latchup Information" << std::endl; + auto genericPrintoutHandler = [&](std::string name, uint8_t idx) { + sif::info << std::setw(MAX_CHANNEL_STR_WIDTH) << std::left << name << std::dec << "| " + << std::setw(4) << std::right << auxHk.latchups[idx] << std::endl; + }; + genericPrintoutHandler("ACU VCC", hk::ACU_VCC); + genericPrintoutHandler("ACU VBAT", hk::ACU_VBAT); + genericPrintoutHandler("PDU1 VCC", hk::PDU1_VCC); + genericPrintoutHandler("PDU1 VBAT", hk::PDU1_VBAT); + genericPrintoutHandler("PDU2 VCC", hk::PDU2_VCC); + genericPrintoutHandler("PDU2 VBAT", hk::PDU2_VBAT); + genericPrintoutHandler("STACK VBAT", hk::STACK_VBAT); + genericPrintoutHandler("STACK 3V3", hk::STACK_3V3); + genericPrintoutHandler("STACK 5V", hk::STACK_5V); + genericPrintoutHandler("GS 3V3", hk::GS3V3); + genericPrintoutHandler("GS 5V", hk::GS5V); + genericPrintoutHandler("X3 VBAT", hk::X3_IDLE_VBAT); + genericPrintoutHandler("X3 VCC", hk::X3_IDLE_VCC); +} + +void P60DockHandler::setDebugMode(bool enable) { this->debugMode = enable; } + +void P60DockHandler::letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t *packet) { + handleDeviceTm(packet, P60Dock::CONFIG_TABLE_SIZE, id); +} diff --git a/mission/power/P60DockHandler.h b/mission/power/P60DockHandler.h new file mode 100644 index 0000000..2d65426 --- /dev/null +++ b/mission/power/P60DockHandler.h @@ -0,0 +1,74 @@ +#ifndef MISSION_POWER_P60DOCKHANDLER_H_ +#define MISSION_POWER_P60DOCKHANDLER_H_ + +#include +#include + +#include "eive/eventSubsystemIds.h" + +/** + * @brief Device handler for the P60Dock. The P60Dock serves as carrier for the ACU, PDU1 and + * PDU2. Via the P60Dock each of these modules can be turned on and off individually. + */ +class P60DockHandler : public GomspaceDeviceHandler { + public: + static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::P60_DOCK_HANDLER; + + //! [EXPORT] : [COMMENT] P60 boot count is broadcasted once at SW startup. P1: Boot count + static constexpr Event P60_BOOT_COUNT = event::makeEvent(SUBSYSTEM_ID, 0, severity::INFO); + //! [EXPORT] : [COMMENT] Battery mode is broadcasted at startup. P1: Mode + static constexpr Event BATT_MODE = event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO); + //! [EXPORT] : [COMMENT] Battery mode has changed. P1: Old mode. P2: New mode + static constexpr Event BATT_MODE_CHANGED = event::makeEvent(SUBSYSTEM_ID, 2, severity::MEDIUM); + + P60DockHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + FailureIsolationBase* customFdir, bool enableHkSets); + virtual ~P60DockHandler(); + + void setDebugMode(bool enable); + + virtual ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + protected: + /** + * @brief As soon as the device is in MODE_NORMAL, this function is executed periodically. + */ + virtual ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + + void letChildHandleHkReply(DeviceCommandId_t id, const uint8_t* packet) override; + void letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t* packet) override; + /** + * This command handles printing the HK table to the console. This is useful for debugging + * purposes + * @return + */ + ReturnValue_t printStatus(DeviceCommandId_t cmd) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + void printHkTableSwitchIV(); + void printHkTableLatchups(); + + private: + P60Dock::CoreHkSet coreHk; + P60Dock::HkTableDataset auxHk; + TableConfig cfg; + bool firstHk = true; + bool debugMode = false; + static constexpr uint8_t MAX_CHANNEL_STR_WIDTH = 16; + + PoolEntry hkCurrents = PoolEntry(P60Dock::hk::CHNLS_LEN); + PoolEntry hkVoltages = PoolEntry(P60Dock::hk::CHNLS_LEN); + PoolEntry outputEnables = PoolEntry(P60Dock::hk::CHNLS_LEN); + PoolEntry latchups = PoolEntry(P60Dock::hk::CHNLS_LEN); + + PoolEntry devicesType = PoolEntry(P60Dock::NUM_DEVS); + PoolEntry devicesStatus = PoolEntry(P60Dock::NUM_DEVS); + /** + * @brief Function extracts the hk table information from the received csp packet and stores + * the values in the p60dockHkTableDataset. + */ + void parseHkTableReply(const uint8_t* packet); +}; + +#endif /* MISSION_POWER_P60DOCKHANDLER_H_ */ diff --git a/mission/power/PcduHandler.cpp b/mission/power/PcduHandler.cpp new file mode 100644 index 0000000..1596d52 --- /dev/null +++ b/mission/power/PcduHandler.cpp @@ -0,0 +1,489 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +PcduHandler::PcduHandler(object_id_t setObjectId, size_t cmdQueueSize) + : SystemObject(setObjectId), + poolManager(this, nullptr), + p60CoreHk(objects::P60DOCK_HANDLER), + pdu1CoreHk(this), + pdu2CoreHk(this), + switcherSet(this), + cmdQueueSize(cmdQueueSize) { + auto mqArgs = MqArgs(setObjectId, static_cast(this)); + commandQueue = QueueFactory::instance()->createMessageQueue( + cmdQueueSize, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); + pwrLock = MutexFactory::instance()->createMutex(); + std::memset(switchStates, SWITCH_STATE_UNKNOWN, sizeof(switchStates)); +} + +PcduHandler::~PcduHandler() {} + +ReturnValue_t PcduHandler::performOperation(uint8_t counter) { + if (counter == DeviceHandlerIF::PERFORM_OPERATION) { + readCommandQueue(); + } + uint8_t switchState5V = 0; + uint8_t switchState3V3 = 0; + { + PoolReadGuard pg(&p60CoreHk.outputEnables); + if (pg.getReadResult() == returnvalue::OK) { + switchState5V = p60CoreHk.outputEnables.value[P60Dock::hk::STACK_5V]; + switchState3V3 = p60CoreHk.outputEnables.value[P60Dock::hk::STACK_3V3]; + } else { + return returnvalue::OK; + } + } + { + PoolReadGuard pg(&switcherSet); + if (pg.getReadResult() == returnvalue::OK) { + if (switcherSet.p60Dock5VStack.value != switchState5V) { + triggerEvent(power::SWITCH_HAS_CHANGED, switchState5V, power::Switches::P60_DOCK_5V_STACK); + MutexGuard mg(pwrLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + switchStates[power::P60_DOCK_5V_STACK] = switchState5V; + } + if (switcherSet.p60Dock3V3Stack.value != switchState3V3) { + triggerEvent(power::SWITCH_HAS_CHANGED, switchState3V3, + power::Switches::P60_DOCK_3V3_STACK); + MutexGuard mg(pwrLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + switchStates[power::P60_DOCK_3V3_STACK] = switchState3V3; + } + switcherSet.p60Dock5VStack.setValid(true); + switcherSet.p60Dock5VStack.value = switchState5V; + switcherSet.p60Dock3V3Stack.setValid(true); + switcherSet.p60Dock3V3Stack.value = switchState3V3; + } + } + return returnvalue::OK; +} + +ReturnValue_t PcduHandler::initialize() { + ReturnValue_t result; + + ipcStore = ObjectManager::instance()->get(objects::IPC_STORE); + if (ipcStore == nullptr) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + result = poolManager.initialize(commandQueue); + if (result != returnvalue::OK) { + return result; + } + + /* Subscribing for housekeeping table update messages of the PDU2 */ + HasLocalDataPoolIF* pdu2Handler = + ObjectManager::instance()->get(objects::PDU2_HANDLER); + if (pdu2Handler == nullptr) { + sif::error << "PCDUHandler::initialize: Invalid pdu2Handler" << std::endl; + return returnvalue::FAILED; + } + result = pdu2Handler->getSubscriptionInterface()->subscribeForSetUpdateMessage( + static_cast(P60System::SetIds::CORE), this->getObjectId(), commandQueue->getId(), + true); + if (result != returnvalue::OK) { + sif::error << "PCDUHandler::initialize: Failed to subscribe for set update messages from " + << "PDU2Handler" << std::endl; + return result; + } + + /* Subscribing for housekeeping table update messages of the PDU1 */ + HasLocalDataPoolIF* pdu1Handler = + ObjectManager::instance()->get(objects::PDU1_HANDLER); + if (pdu1Handler == nullptr) { + sif::error << "PCDUHandler::initialize: Invalid pdu1Handler" << std::endl; + return returnvalue::FAILED; + } + result = pdu1Handler->getSubscriptionInterface()->subscribeForSetUpdateMessage( + static_cast(P60System::SetIds::CORE), this->getObjectId(), commandQueue->getId(), + true); + if (result != returnvalue::OK) { + sif::error << "PCDUHandler::initialize: Failed to subscribe for set update messages from " + << "PDU1Handler" << std::endl; + return result; + } + + return returnvalue::OK; +} + +void PcduHandler::initializeSwitchStates() { + using namespace pcdu; + try { + for (uint8_t idx = 0; idx < power::NUMBER_OF_SWITCHES; idx++) { + if (idx < PDU::CHANNELS_LEN) { + switchStates[idx] = INIT_SWITCHES_PDU1.at(idx); + } else if (idx < PDU::CHANNELS_LEN * 2) { + switchStates[idx] = INIT_SWITCHES_PDU2.at(idx - PDU::CHANNELS_LEN); + } else { + switchStates[idx] = OFF; + } + } + } catch (const std::out_of_range& err) { + sif::error << "PCDUHandler::initializeSwitchStates: " << err.what() << std::endl; + } +} + +void PcduHandler::readCommandQueue() { + ReturnValue_t result = returnvalue::OK; + CommandMessage command; + + for (result = commandQueue->receiveMessage(&command); result == returnvalue::OK; + result = commandQueue->receiveMessage(&command)) { + result = poolManager.handleHousekeepingMessage(&command); + if (result == returnvalue::OK) { + continue; + } + } +} + +MessageQueueId_t PcduHandler::getCommandQueue() const { return commandQueue->getId(); } + +void PcduHandler::handleChangedDataset(sid_t sid, store_address_t storeId, bool* clearMessage) { + if (sid == sid_t(objects::PDU2_HANDLER, static_cast(P60System::SetIds::CORE))) { + updateHkTableDataset(storeId, &pdu2CoreHk, &timeStampPdu2HkDataset); + updatePdu2SwitchStates(); + } else if (sid == sid_t(objects::PDU1_HANDLER, static_cast(P60System::SetIds::CORE))) { + updateHkTableDataset(storeId, &pdu1CoreHk, &timeStampPdu1HkDataset); + updatePdu1SwitchStates(); + } else { + sif::error << "PCDUHandler::handleChangedDataset: Invalid sid" << std::endl; + } +} + +void PcduHandler::updateHkTableDataset(store_address_t storeId, LocalPoolDataSetBase* dataset, + CCSDSTime::CDS_short* datasetTimeStamp) { + ReturnValue_t result; + + HousekeepingSnapshot packetUpdate(reinterpret_cast(datasetTimeStamp), + sizeof(CCSDSTime::CDS_short), dataset); + const uint8_t* packet_ptr = nullptr; + size_t size = 0; + result = ipcStore->getData(storeId, &packet_ptr, &size); + if (result != returnvalue::OK) { + sif::error << "PCDUHandler::updateHkTableDataset: Failed to get data from IPC store, result 0x" + << std::hex << std::setw(4) << std::setfill('0') << result << std::dec + << std::setfill(' ') << std::endl; + result = ipcStore->deleteData(storeId); + return; + } + result = packetUpdate.deSerialize(&packet_ptr, &size, SerializeIF::Endianness::MACHINE); + if (result != returnvalue::OK) { + sif::error << "PCDUHandler::updateHkTableDataset: Failed to deserialize received packet " + "in hk table dataset" + << std::endl; + } + result = ipcStore->deleteData(storeId); + if (result != returnvalue::OK) { + sif::error << "PCDUHandler::updateHkTableDataset: Failed to delete data in IPCStore" + << std::endl; + } +} + +void PcduHandler::updatePdu2SwitchStates() { + using namespace pcdu; + using namespace PDU2; + GOMSPACE::Pdu pdu = GOMSPACE::Pdu::PDU2; + PoolReadGuard rg0(&switcherSet); + if (rg0.getReadResult() == returnvalue::OK) { + for (uint8_t idx = 0; idx < PDU::CHANNELS_LEN; idx++) { + switcherSet.pdu2Switches[idx] = pdu2CoreHk.outputEnables[idx]; + } + switcherSet.pdu2Switches.setValid(true); + MutexGuard mg(pwrLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + checkAndUpdatePduSwitch(pdu, power::PDU2_CH0_Q7S, pdu2CoreHk.outputEnables[Channels::Q7S]); + + checkAndUpdatePduSwitch(pdu, power::Switches::PDU2_CH1_PL_PCDU_BATT_0_14V8, + pdu2CoreHk.outputEnables[Channels::PAYLOAD_PCDU_CH1]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU2_CH2_RW_5V, + pdu2CoreHk.outputEnables[Channels::RW]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU2_CH3_TCS_BOARD_HEATER_IN_8V, + pdu2CoreHk.outputEnables[Channels::TCS_HEATER_IN]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU2_CH4_SUS_REDUNDANT_3V3, + pdu2CoreHk.outputEnables[Channels::SUS_REDUNDANT]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU2_CH5_DEPLOYMENT_MECHANISM_8V, + pdu2CoreHk.outputEnables[Channels::DEPY_MECHANISM]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU2_CH6_PL_PCDU_BATT_1_14V8, + pdu2CoreHk.outputEnables[Channels::PAYLOAD_PCDU_CH6]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU2_CH7_ACS_BOARD_SIDE_B_3V3, + pdu2CoreHk.outputEnables[Channels::ACS_B_SIDE]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU2_CH8_PAYLOAD_CAMERA, + pdu2CoreHk.outputEnables[Channels::PAYLOAD_CAMERA]); + if (firstSwitchInfoPdu2) { + firstSwitchInfoPdu2 = false; + } + } else { + sif::debug << "PCDUHandler::updatePdu2SwitchStates: Failed to read PDU2 Hk Dataset" + << std::endl; + } +} + +void PcduHandler::updatePdu1SwitchStates() { + using namespace pcdu; + using namespace PDU1; + PoolReadGuard rg0(&switcherSet); + GOMSPACE::Pdu pdu = GOMSPACE::Pdu::PDU1; + if (rg0.getReadResult() == returnvalue::OK) { + for (uint8_t idx = 0; idx < PDU::CHANNELS_LEN; idx++) { + switcherSet.pdu1Switches[idx] = pdu1CoreHk.outputEnables[idx]; + } + switcherSet.pdu1Switches.setValid(true); + MutexGuard mg(pwrLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH0_TCS_BOARD_3V3, + pdu1CoreHk.outputEnables[Channels::TCS_BOARD_3V3]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH1_SYRLINKS_12V, + pdu1CoreHk.outputEnables[Channels::SYRLINKS]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH2_STAR_TRACKER_5V, + pdu1CoreHk.outputEnables[Channels::STR]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH3_MGT_5V, + pdu1CoreHk.outputEnables[Channels::MGT]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH4_SUS_NOMINAL_3V3, + pdu1CoreHk.outputEnables[Channels::SUS_NOMINAL]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH5_SOLAR_CELL_EXP_5V, + pdu1CoreHk.outputEnables[Channels::SOL_CELL_EXPERIMENT]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH6_PLOC_12V, + pdu1CoreHk.outputEnables[Channels::PLOC]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH7_ACS_A_SIDE_3V3, + pdu1CoreHk.outputEnables[Channels::ACS_A_SIDE]); + checkAndUpdatePduSwitch(pdu, power::Switches::PDU1_CH8_UNOCCUPIED, + pdu1CoreHk.outputEnables[Channels::UNUSED]); + if (firstSwitchInfoPdu1) { + firstSwitchInfoPdu1 = false; + } + } else { + sif::debug << "PCDUHandler::updatePdu1SwitchStates: Failed to read dataset" << std::endl; + } +} + +LocalDataPoolManager* PcduHandler::getHkManagerHandle() { return &poolManager; } + +ReturnValue_t PcduHandler::sendSwitchCommand(uint8_t switchNr, ReturnValue_t onOff) { + using namespace pcdu; + ReturnValue_t result; + uint16_t memoryAddress = 0; + size_t parameterValueSize = sizeof(uint8_t); + uint8_t parameterValue = 0; + GomspaceDeviceHandler* module = nullptr; + + switch (switchNr) { + case power::PDU1_CH0_TCS_BOARD_3V3: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_TCS_BOARD_3V3; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + case power::PDU1_CH1_SYRLINKS_12V: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_SYRLINKS; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + case power::PDU1_CH2_STAR_TRACKER_5V: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_STAR_TRACKER; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + case power::PDU1_CH3_MGT_5V: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_MGT; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + case power::PDU1_CH4_SUS_NOMINAL_3V3: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_SUS_NOMINAL; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + case power::PDU1_CH5_SOLAR_CELL_EXP_5V: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_SOLAR_CELL_EXP; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + case power::PDU1_CH6_PLOC_12V: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_PLOC; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + case power::PDU1_CH7_ACS_A_SIDE_3V3: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_ACS_BOARD_SIDE_A; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + case power::PDU1_CH8_UNOCCUPIED: { + memoryAddress = PDU1::CONFIG_ADDRESS_OUT_EN_CHANNEL8; + module = ObjectManager::instance()->get(objects::PDU1_HANDLER); + break; + } + // This is a dangerous command. Reject/Igore it for now + case power::PDU2_CH0_Q7S: { + return returnvalue::FAILED; + // memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_Q7S; + // pdu = ObjectManager::instance()->get(objects::PDU2_HANDLER); + // break; + } + case power::PDU2_CH1_PL_PCDU_BATT_0_14V8: { + memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_PAYLOAD_PCDU_CH1; + module = ObjectManager::instance()->get(objects::PDU2_HANDLER); + break; + } + case power::PDU2_CH2_RW_5V: { + memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_RW; + module = ObjectManager::instance()->get(objects::PDU2_HANDLER); + break; + } + case power::PDU2_CH3_TCS_BOARD_HEATER_IN_8V: { + memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_TCS_BOARD_HEATER_IN; + module = ObjectManager::instance()->get(objects::PDU2_HANDLER); + break; + } + case power::PDU2_CH4_SUS_REDUNDANT_3V3: { + memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_SUS_REDUNDANT; + module = ObjectManager::instance()->get(objects::PDU2_HANDLER); + break; + } + case power::PDU2_CH5_DEPLOYMENT_MECHANISM_8V: { + memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_DEPLOYMENT_MECHANISM; + module = ObjectManager::instance()->get(objects::PDU2_HANDLER); + break; + } + case power::PDU2_CH6_PL_PCDU_BATT_1_14V8: { + memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_PAYLOAD_PCDU_CH6; + module = ObjectManager::instance()->get(objects::PDU2_HANDLER); + break; + } + case power::PDU2_CH7_ACS_BOARD_SIDE_B_3V3: { + memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_ACS_BOARD_SIDE_B; + module = ObjectManager::instance()->get(objects::PDU2_HANDLER); + break; + } + case power::PDU2_CH8_PAYLOAD_CAMERA: { + memoryAddress = PDU2::CONFIG_ADDRESS_OUT_EN_PAYLOAD_CAMERA; + module = ObjectManager::instance()->get(objects::PDU2_HANDLER); + break; + } + case power::P60_DOCK_5V_STACK: { + memoryAddress = P60Dock::CONFIG_ADDRESS_OUT_EN_5V_STACK; + module = ObjectManager::instance()->get(objects::P60DOCK_HANDLER); + break; + } + case power::P60_DOCK_3V3_STACK: { + memoryAddress = P60Dock::CONFIG_ADDRESS_OUT_EN_3V3_STACK; + module = ObjectManager::instance()->get(objects::P60DOCK_HANDLER); + break; + } + + default: { + sif::error << "PCDUHandler::sendSwitchCommand: Invalid switch number " << std::endl; + return returnvalue::FAILED; + } + } + + switch (onOff) { + case PowerSwitchIF::SWITCH_ON: + parameterValue = 1; + break; + case PowerSwitchIF::SWITCH_OFF: + parameterValue = 0; + break; + default: + sif::error << "PCDUHandler::sendSwitchCommand: Invalid state commanded" << std::endl; + return returnvalue::FAILED; + } + + GomspaceSetParamMessage setParamMessage(memoryAddress, ¶meterValue, parameterValueSize); + + size_t serializedLength = 0; + uint8_t command[4]; + uint8_t* commandPtr = command; + size_t maxSize = sizeof(command); + setParamMessage.serialize(&commandPtr, &serializedLength, maxSize, SerializeIF::Endianness::BIG); + + store_address_t storeAddress; + result = ipcStore->addData(&storeAddress, command, sizeof(command)); + + CommandMessage message; + ActionMessage::setCommand(&message, GOMSPACE::PARAM_SET, storeAddress); + + result = commandQueue->sendMessage(module->getCommandQueue(), &message, 0); + if (result != returnvalue::OK) { + sif::debug << "PCDUHandler::sendSwitchCommand: Failed to send message to PDU Handler" + << std::endl; + } else { + // Can't use trigger event because of const function constraint, but this hack seems to work + this->forwardEvent(power::SWITCH_CMD_SENT, parameterValue, switchNr); + } + return result; +} + +ReturnValue_t PcduHandler::sendFuseOnCommand(uint8_t fuseNr) { return returnvalue::OK; } + +ReturnValue_t PcduHandler::getSwitchState(uint8_t switchNr) const { + if (switchNr >= power::NUMBER_OF_SWITCHES) { + sif::debug << "PCDUHandler::getSwitchState: Invalid switch number" << std::endl; + return returnvalue::FAILED; + } + uint8_t currentState = 0; + { + MutexGuard mg(pwrLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + currentState = switchStates[switchNr]; + } + if (currentState == SWITCH_STATE_UNKNOWN) { + return PowerSwitchIF::SWITCH_UNKNOWN; + } + if (currentState == 1) { + return PowerSwitchIF::SWITCH_ON; + } else { + return PowerSwitchIF::SWITCH_OFF; + } +} + +ReturnValue_t PcduHandler::getFuseState(uint8_t fuseNr) const { return returnvalue::OK; } + +uint32_t PcduHandler::getSwitchDelayMs(void) const { return 20000; } + +object_id_t PcduHandler::getObjectId() const { return SystemObject::getObjectId(); } + +ReturnValue_t PcduHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + using namespace pcdu; + localDataPoolMap.emplace(PoolIds::PDU1_SWITCHES, &pdu1Switches); + localDataPoolMap.emplace(PoolIds::PDU2_SWITCHES, &pdu2Switches); + localDataPoolMap.emplace(PoolIds::P60DOCK_5V, &p60Dock5VSwitch); + localDataPoolMap.emplace(PoolIds::P60DOCK_3V3, &p60Dock3V3Switch); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(switcherSet.getSid(), false, 5.0)); + return returnvalue::OK; +} + +ReturnValue_t PcduHandler::initializeAfterTaskCreation() { + if (executingTask != nullptr) { + pstIntervalMs = executingTask->getPeriodMs(); + } + this->poolManager.initializeAfterTaskCreation(); + + initializeSwitchStates(); + + return returnvalue::OK; +} + +uint32_t PcduHandler::getPeriodicOperationFrequency() const { return pstIntervalMs; } + +void PcduHandler::setTaskIF(PeriodicTaskIF* task) { executingTask = task; } + +LocalPoolDataSetBase* PcduHandler::getDataSetHandle(sid_t sid) { + if (sid == switcherSet.getSid()) { + return &switcherSet; + } else { + sif::error << "PCDUHandler::getDataSetHandle: Invalid sid" << std::endl; + return nullptr; + } +} + +void PcduHandler::checkAndUpdatePduSwitch(GOMSPACE::Pdu pdu, power::Switches switchIdx, + uint8_t setValue) { + using namespace pcdu; + if (switchStates[switchIdx] != setValue) { + triggerEvent(power::SWITCH_HAS_CHANGED, setValue, switchIdx); + } + switchStates[switchIdx] = setValue; +} diff --git a/mission/power/PcduHandler.h b/mission/power/PcduHandler.h new file mode 100644 index 0000000..73df73e --- /dev/null +++ b/mission/power/PcduHandler.h @@ -0,0 +1,144 @@ +#ifndef MISSION_POWER_PCDUHANDLER_H_ +#define MISSION_POWER_PCDUHANDLER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief The PCDUHandler provides a compact interface to handle all devices related to the + * control of power. + * @details + * This is necessary because the FSFW manages all power related functionalities via one + * power object. This includes for example switching on and off of devices. + */ +class PcduHandler : public PowerSwitchIF, + public HasLocalDataPoolIF, + public SystemObject, + public ExecutableObjectIF { + public: + PcduHandler(object_id_t setObjectId, size_t cmdQueueSize = 20); + virtual ~PcduHandler(); + + virtual ReturnValue_t initialize() override; + virtual ReturnValue_t performOperation(uint8_t counter) override; + virtual void handleChangedDataset(sid_t sid, store_address_t storeId = store_address_t::invalid(), + bool* clearMessage = nullptr) override; + + virtual ReturnValue_t sendSwitchCommand(uint8_t switchNr, ReturnValue_t onOff) override; + virtual ReturnValue_t sendFuseOnCommand(uint8_t fuseNr) override; + /** + * @param switchNr + * @return returnvalue::FAILED if the switch state has not been updated yet. + */ + ReturnValue_t getSwitchState(uint8_t switchNr) const override; + virtual ReturnValue_t getFuseState(uint8_t fuseNr) const override; + virtual uint32_t getSwitchDelayMs(void) const override; + virtual object_id_t getObjectId() const override; + virtual LocalDataPoolManager* getHkManagerHandle() override; + + virtual MessageQueueId_t getCommandQueue() const override; + virtual ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + virtual uint32_t getPeriodicOperationFrequency() const override; + virtual ReturnValue_t initializeAfterTaskCreation() override; + virtual LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + virtual void setTaskIF(PeriodicTaskIF* task_); + + private: + uint32_t pstIntervalMs = 0; + MutexIF* pwrLock = nullptr; + static constexpr MutexIF::TimeoutType LOCK_TYPE = MutexIF::TimeoutType::WAITING; + static constexpr uint32_t LOCK_TIMEOUT = 20; + static constexpr char LOCK_CTX[] = "PcduHandler"; + + /** Housekeeping manager. Handles updates of local pool variables. */ + LocalDataPoolManager poolManager; + + P60Dock::CoreHkSet p60CoreHk; + + /** Hk table dataset of PDU1 */ + PDU1::Pdu1CoreHk pdu1CoreHk; + /** + * The dataset holding the hk table of PDU2. This dataset is a copy of the PDU2 HK dataset + * of the PDU2Handler. Each time the PDU2Handler updates his HK dataset, a copy is sent + * to this object via a HousekeepingMessage. + */ + PDU2::Pdu2CoreHk pdu2CoreHk; + + pcdu::SwitcherStates switcherSet; + + PoolEntry pdu1Switches = + PoolEntry(pcdu::INIT_SWITCHES_PDU1.data(), pcdu::INIT_SWITCHES_PDU1.size()); + PoolEntry pdu2Switches = + PoolEntry(pcdu::INIT_SWITCHES_PDU2.data(), pcdu::INIT_SWITCHES_PDU2.size()); + PoolEntry p60Dock5VSwitch = PoolEntry(); + PoolEntry p60Dock3V3Switch = PoolEntry(); + + /** The timeStamp of the current pdu2HkTableDataset */ + CCSDSTime::CDS_short timeStampPdu2HkDataset; + + /** The timeStamp of the current pdu1HkTableDataset */ + CCSDSTime::CDS_short timeStampPdu1HkDataset; + + uint8_t SWITCH_STATE_UNKNOWN = 2; + uint8_t switchStates[power::NUMBER_OF_SWITCHES]; + /** + * Pointer to the IPCStore. + * This caches the pointer received from the objectManager in the constructor. + */ + StorageManagerIF* ipcStore = nullptr; + + /** + * Message queue to communicate with other objetcs. Used for example to receive + * local pool messages from ACU, PDU1 and PDU2. + */ + MessageQueueIF* commandQueue = nullptr; + + size_t cmdQueueSize; + bool firstSwitchInfoPdu1 = true; + bool firstSwitchInfoPdu2 = true; + + PeriodicTaskIF* executingTask = nullptr; + + void readCommandQueue(); + + /** + * @brief This function sets all switchStates to the initial switch configuration of the + * two PDUs after reboot. + */ + void initializeSwitchStates(); + + /** + * @brief Updates all switchStates related to the PDU2. + * Function is called each time a new hk dataset has been received from the PDU2Handler. + */ + void updatePdu2SwitchStates(); + + /** + * @brief Updates all switchStates related to the PDU1. Called each time the PDU1Handler + * sends a new hk dataset. + */ + void updatePdu1SwitchStates(); + + /** + * @brief In case of an update snapshot message this function handles the update of the + * local dataset. + * @param storeId Storage id of updated dataset. + * @param dataset Pointer to the local dataset. + * @param datasetTimeStamp Pointer to a variable which will hold the timestamp of the updated + * dataset. + */ + void updateHkTableDataset(store_address_t storeId, LocalPoolDataSetBase* dataset, + CCSDSTime::CDS_short* datasetTimeStamp); + void checkAndUpdatePduSwitch(GOMSPACE::Pdu pdu, power::Switches switchIdx, uint8_t setValue); +}; + +#endif /* MISSION_POWER_PCDUHANDLER_H_ */ diff --git a/mission/power/Pdu1Handler.cpp b/mission/power/Pdu1Handler.cpp new file mode 100644 index 0000000..3151b66 --- /dev/null +++ b/mission/power/Pdu1Handler.cpp @@ -0,0 +1,182 @@ +#include +#include +#include + +#include "devices/powerSwitcherList.h" + +Pdu1Handler::Pdu1Handler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie, + FailureIsolationBase *customFdir, bool enableHkSets) + : GomspaceDeviceHandler(objectId, comIF, comCookie, cfg, customFdir, enableHkSets), + coreHk(this), + auxHk(this) { + initPduConfigTable(); +} + +Pdu1Handler::~Pdu1Handler() {} + +ReturnValue_t Pdu1Handler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = GOMSPACE::REQUEST_HK_TABLE; + return buildCommandFromCommand(*id, NULL, 0); +} + +void Pdu1Handler::letChildHandleHkReply(DeviceCommandId_t id, const uint8_t *packet) { + parseHkTableReply(packet); +} + +void Pdu1Handler::assignChannelHookFunction(GOMSPACE::ChannelSwitchHook hook, void *args) { + this->channelSwitchHook = hook; + this->hookArgs = args; +} + +ReturnValue_t Pdu1Handler::setParamCallback(SetParamMessageUnpacker &unpacker, + bool afterExecution) { + using namespace PDU1; + GOMSPACE::Pdu pdu = GOMSPACE::Pdu::PDU1; + if (not afterExecution) { + return returnvalue::OK; + } + if (channelSwitchHook != nullptr and unpacker.getParameterSize() == 1) { + switch (unpacker.getAddress()) { + case (CONFIG_ADDRESS_OUT_EN_TCS_BOARD_3V3): { + channelSwitchHook(pdu, 0, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_SYRLINKS): { + channelSwitchHook(pdu, 1, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_STAR_TRACKER): { + channelSwitchHook(pdu, 2, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_MGT): { + channelSwitchHook(pdu, 3, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_SUS_NOMINAL): { + channelSwitchHook(pdu, 4, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_SOLAR_CELL_EXP): { + channelSwitchHook(pdu, 5, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_PLOC): { + channelSwitchHook(pdu, 6, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_ACS_BOARD_SIDE_A): { + channelSwitchHook(pdu, 7, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_CHANNEL8): { + channelSwitchHook(pdu, 8, unpacker.getParameter()[0], hookArgs); + break; + } + } + } + return returnvalue::OK; +} + +void Pdu1Handler::letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t *packet) { + handleDeviceTm(packet, PDU::CONFIG_TABLE_SIZE, id); +} + +void Pdu1Handler::parseHkTableReply(const uint8_t *packet) { + GomspaceDeviceHandler::parsePduHkTable(coreHk, auxHk, packet); +} + +ReturnValue_t Pdu1Handler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + initializePduPool(localDataPoolMap, poolManager, pcdu::INIT_SWITCHES_PDU1); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(coreHk.getSid(), enableHkSets, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(auxHk.getSid(), enableHkSets, 6000.0)); + return returnvalue::OK; +} + +LocalPoolDataSetBase *Pdu1Handler::getDataSetHandle(sid_t sid) { + if (sid == coreHk.getSid()) { + return &coreHk; + } else if (sid == auxHk.getSid()) { + return &auxHk; + } + return nullptr; +} + +ReturnValue_t Pdu1Handler::printStatus(DeviceCommandId_t cmd) { + ReturnValue_t result = returnvalue::OK; + switch (cmd) { + case (GOMSPACE::PRINT_SWITCH_V_I): { + PoolReadGuard pg(&coreHk); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + break; + } + printHkTableSwitchVI(); + break; + } + case (GOMSPACE::PRINT_LATCHUPS): { + PoolReadGuard pg(&auxHk); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + break; + } + printHkTableLatchups(); + break; + } + default: { + return DeviceHandlerIF::COMMAND_NOT_SUPPORTED; + } + } + if (result != returnvalue::OK) { + sif::warning << "Reading PDU1 HK table failed!" << std::endl; + } + return result; +} + +void Pdu1Handler::printHkTableSwitchVI() { + using namespace PDU1; + sif::info << "PDU1 Info: " << std::endl; + sif::info << "Boot Cause: " << auxHk.bootcause << " | Boot Count: " << std::setw(4) << std::right + << coreHk.bootcount << std::endl; + sif::info << "Reset Cause: " << auxHk.resetcause + << " | Battery Mode: " << static_cast(coreHk.battMode.value) << std::endl; + sif::info << "SwitchState, Currents [mA], Voltages [mV]:" << std::endl; + auto printerHelper = [&](std::string channelStr, Channels idx) { + sif::info << std::setw(30) << std::left << channelStr << std::dec << "| " + << unsigned(coreHk.outputEnables[idx]) << ", " << std::setw(4) << std::right + << coreHk.currents[idx] << ", " << std::setw(4) << coreHk.voltages[idx] << std::endl; + }; + + printerHelper("TCS Board", Channels::TCS_BOARD_3V3); + printerHelper("Syrlinks", Channels::SYRLINKS); + printerHelper("Star Tracker", Channels::STR); + printerHelper("MGT", Channels::MGT); + printerHelper("SUS Nominal", Channels::SUS_NOMINAL); + printerHelper("SCEX", Channels::SOL_CELL_EXPERIMENT); + printerHelper("PLOC", Channels::PLOC); + printerHelper("ACS Board A Side", Channels::ACS_A_SIDE); + printerHelper("Channel 8", Channels::UNUSED); + printerHelper("Syrlinks", Channels::SYRLINKS); +} + +void Pdu1Handler::printHkTableLatchups() { + using namespace PDU1; + sif::info << "PDU1 Latchup Information" << std::endl; + auto printerHelper = [&](std::string channelStr, Channels idx) { + sif::info << std::setw(MAX_CHANNEL_STR_WIDTH) << std::left << "TCS Board" << std::dec << "| " + << std::setw(4) << std::right << auxHk.latchups[idx] << std::endl; + }; + printerHelper("TCS Board", Channels::TCS_BOARD_3V3); + printerHelper("Syrlinks", Channels::SYRLINKS); + printerHelper("Star Tracker", Channels::STR); + printerHelper("MGT", Channels::MGT); + printerHelper("SUS Nominal", Channels::SUS_NOMINAL); + printerHelper("SCEX", Channels::SOL_CELL_EXPERIMENT); + printerHelper("PLOC", Channels::PLOC); + printerHelper("ACS Board A Side", Channels::ACS_A_SIDE); + printerHelper("Channel 8", Channels::UNUSED); + printerHelper("Syrlinks", Channels::SYRLINKS); +} diff --git a/mission/power/Pdu1Handler.h b/mission/power/Pdu1Handler.h new file mode 100644 index 0000000..1037b58 --- /dev/null +++ b/mission/power/Pdu1Handler.h @@ -0,0 +1,59 @@ +#ifndef MISSION_DEVICES_PDU1Handler_H_ +#define MISSION_DEVICES_PDU1Handler_H_ + +#include +#include + +/** + * @brief This is the device handler for the PDU1. + * + * @details The PDU1 controls the + * power supply of the following devices: + * TCS Board, 3.3V, channel 0 + * Syrlinks, 12V, channel 1 + * Star Tracker, 5V, channel 2 + * MGT, 5V, channel 3 + * SUS 1-6 Nominal, 3.3V, channel 4 + * Solar cell experiment, 5V, channel 5 + * PLOC, 12V, channel 6 + * ACS 3.3V for Side A group, channel 7 + * Unoccupied, 5V, channel 8 + */ +class Pdu1Handler : public GomspaceDeviceHandler { + public: + Pdu1Handler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + FailureIsolationBase* customFdir, bool enableHkSets); + virtual ~Pdu1Handler(); + + virtual ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + + void assignChannelHookFunction(GOMSPACE::ChannelSwitchHook hook, void* args); + + protected: + /** + * @brief In MODE_NORMAL, a command will be built periodically by this function. + */ + virtual ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + void letChildHandleHkReply(DeviceCommandId_t id, const uint8_t* packet) override; + void letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t* packet) override; + ReturnValue_t printStatus(DeviceCommandId_t cmd) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + ReturnValue_t setParamCallback(SetParamMessageUnpacker& unpacker, bool afterExectuion) override; + + private: + static constexpr uint8_t MAX_CHANNEL_STR_WIDTH = 24; + + /** Dataset for the housekeeping table of the PDU1 */ + PDU1::Pdu1CoreHk coreHk; + PDU1::Pdu1AuxHk auxHk; + TableConfig cfg; + GOMSPACE::ChannelSwitchHook channelSwitchHook = nullptr; + void* hookArgs = nullptr; + + void printHkTableSwitchVI(); + void printHkTableLatchups(); + void parseHkTableReply(const uint8_t* packet); +}; + +#endif /* MISSION_DEVICES_PDU1Handler_H_ */ diff --git a/mission/power/Pdu2Handler.cpp b/mission/power/Pdu2Handler.cpp new file mode 100644 index 0000000..404c7b7 --- /dev/null +++ b/mission/power/Pdu2Handler.cpp @@ -0,0 +1,179 @@ +#include +#include +#include + +#include "devices/powerSwitcherList.h" + +Pdu2Handler::Pdu2Handler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie, + FailureIsolationBase *customFdir, bool enableHkSets) + : GomspaceDeviceHandler(objectId, comIF, comCookie, cfg, customFdir, enableHkSets), + coreHk(this), + auxHk(this) { + initPduConfigTable(); +} + +Pdu2Handler::~Pdu2Handler() {} + +ReturnValue_t Pdu2Handler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + *id = GOMSPACE::REQUEST_HK_TABLE; + return buildCommandFromCommand(*id, NULL, 0); +} + +void Pdu2Handler::letChildHandleHkReply(DeviceCommandId_t id, const uint8_t *packet) { + parseHkTableReply(packet); +} + +void Pdu2Handler::letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t *packet) { + handleDeviceTm(packet, PDU::CONFIG_TABLE_SIZE, id); +} + +void Pdu2Handler::assignChannelHookFunction(GOMSPACE::ChannelSwitchHook hook, void *args) { + this->channelSwitchHook = hook; + this->hookArgs = args; +} + +LocalPoolDataSetBase *Pdu2Handler::getDataSetHandle(sid_t sid) { + if (sid == coreHk.getSid()) { + return &coreHk; + } else if (sid == auxHk.getSid()) { + return &auxHk; + } + return nullptr; +} + +void Pdu2Handler::parseHkTableReply(const uint8_t *packet) { + GomspaceDeviceHandler::parsePduHkTable(coreHk, auxHk, packet); +} + +ReturnValue_t Pdu2Handler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + initializePduPool(localDataPoolMap, poolManager, pcdu::INIT_SWITCHES_PDU2); + poolManager.subscribeForDiagPeriodicPacket( + subdp::DiagnosticsHkPeriodicParams(coreHk.getSid(), enableHkSets, 30.0)); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(auxHk.getSid(), enableHkSets, 6000.0)); + return returnvalue::OK; +} + +ReturnValue_t Pdu2Handler::printStatus(DeviceCommandId_t cmd) { + ReturnValue_t result = returnvalue::OK; + switch (cmd) { + case (GOMSPACE::PRINT_SWITCH_V_I): { + PoolReadGuard pg(&coreHk); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + break; + } + printHkTableSwitchVI(); + break; + } + case (GOMSPACE::PRINT_LATCHUPS): { + PoolReadGuard pg(&auxHk); + result = pg.getReadResult(); + if (result != returnvalue::OK) { + break; + } + printHkTableLatchups(); + break; + } + default: { + return DeviceHandlerIF::COMMAND_NOT_SUPPORTED; + } + } + if (result != returnvalue::OK) { + sif::warning << "Reading PDU1 HK table failed!" << std::endl; + } + return result; +} + +void Pdu2Handler::printHkTableSwitchVI() { + using namespace PDU2; + sif::info << "PDU2 Info:" << std::endl; + sif::info << "Boot Cause: " << auxHk.bootcause << " | Boot Count: " << std::setw(4) << std::right + << coreHk.bootcount << std::endl; + sif::info << "Reset Cause: " << auxHk.resetcause + << " | Battery Mode: " << static_cast(coreHk.battMode.value) << std::endl; + sif::info << "SwitchState, Currents [mA], Voltages [mV]: " << std::endl; + auto printerHelper = [&](std::string channelStr, Channels idx) { + sif::info << std::setw(30) << std::left << channelStr << std::dec << "| " + << unsigned(coreHk.outputEnables[idx]) << ", " << std::setw(4) << std::right + << coreHk.currents[idx] << ", " << std::setw(4) << coreHk.voltages[idx] << std::endl; + }; + printerHelper("Q7S", Channels::Q7S); + printerHelper("PL PCDU CH1", Channels::PAYLOAD_PCDU_CH1); + printerHelper("Reaction Wheels", Channels::RW); + printerHelper("TCS Board Heater Input", Channels::TCS_HEATER_IN); + printerHelper("SUS Redundant", Channels::SUS_REDUNDANT); + printerHelper("Deployment Mechanism", Channels::DEPY_MECHANISM); + printerHelper("PL PCDU CH6", Channels::PAYLOAD_PCDU_CH6); + printerHelper("ACS Board B Side", Channels::ACS_B_SIDE); + printerHelper("Payload Camera", Channels::PAYLOAD_CAMERA); +} + +void Pdu2Handler::printHkTableLatchups() { + using namespace PDU2; + sif::info << "PDU2 Latchup Information" << std::endl; + auto printerHelper = [&](std::string channelStr, Channels idx) { + sif::info << std::setw(MAX_CHANNEL_STR_WIDTH) << std::left << "TCS Board" << std::dec << "| " + << std::setw(4) << std::right << auxHk.latchups[idx] << std::endl; + }; + printerHelper("Q7S", Channels::Q7S); + printerHelper("PL PCDU CH1", Channels::PAYLOAD_PCDU_CH1); + printerHelper("Reaction Wheels", Channels::RW); + printerHelper("TCS Board Heater Input", Channels::TCS_HEATER_IN); + printerHelper("SUS Redundant", Channels::SUS_REDUNDANT); + printerHelper("Deployment Mechanism", Channels::DEPY_MECHANISM); + printerHelper("PL PCDU CH6", Channels::PAYLOAD_PCDU_CH6); + printerHelper("ACS Board B Side", Channels::ACS_B_SIDE); + printerHelper("Payload Camera", Channels::PAYLOAD_CAMERA); +} + +ReturnValue_t Pdu2Handler::setParamCallback(SetParamMessageUnpacker &unpacker, + bool afterExecution) { + using namespace PDU2; + GOMSPACE::Pdu pdu = GOMSPACE::Pdu::PDU2; + if (not afterExecution) { + return returnvalue::OK; + } + if (channelSwitchHook != nullptr and unpacker.getParameterSize() == 1) { + switch (unpacker.getAddress()) { + case (CONFIG_ADDRESS_OUT_EN_Q7S): { + channelSwitchHook(pdu, 0, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_PAYLOAD_PCDU_CH1): { + channelSwitchHook(pdu, 1, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_RW): { + channelSwitchHook(pdu, 2, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_TCS_BOARD_HEATER_IN): { + channelSwitchHook(pdu, 3, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_SUS_REDUNDANT): { + channelSwitchHook(pdu, 4, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_DEPLOYMENT_MECHANISM): { + channelSwitchHook(pdu, 5, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_PAYLOAD_PCDU_CH6): { + channelSwitchHook(pdu, 6, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_ACS_BOARD_SIDE_B): { + channelSwitchHook(pdu, 7, unpacker.getParameter()[0], hookArgs); + break; + } + case (CONFIG_ADDRESS_OUT_EN_PAYLOAD_CAMERA): { + channelSwitchHook(pdu, 8, unpacker.getParameter()[0], hookArgs); + break; + } + } + } + return returnvalue::OK; +} diff --git a/mission/power/Pdu2Handler.h b/mission/power/Pdu2Handler.h new file mode 100644 index 0000000..8a41b31 --- /dev/null +++ b/mission/power/Pdu2Handler.h @@ -0,0 +1,58 @@ +#ifndef MISSION_POWER_PDU2HANDLER_H_ +#define MISSION_POWER_PDU2HANDLER_H_ + +#include +#include + +/** + * @brief This is the device handler for the PDU2. + * + * @details The PDU2 controls the + * power supply of the following devices: + * Xiphos Q7S, 8V, channel 0 + * Reaction wheels 5V, channel 2 + * TCS Board heater input, 8V, channel 3 + * SUS 8-12 Redundant, 3.3V, channel 4 + * Deployment mechanism, 8V, channel 5 + * P/L PCDU, Battery voltage (Vnominal = 14.8V), channel 1 and channel 6 + * ACS Board (Gyro, MGMs, GPS), 3.3V channel 7 + * Payload Camera, 8V, channel 8 + */ +class Pdu2Handler : public GomspaceDeviceHandler { + public: + Pdu2Handler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie, + FailureIsolationBase* customFdir, bool enableHkSets); + virtual ~Pdu2Handler(); + + virtual ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + void assignChannelHookFunction(GOMSPACE::ChannelSwitchHook hook, void* args); + + protected: + /** + * @brief As soon as the device is in MODE_NORMAL, this function is executed periodically. + */ + virtual ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + void letChildHandleHkReply(DeviceCommandId_t id, const uint8_t* packet) override; + void letChildHandleConfigReply(DeviceCommandId_t id, const uint8_t* packet) override; + ReturnValue_t printStatus(DeviceCommandId_t cmd) override; + ReturnValue_t setParamCallback(SetParamMessageUnpacker& unpacker, bool afterExecution) override; + LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + private: + static constexpr uint8_t MAX_CHANNEL_STR_WIDTH = 24; + + /** Dataset for the housekeeping table of the PDU2 */ + PDU2::Pdu2CoreHk coreHk; + PDU2::Pdu2AuxHk auxHk; + TableConfig cfg; + GOMSPACE::ChannelSwitchHook channelSwitchHook = nullptr; + void* hookArgs = nullptr; + + void printHkTableSwitchVI(); + void printHkTableLatchups(); + + void parseHkTableReply(const uint8_t* packet); +}; + +#endif /* MISSION_POWER_PDU2HANDLER_H_ */ diff --git a/mission/power/bpxBattDefs.h b/mission/power/bpxBattDefs.h new file mode 100644 index 0000000..c25a025 --- /dev/null +++ b/mission/power/bpxBattDefs.h @@ -0,0 +1,250 @@ +#ifndef MISSION_POWER_BPXBATTDEFS_H_ +#define MISSION_POWER_BPXBATTDEFS_H_ + +#include +#include + +#include + +#include "fsfw/devicehandlers/DeviceHandlerIF.h" + +namespace bpxBat { + +enum LocalPoolIds { + CHARGE_CURRENT = 0, + DISCHARGE_CURRENT = 1, + HEATER_CURRENT = 2, + BATT_VOLTAGE = 3, + BATT_TEMP_1 = 4, + BATT_TEMP_2 = 5, + BATT_TEMP_3 = 6, + BATT_TEMP_4 = 7, + REBOOT_COUNTER = 8, + BOOTCAUSE = 9, + + BATTERY_HEATER_MODE = 10, + BATTHEAT_LOW_LIMIT = 11, + BATTHEAT_HIGH_LIMIT = 12 +}; + +static constexpr DeviceCommandId_t GET_HK = 0; +static constexpr DeviceCommandId_t PING = 1; +static constexpr DeviceCommandId_t REBOOT = 2; +static constexpr DeviceCommandId_t RESET_COUNTERS = 3; +// This is the mnemonic GomSpace chose, but this command actually restores the default config +static constexpr DeviceCommandId_t CONFIG_CMD = 4; +static constexpr DeviceCommandId_t CONFIG_GET = 5; +static constexpr DeviceCommandId_t CONFIG_SET = 6; + +static constexpr DeviceCommandId_t MAN_HEAT_ON = 10; +static constexpr DeviceCommandId_t MAN_HEAT_OFF = 11; + +static constexpr uint8_t RESET_COUNTERS_MAGIC_VALUE = 0x42; +static constexpr uint8_t DEFAULT_PING_SENT_BYTE = 0x07; + +static constexpr uint32_t HK_SET_ID = GET_HK; +static constexpr uint32_t CFG_SET_ID = CONFIG_GET; + +static constexpr size_t GET_HK_REPLY_LEN = 23; +static constexpr size_t PING_REPLY_LEN = 3; +static constexpr size_t EMPTY_REPLY_LEN = 2; +static constexpr size_t MAN_HEAT_REPLY_LEN = 3; +static constexpr size_t CONFIG_GET_REPLY_LEN = 5; + +static constexpr uint8_t PORT_PING = 1; +static constexpr uint8_t PORT_REBOOT = 4; +static constexpr uint8_t PORT_GET_HK = 9; +static constexpr uint8_t PORT_RESET_COUNTERS = 15; +static constexpr uint8_t PORT_CONFIG_CMD = 17; +static constexpr uint8_t PORT_CONFIG_GET = 18; +static constexpr uint8_t PORT_CONFIG_SET = 19; +static constexpr uint8_t PORT_MAN_HEAT_ON = 20; +static constexpr uint8_t PORT_MAN_HEAT_OFF = 21; + +static constexpr uint8_t HK_ENTRIES = 10; +static constexpr uint8_t CFG_ENTRIES = 3; + +// Taken from BPX manual 3.14 +typedef struct __attribute__((packed)) { + //! Mode for battheater [0=OFF,1=Auto] + uint8_t battheater_mode; + int8_t battheater_low; + //! Turn heater on at [degC] + int8_t battheater_high; + //! Turn heater off at [degC] +} bpx_config_t; + +//! Not used for more but might still be useful +class BpxHkDeserializer : public SerialLinkedListAdapter { + public: + BpxHkDeserializer() { setLinks(); } + + //! Charge current in mA + SerializeElement chargeCurrent; + //! Discharge current in mA + SerializeElement dischargeCurrent; + //! Heater current in mA + SerializeElement heaterCurrent; + + //! Battery voltage in mV + SerializeElement battVoltage; + //! Battery temperature 1 in degC + SerializeElement battTemp1; + //! Battery temperature 2 in degC + SerializeElement battTemp2; + //! Battery temperature 3 in degC + SerializeElement battTemp3; + //! Battery temperature 4 in degC + SerializeElement battTemp4; + + SerializeElement rebootCounter; + SerializeElement bootcause; + + private: + void setLinks() { + setStart(&chargeCurrent); + chargeCurrent.setNext(&dischargeCurrent); + dischargeCurrent.setNext(&heaterCurrent); + heaterCurrent.setNext(&battVoltage); + battVoltage.setNext(&battTemp1); + battTemp1.setNext(&battTemp2); + battTemp2.setNext(&battTemp3); + battTemp3.setNext(&battTemp4); + battTemp4.setNext(&rebootCounter); + rebootCounter.setNext(&bootcause); + } +}; + +}; // namespace bpxBat + +/** + * @brief BPX HK data holder + */ +class BpxBatteryHk : public StaticLocalDataSet { + public: + /** + * Constructor for data users + * @param gyroId + */ + BpxBatteryHk(object_id_t bpxId) : StaticLocalDataSet(sid_t(bpxId, bpxBat::HK_SET_ID)) { + setAllVariablesReadOnly(); + } + + ReturnValue_t parseRawHk(const uint8_t* data, size_t size) { + size_t remSize = size; + ReturnValue_t result = + chargeCurrent.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = dischargeCurrent.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = heaterCurrent.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = battVoltage.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = battTemp1.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = battTemp2.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = battTemp3.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = battTemp4.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = rebootCounter.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + result = bootcause.deSerialize(&data, &remSize, SerializeIF::Endianness::NETWORK); + if (result != returnvalue::OK) { + return result; + } + return result; + } + + //! Charge current in mA + lp_var_t chargeCurrent = lp_var_t(sid.objectId, bpxBat::CHARGE_CURRENT, this); + //! Discharge current in mA + lp_var_t dischargeCurrent = + lp_var_t(sid.objectId, bpxBat::DISCHARGE_CURRENT, this); + //! Heater current in mA + lp_var_t heaterCurrent = lp_var_t(sid.objectId, bpxBat::HEATER_CURRENT, this); + + //! Battery voltage in mV + lp_var_t battVoltage = lp_var_t(sid.objectId, bpxBat::BATT_VOLTAGE, this); + //! Battery temperature 1 in degC + lp_var_t battTemp1 = lp_var_t(sid.objectId, bpxBat::BATT_TEMP_1, this); + //! Battery temperature 2 in degC + lp_var_t battTemp2 = lp_var_t(sid.objectId, bpxBat::BATT_TEMP_2, this); + //! Battery temperature 3 in degC + lp_var_t battTemp3 = lp_var_t(sid.objectId, bpxBat::BATT_TEMP_3, this); + //! Battery temperature 4 in degC + lp_var_t battTemp4 = lp_var_t(sid.objectId, bpxBat::BATT_TEMP_4, this); + lp_var_t rebootCounter = lp_var_t(sid.objectId, bpxBat::REBOOT_COUNTER, this); + lp_var_t bootcause = lp_var_t(sid.objectId, bpxBat::BOOTCAUSE, this); + + private: + friend class BpxBatteryHandler; + friend class BatteryDummy; + /** + * Constructor for data creator + * @param hkOwner + */ + BpxBatteryHk(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, bpxBat::HK_SET_ID) {} +}; + +class BpxBatteryCfg : public StaticLocalDataSet { + public: + /** + * Constructor for data users + * @param gyroId + */ + BpxBatteryCfg(object_id_t bpxId) : StaticLocalDataSet(sid_t(bpxId, bpxBat::CFG_SET_ID)) { + setAllVariablesReadOnly(); + } + + ReturnValue_t parseRawHk(const uint8_t* data, size_t size) { + if (size < 3) { + return SerializeIF::STREAM_TOO_SHORT; + } + + battheatermode.value = data[0]; + battheaterLow.value = data[1]; + battheaterHigh.value = data[2]; + return returnvalue::OK; + } + + //! Mode for battheater [0=OFF,1=Auto] + lp_var_t battheatermode = + lp_var_t(sid.objectId, bpxBat::BATTERY_HEATER_MODE, this); + //! Turn heater on at [degC] + lp_var_t battheaterLow = lp_var_t(sid.objectId, bpxBat::BATTHEAT_LOW_LIMIT, this); + //! Turn heater off at [degC] + lp_var_t battheaterHigh = + lp_var_t(sid.objectId, bpxBat::BATTHEAT_HIGH_LIMIT, this); + + private: + friend class BpxBatteryHandler; + friend class BatteryDummy; + /** + * Constructor for data creator + * @param hkOwner + */ + BpxBatteryCfg(HasLocalDataPoolIF* hkOwner) : StaticLocalDataSet(hkOwner, bpxBat::CFG_SET_ID) {} +}; + +#endif /* MISSION_POWER_BPXBATTDEFS_H_ */ diff --git a/mission/power/defs.h b/mission/power/defs.h new file mode 100644 index 0000000..28e5eca --- /dev/null +++ b/mission/power/defs.h @@ -0,0 +1,79 @@ +#ifndef MISSION_POWER_DEFS_H_ +#define MISSION_POWER_DEFS_H_ + +#include +#include +#include + +#include "eive/eventSubsystemIds.h" + +namespace power { + +/* Switches are uint8_t datatype and go from 0 to 255 */ +enum Switches : power::Switch_t { + PDU1_CH0_TCS_BOARD_3V3, + PDU1_CH1_SYRLINKS_12V, + PDU1_CH2_STAR_TRACKER_5V, + PDU1_CH3_MGT_5V, + PDU1_CH4_SUS_NOMINAL_3V3, + PDU1_CH5_SOLAR_CELL_EXP_5V, + PDU1_CH6_PLOC_12V, + PDU1_CH7_ACS_A_SIDE_3V3, + PDU1_CH8_UNOCCUPIED, + + PDU2_CH0_Q7S, + PDU2_CH1_PL_PCDU_BATT_0_14V8, + PDU2_CH2_RW_5V, + PDU2_CH3_TCS_BOARD_HEATER_IN_8V, + PDU2_CH4_SUS_REDUNDANT_3V3, + PDU2_CH5_DEPLOYMENT_MECHANISM_8V, + PDU2_CH6_PL_PCDU_BATT_1_14V8, + PDU2_CH7_ACS_BOARD_SIDE_B_3V3, + PDU2_CH8_PAYLOAD_CAMERA, + + P60_DOCK_5V_STACK, + P60_DOCK_3V3_STACK, + NUMBER_OF_SWITCHES +}; + +static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PCDU_HANDLER; +//! [EXPORT] : [COMMENT] Indicates that a FSFW object requested setting a switch +//! P1: 1 if on was requested, 0 for off | P2: Switch Index +static constexpr Event SWITCH_CMD_SENT = event::makeEvent(SUBSYSTEM_ID, 0, severity::INFO); +//! [EXPORT] : [COMMENT] Indicated that a switch state has changed +//! P1: New switch state, 1 for on, 0 for off | P2: Switch Index +static constexpr Event SWITCH_HAS_CHANGED = event::makeEvent(SUBSYSTEM_ID, 1, severity::INFO); +static constexpr Event SWITCHING_Q7S_DENIED = event::makeEvent(SUBSYSTEM_ID, 2, severity::MEDIUM); + +static constexpr Event FDIR_REACTION_IGNORED = event::makeEvent(SUBSYSTEM_ID, 3, severity::MEDIUM); + +//! [EXPORT] : [COMMENT] The dataset read for the inputs of the Power Controller has failed. +static constexpr Event DATASET_READ_FAILED = event::makeEvent(SUBSYSTEM_ID, 4, severity::INFO); +//! [EXPORT] : [COMMENT] The battery voltage read is out of the bounds in which it is supposed to +//! be. +//! P1: 1 too high, 0 too low +//! P2: voltage in V * 10 +static constexpr Event VOLTAGE_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 5, severity::HIGH); +//! [EXPORT] : [COMMENT] Time difference for Coulomb Counter was too large. +//! P1: time in s * 10 +static constexpr Event TIMEDELTA_OUT_OF_BOUNDS = event::makeEvent(SUBSYSTEM_ID, 6, severity::LOW); +//! [EXPORT] : [COMMENT] The State of Charge is below the limit for payload use. Setting Payload to +//! faulty. +static constexpr Event POWER_LEVEL_LOW = event::makeEvent(SUBSYSTEM_ID, 7, severity::HIGH); +//! [EXPORT] : [COMMENT] The State of Charge is below the limit for higher modes. Setting Reaction +//! Wheels to faulty. +static constexpr Event POWER_LEVEL_CRITICAL = event::makeEvent(SUBSYSTEM_ID, 8, severity::HIGH); + +enum class States { IDLE, SWITCHING_POWER, CHECKING_POWER, MODE_COMMANDING }; +enum class OpCodes { NONE, TO_OFF_DONE, TO_NOT_OFF_DONE, TIMEOUT_OCCURED }; + +enum RecoveryCustomStates { IDLE, POWER_SWITCHING_OFF, POWER_SWITCHING_ON, DONE }; +} // namespace power + +namespace duallane { + +enum Submodes : Submode_t { A_SIDE = 0, B_SIDE = 1, DUAL_MODE = 2 }; + +} + +#endif /* MISSION_POWER_DEFS_H_ */ diff --git a/mission/power/gsDefs.h b/mission/power/gsDefs.h new file mode 100644 index 0000000..1ad000d --- /dev/null +++ b/mission/power/gsDefs.h @@ -0,0 +1,755 @@ +#ifndef MISSION_POWER_GSDEFS_H_ +#define MISSION_POWER_GSDEFS_H_ + +#include +#include +#include +#include +#include + +#include + +#include "devices/powerSwitcherList.h" +#include "fsfw/platform.h" +#include "mission/power/defs.h" + +namespace GOMSPACE { + +struct TableInfo { + uint8_t sourceTable; + uint8_t targetTable; +}; + +enum DeviceType { PDU, ACU, P60DOCK }; + +enum class SpecialRequestTypes { + DEFAULT_COM_IF, + GET_PDU_HK, + GET_PDU_CONFIG, + GET_ACU_HK, + GET_ACU_CONFIG, + GET_P60DOCK_HK, + GET_P60DOCK_CONFIG, + SAVE_TABLE, + LOAD_TABLE +}; + +enum CspPorts : uint8_t { + CSP_PING = 1, + CSP_REBOOT = 4, + P60_PORT_RPARAM_ENUM = 7, + P60_PORT_GNDWDT_RESET_ENUM = 9 +}; + +enum class Pdu { PDU1, PDU2 }; + +using ChannelSwitchHook = void (*)(Pdu pdu, uint8_t channel, bool on, void* args); + +static const uint16_t IGNORE_CHECKSUM = 0xbb0; +/** The size of the header of a gomspace CSP packet. */ +static const uint8_t GS_HDR_LENGTH = 12; +/** CSP port to ping gomspace devices. */ +static const uint8_t PING_PORT = 1; +static const uint8_t REBOOT_PORT = 4; +/** CSP port of gomspace devices to request or set parameters */ +static const uint8_t PARAM_PORT = 7; +static const uint8_t P60_PORT_GNDWDT_RESET = 9; + +/** + * Device commands are derived from the rparam.h of the gomspace lib.. + * IDs above 50 are reserved for device specific commands. + */ +static const DeviceCommandId_t PARAM_GET = 0; //!< [EXPORT] : [COMMAND] +static const DeviceCommandId_t PING = 1; //!< [EXPORT] : [COMMAND] +static const DeviceCommandId_t NONE = 2; // Set when no command is pending +static const DeviceCommandId_t REBOOT = 4; //!< [EXPORT] : [COMMAND] +static const DeviceCommandId_t GNDWDT_RESET = 9; //!< [EXPORT] : [COMMAND] +static const DeviceCommandId_t REQUEST_HK_TABLE = 16; //!< [EXPORT] : [COMMAND] +static const DeviceCommandId_t REQUEST_CONFIG_TABLE = 17; //!< [EXPORT] : [COMMAND] +static const DeviceCommandId_t SAVE_TABLE_FILE = 18; +static const DeviceCommandId_t SAVE_TABLE_DEFAULT = 19; +static const DeviceCommandId_t LOAD_TABLE = 20; +static const DeviceCommandId_t PARAM_SET = 255; //!< [EXPORT] : [COMMAND] + +// Not implemented yet +// static const DeviceCommandId_t REQUEST_CALIB_TABLE = 18; //!< [EXPORT] : [COMMAND] +//! [EXPORT] : [COMMAND] Print switch states, voltages and currents to the console +//! For the ACU device, only print voltages and currents of the 6 ACU channels +static const DeviceCommandId_t PRINT_SWITCH_V_I = 32; +static const DeviceCommandId_t PRINT_LATCHUPS = 33; + +enum ParamRequestIds : uint8_t { + GET = 0x00, + REPLY = 0x55, + SET = 0xFF, + TABLE_SPEC = 0x44, + // Copy memory slot to memory slot + COPY = 0x77, + // Load from file to slot. Load from primary slot + LOAD = 0x88, + // Load by name(s) + LOAD_FROM_STORE = 0x89, + // Save to primary slot + SAVE = 0x99, + // Save by name(s) + SAVE_TO_STORE = 0x9a +}; + +enum TableIds : uint8_t { BOARD_PARAMS = 0, CONFIG = 1, CALIBRATION = 2, HK = 4 }; + +} // namespace GOMSPACE + +namespace P60System { + +enum class BatteryModes : uint8_t { CRITICAL = 1, SAFE = 2, NORMAL = 3, FULL = 4 }; + +enum class SetIds : uint32_t { CORE = 1, AUX = 2, CONFIG = 3 }; + +} // namespace P60System + +namespace P60Dock { + +static const uint16_t CONFIG_ADDRESS_OUT_EN_3V3_STACK = 0x71; +static const uint16_t CONFIG_ADDRESS_OUT_EN_5V_STACK = 0x72; + +namespace pool { + +enum Ids : lp_id_t { + P60_CURRENTS, + P60_VOLTAGES, + P60_OUTPUT_ENABLE, + P60DOCK_TEMPERATURE_1, + P60DOCK_TEMPERATURE_2, + P60DOCK_BOOT_CAUSE, + P60DOCK_BOOT_CNT, + P60DOCK_UPTIME, + P60DOCK_RESETCAUSE, + P60DOCK_BATT_MODE, + P60DOCK_HEATER_ON, + P60DOCK_CONV_5V_ENABLE_STATUS, + LATCHUPS, + P60DOCK_DOCK_VBAT, + P60DOCK_DOCK_VCC_CURRENT, + // Difference between charge and discharge + P60DOCK_BATTERY_CURRENT, + P60DOCK_BATTERY_VOLTAGE, + P60DOCK_BATTERY_TEMPERATURE_1, + P60DOCK_BATTERY_TEMPERATURE_2, + DEVICES_TYPE, + DEVICES_STATUS, + P60DOCK_DEVICE_TYPE_GROUP, + P60DOCK_DEVICE_STATUS_GROUP, + P60DOCK_DEARM_STATUS, + P60DOCK_WDT_CNT_GND, + P60DOCK_WDT_CNT_I2C, + P60DOCK_WDT_CNT_CAN, + P60DOCK_WDT_CNT_CSP_1, + P60DOCK_WDT_CNT_CSP_2, + P60DOCK_WDT_GND_LEFT, + P60DOCK_WDT_I2C_LEFT, + P60DOCK_WDT_CAN_LEFT, + P60DOCK_WDT_CSP_LEFT_1, + P60DOCK_WDT_CSP_LEFT_2, + P60DOCK_BATT_CHARGE_CURRENT, + P60DOCK_BATT_DISCHARGE_CURRENT, + P60DOCK_ANT6_DEPL, + P60DOCK_AR6_DEPL, +}; +} + +static constexpr uint8_t NUM_DEVS = 8; + +namespace hk { + +enum Index : uint8_t { + ACU_VCC = 0, + PDU1_VCC = 1, + X3_IDLE_VCC = 2, + PDU2_VCC = 3, + ACU_VBAT = 4, + PDU1_VBAT = 5, + X3_IDLE_VBAT = 6, + PDU2_VBAT = 7, + STACK_VBAT = 8, + STACK_3V3 = 9, + STACK_5V = 10, + GS3V3 = 11, + GS5V = 12, + CHNLS_LEN = 13 +}; + +} + +enum SwitchChannels : uint8_t { + ACU = 0, + PDU1 = 1, + X3_IDLE = 2, + PDU2_VCC = 3, + ACU_VBAT = 4, + PDU1_VBAT = 5, + X3_IDLE_VBAT = 6, + PDU2_VBAT = 7, + STACK_VBAT = 8, + STACK_3V3 = 9, + STACK_5V = 10, + GS3V3 = 11, + GS5V = 12 +}; + +static const uint16_t MAX_CONFIGTABLE_ADDRESS = 408; +static const uint16_t MAX_HKTABLE_ADDRESS = 187; +// Sources: +// GomSpace library lib/p60-dock_client/include/gs/p60-dock/param +static const uint16_t HK_TABLE_SIZE = gsConstants::P60DOCK_HK_SIZE; +static const uint16_t CONFIG_TABLE_SIZE = gsConstants::P60DOCK_PARAM_SIZE; +static const size_t MAX_REPLY_SIZE = CONFIG_TABLE_SIZE; +static const uint16_t CAL_TABLE = 0xAE; +static const uint8_t HK_TABLE_ENTRIES = 100; + +class CoreHkSet : public StaticLocalDataSet<16> { + public: + CoreHkSet(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, static_cast(::P60System::SetIds::CORE)) {} + + CoreHkSet(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, static_cast(::P60System::SetIds::CORE))) {} + + /** Measured output currents */ + lp_vec_t currents = + lp_vec_t(sid.objectId, pool::P60_CURRENTS, this); + + /** Measured output voltages */ + lp_vec_t voltages = + lp_vec_t(sid.objectId, pool::P60_VOLTAGES, this); + + /** Output enable states */ + lp_vec_t outputEnables = + lp_vec_t(sid.objectId, pool::P60_OUTPUT_ENABLE, this); + lp_var_t bootCount = lp_var_t(sid.objectId, pool::P60DOCK_BOOT_CNT, this); + lp_var_t battMode = lp_var_t(sid.objectId, pool::P60DOCK_BATT_MODE, this); + + // Difference between charge and discharge current + lp_var_t batteryCurrent = + lp_var_t(sid.objectId, pool::P60DOCK_BATTERY_CURRENT, this); + lp_var_t batteryVoltage = + lp_var_t(sid.objectId, pool::P60DOCK_BATTERY_VOLTAGE, this); + + lp_var_t temperature1 = lp_var_t(sid.objectId, pool::P60DOCK_TEMPERATURE_1, this); + lp_var_t temperature2 = lp_var_t(sid.objectId, pool::P60DOCK_TEMPERATURE_2, this); +}; +/** + * @brief This class defines a dataset for the hk table of the P60 Dock. + * @details + * The GS port and X3 are not required for EIVE. X3 is another slot on the P60 dock and + * GS is required for a module from Gomspace which is not used. + */ +class HkTableDataset : public StaticLocalDataSet<32> { + public: + HkTableDataset(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, static_cast(::P60System::SetIds::AUX)) {} + + HkTableDataset(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, static_cast(::P60System::SetIds::AUX))) {} + + /** Number of detected latchups on each output channel */ + lp_vec_t latchups = + lp_vec_t(sid.objectId, pool::LATCHUPS, this); + + lp_var_t bootcause = lp_var_t(sid.objectId, pool::P60DOCK_BOOT_CAUSE, this); + lp_var_t uptime = lp_var_t(sid.objectId, pool::P60DOCK_UPTIME, this); + lp_var_t resetcause = lp_var_t(sid.objectId, pool::P60DOCK_RESETCAUSE, this); + + /** Battery heater control only possible on BP4 packs */ + lp_var_t heaterForBp4PackOn = + lp_var_t(sid.objectId, pool::P60DOCK_HEATER_ON, this); + lp_var_t converter5VStatus = + lp_var_t(sid.objectId, pool::P60DOCK_CONV_5V_ENABLE_STATUS, this); + + lp_var_t dockVbatVoltageValue = + lp_var_t(sid.objectId, pool::P60DOCK_DOCK_VBAT, this); + lp_var_t dockVccCurrent = + lp_var_t(sid.objectId, pool::P60DOCK_DOCK_VCC_CURRENT, this); + + lp_var_t batteryTemperature1 = + lp_var_t(sid.objectId, pool::P60DOCK_BATTERY_TEMPERATURE_1, this); + lp_var_t batteryTemperature2 = + lp_var_t(sid.objectId, pool::P60DOCK_BATTERY_TEMPERATURE_2, this); + + lp_var_t dearmStatus = lp_var_t(sid.objectId, pool::P60DOCK_DEARM_STATUS, this); + + /** Number of reboots due to gnd, i2c, csp watchdog timeout */ + lp_var_t wdtCntGnd = lp_var_t(sid.objectId, pool::P60DOCK_WDT_CNT_GND, this); + lp_var_t wdtCntI2c = lp_var_t(sid.objectId, pool::P60DOCK_WDT_CNT_I2C, this); + lp_var_t wdtCntCan = lp_var_t(sid.objectId, pool::P60DOCK_WDT_CNT_CAN, this); + lp_var_t wdtCntCsp1 = + lp_var_t(sid.objectId, pool::P60DOCK_WDT_CNT_CSP_1, this); + lp_var_t wdtCntCsp2 = + lp_var_t(sid.objectId, pool::P60DOCK_WDT_CNT_CSP_2, this); + + lp_var_t wdtGndLeft = + lp_var_t(sid.objectId, pool::P60DOCK_WDT_GND_LEFT, this); + lp_var_t wdtI2cLeft = + lp_var_t(sid.objectId, pool::P60DOCK_WDT_I2C_LEFT, this); + lp_var_t wdtCanLeft = + lp_var_t(sid.objectId, pool::P60DOCK_WDT_CAN_LEFT, this); + lp_var_t wdtCspLeft1 = + lp_var_t(sid.objectId, pool::P60DOCK_WDT_CSP_LEFT_1, this); + lp_var_t wdtCspLeft2 = + lp_var_t(sid.objectId, pool::P60DOCK_WDT_CSP_LEFT_2, this); + lp_var_t batteryChargeCurrent = + lp_var_t(sid.objectId, pool::P60DOCK_BATT_CHARGE_CURRENT, this); + lp_var_t batteryDischargeCurrent = + lp_var_t(sid.objectId, pool::P60DOCK_BATT_DISCHARGE_CURRENT, this); + lp_var_t ant6Depl = lp_var_t(sid.objectId, pool::P60DOCK_ANT6_DEPL, this); + lp_var_t ar6Depl = lp_var_t(sid.objectId, pool::P60DOCK_AR6_DEPL, this); + + lp_vec_t devicesType = + lp_vec_t(sid.objectId, pool::DEVICES_TYPE, this); + lp_vec_t devicesStatus = + lp_vec_t(sid.objectId, pool::DEVICES_STATUS, this); +}; +} // namespace P60Dock + +/** + * @brief Constants common for both PDU1 and PDU2. + */ +namespace PDU { + +namespace pool { +enum Ids { + // IDs for both PDUs + PDU_CURRENTS, + PDU_VOLTAGES, + PDU_VCC, + PDU_VBAT, + PDU_TEMPERATURE, + PDU_CONV_EN, + PDU_OUT_ENABLE, + PDU_BOOTCAUSE, + PDU_BOOTCNT, + PDU_UPTIME, + PDU_RESETCAUSE, + PDU_BATT_MODE, + PDU_LATCHUPS, + PDU_DEVICES, + PDU_STATUSES, + PDU_WDT_CNT_GND, + PDU_WDT_CNT_I2C, + PDU_WDT_CNT_CAN, + PDU_WDT_CNT_CSP1, + PDU_WDT_CNT_CSP2, + PDU_WDT_GND_LEFT, + PDU_WDT_I2C_LEFT, + PDU_WDT_CAN_LEFT, + PDU_WDT_CSP_LEFT1, + PDU_WDT_CSP_LEFT2, + + OUT_ON_CNT, + OUT_OFF_CNT, + INIT_OUT_NORM, + INIT_OUT_SAFE, + INIT_ON_DLY, + INIT_OFF_DLY, + SAFE_OFF_DLY, + CUR_LU_LIM, + CUR_LIM, + CUR_EMA, + OUT_LINK, + OUT_CONV, + OUT_VOLTAGE, + CONV_EN, + CUR_EMA_GAIN, + BATT_HWMAX, + BATT_MAX, + BATT_NORM, + BATT_SAFE, + BATT_CRIT, + WDT_I2C_RST, + WDT_CAN_RST, + WDT_I2C, + WDT_CAN, + WDT_CSP, + WDT_CSP_PING, + WDT_CSP_CHAN, + WDT_CSP_ADDR +}; +} + +static const uint16_t MAX_CONFIGTABLE_ADDRESS = 316; +static const uint16_t MAX_HKTABLE_ADDRESS = 141; +/** The size of the csp reply containing the housekeeping table data */ +static const uint16_t HK_TABLE_SIZE = gsConstants::P60PDU_HK_SIZE; +static const uint16_t CONFIG_TABLE_SIZE = gsConstants::P60PDU_PARAM_SIZE; +/** When retrieving full configuration parameter table */ +static const uint16_t MAX_REPLY_SIZE = CONFIG_TABLE_SIZE; +static const uint8_t HK_TABLE_ENTRIES = 73; + +static constexpr uint8_t CHANNELS_LEN = 9; +static constexpr uint8_t DEVICES_NUM = 8; + +class PduCoreHk : public StaticLocalDataSet<9> { + public: + PduCoreHk(HasLocalDataPoolIF* owner, uint32_t setId) : StaticLocalDataSet(owner, setId) {} + + PduCoreHk(object_id_t objectId, uint32_t setId) : StaticLocalDataSet(sid_t(objectId, setId)) {} + + /** Measured output currents */ + lp_vec_t currents = lp_vec_t(sid.objectId, pool::PDU_CURRENTS, this); + /** Measured output currents */ + lp_vec_t voltages = lp_vec_t(sid.objectId, pool::PDU_VOLTAGES, this); + /** Output switch states */ + lp_vec_t outputEnables = + lp_vec_t(sid.objectId, pool::PDU_OUT_ENABLE, this); + /** Number of reboots */ + lp_var_t bootcount = lp_var_t(sid.objectId, pool::PDU_BOOTCNT, this); + /** Battery mode: 1 = Critical, 2 = Safe, 3 = Normal, 4 = Full */ + lp_var_t battMode = lp_var_t(sid.objectId, pool::PDU_BATT_MODE, this); + lp_var_t temperature = lp_var_t(sid.objectId, pool::PDU_TEMPERATURE, this); + + /** Measured VCC */ + lp_var_t vcc = lp_var_t(sid.objectId, pool::PDU_VCC, this); + /** Measured VBAT */ + lp_var_t vbat = lp_var_t(sid.objectId, pool::PDU_VBAT, this); +}; + +class PduConfig : public StaticLocalDataSet<32> { + public: + PduConfig(HasLocalDataPoolIF* owner, uint32_t setId) : StaticLocalDataSet(owner, setId) {} + + lp_vec_t outOnDelaySecs = + lp_vec_t(sid.objectId, pool::OUT_ON_CNT, this); + lp_vec_t outOffDelaySecs = + lp_vec_t(sid.objectId, pool::OUT_OFF_CNT, this); + lp_vec_t initOutNorm = lp_vec_t(sid.objectId, pool::INIT_OUT_NORM, this); + lp_vec_t initOutSafe = lp_vec_t(sid.objectId, pool::INIT_OUT_SAFE, this); + lp_vec_t initOnDly = lp_vec_t(sid.objectId, pool::INIT_ON_DLY, this); + lp_vec_t initOffDly = lp_vec_t(sid.objectId, pool::INIT_OFF_DLY, this); + lp_vec_t safeOffDly = lp_vec_t(sid.objectId, pool::SAFE_OFF_DLY, this); + lp_vec_t curLuLim = lp_vec_t(sid.objectId, pool::CUR_LU_LIM, this); + lp_vec_t curLim = lp_vec_t(sid.objectId, pool::CUR_LIM, this); + lp_vec_t curEma = lp_vec_t(sid.objectId, pool::CUR_EMA, this); + lp_vec_t outLink = lp_vec_t(sid.objectId, pool::OUT_LINK, this); + lp_vec_t outConv = lp_vec_t(sid.objectId, pool::OUT_CONV, this); + lp_vec_t outVoltage = lp_vec_t(sid.objectId, pool::OUT_VOLTAGE, this); + lp_vec_t convEnable = lp_vec_t(sid.objectId, pool::CONV_EN, this); + lp_var_t curEmaGain = lp_var_t(sid.objectId, pool::CUR_EMA_GAIN, this); + lp_var_t battHwMax = lp_var_t(sid.objectId, pool::BATT_HWMAX, this); + lp_var_t battMax = lp_var_t(sid.objectId, pool::BATT_MAX, this); + lp_var_t battNorm = lp_var_t(sid.objectId, pool::BATT_NORM, this); + lp_var_t battSafe = lp_var_t(sid.objectId, pool::BATT_SAFE, this); + lp_var_t battCrit = lp_var_t(sid.objectId, pool::BATT_CRIT, this); + lp_var_t wdtI2cRst = lp_var_t(sid.objectId, pool::WDT_I2C_RST, this); + lp_var_t wdtCanRst = lp_var_t(sid.objectId, pool::WDT_CAN_RST, this); + lp_var_t wdtI2c = lp_var_t(sid.objectId, pool::WDT_I2C, this); + lp_var_t wdtCan = lp_var_t(sid.objectId, pool::WDT_CAN, this); + lp_vec_t wdtCsp = lp_vec_t(sid.objectId, pool::WDT_CSP, this); + lp_vec_t wdtCspPing = lp_vec_t(sid.objectId, pool::WDT_CSP_PING, this); + lp_vec_t wdtCspChannel = lp_vec_t(sid.objectId, pool::WDT_CSP_CHAN, this); + lp_vec_t wdtCspAddr = lp_vec_t(sid.objectId, pool::WDT_CSP_ADDR, this); +}; + +/** + * @brief This class defines a dataset for the hk table of a PDU + */ +class PduAuxHk : public StaticLocalDataSet<36> { + public: + PduAuxHk(HasLocalDataPoolIF* owner, uint32_t setId) : StaticLocalDataSet(owner, setId) {} + + PduAuxHk(object_id_t objectId, uint32_t setId) : StaticLocalDataSet(sid_t(objectId, setId)) {} + + /** Output converter enable status */ + lp_vec_t converterEnable = + lp_vec_t(sid.objectId, pool::PDU_CONV_EN, this); + + lp_var_t bootcause = lp_var_t(sid.objectId, pool::PDU_BOOTCAUSE, this); + + /** Uptime in seconds */ + lp_var_t uptime = lp_var_t(sid.objectId, pool::PDU_UPTIME, this); + lp_var_t resetcause = lp_var_t(sid.objectId, pool::PDU_RESETCAUSE, this); + + /** Number of detected latchups on each output channel */ + lp_vec_t latchups = lp_vec_t(sid.objectId, pool::PDU_LATCHUPS, this); + + /** + * There are 8 devices on the PDU. FRAM, ADCs, temperature sensor etc. Each device is + * identified by an ID. Refer also to gs-man-nanopower-p60-pdu-200-1.pdf on pages 17 and 18. + */ + lp_vec_t deviceTypes = lp_vec_t(sid.objectId, pool::PDU_DEVICES, this); + /** The status of each device. 0 = None, 1 = Ok, 2 = Error, 3 = Not found */ + lp_vec_t devicesStatus = lp_vec_t(sid.objectId, pool::PDU_STATUSES, this); + + /** Number of reboots triggered by the ground watchdog */ + lp_var_t gndWdtReboots = lp_var_t(sid.objectId, pool::PDU_WDT_CNT_GND, this); + /** Number of reboots triggered through the I2C watchdog. Not relevant for EIVE. */ + lp_var_t i2cWdtReboots = lp_var_t(sid.objectId, pool::PDU_WDT_CNT_I2C, this); + /** Number of reboots triggered through the CAN watchdog */ + lp_var_t canWdtReboots = lp_var_t(sid.objectId, pool::PDU_WDT_CNT_CAN, this); + /** Number of reboots triggered through the CSP watchdog */ + lp_var_t csp1WdtReboots = + lp_var_t(sid.objectId, pool::PDU_WDT_CNT_CSP1, this); + lp_var_t csp2WdtReboots = + lp_var_t(sid.objectId, pool::PDU_WDT_CNT_CSP2, this); + /** Ground watchdog remaining seconds before rebooting */ + lp_var_t groundWatchdogSecondsLeft = + lp_var_t(sid.objectId, pool::PDU_WDT_GND_LEFT, this); + /** I2C watchdog remaining seconds before rebooting. Not relevant for EIVE. */ + lp_var_t i2cWatchdogSecondsLeft = + lp_var_t(sid.objectId, pool::PDU_WDT_I2C_LEFT, this); + /** CAN watchdog remaining seconds before rebooting. */ + lp_var_t canWatchdogSecondsLeft = + lp_var_t(sid.objectId, pool::PDU_WDT_CAN_LEFT, this); + /** CSP watchdogs remaining pings before rebooting. */ + lp_var_t csp2WatchdogPingsLeft = + lp_var_t(sid.objectId, pool::PDU_WDT_CSP_LEFT1, this); + lp_var_t csp1WatchdogPingsLeft = + lp_var_t(sid.objectId, pool::PDU_WDT_CSP_LEFT2, this); +}; +} // namespace PDU + +namespace PDU1 { + +enum Channels : uint8_t { + TCS_BOARD_3V3 = 0, + SYRLINKS = 1, + STR = 2, + MGT = 3, + SUS_NOMINAL = 4, + SOL_CELL_EXPERIMENT = 5, + PLOC = 6, + ACS_A_SIDE = 7, + UNUSED = 8, + LEN = 9 +}; + +/** + * Addresses within configuration table to enable or disable output channels. Refer also to + * gs-man-nanopower-p60-pdu-200.pdf on page 16. + */ +static const uint16_t CONFIG_ADDRESS_OUT_EN_TCS_BOARD_3V3 = 0x48; +static const uint16_t CONFIG_ADDRESS_OUT_EN_SYRLINKS = 0x49; +static const uint16_t CONFIG_ADDRESS_OUT_EN_STAR_TRACKER = 0x4A; +static const uint16_t CONFIG_ADDRESS_OUT_EN_MGT = 0x4B; +static const uint16_t CONFIG_ADDRESS_OUT_EN_SUS_NOMINAL = 0x4C; +static const uint16_t CONFIG_ADDRESS_OUT_EN_SOLAR_CELL_EXP = 0x4D; +static const uint16_t CONFIG_ADDRESS_OUT_EN_PLOC = 0x4E; +static const uint16_t CONFIG_ADDRESS_OUT_EN_ACS_BOARD_SIDE_A = 0x4F; +static const uint16_t CONFIG_ADDRESS_OUT_EN_CHANNEL8 = 0x50; + +class Pdu1CoreHk : public ::PDU::PduCoreHk { + public: + Pdu1CoreHk(HasLocalDataPoolIF* owner) + : PduCoreHk(owner, static_cast(::P60System::SetIds::CORE)) {} + + Pdu1CoreHk(object_id_t objectId) + : PduCoreHk(objectId, static_cast(::P60System::SetIds::CORE)) {} +}; + +class Pdu1AuxHk : public ::PDU::PduAuxHk { + public: + Pdu1AuxHk(HasLocalDataPoolIF* owner) + : PduAuxHk(owner, static_cast(::P60System::SetIds::AUX)) {} + + Pdu1AuxHk(object_id_t objectId) + : PduAuxHk(objectId, static_cast(::P60System::SetIds::AUX)) {} +}; + +class Pdu1Config : public ::PDU::PduConfig { + public: + Pdu1Config(HasLocalDataPoolIF* owner) + : PduConfig(owner, static_cast(::P60System::SetIds::CONFIG)) {} +}; + +} // namespace PDU1 + +namespace PDU2 { + +enum Channels : uint8_t { + Q7S = 0, + PAYLOAD_PCDU_CH1 = 1, + RW = 2, + TCS_HEATER_IN = 3, + SUS_REDUNDANT = 4, + DEPY_MECHANISM = 5, + PAYLOAD_PCDU_CH6 = 6, + ACS_B_SIDE = 7, + PAYLOAD_CAMERA = 8, + LEN = 9 +}; + +/** + * Addresses within configuration table to enable or disable output channels. Refer also to + * gs-man-nanopower-p60-pdu-200.pdf on page 16. + */ +static const uint16_t CONFIG_ADDRESS_OUT_EN_Q7S = 0x48; +static const uint16_t CONFIG_ADDRESS_OUT_EN_PAYLOAD_PCDU_CH1 = 0x49; +static const uint16_t CONFIG_ADDRESS_OUT_EN_RW = 0x4A; +static const uint16_t CONFIG_ADDRESS_OUT_EN_TCS_BOARD_HEATER_IN = 0x4B; +static const uint16_t CONFIG_ADDRESS_OUT_EN_SUS_REDUNDANT = 0x4C; +static const uint16_t CONFIG_ADDRESS_OUT_EN_DEPLOYMENT_MECHANISM = 0x4D; +static const uint16_t CONFIG_ADDRESS_OUT_EN_PAYLOAD_PCDU_CH6 = 0x4E; +static const uint16_t CONFIG_ADDRESS_OUT_EN_ACS_BOARD_SIDE_B = 0x4F; +static const uint16_t CONFIG_ADDRESS_OUT_EN_PAYLOAD_CAMERA = 0x50; + +class Pdu2CoreHk : public ::PDU::PduCoreHk { + public: + Pdu2CoreHk(HasLocalDataPoolIF* owner) + : PduCoreHk(owner, static_cast(::P60System::SetIds::CORE)) {} + + Pdu2CoreHk(object_id_t objectId) + : PduCoreHk(objectId, static_cast(::P60System::SetIds::CORE)) {} +}; + +class Pdu2AuxHk : public ::PDU::PduAuxHk { + public: + Pdu2AuxHk(HasLocalDataPoolIF* owner) + : PduAuxHk(owner, static_cast(::P60System::SetIds::AUX)) {} + + Pdu2AuxHk(object_id_t objectId) + : PduAuxHk(objectId, static_cast(::P60System::SetIds::AUX)) {} +}; + +class Pdu2Config : public ::PDU::PduConfig { + public: + Pdu2Config(HasLocalDataPoolIF* owner) + : PduConfig(owner, static_cast(::P60System::SetIds::CONFIG)) {} +}; + +} // namespace PDU2 + +namespace ACU { + +namespace pool { +enum Ids : lp_id_t { + /** ACU Ids */ + ACU_CURRENT_IN_CHANNELS, + ACU_VOLTAGE_IN_CHANNELS, + ACU_VCC, + ACU_VBAT, + ACU_TEMPERATURES, + ACU_MPPT_MODE, + ACU_VBOOST_IN_CHANNELS, + ACU_POWER_IN_CHANNELS, + ACU_DAC_ENABLES, + ACU_DAC_RAW_CHANNELS, + ACU_BOOTCAUSE, + ACU_BOOTCNT, + ACU_UPTIME, + ACU_RESET_CAUSE, + ACU_MPPT_TIME, + ACU_MPPT_PERIOD, + ACU_DEVICES, + ACU_DEVICES_STATUS, + ACU_WDT_CNT_GND, + ACU_WDT_GND_LEFT +}; +} + +static const uint16_t MAX_CONFIGTABLE_ADDRESS = 26; +static const uint16_t MAX_HKTABLE_ADDRESS = 120; +static const uint8_t HK_TABLE_ENTRIES = 64; +static const uint16_t HK_TABLE_SIZE = gsConstants::P60ACU_HK_SIZE; +static const uint16_t CONFIG_TABLE_SIZE = gsConstants::P60ACU_PARAM_SIZE; +static const size_t MAX_REPLY_SIZE = HK_TABLE_SIZE; + +class CoreHk : public StaticLocalDataSet<14> { + public: + CoreHk(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, static_cast(::P60System::SetIds::CORE)) {} + + CoreHk(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, static_cast(::P60System::SetIds::CORE))) {} + + lp_var_t mpptMode = lp_var_t(sid.objectId, pool::ACU_MPPT_MODE, this); + + lp_vec_t currentInChannels = + lp_vec_t(sid.objectId, pool::ACU_CURRENT_IN_CHANNELS, this); + lp_vec_t voltageInChannels = + lp_vec_t(sid.objectId, pool::ACU_VOLTAGE_IN_CHANNELS, this); + + lp_var_t vcc = lp_var_t(sid.objectId, pool::ACU_VCC, this); + lp_var_t vbat = lp_var_t(sid.objectId, pool::ACU_VBAT, this); + + lp_vec_t vboostInChannels = + lp_vec_t(sid.objectId, pool::ACU_VBOOST_IN_CHANNELS, this); + lp_vec_t powerInChannels = + lp_vec_t(sid.objectId, pool::ACU_POWER_IN_CHANNELS, this); + + lp_vec_t temperatures = lp_vec_t(sid.objectId, pool::ACU_TEMPERATURES, this); + + lp_var_t bootcnt = lp_var_t(sid.objectId, pool::ACU_BOOTCNT, this); + lp_var_t uptime = lp_var_t(sid.objectId, pool::ACU_UPTIME, this); + lp_var_t mpptTime = lp_var_t(sid.objectId, pool::ACU_MPPT_TIME, this); + lp_var_t mpptPeriod = lp_var_t(sid.objectId, pool::ACU_MPPT_PERIOD, this); +}; +/** + * @brief This class defines a dataset for the hk table of the ACU. + */ +class AuxHk : public StaticLocalDataSet<12> { + public: + AuxHk(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, static_cast(::P60System::SetIds::AUX)) {} + + AuxHk(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, static_cast(::P60System::SetIds::AUX))) {} + + lp_vec_t dacEnables = lp_vec_t(sid.objectId, pool::ACU_DAC_ENABLES, this); + + lp_vec_t dacRawChannelVals = + lp_vec_t(sid.objectId, pool::ACU_DAC_RAW_CHANNELS, this); + + lp_var_t bootCause = lp_var_t(sid.objectId, pool::ACU_BOOTCAUSE, this); + lp_var_t resetCause = lp_var_t(sid.objectId, pool::ACU_RESET_CAUSE, this); + + lp_var_t wdtCntGnd = lp_var_t(sid.objectId, pool::ACU_WDT_CNT_GND, this); + lp_var_t wdtGndLeft = lp_var_t(sid.objectId, pool::ACU_WDT_GND_LEFT, this); + + /** + * There are 8 devices on the PDU. FRAM, ADCs, temperature sensor etc. Each device is + * identified by an ID. Refer also to gs-man-nanopower-p60-pdu-200-1.pdf on pages 17 and 18. + */ + lp_vec_t deviceTypes = lp_vec_t(sid.objectId, pool::ACU_DEVICES, this); + /** The status of each device. 0 = None, 1 = Ok, 2 = Error, 3 = Not found */ + lp_vec_t devicesStatus = + lp_vec_t(sid.objectId, pool::ACU_DEVICES_STATUS, this); +}; +} // namespace ACU + +namespace pcdu { + +enum PoolIds : uint32_t { PDU1_SWITCHES, PDU2_SWITCHES, P60DOCK_5V, P60DOCK_3V3 }; + +static const uint8_t ON = 1; +static const uint8_t OFF = 0; + +// Output states after reboot of the PDUs + +const std::array INIT_SWITCHES_PDU1 = { +// Because the TE0720 is not connected to the PCDU, this switch is always on +#ifdef TE0720_1CFA + ON, +#else + OFF, +#endif + OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, +}; +const std::array INIT_SWITCHES_PDU2 = {ON, OFF, OFF, OFF, OFF, + OFF, OFF, OFF, OFF}; + +static constexpr uint32_t SWITCHER_SET_ID = 0; + +class SwitcherStates : public StaticLocalDataSet { + public: + SwitcherStates(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, SWITCHER_SET_ID) {} + + SwitcherStates(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, SWITCHER_SET_ID)) {} + + lp_vec_t pdu1Switches = + lp_vec_t(sid.objectId, PDU1_SWITCHES, this); + lp_vec_t pdu2Switches = + lp_vec_t(sid.objectId, PDU2_SWITCHES, this); + lp_var_t p60Dock5VStack = lp_var_t(sid.objectId, P60DOCK_5V, this); + lp_var_t p60Dock3V3Stack = lp_var_t(sid.objectId, P60DOCK_3V3, this); +}; + +} // namespace pcdu +#endif /* MISSION_POWER_GSDEFS_H_ */ diff --git a/mission/power/gsLibDefs.h b/mission/power/gsLibDefs.h new file mode 100644 index 0000000..57525a3 --- /dev/null +++ b/mission/power/gsLibDefs.h @@ -0,0 +1,33 @@ +#ifndef MISSION_POWER_GSLIBDEFS_H_ +#define MISSION_POWER_GSLIBDEFS_H_ + +#include "fsfw/platform.h" + +#ifdef PLATFORM_UNIX + +// I really don't want to pull in all of those GomSpace headers just for 6 constants.. +// Those are the headers which contain the defines which were just hardcoded below. + +// #include "p60acu_hk.h" +// #include "p60acu_param.h" +// #include "p60dock_hk.h" +// #include "p60dock_param.h" +// #include "p60pdu_hk.h" +// #include "p60pdu_param.h" + +#endif + +#include + +namespace gsConstants { + +static constexpr uint32_t P60DOCK_HK_SIZE = 0xBE; +static constexpr uint32_t P60DOCK_PARAM_SIZE = 0x19C; +static constexpr uint32_t P60PDU_HK_SIZE = 0x90; +static constexpr uint32_t P60PDU_PARAM_SIZE = 0x13E; +static constexpr uint32_t P60ACU_HK_SIZE = 0x7C; +static constexpr uint32_t P60ACU_PARAM_SIZE = 0x1B; + +} // namespace gsConstants + +#endif /* MISSION_POWER_GSLIBDEFS_H_ */ diff --git a/mission/scheduling.cpp b/mission/scheduling.cpp new file mode 100644 index 0000000..4dc5bad --- /dev/null +++ b/mission/scheduling.cpp @@ -0,0 +1,50 @@ +#include "scheduling.h" + +#include + +#include "fsfw/tasks/PeriodicTaskIF.h" + +void scheduling::scheduleTmpTempSensors(PeriodicTaskIF* tmpTask, bool schedulePlPcdu1) { + std::vector tmpIds = {objects::TMP1075_HANDLER_TCS_0, objects::TMP1075_HANDLER_TCS_1, + objects::TMP1075_HANDLER_PLPCDU_0, + objects::TMP1075_HANDLER_IF_BOARD}; + if (schedulePlPcdu1) { + tmpIds.push_back(objects::TMP1075_HANDLER_PLPCDU_1); + } + for (const auto& tmpId : tmpIds) { + tmpTask->addComponent(tmpId, DeviceHandlerIF::PERFORM_OPERATION); + tmpTask->addComponent(tmpId, DeviceHandlerIF::SEND_WRITE); + tmpTask->addComponent(tmpId, DeviceHandlerIF::GET_WRITE); + tmpTask->addComponent(tmpId, DeviceHandlerIF::SEND_READ); + tmpTask->addComponent(tmpId, DeviceHandlerIF::GET_READ); + } +} + +void scheduling::scheduleRtdSensors(PeriodicTaskIF* tcsTask) { + const std::array rtdIds = { + objects::RTD_0_IC3_PLOC_HEATSPREADER, + objects::RTD_1_IC4_PLOC_MISSIONBOARD, + objects::RTD_2_IC5_4K_CAMERA, + objects::RTD_3_IC6_DAC_HEATSPREADER, + objects::RTD_4_IC7_STARTRACKER, + objects::RTD_5_IC8_RW1_MX_MY, + objects::RTD_6_IC9_DRO, + objects::RTD_7_IC10_SCEX, + objects::RTD_8_IC11_X8, + objects::RTD_9_IC12_HPA, + objects::RTD_10_IC13_PL_TX, + objects::RTD_11_IC14_MPA, + objects::RTD_12_IC15_ACU, + objects::RTD_13_IC16_PLPCDU_HEATSPREADER, + objects::RTD_14_IC17_TCS_BOARD, + objects::RTD_15_IC18_IMTQ, + }; + + for (const auto& rtd : rtdIds) { + tcsTask->addComponent(rtd, DeviceHandlerIF::PERFORM_OPERATION); + tcsTask->addComponent(rtd, DeviceHandlerIF::SEND_WRITE); + tcsTask->addComponent(rtd, DeviceHandlerIF::GET_WRITE); + tcsTask->addComponent(rtd, DeviceHandlerIF::SEND_READ); + tcsTask->addComponent(rtd, DeviceHandlerIF::GET_READ); + } +} diff --git a/mission/scheduling.h b/mission/scheduling.h new file mode 100644 index 0000000..947164e --- /dev/null +++ b/mission/scheduling.h @@ -0,0 +1,11 @@ +#ifndef EIVE_OBSW_SCHEDULING_H +#define EIVE_OBSW_SCHEDULING_H + +class PeriodicTaskIF; + +namespace scheduling { +void scheduleTmpTempSensors(PeriodicTaskIF* tmpSensors, bool schedulePlPcdu1); +void scheduleRtdSensors(PeriodicTaskIF* periodicTask); + +} // namespace scheduling +#endif // EIVE_OBSW_SCHEDULING_H diff --git a/mission/sysDefs.h b/mission/sysDefs.h new file mode 100644 index 0000000..924b1a9 --- /dev/null +++ b/mission/sysDefs.h @@ -0,0 +1,341 @@ +#ifndef MISSION_SYSDEFS_H_ +#define MISSION_SYSDEFS_H_ + +#include +#include +#include +#include + +#include +#include + +#include "eive/eventSubsystemIds.h" + +namespace signals { + +extern std::atomic_bool CFDP_CHANNEL_THROTTLE_SIGNAL; +extern std::atomic_uint16_t I2C_FATAL_ERRORS; +extern std::atomic_uint32_t CFDP_MSG_COUNTER; + +} // namespace signals + +namespace satsystem { + +enum Mode : Mode_t { + BOOT = 5, + // DO NOT CHANGE THE ORDER starting from here, breaks ACS mode checks. + SAFE = 10, + PTG_IDLE = 11, + PTG_NADIR = 12, + PTG_TARGET = 13, + PTG_TARGET_GS = 14, + PTG_INERTIAL = 15, +}; +} + +namespace xsc { + +enum Chip : int { CHIP_0, CHIP_1, NO_CHIP, SELF_CHIP, ALL_CHIP }; +enum Copy : int { COPY_0, COPY_1, NO_COPY, SELF_COPY, ALL_COPY }; + +} // namespace xsc + +namespace core { + +extern std::atomic_bool SAVE_PUS_SEQUENCE_COUNT; +extern std::atomic_bool SAVE_CFDP_SEQUENCE_COUNT; + +// TODO: Support for status? Or maybe some command to quickly get information whether a unit +// is running. +enum SystemctlCmd : uint8_t { START = 0, STOP = 1, RESTART = 2, NUM_CMDS = 3 }; + +static constexpr char CONF_FOLDER[] = "conf"; + +static constexpr char VERSION_FILE_NAME[] = "version.txt"; +static constexpr char LEGACY_REBOOT_WATCHDOG_FILE_NAME[] = "reboot.txt"; +static constexpr char REBOOT_WATCHDOG_FILE_NAME[] = "reboot_watchdog.txt"; +static constexpr char REBOOT_COUNTER_FILE_NAME[] = "reboot_counters.txt"; +static constexpr char LEAP_SECONDS_FILE_NAME[] = "leapseconds.txt"; +static constexpr char TIME_FILE_NAME[] = "time_backup.txt"; + +static constexpr uint32_t SYS_ROM_BASE_ADDR = 0x80000000; + +static constexpr ActionId_t ANNOUNCE_VERSION = 1; +static constexpr ActionId_t ANNOUNCE_CURRENT_IMAGE = 2; +static constexpr ActionId_t ANNOUNCE_BOOT_COUNTS = 3; +static constexpr ActionId_t SWITCH_REBOOT_FILE_HANDLING = 5; +static constexpr ActionId_t RESET_REBOOT_COUNTERS = 6; +static constexpr ActionId_t SWITCH_IMG_LOCK = 7; +static constexpr ActionId_t SET_MAX_REBOOT_CNT = 8; +static constexpr ActionId_t READ_REBOOT_MECHANISM_INFO = 9; + +static constexpr ActionId_t OBSW_UPDATE_FROM_SD_0 = 10; +static constexpr ActionId_t OBSW_UPDATE_FROM_SD_1 = 11; +static constexpr ActionId_t OBSW_UPDATE_FROM_TMP = 12; + +static constexpr ActionId_t SWITCH_TO_SD_0 = 16; +static constexpr ActionId_t SWITCH_TO_SD_1 = 17; +static constexpr ActionId_t SWITCH_TO_BOTH_SD_CARDS = 18; + +//! Reboot using the xsc_boot_copy command +static constexpr ActionId_t XSC_REBOOT_OBC = 32; +static constexpr ActionId_t MOUNT_OTHER_COPY = 33; +//! Reboot using the reboot command +static constexpr ActionId_t REBOOT_OBC = 34; + +static constexpr ActionId_t EXECUTE_SHELL_CMD_BLOCKING = 40; +static constexpr ActionId_t EXECUTE_SHELL_CMD_NON_BLOCKING = 41; +static constexpr ActionId_t SYSTEMCTL_CMD_EXECUTOR = 42; + +static constexpr ActionId_t LIST_DIRECTORY_INTO_FILE = 50; +static constexpr ActionId_t LIST_DIRECTORY_DUMP_DIRECTLY = 51; +static constexpr ActionId_t CP_HELPER = 52; +static constexpr ActionId_t MV_HELPER = 53; +static constexpr ActionId_t RM_HELPER = 54; +static constexpr ActionId_t MKDIR_HELPER = 55; + +static constexpr ActionId_t UPDATE_LEAP_SECONDS = 60; + +static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::CORE; + +static constexpr Event ALLOC_FAILURE = event::makeEvent(SUBSYSTEM_ID, 0, severity::MEDIUM); +//! [EXPORT] : [COMMENT] Software reboot occurred. Can also be a systemd reboot. +//! P1: Current Chip, P2: Current Copy +static constexpr Event REBOOT_SW = event::makeEvent(SUBSYSTEM_ID, 1, severity::LOW); +//! [EXPORT] : [COMMENT] The reboot mechanism was triggered. +//! P1: First 16 bits: Last Chip, Last 16 bits: Last Copy, +//! P2: Each byte is the respective reboot count for the slots +static constexpr Event REBOOT_MECHANISM_TRIGGERED = + event::makeEvent(SUBSYSTEM_ID, 2, severity::MEDIUM); +//! Trying to find a way how to determine that the reboot came from ProASIC3 or PCDU.. +static constexpr Event REBOOT_HW = event::makeEvent(SUBSYSTEM_ID, 3, severity::MEDIUM); +//! [EXPORT] : [COMMENT] No SD card was active. Core controller will attempt to re-initialize +//! a SD card. +static constexpr Event NO_SD_CARD_ACTIVE = event::makeEvent(SUBSYSTEM_ID, 4, severity::HIGH); +//! [EXPORT] : [COMMENT] +//! P1: Byte 0: Major, Byte 1: Minor, Byte 2: Patch, Byte 3: Has Git Hash +//! P2: First four letters of Git SHA is the last byte of P1 is set. +static constexpr Event VERSION_INFO = event::makeEvent(SUBSYSTEM_ID, 5, severity::INFO); +//! [EXPORT] : [COMMENT] P1: Current Chip, P2: Current Copy +static constexpr Event CURRENT_IMAGE_INFO = event::makeEvent(SUBSYSTEM_ID, 6, severity::INFO); +//! [EXPORT] : [COMMENT] Total reboot counter, which is the sum of the boot count of all +//! individual images. +static constexpr Event REBOOT_COUNTER = event::makeEvent(SUBSYSTEM_ID, 7, severity::INFO); +//! [EXPORT] : [COMMENT] Get the boot count of the individual images. +//! P1: First 16 bits boot count of image 0 0, last 16 bits boot count of image 0 1. +//! P2: First 16 bits boot count of image 1 0, last 16 bits boot count of image 1 1. +static constexpr Event INDIVIDUAL_BOOT_COUNTS = event::makeEvent(SUBSYSTEM_ID, 8, severity::INFO); +//! [EXPORT] : [COMMENT] I2C is unavailable. Trying recovery of I2C bus by power cycling all I2C +//! devices. +static constexpr Event TRYING_I2C_RECOVERY = event::makeEvent(SUBSYSTEM_ID, 10, severity::HIGH); +//! [EXPORT] : [COMMENT] I2C is unavailable. Recovery did not work, performing full reboot. +static constexpr Event I2C_REBOOT = event::makeEvent(SUBSYSTEM_ID, 11, severity::HIGH); +//! [EXPORT] : [COMMENT] PDEC recovery through reset was not possible, performing full reboot. +static constexpr Event PDEC_REBOOT = event::makeEvent(SUBSYSTEM_ID, 12, severity::HIGH); +//! [EXPORT] : [COMMENT] Version information of the firmware (not OBSW). +//! P1: Byte 0: Major, Byte 1: Minor, Byte 2: Patch, Byte 3: Has Git Hash +//! P2: First four letters of Git SHA is the last byte of P1 is set. +static constexpr Event FIRMWARE_INFO = event::makeEvent(SUBSYSTEM_ID, 13, severity::INFO); +//! [EXPORT] : [COMMENT] Active SD card info. SD States: 0: OFF, 1: ON, 2: MOUNTED. +//! P1: Active SD Card Index, 0 if none is active +//! P2: First two bytes: SD state of SD card 0, last two bytes SD state of SD card 1 +static constexpr Event ACTIVE_SD_INFO = event::makeEvent(SUBSYSTEM_ID, 14, severity::INFO); + +class ListDirectoryCmdBase { + public: // TODO: Packet definition for clean deserialization + // 3 bytes for a and R flag, at least 5 bytes for minimum valid path /tmp with + // null termination + static constexpr size_t MIN_DATA_LEN = 8; + + ListDirectoryCmdBase(const uint8_t* data, size_t maxSize) : data(data), maxSize(maxSize) {} + virtual ~ListDirectoryCmdBase() = default; + + virtual ReturnValue_t parse() { + if (maxSize < MIN_DATA_LEN) { + return SerializeIF::STREAM_TOO_SHORT; + } + aFlag = data[0]; + rFlag = data[1]; + compressOption = data[2]; + + repoNameLen = strnlen(reinterpret_cast(data + 3), maxSize - 3); + // Last byte MUST be null terminated! + if (repoNameLen >= maxSize - 3) { + return HasActionsIF::INVALID_PARAMETERS; + } + repoName = reinterpret_cast(data + 3); + return returnvalue::OK; + } + + bool aFlagSet() const { return this->aFlag; } + bool rFlagSet() const { return this->rFlag; } + + bool compressionOptionSet() const { return this->compressOption; } + + const char* getRepoName(size_t& repoNameLen) const { + repoNameLen = this->repoNameLen; + return this->repoName; + } + + size_t getBaseSize() { + // Include NULL termination + if (repoName != nullptr) { + return 3 + repoNameLen + 1; + } + return 0; + } + + protected: + const uint8_t* data; + size_t maxSize; + + bool aFlag = false; + bool rFlag = false; + bool compressOption = false; + const char* repoName = nullptr; + size_t repoNameLen = 0; +}; + +class ListDirectoryIntoFile : public ListDirectoryCmdBase { + public: + // TODO: Packet definition for clean deserialization + // 3 bytes for a and R flag, at least 5 bytes for minimum valid path /tmp with + // null termination, at least 7 bytes for minimum target file name /tmp/a with + // null termination. + static constexpr size_t MIN_DATA_LEN = 15; + + ListDirectoryIntoFile(const uint8_t* data, size_t maxSize) + : ListDirectoryCmdBase(data, maxSize) {} + ReturnValue_t parse() override { + if (maxSize < MIN_DATA_LEN) { + return SerializeIF::STREAM_TOO_SHORT; + } + ReturnValue_t result = ListDirectoryCmdBase::parse(); + if (result != returnvalue::OK) { + return result; + } + + targetNameLen = + strnlen(reinterpret_cast(data + getBaseSize()), maxSize - getBaseSize()); + if (targetNameLen >= maxSize - getBaseSize()) { + // Again: String MUST be null terminated. + return HasActionsIF::INVALID_PARAMETERS; + } + targetName = reinterpret_cast(data + getBaseSize()); + return result; + } + const char* getTargetName(size_t& targetNameLen) const { + targetNameLen = this->targetNameLen; + return this->targetName; + } + + private: + const char* targetName = nullptr; + size_t targetNameLen = 0; +}; + +struct SourceTargetPair { + const char* sourceName = nullptr; + size_t sourceNameSize = 0; + const char* targetName = nullptr; + size_t targetNameSize = 0; +}; + +static ReturnValue_t parseDestTargetString(const uint8_t* data, size_t maxLen, + SourceTargetPair& destTgt) { + if (maxLen < 4) { + return SerializeIF::STREAM_TOO_SHORT; + } + destTgt.sourceNameSize = strnlen(reinterpret_cast(data), maxLen); + if (destTgt.sourceNameSize >= maxLen) { + return HasActionsIF::INVALID_PARAMETERS; + } + destTgt.sourceName = reinterpret_cast(data); + size_t remainingLen = maxLen - destTgt.sourceNameSize - 1; + if (remainingLen == 0) { + return HasActionsIF::INVALID_PARAMETERS; + } + destTgt.targetNameSize = + strnlen(reinterpret_cast(data + destTgt.sourceNameSize + 1), remainingLen); + if (destTgt.targetNameSize >= remainingLen) { + return HasActionsIF::INVALID_PARAMETERS; + } + destTgt.targetName = reinterpret_cast(data + destTgt.sourceNameSize + 1); + return returnvalue::OK; +} + +class CpHelperParser { + public: + CpHelperParser(const uint8_t* data, size_t maxLen) : data(data), maxLen(maxLen) {} + + ReturnValue_t parse() { + if (maxLen < 2) { + return SerializeIF::STREAM_TOO_SHORT; + } + recursiveOpt = data[0]; + forceOpt = data[1]; + return parseDestTargetString(data + 2, maxLen - 2, destTgt); + } + const SourceTargetPair& destTgtPair() const { return destTgt; } + bool isRecursiveOptSet() const { return recursiveOpt; } + bool isForceOptSet() const { return forceOpt; } + + private: + const uint8_t* data; + size_t maxLen; + bool recursiveOpt = false; + bool forceOpt = false; + SourceTargetPair destTgt; +}; + +class MvHelperParser { + public: + MvHelperParser(const uint8_t* data, size_t maxLen) : data(data), maxLen(maxLen) {} + + ReturnValue_t parse() { return parseDestTargetString(data, maxLen, destTgt); } + const SourceTargetPair& destTgtPair() const { return destTgt; } + + private: + const uint8_t* data; + size_t maxLen; + SourceTargetPair destTgt; +}; + +class RmHelperParser { + public: + RmHelperParser(const uint8_t* data, size_t maxLen) : data(data), maxLen(maxLen) {} + + ReturnValue_t parse() { + if (maxLen < 2) { + return SerializeIF::STREAM_TOO_SHORT; + } + recursiveOpt = data[0]; + forceOpt = data[1]; + removeTargetSize = strnlen(reinterpret_cast(data + 2), maxLen - 2); + // Must be null-terminated + if (removeTargetSize >= maxLen - 2) { + return HasActionsIF::INVALID_PARAMETERS; + } + removeTarget = reinterpret_cast(data + 2); + return returnvalue::OK; + } + bool isRecursiveOptSet() const { return recursiveOpt; } + bool isForceOptSet() const { return forceOpt; } + + const char* getRemoveTarget(size_t& removeTargetSize) { + removeTargetSize = this->removeTargetSize; + return removeTarget; + } + + private: + const uint8_t* data; + size_t maxLen; + bool recursiveOpt = false; + bool forceOpt = false; + const char* removeTarget = nullptr; + size_t removeTargetSize = 0; +}; + +} // namespace core + +#endif /* MISSION_SYSDEFS_H_ */ diff --git a/mission/system/CMakeLists.txt b/mission/system/CMakeLists.txt new file mode 100644 index 0000000..403cb82 --- /dev/null +++ b/mission/system/CMakeLists.txt @@ -0,0 +1,11 @@ +add_subdirectory(objects) +add_subdirectory(acs) +add_subdirectory(tcs) +add_subdirectory(com) +add_subdirectory(power) +add_subdirectory(payload) + +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE systemTree.cpp DualLanePowerStateMachine.cpp EiveSystem.cpp + treeUtil.cpp SharedPowerAssemblyBase.cpp) diff --git a/mission/system/DualLanePowerStateMachine.cpp b/mission/system/DualLanePowerStateMachine.cpp new file mode 100644 index 0000000..2af2fc3 --- /dev/null +++ b/mission/system/DualLanePowerStateMachine.cpp @@ -0,0 +1,111 @@ +#include "DualLanePowerStateMachine.h" + +#include +#include + +DualLanePowerStateMachine::DualLanePowerStateMachine(power::Switch_t switchA, + power::Switch_t switchB, + PowerSwitchIF* pwrSwitcher, + dur_millis_t checkTimeout) + : PowerStateMachineBase(pwrSwitcher, checkTimeout), SWITCH_A(switchA), SWITCH_B(switchB) {} + +power::OpCodes DualLanePowerStateMachine::fsm() { + using namespace duallane; + ReturnValue_t switchStateA = returnvalue::OK; + ReturnValue_t switchStateB = returnvalue::OK; + if (state == power::States::IDLE or state == power::States::MODE_COMMANDING) { + return opResult; + } + if (state == power::States::SWITCHING_POWER or state == power::States::CHECKING_POWER) { + switchStateA = pwrSwitcher->getSwitchState(SWITCH_A); + switchStateB = pwrSwitcher->getSwitchState(SWITCH_B); + } else { + return opResult; + } + if (targetMode == HasModesIF::MODE_OFF) { + if (switchStateA == PowerSwitchIF::SWITCH_OFF and switchStateB == PowerSwitchIF::SWITCH_OFF) { + state = power::States::IDLE; + opResult = power::OpCodes::TO_OFF_DONE; + return opResult; + } + } else { + switch (targetSubmode) { + case (A_SIDE): { + if (switchStateA == PowerSwitchIF::SWITCH_ON and + switchStateB == PowerSwitchIF::SWITCH_OFF) { + state = power::States::MODE_COMMANDING; + opResult = power::OpCodes::TO_NOT_OFF_DONE; + return opResult; + } + break; + } + case (B_SIDE): { + if (switchStateA == PowerSwitchIF::SWITCH_OFF and + switchStateB == PowerSwitchIF::SWITCH_ON) { + state = power::States::MODE_COMMANDING; + opResult = power::OpCodes::TO_NOT_OFF_DONE; + return opResult; + } + break; + } + case (DUAL_MODE): { + if (switchStateA == PowerSwitchIF::SWITCH_ON and switchStateB == PowerSwitchIF::SWITCH_ON) { + state = power::States::MODE_COMMANDING; + opResult = power::OpCodes::TO_NOT_OFF_DONE; + return opResult; + } + } + } + } + if (state == power::States::SWITCHING_POWER) { + if (targetMode == HasModesIF::MODE_OFF) { + if (switchStateA != PowerSwitchIF::SWITCH_OFF) { + pwrSwitcher->sendSwitchCommand(SWITCH_A, PowerSwitchIF::SWITCH_OFF); + } + if (switchStateB != PowerSwitchIF::SWITCH_OFF) { + pwrSwitcher->sendSwitchCommand(SWITCH_B, PowerSwitchIF::SWITCH_OFF); + } + checkTimeout.resetTimer(); + } else { + switch (targetSubmode) { + case (A_SIDE): { + if (switchStateA != PowerSwitchIF::SWITCH_ON) { + pwrSwitcher->sendSwitchCommand(SWITCH_A, PowerSwitchIF::SWITCH_ON); + } + if (switchStateB != PowerSwitchIF::SWITCH_OFF) { + pwrSwitcher->sendSwitchCommand(SWITCH_B, PowerSwitchIF::SWITCH_OFF); + } + checkTimeout.resetTimer(); + break; + } + case (B_SIDE): { + if (switchStateA != PowerSwitchIF::SWITCH_OFF) { + pwrSwitcher->sendSwitchCommand(SWITCH_A, PowerSwitchIF::SWITCH_OFF); + } + if (switchStateB != PowerSwitchIF::SWITCH_ON) { + pwrSwitcher->sendSwitchCommand(SWITCH_B, PowerSwitchIF::SWITCH_ON); + } + checkTimeout.resetTimer(); + break; + } + case (DUAL_MODE): { + if (switchStateA != PowerSwitchIF::SWITCH_ON) { + pwrSwitcher->sendSwitchCommand(SWITCH_A, PowerSwitchIF::SWITCH_ON); + } + if (switchStateB != PowerSwitchIF::SWITCH_ON) { + pwrSwitcher->sendSwitchCommand(SWITCH_B, PowerSwitchIF::SWITCH_ON); + } + checkTimeout.resetTimer(); + break; + } + } + } + state = power::States::CHECKING_POWER; + } + if (state == power::States::CHECKING_POWER) { + if (checkTimeout.hasTimedOut()) { + return power::OpCodes::TIMEOUT_OCCURED; + } + } + return opResult; +} diff --git a/mission/system/DualLanePowerStateMachine.h b/mission/system/DualLanePowerStateMachine.h new file mode 100644 index 0000000..d3f596f --- /dev/null +++ b/mission/system/DualLanePowerStateMachine.h @@ -0,0 +1,25 @@ +#ifndef MISSION_SYSTEM_DUALLANEPOWERSTATEMACHINE_H_ +#define MISSION_SYSTEM_DUALLANEPOWERSTATEMACHINE_H_ + +#include +#include +#include + +#include "mission/power/defs.h" + +class AssemblyBase; +class PowerSwitchIF; + +class DualLanePowerStateMachine : public PowerStateMachineBase { + public: + DualLanePowerStateMachine(power::Switch_t switchA, power::Switch_t switchB, + PowerSwitchIF* pwrSwitcher, dur_millis_t checkTimeout = 5000); + power::OpCodes fsm() override; + + const power::Switch_t SWITCH_A; + const power::Switch_t SWITCH_B; + + private: +}; + +#endif /* MISSION_SYSTEM_DUALLANEPOWERSTATEMACHINE_H_ */ diff --git a/mission/system/EiveSystem.cpp b/mission/system/EiveSystem.cpp new file mode 100644 index 0000000..bb0f5e6 --- /dev/null +++ b/mission/system/EiveSystem.cpp @@ -0,0 +1,498 @@ +#include "EiveSystem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linux/ipcore/pdec.h" +#include "mission/power/bpxBattDefs.h" +#include "mission/power/defs.h" +#include "mission/sysDefs.h" + +EiveSystem::EiveSystem(object_id_t setObjectId, uint32_t maxNumberOfSequences, + uint32_t maxNumberOfTables, std::atomic_uint16_t& i2cErrors) + : Subsystem(setObjectId, maxNumberOfSequences, maxNumberOfTables), + actionHelper(this, commandQueue), + i2cErrors(i2cErrors) { + auto mqArgs = MqArgs(SubsystemBase::getObjectId(), static_cast(this)); + eventQueue = + QueueFactory::instance()->createMessageQueue(10, EventMessage::EVENT_MESSAGE_SIZE, &mqArgs); +} + +void EiveSystem::announceMode(bool recursive) { + const char* modeStr = "UNKNOWN"; + switch (mode) { + case (satsystem::Mode::BOOT): { + modeStr = "OFF/BOOT"; + break; + } + case (satsystem::Mode::SAFE): { + modeStr = "SAFE"; + break; + } + case (satsystem::Mode::PTG_IDLE): { + modeStr = "POINTING IDLE"; + break; + } + case (satsystem::Mode::PTG_NADIR): { + modeStr = "POINTING NADIR"; + break; + } + case (satsystem::Mode::PTG_TARGET): { + modeStr = "POINTING TARGET"; + break; + } + case (satsystem::Mode::PTG_TARGET_GS): { + modeStr = "POINTING TARGET GS"; + break; + } + case (satsystem::Mode::PTG_INERTIAL): { + modeStr = "POINTING INERTIAL"; + break; + } + } + sif::info << "EIVE system is now in " << modeStr << " mode" << std::endl; + return Subsystem::announceMode(recursive); +} + +void EiveSystem::performChildOperation() { + Subsystem::performChildOperation(); + handleEventMessages(); + if (not isInTransition and performSafeRecovery) { + commandSelfToSafe(); + performSafeRecovery = false; + return; + } + pdecRecoveryLogic(); + i2cRecoveryLogic(); + if (forcePlOffState != ForcePlOffState::NONE) { + forceOffPayload(); + } +} + +ReturnValue_t EiveSystem::initialize() { + if (powerSwitcher == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + ReturnValue_t result = actionHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + + auto* plSs = ObjectManager::instance()->get(objects::PL_SUBSYSTEM); + if (plSs == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + plSsQueueId = plSs->getCommandQueue(); + + auto* plPcdu = ObjectManager::instance()->get(objects::PLPCDU_HANDLER); + if (plPcdu == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + plPcduQueueId = plPcdu->getCommandQueue(); + + auto* plocMpsoc = ObjectManager::instance()->get(objects::PLOC_MPSOC_HANDLER); + if (plocMpsoc == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + plocMpsocQueueId = plocMpsoc->getCommandQueue(); + + auto* plocSupervisor = + ObjectManager::instance()->get(objects::PLOC_SUPERVISOR_HANDLER); + if (plocSupervisor == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + plocSupervisorQueueId = plocSupervisor->getCommandQueue(); + + auto* camera = ObjectManager::instance()->get(objects::CAM_SWITCHER); + if (camera == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + cameraQueueId = camera->getCommandQueue(); + + auto* scex = ObjectManager::instance()->get(objects::SCEX); + if (scex == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + scexQueueId = scex->getCommandQueue(); + + auto* radSensor = ObjectManager::instance()->get(objects::RAD_SENSOR); + if (radSensor == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + radSensorQueueId = radSensor->getCommandQueue(); + + auto* str = ObjectManager::instance()->get(objects::STAR_TRACKER); + if (str == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + strQueueId = str->getCommandQueue(); + + auto* bpxDest = ObjectManager::instance()->get(objects::BPX_BATT_HANDLER); + if (bpxDest == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + bpxBattQueueId = bpxDest->getCommandQueue(); + + auto* coreCtrl = ObjectManager::instance()->get(objects::CORE_CONTROLLER); + if (coreCtrl == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + coreCtrlQueueId = coreCtrl->getCommandQueue(); + + auto* pdecHandler = ObjectManager::instance()->get(objects::PDEC_HANDLER); + if (pdecHandler == nullptr) { + return ObjectManager::CHILD_INIT_FAILED; + } + pdecHandlerQueueId = pdecHandler->getCommandQueue(); + + auto* manager = ObjectManager::instance()->get(objects::EVENT_MANAGER); + if (manager == nullptr) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "AcsSubsystem::initialize: Invalid event manager" << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + } + result = manager->registerListener(eventQueue->getId()); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "AcsSubsystem::registerListener: Failed to register as " + "listener" + << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + } + manager->subscribeToEvent(eventQueue->getId(), + event::getEventId(tcsCtrl::PCDU_SYSTEM_OVERHEATING)); + manager->subscribeToEvent(eventQueue->getId(), event::getEventId(tcsCtrl::OBC_OVERHEATING)); + manager->subscribeToEvent(eventQueue->getId(), event::getEventId(tcsCtrl::MGT_OVERHEATING)); + manager->subscribeToEvent(eventQueue->getId(), event::getEventId(pdec::INVALID_TC_FRAME)); + manager->subscribeToEvent(eventQueue->getId(), event::getEventId(power::POWER_LEVEL_LOW)); + manager->subscribeToEvent(eventQueue->getId(), event::getEventId(power::POWER_LEVEL_CRITICAL)); + manager->subscribeToEvent(eventQueue->getId(), event::getEventId(acs::PTG_RATE_VIOLATION)); + return Subsystem::initialize(); +} + +void EiveSystem::handleEventMessages() { + EventMessage event; + for (ReturnValue_t status = eventQueue->receiveMessage(&event); status == returnvalue::OK; + status = eventQueue->receiveMessage(&event)) { + switch (event.getMessageId()) { + case EventMessage::EVENT_MESSAGE: + switch (event.getEvent()) { + case pdec::INVALID_TC_FRAME: { + if (event.getParameter1() == pdec::FRAME_DIRTY_RETVAL) { + frameDirtyErrorCounter++; + // Check whether threshold was reached after 10 seconds. + if (frameDirtyErrorCounter == 1) { + frameDirtyCheckCd.resetTimer(); + } + } + break; + } + case tcsCtrl::OBC_OVERHEATING: + case tcsCtrl::MGT_OVERHEATING: + case tcsCtrl::PCDU_SYSTEM_OVERHEATING: { + if (isInTransition) { + performSafeRecovery = true; + return; + } + + commandSelfToSafe(); + break; + } + case power::POWER_LEVEL_LOW: { + forcePlOffState = ForcePlOffState::FORCE_ALL_EXCEPT_SUPV_OFF; + break; + } + case power::POWER_LEVEL_CRITICAL: { + // Force payload off in any case. It really should not be on when the power level + // becomes critical, but better be safe than sorry.. + forcePlOffState = ForcePlOffState::FORCE_ALL_EXCEPT_SUPV_OFF; + // Also set the STR assembly to faulty, which should cause a fallback to SAFE mode. + CommandMessage msg; + HealthMessage::setHealthMessage(&msg, HealthMessage::HEALTH_SET, HasHealthIF::FAULTY); + ReturnValue_t result = MessageQueueSenderIF::sendMessage( + strQueueId, &msg, MessageQueueIF::NO_QUEUE, false); + if (result != returnvalue::OK) { + sif::error << "EIVE System: Sending FAULTY command to STR Assembly failed" + << std::endl; + } + break; + } + case acs::PTG_RATE_VIOLATION: { + CommandMessage msg; + HealthMessage::setHealthMessage(&msg, HealthMessage::HEALTH_SET, HasHealthIF::FAULTY); + ReturnValue_t result = MessageQueueSenderIF::sendMessage( + strQueueId, &msg, MessageQueueIF::NO_QUEUE, false); + if (result != returnvalue::OK) { + sif::error << "EIVE System: Sending FAULTY command to STR Assembly failed" + << std::endl; + } + } + } + break; + default: + sif::debug << "EiveSystem: Did not subscribe to event " << event.getEvent() << std::endl; + break; + } + } +} + +MessageQueueId_t EiveSystem::getCommandQueue() const { return Subsystem::getCommandQueue(); } + +ReturnValue_t EiveSystem::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + switch (actionId) { + case (EXECUTE_I2C_REBOOT): { + triggerEvent(core::TRYING_I2C_RECOVERY); + performI2cReboot = true; + i2cRebootState = I2cRebootState::SYSTEM_MODE_BOOT; + this->actionCommandedBy = commandedBy; + return returnvalue::OK; + } + default: { + return HasActionsIF::INVALID_ACTION_ID; + } + } + return returnvalue::OK; +} + +void EiveSystem::setI2cRecoveryParams(PowerSwitchIF* pwrSwitcher) { + this->powerSwitcher = pwrSwitcher; +} + +void EiveSystem::i2cRecoveryLogic() { + ReturnValue_t result; + if (not performI2cReboot) { + // If a recovery worked, need to reset these flags and the error count after some time. + if (i2cRecoveryClearCountdown.hasTimedOut()) { + i2cErrors = 0; + alreadyTriedI2cRecovery = false; + i2cRebootHandlingCountdown.resetTimer(); + } + // If an I2C recovery is not ongoing and the I2C error counter is above a threshold, try + // recovery or reboot if recovery was already attempted. + if (i2cErrors >= 5) { + if (not alreadyTriedI2cRecovery) { + // Try recovery. + executeAction(EXECUTE_I2C_REBOOT, MessageQueueIF::NO_QUEUE, nullptr, 0); + } else { + if (waitingForI2cReboot) { + return; + } + triggerEvent(core::I2C_REBOOT); + // Some delay to ensure that the event is stored in the persistent TM store as well. + TaskFactory::delayTask(500); + // We already tried an I2C recovery but the bus is still broken. + // Send reboot request to core controller. + result = sendSelfRebootCommand(); + if (result != returnvalue::OK) { + sif::error << "Sending a reboot command has failed" << std::endl; + // If the previous operation failed, it should be re-attempted the next task cycle. + return; + } + waitingForI2cReboot = true; + return; + } + } + } + if (not isInTransition and performI2cReboot) { + switch (i2cRebootState) { + case (I2cRebootState::NONE): { + break; + } + case (I2cRebootState::SYSTEM_MODE_BOOT): { + startTransition(satsystem::Mode::BOOT, 0); + i2cRebootState = I2cRebootState::SWITCH_3V3_STACK_OFF_AND_BATT_REBOOT; + i2cRebootHandlingCountdown.resetTimer(); + break; + } + case (I2cRebootState::SWITCH_3V3_STACK_OFF_AND_BATT_REBOOT): { + if (mode == satsystem::Mode::BOOT) { + result = powerSwitcher->sendSwitchCommand(power::Switches::P60_DOCK_3V3_STACK, + PowerSwitchIF::SWITCH_OFF); + if (result != returnvalue::OK) { + actionHelper.finish(false, actionCommandedBy, EXECUTE_I2C_REBOOT, result); + commonI2cRecoverySequenceFinish(); + return; + } + CommandMessage msg; + store_address_t dummy{}; + ActionMessage::setCommand(&msg, bpxBat::REBOOT, dummy); + result = commandQueue->sendMessage(bpxBattQueueId, &msg); + if (result != returnvalue::OK) { + actionHelper.finish(false, actionCommandedBy, EXECUTE_I2C_REBOOT, result); + commonI2cRecoverySequenceFinish(); + return; + } + i2cRebootState = I2cRebootState::WAIT_CYCLE; + } + break; + } + case (I2cRebootState::WAIT_CYCLE): { + i2cRebootState = I2cRebootState::SWITCH_3V3_STACK_ON; + break; + } + case (I2cRebootState::SWITCH_3V3_STACK_ON): { + result = powerSwitcher->sendSwitchCommand(power::Switches::P60_DOCK_3V3_STACK, + PowerSwitchIF::SWITCH_ON); + if (result != returnvalue::OK) { + actionHelper.finish(false, actionCommandedBy, EXECUTE_I2C_REBOOT, result); + commonI2cRecoverySequenceFinish(); + return; + } + i2cRebootState = I2cRebootState::SYSTEM_MODE_SAFE; + break; + } + case (I2cRebootState::SYSTEM_MODE_SAFE): { + if (powerSwitcher->getSwitchState(power::Switches::P60_DOCK_3V3_STACK) == + PowerSwitchIF::SWITCH_ON) { + // This should always be accepted + commonI2cRecoverySequenceFinish(); + actionHelper.finish(true, actionCommandedBy, EXECUTE_I2C_REBOOT); + } + break; + } + default: { + sif::error << "EiveSystem: Unexpected I2C reboot state" << std::endl; + break; + } + } + // Timeout handling for the internal procedure. + if (i2cRebootState != I2cRebootState::NONE and i2cRebootHandlingCountdown.hasTimedOut()) { + actionHelper.finish(false, actionCommandedBy, EXECUTE_I2C_REBOOT, returnvalue::FAILED); + // Command stack back on in any case. + powerSwitcher->sendSwitchCommand(power::Switches::P60_DOCK_3V3_STACK, + PowerSwitchIF::SWITCH_ON); + commonI2cRecoverySequenceFinish(); + } + } +} + +void EiveSystem::commandSelfToSafe() { startTransition(satsystem::Mode::SAFE, 0); } + +ReturnValue_t EiveSystem::sendFullRebootCommand() { + CommandMessage msg; + ActionMessage::setCommand(&msg, core::REBOOT_OBC, store_address_t()); + return commandQueue->sendMessage(coreCtrlQueueId, &msg); +} + +void EiveSystem::pdecRecoveryLogic() { + // PDEC reset has happened too often in the last time. Perform reboot to same image. + if (pdecResetCounter >= PDEC_RESET_MAX_COUNT_BEFORE_REBOOT) { + if (waitingForPdecReboot) { + return; + } + triggerEvent(core::PDEC_REBOOT); + // Some delay to ensure that the event is stored in the persistent TM store as well. + TaskFactory::delayTask(500); + // Send reboot command. + ReturnValue_t result = sendSelfRebootCommand(); + if (result != returnvalue::OK) { + sif::error << "Sending a reboot command has failed" << std::endl; + // If the previous operation failed, it should be re-attempted the next task cycle. + pdecResetCounterResetCd.resetTimer(); + return; + } + waitingForPdecReboot = true; + return; + } + if (pdecResetCounterResetCd.hasTimedOut()) { + pdecResetCounter = 0; + } + if (frameDirtyCheckCd.hasTimedOut() and frameDirtyErrorCounter > 0) { + if (frameDirtyErrorCounter >= FRAME_DIRTY_COM_REBOOT_LIMIT) { + // Try one full PDEC reset. + CommandMessage msg; + store_address_t dummy{}; + ActionMessage::setCommand(&msg, pdec::RESET_PDEC_WITH_REINIITALIZATION, dummy); + commandQueue->sendMessage(pdecHandlerQueueId, &msg); + pdecResetCounterResetCd.resetTimer(); + pdecResetCounter++; + } + frameDirtyErrorCounter = 0; + } +} + +void EiveSystem::forceOffPayload() { + CommandMessage msg; + ReturnValue_t result; + // set PL to faulty + HealthMessage::setHealthMessage(&msg, HealthMessage::HEALTH_SET, HasHealthIF::FAULTY); + + if (forcePlOffState == ForcePlOffState::FORCE_ALL_EXCEPT_SUPV_OFF) { + result = commandQueue->sendMessage(plocMpsocQueueId, &msg); + if (result != returnvalue::OK) { + sif::error << "EIVE System: Sending FAULTY command to PLOC MPSOC failed" << std::endl; + } + result = commandQueue->sendMessage(cameraQueueId, &msg); + if (result != returnvalue::OK) { + sif::error << "EIVE System: Sending FAULTY command to PL CAM failed" << std::endl; + } + result = commandQueue->sendMessage(scexQueueId, &msg); + if (result != returnvalue::OK) { + sif::error << "EIVE System: Sending FAULTY command to SCEX failed" << std::endl; + } + result = commandQueue->sendMessage(radSensorQueueId, &msg); + if (result != returnvalue::OK) { + sif::error << "EIVE System: Sending FAULTY command to RAD SENSOR failed" << std::endl; + } + result = commandQueue->sendMessage(plPcduQueueId, &msg); + if (result != returnvalue::OK) { + sif::error << "EIVE System: Sending FAULTY command to PL PCDU failed" << std::endl; + } + forcePlOffState = ForcePlOffState::WAITING; + supvOffDelay.resetTimer(); + } + + if (forcePlOffState == ForcePlOffState::WAITING and supvOffDelay.hasTimedOut()) { + forcePlOffState = ForcePlOffState::FORCE_SUPV_OFF; + } + + if (forcePlOffState == ForcePlOffState::FORCE_SUPV_OFF) { + result = commandQueue->sendMessage(plocSupervisorQueueId, &msg); + if (result != returnvalue::OK) { + sif::error << "EIVE System: Sending FAULTY command to PLOC SUPERVISOR failed" << std::endl; + } + forcePlOffState = ForcePlOffState::NONE; + } +} + +void EiveSystem::commonI2cRecoverySequenceFinish() { + alreadyTriedI2cRecovery = true; + performI2cReboot = false; + i2cRebootState = I2cRebootState::NONE; + // Reset this counter and the recovery clear countdown. If I2C devices are still problematic, + // we will get a full reboot next time this count goes above 5. + i2cErrors = 0; + i2cRecoveryClearCountdown.resetTimer(); + // This should always be accepted + commandSelfToSafe(); +} + +ReturnValue_t EiveSystem::handleCommandMessage(CommandMessage* message) { + if (message->getMessageType() == messagetypes::ACTION) { + return actionHelper.handleActionMessage(message); + } + return Subsystem::handleCommandMessage(message); +} + +ReturnValue_t EiveSystem::sendSelfRebootCommand() { + CommandMessage msg; + uint8_t data[1]; + // This option is used to target the same image. + data[0] = true; + store_address_t storeId; + ReturnValue_t result = IPCStore->addData(&storeId, data, sizeof(data)); + if (result != returnvalue::OK) { + return result; + } + ActionMessage::setCommand(&msg, core::XSC_REBOOT_OBC, storeId); + return commandQueue->sendMessage(coreCtrlQueueId, &msg); +} diff --git a/mission/system/EiveSystem.h b/mission/system/EiveSystem.h new file mode 100644 index 0000000..fed5791 --- /dev/null +++ b/mission/system/EiveSystem.h @@ -0,0 +1,97 @@ +#ifndef MISSION_SYSTEM_EIVESYSTEM_H_ +#define MISSION_SYSTEM_EIVESYSTEM_H_ + +#include +#include +#include + +#include + +class EiveSystem : public Subsystem, public HasActionsIF { + public: + static constexpr uint8_t FRAME_DIRTY_COM_REBOOT_LIMIT = 4; + static constexpr uint32_t PDEC_RESET_MAX_COUNT_BEFORE_REBOOT = 10; + + static constexpr ActionId_t EXECUTE_I2C_REBOOT = 10; + + EiveSystem(object_id_t setObjectId, uint32_t maxNumberOfSequences, uint32_t maxNumberOfTables, + std::atomic_uint16_t& i2cErrors); + + void setI2cRecoveryParams(PowerSwitchIF* pwrSwitcher); + + [[nodiscard]] MessageQueueId_t getCommandQueue() const override; + + private: + enum class ForcePlOffState { + NONE, + FORCE_ALL_EXCEPT_SUPV_OFF, + WAITING, + FORCE_SUPV_OFF + } forcePlOffState = ForcePlOffState::NONE; + enum class I2cRebootState { + NONE, + SYSTEM_MODE_BOOT, + SWITCH_3V3_STACK_OFF_AND_BATT_REBOOT, + WAIT_CYCLE, + SWITCH_3V3_STACK_ON, + SYSTEM_MODE_SAFE + } i2cRebootState = I2cRebootState::NONE; + + MessageQueueIF* eventQueue = nullptr; + bool performSafeRecovery = false; + bool performI2cReboot = false; + bool alreadyTriedI2cRecovery = false; + + uint8_t frameDirtyErrorCounter = 0; + Countdown supvOffDelay = Countdown(3000); + Countdown frameDirtyCheckCd = Countdown(10000); + // If the PDEC reset was already attempted in the last 2 minutes, there is a high chance that + // only a full reboot will fix the issue. + Countdown pdecResetCounterResetCd = Countdown(120000); + bool waitingForI2cReboot = false; + bool waitingForPdecReboot = false; + + uint32_t pdecResetCounter = 0; + ActionHelper actionHelper; + PowerSwitchIF* powerSwitcher = nullptr; + std::atomic_uint16_t& i2cErrors; + + MessageQueueId_t plSsQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t plPcduQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t plocMpsocQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t plocSupervisorQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t cameraQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t scexQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t radSensorQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t strQueueId = MessageQueueIF::NO_QUEUE; + + MessageQueueId_t pdecHandlerQueueId = MessageQueueIF::NO_QUEUE; + + MessageQueueId_t bpxBattQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t coreCtrlQueueId = MessageQueueIF::NO_QUEUE; + MessageQueueId_t actionCommandedBy = MessageQueueIF::NO_QUEUE; + Countdown i2cRebootHandlingCountdown = Countdown(10000); + // After 1 minute, clear the flag to avoid full reboots on I2C issues. + Countdown i2cRecoveryClearCountdown = Countdown(60000); + ReturnValue_t initialize() override; + void performChildOperation() override; + void announceMode(bool recursive) override; + + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + ReturnValue_t handleCommandMessage(CommandMessage* message) override; + + ReturnValue_t sendFullRebootCommand(); + ReturnValue_t sendSelfRebootCommand(); + + void forceOffPayload(); + + void pdecRecoveryLogic(); + + void i2cRecoveryLogic(); + void handleEventMessages(); + void commandSelfToSafe(); + void commonI2cRecoverySequenceFinish(); +}; + +#endif /* MISSION_SYSTEM_EIVESYSTEM_H_ */ diff --git a/mission/system/SharedPowerAssemblyBase.cpp b/mission/system/SharedPowerAssemblyBase.cpp new file mode 100644 index 0000000..5c325b5 --- /dev/null +++ b/mission/system/SharedPowerAssemblyBase.cpp @@ -0,0 +1,91 @@ +#include "SharedPowerAssemblyBase.h" + +SharedPowerAssemblyBase::SharedPowerAssemblyBase(object_id_t objectId, PowerSwitchIF* pwrSwitcher, + power::Switch_t switchId, uint16_t cmdQueueDepth) + : AssemblyBase(objectId, cmdQueueDepth), switcher(pwrSwitcher, switchId) {} + +void SharedPowerAssemblyBase::performChildOperation() { + auto state = switcher.getState(); + if (state != PowerSwitcher::WAIT_OFF and state != PowerSwitcher::WAIT_ON) { + AssemblyBase::performChildOperation(); + return; + } + switcher.doStateMachine(); + if (state == PowerSwitcher::WAIT_OFF and switcher.getState() == PowerSwitcher::SWITCH_IS_OFF) { + // Indicator that a transition to off is finished + AssemblyBase::handleModeReached(); + } else if (state == PowerSwitcher::WAIT_ON and + switcher.getState() == PowerSwitcher::SWITCH_IS_ON) { + // Indicator that mode commanding can be performed now + AssemblyBase::startTransition(targetMode, targetSubmode); + } +} + +void SharedPowerAssemblyBase::startTransition(Mode_t mode, Submode_t submode) { + if (mode != MODE_OFF) { + switcher.turnOn(true); + switcher.doStateMachine(); + if (switcher.getState() == PowerSwitcher::SWITCH_IS_ON) { + AssemblyBase::startTransition(mode, submode); + } else { + // Need to wait with mode commanding until power switcher is done + targetMode = mode; + targetSubmode = submode; + } + } else { + // Perform regular mode commanding first + AssemblyBase::startTransition(mode, submode); + } +} + +void SharedPowerAssemblyBase::handleModeReached() { + if (targetMode == MODE_OFF) { + switcher.turnOff(true); + switcher.doStateMachine(); + // Need to wait with call to AssemblyBase::handleModeReached until power switcher is done + if (switcher.getState() == PowerSwitcher::SWITCH_IS_OFF) { + AssemblyBase::handleModeReached(); + } + } else { + AssemblyBase::handleModeReached(); + } +} + +bool SharedPowerAssemblyBase::checkAndHandleRecovery() { + using namespace power; + if (recoveryState == RECOVERY_IDLE) { + return AssemblyBase::checkAndHandleRecovery(); + } + if (customRecoveryStates == IDLE) { + switcher.turnOff(); + customRecoveryStates = RecoveryCustomStates::POWER_SWITCHING_OFF; + } + if (customRecoveryStates == POWER_SWITCHING_OFF) { + switcher.doStateMachine(); + if (switcher.getState() == PowerSwitcher::SWITCH_IS_OFF) { + customRecoveryStates = RecoveryCustomStates::POWER_SWITCHING_ON; + switcher.turnOn(); + } + } + if (customRecoveryStates == POWER_SWITCHING_ON) { + switcher.doStateMachine(); + if (switcher.getState() == PowerSwitcher::SWITCH_IS_ON) { + customRecoveryStates = RecoveryCustomStates::DONE; + } + } + if (customRecoveryStates == DONE) { + bool pendingRecovery = AssemblyBase::checkAndHandleRecovery(); + if (not pendingRecovery) { + customRecoveryStates = RecoveryCustomStates::IDLE; + } + // For a recovery on one side, only do the recovery once + for (auto& child : childrenMap) { + if (healthHelper.healthTable->getHealth(child.first) == HasHealthIF::NEEDS_RECOVERY) { + sendHealthCommand(child.second.commandQueue, HEALTHY); + child.second.healthChanged = false; + } + } + return pendingRecovery; + } + return true; +} diff --git a/mission/system/SharedPowerAssemblyBase.h b/mission/system/SharedPowerAssemblyBase.h new file mode 100644 index 0000000..c269a52 --- /dev/null +++ b/mission/system/SharedPowerAssemblyBase.h @@ -0,0 +1,27 @@ +#ifndef MISSION_SYSTEM_SHAREDPOWERASSEMBLYBASE_H_ +#define MISSION_SYSTEM_SHAREDPOWERASSEMBLYBASE_H_ + +#include +#include +#include + +/** + * Base class which contains common functions for assemblies where the power line is shared + * among the devices in the assembly. + */ +class SharedPowerAssemblyBase : public AssemblyBase { + public: + SharedPowerAssemblyBase(object_id_t objectId, PowerSwitchIF* pwrSwitcher, + power::Switch_t switchId, uint16_t cmdQueueDepth = 8); + + protected: + PowerSwitcher switcher; + power::RecoveryCustomStates customRecoveryStates = power::RecoveryCustomStates::IDLE; + + void performChildOperation() override; + void startTransition(Mode_t mode, Submode_t submode) override; + void handleModeReached() override; + virtual bool checkAndHandleRecovery() override; +}; + +#endif /* MISSION_SYSTEM_SHAREDPOWERASSEMBLYBASE_H_ */ diff --git a/mission/system/acs/AcsBoardAssembly.cpp b/mission/system/acs/AcsBoardAssembly.cpp new file mode 100644 index 0000000..755f5b7 --- /dev/null +++ b/mission/system/acs/AcsBoardAssembly.cpp @@ -0,0 +1,407 @@ +#include "AcsBoardAssembly.h" + +#include +#include +#include + +#include "OBSWConfig.h" + +AcsBoardAssembly::AcsBoardAssembly(object_id_t objectId, PowerSwitchIF* switcher, + AcsBoardHelper helper, GpioIF* gpioIF) + : DualLaneAssemblyBase(objectId, switcher, SWITCH_A, SWITCH_B, POWER_STATE_MACHINE_TIMEOUT, + SIDE_SWITCH_TRANSITION_NOT_ALLOWED, TRANSITION_OTHER_SIDE_FAILED), + helper(helper), + gpioIF(gpioIF) { + if (switcher == nullptr) { + sif::error << "AcsBoardAssembly::AcsBoardAssembly: Invalid Power Switcher " + "IF passed" + << std::endl; + } + if (gpioIF == nullptr) { + sif::error << "AcsBoardAssembly::AcsBoardAssembly: Invalid GPIO IF passed" << std::endl; + } + ModeListEntry entry; + initModeTableEntry(helper.mgm0Lis3IdSideA, entry, modeTable); + initModeTableEntry(helper.mgm1Rm3100IdSideA, entry, modeTable); + initModeTableEntry(helper.mgm2Lis3IdSideB, entry, modeTable); + initModeTableEntry(helper.mgm3Rm3100IdSideB, entry, modeTable); + initModeTableEntry(helper.gyro0AdisIdSideA, entry, modeTable); + initModeTableEntry(helper.gyro1L3gIdSideA, entry, modeTable); + initModeTableEntry(helper.gyro2AdisIdSideB, entry, modeTable); + initModeTableEntry(helper.gyro3L3gIdSideB, entry, modeTable); + initModeTableEntry(helper.gpsId, entry, modeTable); +} + +ReturnValue_t AcsBoardAssembly::commandChildren(Mode_t mode, Submode_t submode) { + using namespace duallane; + ReturnValue_t result = returnvalue::OK; + refreshHelperModes(); + // Initialize the mode table to ensure all devices are in a defined state + modeTable[ModeTableIdx::GYRO_0_A].setMode(MODE_OFF); + modeTable[ModeTableIdx::GYRO_0_A].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::GYRO_1_A].setMode(MODE_OFF); + modeTable[ModeTableIdx::GYRO_1_A].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::GYRO_2_B].setMode(MODE_OFF); + modeTable[ModeTableIdx::GYRO_2_B].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::GYRO_3_B].setMode(MODE_OFF); + modeTable[ModeTableIdx::GYRO_3_B].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::MGM_0_A].setMode(MODE_OFF); + modeTable[ModeTableIdx::MGM_0_A].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::MGM_1_A].setMode(MODE_OFF); + modeTable[ModeTableIdx::MGM_1_A].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::MGM_2_B].setMode(MODE_OFF); + modeTable[ModeTableIdx::MGM_2_B].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::MGM_3_B].setMode(MODE_OFF); + modeTable[ModeTableIdx::MGM_3_B].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::GPS].setMode(MODE_OFF); + modeTable[ModeTableIdx::GPS].setSubmode(SUBMODE_NONE); + if (recoveryState == RecoveryState::RECOVERY_IDLE) { + result = checkAndHandleHealthStates(mode, submode); + if (result != returnvalue::OK) { + return result; + } + } + if (recoveryState != RecoveryState::RECOVERY_STARTED) { + if (mode == DeviceHandlerIF::MODE_NORMAL or mode == MODE_ON) { + result = handleNormalOrOnModeCmd(mode, submode); + } + } + HybridIterator tableIter(modeTable.begin(), modeTable.end()); + executeTable(tableIter); + return result; +} + +ReturnValue_t AcsBoardAssembly::checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) { + using namespace duallane; + refreshHelperModes(); + if (wantedSubmode == A_SIDE) { + if ((helper.gyro0SideAMode != wantedMode and helper.gyro1SideAMode != wantedMode) or + (helper.mgm0SideAMode != wantedMode and helper.mgm1SideAMode != wantedMode) or + (helper.gpsMode != MODE_ON) or gps0HealthDevFaulty()) { + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + return returnvalue::OK; + } else if (wantedSubmode == B_SIDE) { + if ((helper.gyro2SideBMode != wantedMode and helper.gyro3SideBMode != wantedMode) or + (helper.mgm2SideBMode != wantedMode and helper.mgm3SideBMode != wantedMode) or + (helper.gpsMode != MODE_ON) or gps1HealthDevFaulty()) { + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + return returnvalue::OK; + } else if (wantedSubmode == DUAL_MODE) { + if ((helper.gyro0SideAMode != wantedMode and helper.gyro1SideAMode != wantedMode and + helper.gyro2AdisIdSideB != wantedMode and helper.gyro3SideBMode != wantedMode) or + (helper.mgm0SideAMode != wantedMode and helper.mgm1SideAMode != wantedMode and + helper.mgm2SideBMode != wantedMode and helper.mgm3SideBMode != wantedMode) or + helper.gpsMode != MODE_ON) { + // Trigger event, but don't start any other transitions. This is the last fallback mode. + if (dualModeErrorSwitch) { + triggerEvent(NOT_ENOUGH_DEVICES_DUAL_MODE, 0, 0); + dualModeErrorSwitch = false; + } + return returnvalue::OK; + } + return returnvalue::OK; + } + return returnvalue::OK; +} + +ReturnValue_t AcsBoardAssembly::handleNormalOrOnModeCmd(Mode_t mode, Submode_t submode) { + using namespace duallane; + ReturnValue_t result = returnvalue::OK; + bool needsSecondStep = false; + handleSideSwitchStates(submode, needsSecondStep); + auto cmdSeq = [&](object_id_t objectId, Mode_t devMode, ModeTableIdx tableIdx) { + if (mode == devMode) { + modeTable[tableIdx].setMode(mode); + } else if (isModeCommandable(objectId, devMode)) { + modeTable[tableIdx].setMode(mode); + modeTable[tableIdx].setSubmode(SUBMODE_NONE); + } + }; + + bool gpsUsable = isGpsUsable(submode); + auto gpsCmd = [&](bool gnss0NReset, bool gnss1NReset, uint8_t gnssSelect) { + if (gpsUsable) { + if (mode == MODE_ON or mode == DeviceHandlerIF::MODE_NORMAL) { + modeTable[ModeTableIdx::GPS].setMode(MODE_ON); + } else if (mode == MODE_OFF) { + gnss0NReset = true; + gnss1NReset = true; + modeTable[ModeTableIdx::GPS].setMode(MODE_OFF); + } + modeTable[ModeTableIdx::GPS].setSubmode(SUBMODE_NONE); + + gpioHandler(gpioIds::GNSS_0_NRESET, gnss0NReset, + "AcsBoardAssembly::handleNormalOrOnModeCmd: Could not pull nReset pin" + "of GNSS 0"); + gpioHandler(gpioIds::GNSS_1_NRESET, gnss1NReset, + "AcsBoardAssembly::handleNormalOrOnModeCmd: Could not pull nReset pin" + "of GNSS 1"); + gpioHandler(gpioIds::GNSS_SELECT, gnssSelect, + "AcsBoardAssembly::handleNormalOrOnModeCmd: Could not pull GNSS select"); + } + }; + switch (submode) { + case (A_SIDE): { + modeTable[ModeTableIdx::GYRO_2_B].setMode(MODE_OFF); + modeTable[ModeTableIdx::GYRO_2_B].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::GYRO_3_B].setMode(MODE_OFF); + modeTable[ModeTableIdx::GYRO_3_B].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::MGM_2_B].setMode(MODE_OFF); + modeTable[ModeTableIdx::MGM_2_B].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::MGM_3_B].setMode(MODE_OFF); + modeTable[ModeTableIdx::MGM_3_B].setSubmode(SUBMODE_NONE); + cmdSeq(helper.gyro0AdisIdSideA, helper.gyro0SideAMode, ModeTableIdx::GYRO_0_A); + cmdSeq(helper.gyro1L3gIdSideA, helper.gyro1SideAMode, ModeTableIdx::GYRO_1_A); + cmdSeq(helper.mgm0Lis3IdSideA, helper.mgm0SideAMode, ModeTableIdx::MGM_0_A); + cmdSeq(helper.mgm1Rm3100IdSideA, helper.mgm1SideAMode, ModeTableIdx::MGM_1_A); + gpsCmd(true, false, 0); + break; + } + case (B_SIDE): { + modeTable[ModeTableIdx::GYRO_0_A].setMode(MODE_OFF); + modeTable[ModeTableIdx::GYRO_0_A].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::GYRO_1_A].setMode(MODE_OFF); + modeTable[ModeTableIdx::GYRO_1_A].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::MGM_0_A].setMode(MODE_OFF); + modeTable[ModeTableIdx::MGM_0_A].setSubmode(SUBMODE_NONE); + modeTable[ModeTableIdx::MGM_1_A].setMode(MODE_OFF); + modeTable[ModeTableIdx::MGM_1_A].setSubmode(SUBMODE_NONE); + cmdSeq(helper.gyro2AdisIdSideB, helper.gyro2SideBMode, ModeTableIdx::GYRO_2_B); + cmdSeq(helper.gyro3L3gIdSideB, helper.gyro3SideBMode, ModeTableIdx::GYRO_3_B); + cmdSeq(helper.mgm2Lis3IdSideB, helper.mgm2SideBMode, ModeTableIdx::MGM_2_B); + cmdSeq(helper.mgm3Rm3100IdSideB, helper.mgm3SideBMode, ModeTableIdx::MGM_3_B); + gpsCmd(false, true, 1); + break; + } + case (DUAL_MODE): { + cmdSeq(helper.gyro0AdisIdSideA, helper.gyro0SideAMode, ModeTableIdx::GYRO_0_A); + cmdSeq(helper.gyro1L3gIdSideA, helper.gyro1SideAMode, ModeTableIdx::GYRO_1_A); + cmdSeq(helper.mgm0Lis3IdSideA, helper.mgm0SideAMode, ModeTableIdx::MGM_0_A); + cmdSeq(helper.mgm1Rm3100IdSideA, helper.mgm1SideAMode, ModeTableIdx::MGM_1_A); + cmdSeq(helper.gyro2AdisIdSideB, helper.gyro2SideBMode, ModeTableIdx::GYRO_2_B); + cmdSeq(helper.gyro3L3gIdSideB, helper.gyro3SideBMode, ModeTableIdx::GYRO_3_B); + cmdSeq(helper.mgm2Lis3IdSideB, helper.mgm2SideBMode, ModeTableIdx::MGM_2_B); + cmdSeq(helper.mgm3Rm3100IdSideB, helper.mgm3SideBMode, ModeTableIdx::MGM_3_B); + if (defaultSubmode == Submodes::A_SIDE) { + gpsCmd(true, true, 0); + } else { + gpsCmd(true, true, 1); + } + break; + } + default: { + sif::error << "AcsBoardAssembly::handleNormalModeCmd: Unknown submode" << std::endl; + } + } + if (needsSecondStep) { + result = NEED_SECOND_STEP; + } + return result; +} + +void AcsBoardAssembly::selectGpsInDualMode(duallane::Submodes side) { + using namespace duallane; + if (submode != Submodes::DUAL_MODE) { + return; + } + ReturnValue_t result = returnvalue::OK; + if (side == Submodes::A_SIDE) { + result = gpioIF->pullLow(gpioIds::GNSS_SELECT); + } else { + result = gpioIF->pullHigh(gpioIds::GNSS_SELECT); + } + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::error << "AcsBoardAssembly::switchGpsInDualMode: Switching GPS failed" << std::endl; +#endif + } +} + +void AcsBoardAssembly::gpioHandler(gpioId_t gpio, bool high, std::string error) { + ReturnValue_t result = returnvalue::OK; + if (high) { + result = gpioIF->pullHigh(gpio); + } else { + result = gpioIF->pullLow(gpio); + } + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::error << error << std::endl; +#endif + } +} + +void AcsBoardAssembly::refreshHelperModes() { + try { + helper.gyro0SideAMode = childrenMap.at(helper.gyro0AdisIdSideA).mode; + helper.gyro1SideAMode = childrenMap.at(helper.gyro1L3gIdSideA).mode; + helper.gyro2SideBMode = childrenMap.at(helper.gyro2AdisIdSideB).mode; + helper.gyro3SideBMode = childrenMap.at(helper.gyro3L3gIdSideB).mode; + helper.mgm0SideAMode = childrenMap.at(helper.mgm0Lis3IdSideA).mode; + helper.mgm1SideAMode = childrenMap.at(helper.mgm1Rm3100IdSideA).mode; + helper.mgm2SideBMode = childrenMap.at(helper.mgm2Lis3IdSideB).mode; + helper.mgm3SideBMode = childrenMap.at(helper.mgm3Rm3100IdSideB).mode; + helper.gpsMode = childrenMap.at(helper.gpsId).mode; + } catch (const std::out_of_range& e) { + sif::error << "AcsBoardAssembly::refreshHelperModes: Invalid map: " << e.what() << std::endl; + } +} + +ReturnValue_t AcsBoardAssembly::initialize() { + for (const auto& child : childrenMap) { + updateChildModeByObjId(child.first, MODE_OFF, 0); + } + return AssemblyBase::initialize(); +} + +ReturnValue_t AcsBoardAssembly::checkAndHandleHealthStates(Mode_t commandedMode, + Submode_t commandedSubmode) { + using namespace returnvalue; + ReturnValue_t status = returnvalue::OK; + bool healthNeedsToBeOverwritten = false; + auto checkAcsBoardSensorGroup = [&](object_id_t o0, object_id_t o1, object_id_t o2, + object_id_t o3) { + HealthState h0 = healthHelper.healthTable->getHealth(o0); + HealthState h1 = healthHelper.healthTable->getHealth(o1); + HealthState h2 = healthHelper.healthTable->getHealth(o2); + HealthState h3 = healthHelper.healthTable->getHealth(o3); + // All device are faulty or permanent faulty, but this is a safe mode assembly, so we need + // to restore the devices. + if ((h0 == FAULTY or h0 == PERMANENT_FAULTY) and (h1 == FAULTY or h1 == PERMANENT_FAULTY) and + (h2 == FAULTY or h2 == PERMANENT_FAULTY) and (h3 == FAULTY or h3 == PERMANENT_FAULTY)) { + uint8_t numPermFaulty = 0; + if (h0 == PERMANENT_FAULTY) { + numPermFaulty++; + } + if (h1 == PERMANENT_FAULTY) { + numPermFaulty++; + } + if (h2 == PERMANENT_FAULTY) { + numPermFaulty++; + } + if (h3 == PERMANENT_FAULTY) { + numPermFaulty++; + } + if (numPermFaulty < 4) { + // Some are faulty and some are permanent faulty, so only set faulty ones to + // EXTERNAL_CONTROL. + if (h0 == FAULTY) { + overwriteDeviceHealth(o0, h0); + } + if (h1 == FAULTY) { + overwriteDeviceHealth(o1, h1); + } + if (h2 == FAULTY) { + overwriteDeviceHealth(o2, h2); + } + if (h3 == FAULTY) { + overwriteDeviceHealth(o3, h3); + } + } else { + // All permanent faulty, so set all to EXTERNAL_CONTROL + overwriteDeviceHealth(o0, h0); + overwriteDeviceHealth(o1, h1); + overwriteDeviceHealth(o2, h2); + overwriteDeviceHealth(o3, h3); + } + healthNeedsToBeOverwritten = true; + } + if (h0 == EXTERNAL_CONTROL or h1 == EXTERNAL_CONTROL or h2 == EXTERNAL_CONTROL or + h3 == EXTERNAL_CONTROL) { + modeHelper.setForced(true); + } + }; + if (healthHelper.healthTable->getHealth(helper.healthDevGps0) == EXTERNAL_CONTROL or + healthHelper.healthTable->getHealth(helper.healthDevGps1) == EXTERNAL_CONTROL) { + modeHelper.setForced(true); + } + if (healthHelper.healthTable->getHealth(helper.healthDevGps0) == PERMANENT_FAULTY and + healthHelper.healthTable->getHealth(helper.healthDevGps1) == FAULTY) { + overwriteDeviceHealth(helper.healthDevGps1, FAULTY); + healthNeedsToBeOverwritten = true; + } else if (healthHelper.healthTable->getHealth(helper.healthDevGps1) == PERMANENT_FAULTY and + healthHelper.healthTable->getHealth(helper.healthDevGps0) == FAULTY) { + overwriteDeviceHealth(helper.healthDevGps0, FAULTY); + } else if (healthHelper.healthTable->isFaulty(helper.healthDevGps0) and + healthHelper.healthTable->isFaulty(helper.healthDevGps1)) { + overwriteDeviceHealth(helper.healthDevGps0, + healthHelper.healthTable->getHealth(helper.healthDevGps0)); + overwriteDeviceHealth(helper.healthDevGps1, + healthHelper.healthTable->getHealth(helper.healthDevGps1)); + healthNeedsToBeOverwritten = true; + } + + if (commandedSubmode == duallane::DUAL_MODE) { + checkAcsBoardSensorGroup(helper.mgm0Lis3IdSideA, helper.mgm1Rm3100IdSideA, + helper.mgm2Lis3IdSideB, helper.mgm3Rm3100IdSideB); + checkAcsBoardSensorGroup(helper.gyro0AdisIdSideA, helper.gyro1L3gIdSideA, + + helper.gyro2AdisIdSideB, helper.gyro3L3gIdSideB); + } + if (healthNeedsToBeOverwritten) { + // If we are overwriting the health states, we are already in a transition to dual mode, + // and we would like that transition to complete. The default behaviour is to go back to the + // old mode. We force our behaviour by overwriting the internal modes. + mode = commandedMode; + submode = commandedSubmode; + return NEED_TO_CHANGE_HEALTH; + } + return status; +} + +void AcsBoardAssembly::handleChildrenLostMode(ReturnValue_t result) { + using namespace duallane; + // Special handling to account for GPS devices being faulty. If the GPS device on the other + // side is marked faulty, directly to to dual side. + if (submode == Submodes::A_SIDE) { + if (gps0HealthDevFaulty()) { + triggerEvent(DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY, submode, 0); + startTransition(mode, Submodes::DUAL_MODE); + return; + } + } else if (submode == Submodes::B_SIDE) { + if (gps1HealthDevFaulty()) { + triggerEvent(DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY, submode, 0); + startTransition(mode, Submodes::DUAL_MODE); + return; + } + } + DualLaneAssemblyBase::handleChildrenLostMode(result); +} + +bool AcsBoardAssembly::gps0HealthDevFaulty() const { + auto health = healthHelper.healthTable->getHealth(helper.healthDevGps0); + if (health == FAULTY or health == PERMANENT_FAULTY) { + return true; + } + return false; +} + +bool AcsBoardAssembly::gps1HealthDevFaulty() const { + auto health = healthHelper.healthTable->getHealth(helper.healthDevGps1); + if (health == FAULTY or health == PERMANENT_FAULTY) { + return true; + } + return false; +} + +bool AcsBoardAssembly::isGpsUsable(uint8_t targetSubmode) const { + if (targetSubmode == duallane::A_SIDE and + healthHelper.healthTable->isFaulty(helper.healthDevGps0)) { + // Causes a OFF command to be sent, which triggers a side switch or a switch to dual side. + return false; + } + if (targetSubmode == duallane::B_SIDE and + healthHelper.healthTable->isFaulty(helper.healthDevGps1)) { + // Causes a OFF command to be sent, which triggers a side switch or a switch to dual side. + return false; + } + auto gpsIter = childrenMap.find(helper.gpsId); + // Check if device is already in target mode + if (gpsIter != childrenMap.end() and gpsIter->second.mode == mode) { + return true; + } + return true; +} diff --git a/mission/system/acs/AcsBoardAssembly.h b/mission/system/acs/AcsBoardAssembly.h new file mode 100644 index 0000000..01e7486 --- /dev/null +++ b/mission/system/acs/AcsBoardAssembly.h @@ -0,0 +1,143 @@ +#ifndef MISSION_SYSTEM_ACSBOARDASSEMBLY_H_ +#define MISSION_SYSTEM_ACSBOARDASSEMBLY_H_ + +#include +#include +#include + +#include "DualLaneAssemblyBase.h" +#include "eive/eventSubsystemIds.h" +#include "mission/system/DualLanePowerStateMachine.h" + +struct AcsBoardHelper { + AcsBoardHelper(object_id_t mgm0Id, object_id_t mgm1Id, object_id_t mgm2Id, object_id_t mgm3Id, + object_id_t gyro0Id, object_id_t gyro1Id, object_id_t gyro2Id, object_id_t gyro3Id, + object_id_t gpsId, object_id_t gps0HealthDev, object_id_t gps1HealthDev) + : mgm0Lis3IdSideA(mgm0Id), + mgm1Rm3100IdSideA(mgm1Id), + mgm2Lis3IdSideB(mgm2Id), + mgm3Rm3100IdSideB(mgm3Id), + gyro0AdisIdSideA(gyro0Id), + gyro1L3gIdSideA(gyro1Id), + gyro2AdisIdSideB(gyro2Id), + gyro3L3gIdSideB(gyro3Id), + gpsId(gpsId), + healthDevGps0(gps0HealthDev), + healthDevGps1(gps1HealthDev) {} + + object_id_t mgm0Lis3IdSideA = objects::NO_OBJECT; + object_id_t mgm1Rm3100IdSideA = objects::NO_OBJECT; + object_id_t mgm2Lis3IdSideB = objects::NO_OBJECT; + object_id_t mgm3Rm3100IdSideB = objects::NO_OBJECT; + + object_id_t gyro0AdisIdSideA = objects::NO_OBJECT; + object_id_t gyro1L3gIdSideA = objects::NO_OBJECT; + object_id_t gyro2AdisIdSideB = objects::NO_OBJECT; + object_id_t gyro3L3gIdSideB = objects::NO_OBJECT; + + object_id_t gpsId = objects::NO_OBJECT; + + object_id_t healthDevGps0 = objects::NO_OBJECT; + object_id_t healthDevGps1 = objects::NO_OBJECT; + + Mode_t gyro0SideAMode = HasModesIF::MODE_OFF; + Mode_t gyro1SideAMode = HasModesIF::MODE_OFF; + Mode_t gyro2SideBMode = HasModesIF::MODE_OFF; + Mode_t gyro3SideBMode = HasModesIF::MODE_OFF; + Mode_t mgm0SideAMode = HasModesIF::MODE_OFF; + Mode_t mgm1SideAMode = HasModesIF::MODE_OFF; + Mode_t mgm2SideBMode = HasModesIF::MODE_OFF; + Mode_t mgm3SideBMode = HasModesIF::MODE_OFF; + Mode_t gpsMode = HasModesIF::MODE_OFF; +}; + +enum ModeTableIdx : uint8_t { + MGM_0_A = 0, + MGM_1_A = 1, + MGM_2_B = 2, + MGM_3_B = 3, + GYRO_0_A = 4, + GYRO_1_A = 5, + GYRO_2_B = 6, + GYRO_3_B = 7, + GPS = 8 +}; + +class PowerSwitchIF; +class GpioIF; + +/** + * @brief Assembly class which manages redundant ACS board sides + * @details + * This class takes care of ensuring that enough devices on the ACS board are available at all + * times. It does so by doing autonomous transitions to the redundant side or activating both sides + * if not enough devices are available. + * + * This class also takes care of switching on the A side and/or B side power lanes. Normally, + * doing this task would be performed by the device handlers, but this is not possible for the + * ACS board where multiple sensors share the same power supply. + */ +class AcsBoardAssembly : public DualLaneAssemblyBase { + public: + // Use these variables instead of magic numbers when generator was updated + // TRANSITION_OTHER_SIDE_FAILED_ID + // NOT_ENOUGH_DEVICES_DUAL_MODE_ID + // POWER_STATE_MACHINE_TIMEOUT_ID + // SIDE_SWITCH_TRANSITION_NOT_ALLOWED_ID + static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::ACS_BOARD_ASS; + static constexpr Event TRANSITION_OTHER_SIDE_FAILED = + event::makeEvent(SUBSYSTEM_ID, 0, severity::HIGH); + static constexpr Event NOT_ENOUGH_DEVICES_DUAL_MODE = + event::makeEvent(SUBSYSTEM_ID, 1, severity::HIGH); + static constexpr Event POWER_STATE_MACHINE_TIMEOUT = + event::makeEvent(SUBSYSTEM_ID, 2, severity::MEDIUM); + //! [EXPORT] : [COMMENT] Not implemented, would increase already high complexity. Operator + //! should instead command the assembly off first and then command the assembly on into the + //! desired mode/submode combination + static constexpr Event SIDE_SWITCH_TRANSITION_NOT_ALLOWED = + event::makeEvent(SUBSYSTEM_ID, 3, severity::LOW); + //! [EXPORT] : [COMMENT] This is triggered when the assembly would have normally switched + //! the board side, but the GPS device of the other side was marked faulty. + //! P1: Current submode. + static constexpr Event DIRECT_TRANSITION_TO_DUAL_OTHER_GPS_FAULTY = + event::makeEvent(SUBSYSTEM_ID, 4, severity::MEDIUM); + + static constexpr uint8_t NUMBER_DEVICES_MODE_TABLE = 9; + + AcsBoardAssembly(object_id_t objectId, PowerSwitchIF* pwrSwitcher, AcsBoardHelper helper, + GpioIF* gpioIF); + + /** + * In dual mode, the A side or the B side GPS device can be used, but not both. + * This function can be used to switch the used GPS device. + * @param side + */ + void selectGpsInDualMode(duallane::Submodes side); + + private: + static constexpr power::Switches SWITCH_A = power::Switches::PDU1_CH7_ACS_A_SIDE_3V3; + static constexpr power::Switches SWITCH_B = power::Switches::PDU2_CH7_ACS_BOARD_SIDE_B_3V3; + + AcsBoardHelper helper; + GpioIF* gpioIF = nullptr; + + FixedArrayList modeTable; + void gpioHandler(gpioId_t gpio, bool high, std::string error); + + ReturnValue_t initialize() override; + + // AssemblyBase overrides + ReturnValue_t commandChildren(Mode_t mode, Submode_t submode) override; + ReturnValue_t checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) override; + + void handleChildrenLostMode(ReturnValue_t result) override; + + ReturnValue_t handleNormalOrOnModeCmd(Mode_t mode, Submode_t submode); + ReturnValue_t checkAndHandleHealthStates(Mode_t deviceMode, Submode_t deviceSubmode); + void refreshHelperModes(); + bool gps0HealthDevFaulty() const; + bool gps1HealthDevFaulty() const; + bool isGpsUsable(uint8_t targetSubmode) const; +}; + +#endif /* MISSION_SYSTEM_ACSBOARDASSEMBLY_H_ */ diff --git a/mission/system/acs/AcsBoardFdir.cpp b/mission/system/acs/AcsBoardFdir.cpp new file mode 100644 index 0000000..5e97c37 --- /dev/null +++ b/mission/system/acs/AcsBoardFdir.cpp @@ -0,0 +1,6 @@ +#include "AcsBoardFdir.h" + +#include "eive/objects.h" + +AcsBoardFdir::AcsBoardFdir(object_id_t sensorId) + : DeviceHandlerFailureIsolation(sensorId, objects::ACS_BOARD_ASS) {} diff --git a/mission/system/acs/AcsBoardFdir.h b/mission/system/acs/AcsBoardFdir.h new file mode 100644 index 0000000..da843f1 --- /dev/null +++ b/mission/system/acs/AcsBoardFdir.h @@ -0,0 +1,11 @@ +#ifndef MISSION_SYSTEM_ACSBOARDFDIR_H_ +#define MISSION_SYSTEM_ACSBOARDFDIR_H_ + +#include + +class AcsBoardFdir : public DeviceHandlerFailureIsolation { + public: + AcsBoardFdir(object_id_t sensorId); +}; + +#endif /* MISSION_SYSTEM_ACSBOARDFDIR_H_ */ diff --git a/mission/system/acs/AcsSubsystem.cpp b/mission/system/acs/AcsSubsystem.cpp new file mode 100644 index 0000000..2b1d9ef --- /dev/null +++ b/mission/system/acs/AcsSubsystem.cpp @@ -0,0 +1,17 @@ +#include "AcsSubsystem.h" + +#include +#include + +#include "fsfw/modes/ModeMessage.h" +#include "mission/acs/defs.h" + +AcsSubsystem::AcsSubsystem(object_id_t setObjectId, uint32_t maxNumberOfSequences, + uint32_t maxNumberOfTables) + : Subsystem(setObjectId, maxNumberOfSequences, maxNumberOfTables) {} + +void AcsSubsystem::announceMode(bool recursive) { + const char* modeStr = acs::getModeStr(static_cast(mode)); + sif::info << "ACS subsystem is now in " << modeStr << " mode" << std::endl; + return Subsystem::announceMode(recursive); +} diff --git a/mission/system/acs/AcsSubsystem.h b/mission/system/acs/AcsSubsystem.h new file mode 100644 index 0000000..fc6b238 --- /dev/null +++ b/mission/system/acs/AcsSubsystem.h @@ -0,0 +1,14 @@ +#ifndef MISSION_SYSTEM_ACSSUBSYSTEM_H_ +#define MISSION_SYSTEM_ACSSUBSYSTEM_H_ + +#include + +class AcsSubsystem : public Subsystem { + public: + AcsSubsystem(object_id_t setObjectId, uint32_t maxNumberOfSequences, uint32_t maxNumberOfTables); + + private: + void announceMode(bool recursive) override; +}; + +#endif /* MISSION_SYSTEM_ACSSUBSYSTEM_H_ */ diff --git a/mission/system/acs/CMakeLists.txt b/mission/system/acs/CMakeLists.txt new file mode 100644 index 0000000..b7f927a --- /dev/null +++ b/mission/system/acs/CMakeLists.txt @@ -0,0 +1,13 @@ +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE AcsBoardAssembly.cpp + AcsSubsystem.cpp + StrAssembly.cpp + DualLaneAssemblyBase.cpp + ImtqAssembly.cpp + RwAssembly.cpp + SusAssembly.cpp + AcsBoardFdir.cpp + acsModeTree.cpp + SusFdir.cpp + StrFdir.cpp) diff --git a/mission/system/acs/DualLaneAssemblyBase.cpp b/mission/system/acs/DualLaneAssemblyBase.cpp new file mode 100644 index 0000000..dc97908 --- /dev/null +++ b/mission/system/acs/DualLaneAssemblyBase.cpp @@ -0,0 +1,286 @@ +#include "DualLaneAssemblyBase.h" + +#include + +#include "OBSWConfig.h" + +DualLaneAssemblyBase::DualLaneAssemblyBase(object_id_t objectId, PowerSwitchIF* pwrSwitcher, + power::Switches switch1, power::Switches switch2, + Event pwrTimeoutEvent, Event sideSwitchNotAllowedEvent, + Event transitionOtherSideFailedEvent) + : AssemblyBase(objectId, 20), + pwrStateMachine(switch1, switch2, pwrSwitcher), + pwrTimeoutEvent(pwrTimeoutEvent), + sideSwitchNotAllowedEvent(sideSwitchNotAllowedEvent), + transitionOtherSideFailedEvent(transitionOtherSideFailedEvent) { + eventQueue = QueueFactory::instance()->createMessageQueue(10); +} + +void DualLaneAssemblyBase::performChildOperation() { + using namespace duallane; + if (pwrStateMachine.active()) { + ReturnValue_t result = pwrStateMachineWrapper(); + if (result != returnvalue::OK) { + handleModeTransitionFailed(result); + } + } + // Only perform the regular child operation if the power state machine is not active. + // It does not make any sense to command device modes while the power switcher is busy + // switching off or on devices. + if (not pwrStateMachine.active()) { + AssemblyBase::performChildOperation(); + // TODO: Handle Event Queue + } +} + +void DualLaneAssemblyBase::startTransition(Mode_t mode, Submode_t submode) { + using namespace duallane; + pwrStateMachine.reset(); + dualToSingleSideTransition = false; + sideSwitchState = SideSwitchState::NONE; + + if (mode != MODE_OFF) { + // Special exception: A transition from dual side to single mode must be handled like + // going OFF. + if ((this->mode == MODE_ON or this->mode == DeviceHandlerIF::MODE_NORMAL) and + this->submode == DUAL_MODE and submode != DUAL_MODE) { + dualToSingleSideTransition = true; + AssemblyBase::startTransition(mode, submode); + return; + } + if (sideSwitchState == SideSwitchState::NONE and sideSwitchTransition(mode, submode)) { + sideSwitchState = SideSwitchState::REQUESTED; + } + uint8_t pwrSubmode = submode; + if (sideSwitchState == SideSwitchState::REQUESTED) { + pwrSubmode = duallane::DUAL_MODE; + } + // If anything other than MODE_OFF is commanded, perform power state machine first + // Cache the target modes, required by power state machine + pwrStateMachine.start(mode, pwrSubmode); + // Cache these for later after the power state machine has finished + targetMode = mode; + targetSubmode = submode; + } else { + AssemblyBase::startTransition(mode, submode); + } +} + +bool DualLaneAssemblyBase::isModeCommandable(object_id_t object, Mode_t mode) { + if (healthHelper.healthTable->isFaulty(object)) { + return false; + } + + // Check if device is already in target mode + if (childrenMap[object].mode == mode) { + return true; + } + // Check for external control health state is done by base class. + return true; +} + +ReturnValue_t DualLaneAssemblyBase::pwrStateMachineWrapper() { + using namespace power; + OpCodes opCode = pwrStateMachine.fsm(); + if (customRecoveryStates == RecoveryCustomStates::IDLE) { + if (opCode == OpCodes::NONE) { + return returnvalue::OK; + } else if (opCode == OpCodes::TO_OFF_DONE) { + // Will be called for transitions to MODE_OFF, where everything is done after power switching + finishModeOp(); + } else if (opCode == OpCodes::TO_NOT_OFF_DONE) { + if (dualToSingleSideTransition) { + finishModeOp(); + } else { + // Will be called for transitions from MODE_OFF to anything else, where the mode still has + // to be commanded after power switching + AssemblyBase::startTransition(targetMode, targetSubmode); + } + } else if (opCode == OpCodes::TIMEOUT_OCCURED) { + if (powerRetryCounter == 0) { + powerRetryCounter++; + pwrStateMachine.reset(); + } else { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::warning << "Timeout occured in power state machine" << std::endl; +#endif + triggerEvent(pwrTimeoutEvent, 0, 0); + return returnvalue::FAILED; + } + } + } + return returnvalue::OK; +} + +ReturnValue_t DualLaneAssemblyBase::isModeCombinationValid(Mode_t mode, Submode_t submode) { + using namespace duallane; + if (mode != MODE_OFF and (submode != A_SIDE and submode != B_SIDE and submode != DUAL_MODE)) { + return HasModesIF::INVALID_SUBMODE; + } + if (mode == MODE_OFF and submode != SUBMODE_NONE) { + return HasModesIF::INVALID_SUBMODE; + } + return returnvalue::OK; +} + +void DualLaneAssemblyBase::handleModeReached() { + using namespace duallane; + if (targetMode == MODE_OFF) { + pwrStateMachine.start(targetMode, targetSubmode); + // Now we can switch off the power. After that, the AssemblyBase::handleModeReached function + // will be called + // Ignore failures for now. + pwrStateMachineWrapper(); + } else { + // For dual to single side transition, devices should be logically off, but the switch + // handling still needs to be done. + if (dualToSingleSideTransition) { + if (sideSwitchState == SideSwitchState::DISABLE_OTHER_SIDE) { + pwrStateMachine.start(targetMode, targetSubmodeForSideSwitch); + } else { + pwrStateMachine.start(targetMode, targetSubmode); + } + pwrStateMachineWrapper(); + return; + } + finishModeOp(); + } +} + +MessageQueueId_t DualLaneAssemblyBase::getEventReceptionQueue() { return eventQueue->getId(); } + +void DualLaneAssemblyBase::handleChildrenLostMode(ReturnValue_t result) { + using namespace duallane; + // Some ACS board components are required for Safe-Mode. It would be good if the software + // transitions from A side to B side and from B side to dual mode autonomously + // to ensure that that enough sensors are available without an operators intervention. + // Therefore, the lost mode handler was overwritten to start these transitions + Submode_t nextSubmode = Submodes::A_SIDE; + if (submode == Submodes::A_SIDE) { + nextSubmode = Submodes::B_SIDE; + } + if (not tryingOtherSide) { + triggerEvent(CANT_KEEP_MODE, mode, submode); + startTransition(mode, nextSubmode); + tryingOtherSide = true; + } else { + // Not sure when this would happen. This flag is reset if the mode was reached. If it + // was not reached, the transition failure handler should be called. + sif::error << "DualLaneAssemblyBase::handleChildrenLostMode: Wrong handler called" << std::endl; + triggerEvent(transitionOtherSideFailedEvent, mode, targetSubmode); + startTransition(mode, Submodes::DUAL_MODE); + } +} + +void DualLaneAssemblyBase::handleModeTransitionFailed(ReturnValue_t result) { + using namespace duallane; + Submode_t nextSubmode = Submodes::A_SIDE; + if (submode == Submodes::A_SIDE) { + nextSubmode = Submodes::B_SIDE; + } + // Check whether the transition was started because the mode could not be kept (not commanded). + // If this is not the case, start transition to other side. If it is the case, start + // transition to dual mode. + if (not tryingOtherSide) { + triggerEvent(CANT_KEEP_MODE, mode, submode); + startTransition(targetMode, nextSubmode); + tryingOtherSide = true; + } else { + triggerEvent(transitionOtherSideFailedEvent, targetMode, targetSubmode); + startTransition(targetMode, Submodes::DUAL_MODE); + } +} + +bool DualLaneAssemblyBase::checkAndHandleRecovery() { + using namespace power; + OpCodes opCode = OpCodes::NONE; + if (recoveryState == RECOVERY_IDLE) { + return AssemblyBase::checkAndHandleRecovery(); + } + if (customRecoveryStates == IDLE) { + pwrStateMachine.start(MODE_OFF, 0); + customRecoveryStates = RecoveryCustomStates::POWER_SWITCHING_OFF; + } + if (customRecoveryStates == POWER_SWITCHING_OFF) { + opCode = pwrStateMachine.fsm(); + if (opCode == OpCodes::TO_OFF_DONE or opCode == OpCodes::TIMEOUT_OCCURED) { + customRecoveryStates = RecoveryCustomStates::POWER_SWITCHING_ON; + // Command power back on in any case. + pwrStateMachine.start(HasModesIF::MODE_ON, targetSubmode); + } + } + if (customRecoveryStates == POWER_SWITCHING_ON) { + opCode = pwrStateMachine.fsm(); + if (opCode == OpCodes::TO_NOT_OFF_DONE or opCode == OpCodes::TIMEOUT_OCCURED) { + customRecoveryStates = RecoveryCustomStates::DONE; + } + } + if (customRecoveryStates == DONE) { + bool pendingRecovery = AssemblyBase::checkAndHandleRecovery(); + if (not pendingRecovery) { + pwrStateMachine.reset(); + customRecoveryStates = RecoveryCustomStates::IDLE; + } + // For a recovery on one side, only do the recovery once + for (auto& child : childrenMap) { + if (healthHelper.healthTable->getHealth(child.first) == HasHealthIF::NEEDS_RECOVERY) { + sendHealthCommand(child.second.commandQueue, HEALTHY); + child.second.healthChanged = false; + } + } + return pendingRecovery; + } + return true; +} + +bool DualLaneAssemblyBase::sideSwitchTransition(Mode_t mode, Submode_t submode) { + using namespace duallane; + if (this->mode == MODE_OFF) { + return false; + } + if (this->mode == MODE_ON or this->mode == DeviceHandlerIF::MODE_NORMAL) { + if ((this->submode == Submodes::A_SIDE and submode == Submodes::B_SIDE) or + (this->submode == Submodes::B_SIDE and submode == Submodes::A_SIDE)) { + return true; + } + return false; + } + return false; +} + +void DualLaneAssemblyBase::handleSideSwitchStates(uint8_t& submode, bool& needsSecondStep) { + if (sideSwitchState == SideSwitchState::REQUESTED) { + sideSwitchState = SideSwitchState::TO_DUAL; + } + // Switch to dual side first, and later switch back to the otherside + if (sideSwitchState == SideSwitchState::TO_DUAL) { + targetSubmodeForSideSwitch = static_cast(submode); + submode = duallane::Submodes::DUAL_MODE; + sideSwitchState = SideSwitchState::DISABLE_OTHER_SIDE; + // TODO: Ugly hack. The base class should support arbitrary number of steps.. + needsSecondStep = true; + } else if (sideSwitchState == SideSwitchState::DISABLE_OTHER_SIDE) { + // Set this flag because the power needs to be switched off. + dualToSingleSideTransition = true; + submode = targetSubmodeForSideSwitch; + } +} + +void DualLaneAssemblyBase::finishModeOp() { + using namespace duallane; + AssemblyBase::handleModeReached(); + pwrStateMachine.reset(); + powerRetryCounter = 0; + tryingOtherSide = false; + sideSwitchState = SideSwitchState::NONE; + dualToSingleSideTransition = false; + dualModeErrorSwitch = true; +} + +void DualLaneAssemblyBase::setPreferredSide(duallane::Submodes submode) { + using namespace duallane; + if (submode != Submodes::A_SIDE and submode != Submodes::B_SIDE) { + return; + } + this->defaultSubmode = submode; +} diff --git a/mission/system/acs/DualLaneAssemblyBase.h b/mission/system/acs/DualLaneAssemblyBase.h new file mode 100644 index 0000000..a30bad5 --- /dev/null +++ b/mission/system/acs/DualLaneAssemblyBase.h @@ -0,0 +1,111 @@ +#ifndef MISSION_SYSTEM_DUALLANEASSEMBLYBASE_H_ +#define MISSION_SYSTEM_DUALLANEASSEMBLYBASE_H_ + +#include +#include + +/** + * @brief Encapsulates assemblies which are also responsible for dual lane power switching + * @details + * This is the base class for both the ACS board and the SUS board. Both boards have redundant + * power lanes and are required for the majority of satellite modes. Therefore, there is a lot + * of common code, for example the power switching. + */ +class DualLaneAssemblyBase : public AssemblyBase, public ConfirmsFailuresIF { + public: + static constexpr UniqueEventId_t TRANSITION_OTHER_SIDE_FAILED_ID = 0; + static constexpr UniqueEventId_t NOT_ENOUGH_DEVICES_DUAL_MODE_ID = 1; + static constexpr UniqueEventId_t POWER_STATE_MACHINE_TIMEOUT_ID = 2; + static constexpr UniqueEventId_t SIDE_SWITCH_TRANSITION_NOT_ALLOWED_ID = 3; + + DualLaneAssemblyBase(object_id_t objectId, PowerSwitchIF* pwrSwitcher, power::Switches switch1, + power::Switches switch2, Event pwrSwitchTimeoutEvent, + Event sideSwitchNotAllowedEvent, Event transitionOtherSideFailedEvent); + + protected: + // This helper object complete encapsulates power switching + DualLanePowerStateMachine pwrStateMachine; + Event pwrTimeoutEvent; + Event sideSwitchNotAllowedEvent; + Event transitionOtherSideFailedEvent; + uint8_t powerRetryCounter = 0; + bool tryingOtherSide = false; + bool dualModeErrorSwitch = true; + bool dualToSingleSideTransition = false; + duallane::Submodes defaultSubmode = duallane::Submodes::A_SIDE; + + enum SideSwitchState { NONE, REQUESTED, TO_DUAL, DISABLE_OTHER_SIDE }; + + SideSwitchState sideSwitchState = SideSwitchState::NONE; + duallane::Submodes targetSubmodeForSideSwitch = duallane::Submodes::B_SIDE; + + power::RecoveryCustomStates customRecoveryStates = power::RecoveryCustomStates::IDLE; + + MessageQueueIF* eventQueue = nullptr; + + /** + * To be called in mode command packer function of the child class. + * @param submode + * @param needsSecondStep + */ + void handleSideSwitchStates(uint8_t& submode, bool& needsSecondStep); + + /** + * Check whether it makes sense to send mode commands to the device. + * @param object + * @param mode + * @return + */ + bool isModeCommandable(object_id_t object, Mode_t mode); + + /** + * Thin wrapper function which is required because the helper class + * can not access protected member functions. + * @param mode + * @param submode + */ + virtual ReturnValue_t pwrStateMachineWrapper(); + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; + + /** + * Custom recovery implementation to ensure that the power lines are commanded off for a + * recovery. + * @return + */ + virtual bool checkAndHandleRecovery() override; + void setPreferredSide(duallane::Submodes submode); + virtual void performChildOperation() override; + virtual void startTransition(Mode_t mode, Submode_t submode) override; + virtual void handleChildrenLostMode(ReturnValue_t result) override; + virtual void handleModeTransitionFailed(ReturnValue_t result) override; + virtual void handleModeReached() override; + ReturnValue_t checkAndHandleHealthState(Mode_t deviceMode, Submode_t deviceSubmode); + + MessageQueueId_t getEventReceptionQueue() override; + + bool sideSwitchTransition(Mode_t mode, Submode_t submode); + + /** + * Implemented by user. Will be called if a full mode operation has finished. + * This includes both the regular mode state machine operations and the power state machine + * operations + */ + virtual void finishModeOp(); + + template + void initModeTableEntry(object_id_t id, ModeListEntry& entry, + FixedArrayList& modeTable); + + private: +}; + +template +inline void DualLaneAssemblyBase::initModeTableEntry( + object_id_t id, ModeListEntry& entry, FixedArrayList& modeTable) { + entry.setObject(id); + entry.setMode(MODE_OFF); + entry.setSubmode(SUBMODE_NONE); + modeTable.insert(entry); +} + +#endif /* MISSION_SYSTEM_DUALLANEASSEMBLYBASE_H_ */ diff --git a/mission/system/acs/ImtqAssembly.cpp b/mission/system/acs/ImtqAssembly.cpp new file mode 100644 index 0000000..e973a11 --- /dev/null +++ b/mission/system/acs/ImtqAssembly.cpp @@ -0,0 +1,57 @@ +#include "ImtqAssembly.h" + +#include + +using namespace returnvalue; + +ImtqAssembly::ImtqAssembly(object_id_t objectId) : AssemblyBase(objectId) { + ModeListEntry entry; + entry.setObject(objects::IMTQ_HANDLER); + entry.setMode(MODE_OFF); + entry.setSubmode(SUBMODE_NONE); + commandTable.insert(entry); +} + +ReturnValue_t ImtqAssembly::commandChildren(Mode_t mode, Submode_t submode) { + commandTable[0].setMode(mode); + commandTable[0].setSubmode(submode); + if (recoveryState == RECOVERY_IDLE) { + ReturnValue_t result = checkAndHandleHealthState(mode, submode); + if (result == NEED_TO_CHANGE_HEALTH) { + return result; + } + } + HybridIterator iter(commandTable.begin(), commandTable.end()); + executeTable(iter); + return returnvalue::OK; +} + +ReturnValue_t ImtqAssembly::checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) { + if (childrenMap[objects::IMTQ_HANDLER].mode != wantedMode) { + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + return returnvalue::OK; +} + +ReturnValue_t ImtqAssembly::isModeCombinationValid(Mode_t mode, Submode_t submode) { + if (mode == MODE_ON or mode == DeviceHandlerIF::MODE_NORMAL or mode == MODE_OFF) { + if (submode != SUBMODE_NONE) { + return HasModesIF::INVALID_SUBMODE; + } + return returnvalue::OK; + } + return HasModesIF::INVALID_MODE; +} + +ReturnValue_t ImtqAssembly::checkAndHandleHealthState(Mode_t deviceMode, Submode_t deviceSubmode) { + HealthState health = healthHelper.healthTable->getHealth(objects::IMTQ_HANDLER); + if (health == FAULTY or health == PERMANENT_FAULTY) { + overwriteDeviceHealth(objects::IMTQ_HANDLER, health); + return NEED_TO_CHANGE_HEALTH; + } else if (health == EXTERNAL_CONTROL) { + modeHelper.setForced(true); + } + return OK; +} + +void ImtqAssembly::handleChildrenLostMode(ReturnValue_t result) { startTransition(mode, submode); } diff --git a/mission/system/acs/ImtqAssembly.h b/mission/system/acs/ImtqAssembly.h new file mode 100644 index 0000000..5906170 --- /dev/null +++ b/mission/system/acs/ImtqAssembly.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class ImtqAssembly : public AssemblyBase { + public: + ImtqAssembly(object_id_t objectId); + + private: + FixedArrayList commandTable; + + ReturnValue_t commandChildren(Mode_t mode, Submode_t submode) override; + ReturnValue_t checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) override; + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; + void handleChildrenLostMode(ReturnValue_t result) override; + + ReturnValue_t checkAndHandleHealthState(Mode_t deviceMode, Submode_t deviceSubmode); +}; diff --git a/mission/system/acs/RwAssembly.cpp b/mission/system/acs/RwAssembly.cpp new file mode 100644 index 0000000..68b7c2d --- /dev/null +++ b/mission/system/acs/RwAssembly.cpp @@ -0,0 +1,98 @@ +#include "RwAssembly.h" + +RwAssembly::RwAssembly(object_id_t objectId, PowerSwitchIF* pwrSwitcher, power::Switch_t switchId, + RwHelper helper) + : SharedPowerAssemblyBase(objectId, pwrSwitcher, switchId), helper(helper) { + ModeListEntry entry; + for (uint8_t idx = 0; idx < NUMBER_RWS; idx++) { + entry.setObject(helper.rwIds[idx]); + entry.setMode(MODE_OFF); + entry.setSubmode(SUBMODE_NONE); + modeTable.insert(entry); + } +} + +ReturnValue_t RwAssembly::commandChildren(Mode_t mode, Submode_t submode) { + ReturnValue_t result = returnvalue::OK; + // Initialize the mode table to ensure all devices are in a defined state + for (uint8_t idx = 0; idx < NUMBER_RWS; idx++) { + modeTable[idx].setMode(MODE_OFF); + modeTable[idx].setSubmode(SUBMODE_NONE); + } + if (recoveryState != RecoveryState::RECOVERY_STARTED) { + if (mode == DeviceHandlerIF::MODE_NORMAL or mode == MODE_ON) { + result = handleNormalOrOnModeCmd(mode, submode); + } + } + HybridIterator tableIter(modeTable.begin(), modeTable.end()); + executeTable(tableIter); + return result; +} + +ReturnValue_t RwAssembly::checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) { + int devsInCorrectMode = 0; + try { + for (uint8_t idx = 0; idx < NUMBER_RWS; idx++) { + if (childrenMap.at(helper.rwIds[idx]).mode == wantedMode) { + devsInCorrectMode++; + } + } + } catch (const std::out_of_range& e) { + sif::error << "RW ASSY: Invalid children map: " << e.what() << std::endl; + } + if (devsInCorrectMode < 3) { + if (warningSwitch) { + sif::warning << "RW ASSY: Only " << devsInCorrectMode << " devices in correct mode" + << std::endl; + warningSwitch = false; + } + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + return returnvalue::OK; +} + +ReturnValue_t RwAssembly::isModeCombinationValid(Mode_t mode, Submode_t submode) { + if (mode == MODE_ON or mode == MODE_OFF or mode == DeviceHandlerIF::MODE_NORMAL) { + return returnvalue::OK; + } + return HasModesIF::INVALID_MODE; +} + +ReturnValue_t RwAssembly::handleNormalOrOnModeCmd(Mode_t mode, Submode_t submode) { + ReturnValue_t result = returnvalue::OK; + object_id_t objId = 0; + try { + for (uint8_t idx = 0; idx < NUMBER_RWS; idx++) { + if (isUseable(objId, mode)) { + modeTable[idx].setMode(mode); + modeTable[idx].setSubmode(submode); + } + } + } catch (const std::out_of_range& e) { + sif::error << "RW ASSY: Invalid children map: " << e.what() << std::endl; + } + return result; +} + +bool RwAssembly::isUseable(object_id_t object, Mode_t mode) { + if (healthHelper.healthTable->isFaulty(object)) { + return false; + } + + // Check if device is already in target mode + if (childrenMap[object].mode == mode) { + return true; + } + + if (healthHelper.healthTable->isCommandable(object)) { + return true; + } + return false; +} + +ReturnValue_t RwAssembly::initialize() { + for (auto objId : helper.rwIds) { + updateChildModeByObjId(objId, MODE_OFF, SUBMODE_NONE); + } + return AssemblyBase::initialize(); +} diff --git a/mission/system/acs/RwAssembly.h b/mission/system/acs/RwAssembly.h new file mode 100644 index 0000000..178595b --- /dev/null +++ b/mission/system/acs/RwAssembly.h @@ -0,0 +1,41 @@ +#ifndef MISSION_SYSTEM_RWASS_H_ +#define MISSION_SYSTEM_RWASS_H_ + +#include +#include + +struct RwHelper { + RwHelper(std::array rwIds) : rwIds(rwIds) {} + + std::array rwIds = {}; +}; + +class RwAssembly : public SharedPowerAssemblyBase { + public: + RwAssembly(object_id_t objectId, PowerSwitchIF* pwrSwitcher, power::Switch_t switchId, + RwHelper helper); + + private: + static constexpr uint8_t NUMBER_RWS = 4; + RwHelper helper; + bool warningSwitch = true; + FixedArrayList modeTable; + + ReturnValue_t initialize() override; + + ReturnValue_t handleNormalOrOnModeCmd(Mode_t mode, Submode_t submode); + /** + * Check whether it makes sense to send mode commands to the device + * @param object + * @param mode + * @return + */ + bool isUseable(object_id_t object, Mode_t mode); + + // AssemblyBase implementation + ReturnValue_t commandChildren(Mode_t mode, Submode_t submode) override; + ReturnValue_t checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) override; + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; +}; + +#endif /* MISSION_SYSTEM_RWASS_H_ */ diff --git a/mission/system/acs/StrAssembly.cpp b/mission/system/acs/StrAssembly.cpp new file mode 100644 index 0000000..a6ae600 --- /dev/null +++ b/mission/system/acs/StrAssembly.cpp @@ -0,0 +1,44 @@ +#include "StrAssembly.h" + +#include + +#include "mission/acs/str/strHelpers.h" + +StrAssembly::StrAssembly(object_id_t objectId) : AssemblyBase(objectId) { + ModeListEntry entry; + entry.setObject(objects::STAR_TRACKER); + entry.setMode(MODE_OFF); + entry.setSubmode(SUBMODE_NONE); + commandTable.insert(entry); +} + +ReturnValue_t StrAssembly::commandChildren(Mode_t mode, Submode_t submode) { + // To ensure consistent state. + commandTable[0].setMode(MODE_OFF); + commandTable[0].setSubmode(submode); + if (healthHelper.healthTable->getHealth(objects::STAR_TRACKER) != FAULTY) { + commandTable[0].setMode(mode); + commandTable[0].setSubmode(submode); + } + HybridIterator iter(commandTable.begin(), commandTable.end()); + executeTable(iter); + return returnvalue::OK; +} + +ReturnValue_t StrAssembly::checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) { + if (childrenMap[objects::STAR_TRACKER].mode != wantedMode) { + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + return returnvalue::OK; +} + +ReturnValue_t StrAssembly::isModeCombinationValid(Mode_t mode, Submode_t submode) { + if ((mode == DeviceHandlerIF::MODE_NORMAL or mode == MODE_OFF) and submode != SUBMODE_NONE) { + return HasModesIF::INVALID_SUBMODE; + } + if (mode == MODE_ON and + (submode != startracker::SUBMODE_BOOTLOADER and submode != startracker::SUBMODE_FIRMWARE)) { + return HasModesIF::INVALID_SUBMODE; + } + return returnvalue::OK; +} diff --git a/mission/system/acs/StrAssembly.h b/mission/system/acs/StrAssembly.h new file mode 100644 index 0000000..417a243 --- /dev/null +++ b/mission/system/acs/StrAssembly.h @@ -0,0 +1,18 @@ +#ifndef MISSION_SYSTEM_OBJECTS_STRASSEMBLY_H_ +#define MISSION_SYSTEM_OBJECTS_STRASSEMBLY_H_ + +#include "fsfw/devicehandlers/AssemblyBase.h" + +class StrAssembly : public AssemblyBase { + public: + StrAssembly(object_id_t objectId); + + private: + FixedArrayList commandTable; + + ReturnValue_t commandChildren(Mode_t mode, Submode_t submode) override; + ReturnValue_t checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) override; + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; +}; + +#endif /* MISSION_SYSTEM_OBJECTS_STRASSEMBLY_H_ */ diff --git a/mission/system/acs/StrFdir.cpp b/mission/system/acs/StrFdir.cpp new file mode 100644 index 0000000..91e8326 --- /dev/null +++ b/mission/system/acs/StrFdir.cpp @@ -0,0 +1,26 @@ +#include "StrFdir.h" + +#include +#include +#include + +StrFdir::StrFdir(object_id_t strObject) + : DeviceHandlerFailureIsolation(strObject, objects::NO_OBJECT) {} + +ReturnValue_t StrFdir::eventReceived(EventMessage* event) { + if (event->getEvent() == acs::PTG_CTRL_NO_ATTITUDE_INFORMATION) { + setFaulty(event->getEvent()); + return returnvalue::OK; + } + return DeviceHandlerFailureIsolation::eventReceived(event); +} + +ReturnValue_t StrFdir::initialize() { + ReturnValue_t result = DeviceHandlerFailureIsolation::initialize(); + if (result != returnvalue::OK) { + return result; + } + EventManagerIF* manager = ObjectManager::instance()->get(objects::EVENT_MANAGER); + return manager->subscribeToEvent(eventQueue->getId(), + event::getEventId(acs::PTG_CTRL_NO_ATTITUDE_INFORMATION)); +} diff --git a/mission/system/acs/StrFdir.h b/mission/system/acs/StrFdir.h new file mode 100644 index 0000000..a40a3cc --- /dev/null +++ b/mission/system/acs/StrFdir.h @@ -0,0 +1,13 @@ +#ifndef MISSION_SYSTEM_FDIR_STRFDIR_H_ +#define MISSION_SYSTEM_FDIR_STRFDIR_H_ + +#include + +class StrFdir : public DeviceHandlerFailureIsolation { + public: + StrFdir(object_id_t strObject); + ReturnValue_t initialize() override; + ReturnValue_t eventReceived(EventMessage* event) override; +}; + +#endif /* MISSION_SYSTEM_FDIR_STRFDIR_H_ */ diff --git a/mission/system/acs/SusAssembly.cpp b/mission/system/acs/SusAssembly.cpp new file mode 100644 index 0000000..8d25acd --- /dev/null +++ b/mission/system/acs/SusAssembly.cpp @@ -0,0 +1,171 @@ +#include "SusAssembly.h" + +#include +#include +#include + +SusAssembly::SusAssembly(object_id_t objectId, PowerSwitchIF* pwrSwitcher, SusAssHelper helper) + : DualLaneAssemblyBase(objectId, pwrSwitcher, SWITCH_NOM, SWITCH_RED, + POWER_STATE_MACHINE_TIMEOUT, SIDE_SWITCH_TRANSITION_NOT_ALLOWED, + TRANSITION_OTHER_SIDE_FAILED), + helper(helper), + pwrSwitcher(pwrSwitcher) { + ModeListEntry entry; + for (uint8_t idx = 0; idx < NUMBER_SUN_SENSORS; idx++) { + initModeTableEntry(helper.susIds[idx], entry, modeTable); + } +} + +ReturnValue_t SusAssembly::commandChildren(Mode_t mode, Submode_t submode) { + ReturnValue_t result = returnvalue::OK; + refreshHelperModes(); + // Initialize the mode table to ensure all devices are in a defined state + for (uint8_t idx = 0; idx < NUMBER_SUN_SENSORS; idx++) { + modeTable[idx].setMode(MODE_OFF); + modeTable[idx].setSubmode(SUBMODE_NONE); + } + if (recoveryState == RecoveryState::RECOVERY_IDLE) { + result = checkAndHandleHealthStates(mode, submode); + if (result != returnvalue::OK) { + return result; + } + } + if (recoveryState != RecoveryState::RECOVERY_STARTED) { + if (mode == DeviceHandlerIF::MODE_NORMAL or mode == MODE_ON) { + result = handleNormalOrOnModeCmd(mode, submode); + } + } + + HybridIterator tableIter(modeTable.begin(), modeTable.end()); + executeTable(tableIter); + return result; +} + +ReturnValue_t SusAssembly::handleNormalOrOnModeCmd(Mode_t mode, Submode_t submode) { + using namespace duallane; + ReturnValue_t result = returnvalue::OK; + bool needsSecondStep = false; + handleSideSwitchStates(submode, needsSecondStep); + auto cmdSeq = [&](object_id_t objectId, Mode_t devMode, uint8_t tableIdx) { + if (isModeCommandable(objectId, devMode)) { + modeTable[tableIdx].setMode(mode); + modeTable[tableIdx].setSubmode(SUBMODE_NONE); + } + }; + switch (submode) { + case (A_SIDE): { + for (uint8_t idx = 0; idx < NUMBER_SUN_SENSORS_ONE_SIDE; idx++) { + cmdSeq(helper.susIds[idx], helper.susModes[idx], idx); + // Switch off devices on redundant side + modeTable[idx + NUMBER_SUN_SENSORS_ONE_SIDE].setMode(MODE_OFF); + modeTable[idx + NUMBER_SUN_SENSORS_ONE_SIDE].setSubmode(SUBMODE_NONE); + } + break; + } + case (B_SIDE): { + for (uint8_t idx = NUMBER_SUN_SENSORS_ONE_SIDE; idx < NUMBER_SUN_SENSORS; idx++) { + cmdSeq(helper.susIds[idx], helper.susModes[idx], idx); + // Switch devices on nominal side + modeTable[idx - NUMBER_SUN_SENSORS_ONE_SIDE].setMode(MODE_OFF); + modeTable[idx - NUMBER_SUN_SENSORS_ONE_SIDE].setSubmode(SUBMODE_NONE); + } + break; + } + case (DUAL_MODE): { + for (uint8_t idx = 0; idx < NUMBER_SUN_SENSORS; idx++) { + cmdSeq(helper.susIds[idx], helper.susModes[idx], idx); + } + break; + } + } + if (needsSecondStep) { + result = NEED_SECOND_STEP; + } + return result; +} + +ReturnValue_t SusAssembly::checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) { + using namespace duallane; + refreshHelperModes(); + if (wantedSubmode == A_SIDE) { + for (uint8_t idx = 0; idx < NUMBER_SUN_SENSORS_ONE_SIDE; idx++) { + if (helper.susModes[idx] != wantedMode) { + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + } + return returnvalue::OK; + } else if (wantedSubmode == B_SIDE) { + for (uint8_t idx = NUMBER_SUN_SENSORS_ONE_SIDE; idx < NUMBER_SUN_SENSORS; idx++) { + if (helper.susModes[idx] != wantedMode) { + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + } + return returnvalue::OK; + } else { + // Trigger event if devices are faulty? This is the last fallback mode, returning + // a failure here would trigger a transition to MODE_OFF unless handleModeTransitionFailed + // is overriden + return returnvalue::OK; + } + return returnvalue::OK; +} + +ReturnValue_t SusAssembly::initialize() { + for (const auto& child : childrenMap) { + updateChildModeByObjId(child.first, MODE_OFF, 0); + } + return AssemblyBase::initialize(); +} + +void SusAssembly::refreshHelperModes() { + for (uint8_t idx = 0; idx < helper.susModes.size(); idx++) { + helper.susModes[idx] = childrenMap[helper.susIds[idx]].mode; + } +} + +ReturnValue_t SusAssembly::checkAndHandleHealthStates(Mode_t commandedMode, + Submode_t commandedSubmode) { + using namespace returnvalue; + ReturnValue_t status = returnvalue::OK; + bool needsHealthOverwritten = false; + auto checkSusGroup = [&](object_id_t devNom, object_id_t devRed) { + HealthState healthNom = healthHelper.healthTable->getHealth(devNom); + HealthState healthRed = healthHelper.healthTable->getHealth(devRed); + if (healthNom == PERMANENT_FAULTY and healthRed == FAULTY) { + overwriteDeviceHealth(devRed, healthRed); + needsHealthOverwritten = true; + } else if (healthNom == FAULTY and healthRed == PERMANENT_FAULTY) { + overwriteDeviceHealth(devNom, healthNom); + needsHealthOverwritten = true; + } else if ((healthNom == FAULTY or healthNom == PERMANENT_FAULTY) and + (healthRed == FAULTY or healthRed == PERMANENT_FAULTY)) { + overwriteDeviceHealth(devNom, healthNom); + overwriteDeviceHealth(devRed, healthRed); + needsHealthOverwritten = true; + } + }; + auto checkHealthForOneDev = [&](object_id_t dev) { + HealthState health = healthHelper.healthTable->getHealth(dev); + if (health == EXTERNAL_CONTROL) { + modeHelper.setForced(true); + } + }; + if (commandedSubmode == duallane::DUAL_MODE) { + uint8_t idx = 0; + for (idx = 0; idx < 6; idx++) { + checkSusGroup(helper.susIds[idx], helper.susIds[idx + 6]); + checkHealthForOneDev(helper.susIds[idx]); + } + for (idx = 6; idx < 12; idx++) { + checkHealthForOneDev(helper.susIds[idx]); + } + } + if (needsHealthOverwritten) { + mode = commandedMode; + submode = commandedSubmode; + // We need second step instead of NEED_TO_CHANGE_HEALTH because we do not want recovery + // handling. + return NEED_TO_CHANGE_HEALTH; + } + return status; +} diff --git a/mission/system/acs/SusAssembly.h b/mission/system/acs/SusAssembly.h new file mode 100644 index 0000000..2002fac --- /dev/null +++ b/mission/system/acs/SusAssembly.h @@ -0,0 +1,65 @@ +#ifndef MISSION_SYSTEM_SUSASSEMBLY_H_ +#define MISSION_SYSTEM_SUSASSEMBLY_H_ + +#include +#include + +#include "DualLaneAssemblyBase.h" +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +struct SusAssHelper { + public: + SusAssHelper(std::array susIds) : susIds(susIds) {} + std::array susIds = {objects::NO_OBJECT}; + std::array susModes = {HasModesIF::MODE_OFF}; +}; + +class PowerSwitchIF; + +class SusAssembly : public DualLaneAssemblyBase { + public: + static constexpr uint8_t NUMBER_SUN_SENSORS_ONE_SIDE = 6; + static constexpr uint8_t NUMBER_SUN_SENSORS = 12; + + // Use these variables instead of magic numbers when generator was updated + // TRANSITION_OTHER_SIDE_FAILED_ID + // NOT_ENOUGH_DEVICES_DUAL_MODE_ID + // POWER_STATE_MACHINE_TIMEOUT_ID + // SIDE_SWITCH_TRANSITION_NOT_ALLOWED_ID + static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::SUS_BOARD_ASS; + static constexpr Event TRANSITION_OTHER_SIDE_FAILED = + event::makeEvent(SUBSYSTEM_ID, 0, severity::HIGH); + static constexpr Event NOT_ENOUGH_DEVICES_DUAL_MODE = + event::makeEvent(SUBSYSTEM_ID, 1, severity::HIGH); + static constexpr Event POWER_STATE_MACHINE_TIMEOUT = + event::makeEvent(SUBSYSTEM_ID, 2, severity::MEDIUM); + //! [EXPORT] : [COMMENT] Not implemented, would increase already high complexity. Operator + //! should instead command the assembly off first and then command the assembly on into the + //! desired mode/submode combination + static constexpr Event SIDE_SWITCH_TRANSITION_NOT_ALLOWED = + event::makeEvent(SUBSYSTEM_ID, 3, severity::LOW); + + SusAssembly(object_id_t objectId, PowerSwitchIF* pwrSwitcher, SusAssHelper helper); + + private: + enum class States { IDLE, SWITCHING_POWER, MODE_COMMANDING } state = States::IDLE; + static constexpr power::Switches SWITCH_NOM = power::Switches::PDU1_CH4_SUS_NOMINAL_3V3; + static constexpr power::Switches SWITCH_RED = power::Switches::PDU2_CH4_SUS_REDUNDANT_3V3; + FixedArrayList modeTable; + + SusAssHelper helper; + PowerSwitchIF* pwrSwitcher = nullptr; + ReturnValue_t initialize() override; + + // AssemblyBase overrides + ReturnValue_t commandChildren(Mode_t mode, Submode_t submode) override; + ReturnValue_t checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) override; + + void powerStateMachine(Mode_t mode, Submode_t submode); + ReturnValue_t handleNormalOrOnModeCmd(Mode_t mode, Submode_t submode); + void refreshHelperModes(); + ReturnValue_t checkAndHandleHealthStates(Mode_t deviceMode, Submode_t deviceSubmode); +}; + +#endif /* MISSION_SYSTEM_SUSASSEMBLY_H_ */ diff --git a/mission/system/acs/SusFdir.cpp b/mission/system/acs/SusFdir.cpp new file mode 100644 index 0000000..de8eccc --- /dev/null +++ b/mission/system/acs/SusFdir.cpp @@ -0,0 +1,7 @@ +#include "SusFdir.h" + +#include "eive/objects.h" +#include "mission/acs/susMax1227Helpers.h" + +SusFdir::SusFdir(object_id_t sensorId) + : DeviceHandlerFailureIsolation(sensorId, objects::SUS_BOARD_ASS) {} diff --git a/mission/system/acs/SusFdir.h b/mission/system/acs/SusFdir.h new file mode 100644 index 0000000..b69d522 --- /dev/null +++ b/mission/system/acs/SusFdir.h @@ -0,0 +1,11 @@ +#ifndef MISSION_SYSTEM_SUSFDIR_H_ +#define MISSION_SYSTEM_SUSFDIR_H_ + +#include + +class SusFdir : public DeviceHandlerFailureIsolation { + public: + SusFdir(object_id_t sensorId); +}; + +#endif /* MISSION_SYSTEM_SUSFDIR_H_ */ diff --git a/mission/system/acs/acsModeTree.cpp b/mission/system/acs/acsModeTree.cpp new file mode 100644 index 0000000..9751dff --- /dev/null +++ b/mission/system/acs/acsModeTree.cpp @@ -0,0 +1,495 @@ +#include "acsModeTree.h" + +#include +#include +#include +#include +#include + +#include + +#include "eive/objects.h" +#include "mission/acs/defs.h" +#include "mission/power/defs.h" +#include "mission/system/treeUtil.h" + +AcsSubsystem satsystem::acs::ACS_SUBSYSTEM(objects::ACS_SUBSYSTEM, 12, 24); + +namespace { +// Alias for checker function +const auto check = subsystem::checkInsert; + +void buildOffSequence(Subsystem& ss, ModeListEntry& eh); +void buildSafeSequence(Subsystem& ss, ModeListEntry& entryHelper); +void buildIdleSequence(Subsystem& ss, ModeListEntry& entryHelper); +void buildTargetPtNadirSequence(Subsystem& ss, ModeListEntry& eh); +void buildTargetPtSequence(Subsystem& ss, ModeListEntry& entryHelper); +void buildTargetPtGsSequence(Subsystem& ss, ModeListEntry& entryHelper); +void buildTargetPtInertialSequence(Subsystem& ss, ModeListEntry& entryHelper); + +} // namespace + +static const auto OFF = HasModesIF::MODE_OFF; +static const auto NML = DeviceHandlerIF::MODE_NORMAL; + +auto SUS_BOARD_NML_TRANS = std::make_pair(0x20, FixedArrayList()); + +auto ACS_SEQUENCE_OFF = std::make_pair(acs::AcsMode::OFF, FixedArrayList()); +auto ACS_TABLE_OFF_TGT = + std::make_pair((acs::AcsMode::OFF << 24) | 1, FixedArrayList()); +auto ACS_TABLE_OFF_TRANS_0 = + std::make_pair((acs::AcsMode::OFF << 24) | 2, FixedArrayList()); +auto ACS_TABLE_OFF_TRANS_1 = + std::make_pair((acs::AcsMode::OFF << 24) | 3, FixedArrayList()); + +auto ACS_SEQUENCE_SAFE = std::make_pair(acs::AcsMode::SAFE, FixedArrayList()); +auto ACS_TABLE_SAFE_TGT = + std::make_pair((acs::AcsMode::SAFE << 24) | 1, FixedArrayList()); +auto ACS_TABLE_SAFE_TRANS_0 = + std::make_pair((acs::AcsMode::SAFE << 24) | 2, FixedArrayList()); +auto ACS_TABLE_SAFE_TRANS_1 = + std::make_pair((acs::AcsMode::SAFE << 24) | 3, FixedArrayList()); + +auto ACS_SEQUENCE_IDLE = std::make_pair(acs::AcsMode::PTG_IDLE, FixedArrayList()); +auto ACS_TABLE_IDLE_TGT = + std::make_pair((acs::AcsMode::PTG_IDLE << 24) | 1, FixedArrayList()); +auto ACS_TABLE_IDLE_TRANS_0 = + std::make_pair((acs::AcsMode::PTG_IDLE << 24) | 2, FixedArrayList()); +auto ACS_TABLE_IDLE_TRANS_1 = + std::make_pair((acs::AcsMode::PTG_IDLE << 24) | 3, FixedArrayList()); + +auto ACS_TABLE_PTG_TRANS_0 = + std::make_pair((acs::AcsMode::PTG_TARGET << 24) | 2, FixedArrayList()); + +auto ACS_SEQUENCE_PTG_TARGET = + std::make_pair(acs::AcsMode::PTG_TARGET, FixedArrayList()); +auto ACS_TABLE_PTG_TARGET_TGT = + std::make_pair((acs::AcsMode::PTG_TARGET << 24) | 1, FixedArrayList()); +auto ACS_TABLE_PTG_TARGET_TRANS_1 = + std::make_pair((acs::AcsMode::PTG_TARGET << 24) | 3, FixedArrayList()); + +auto ACS_SEQUENCE_PTG_TARGET_GS = + std::make_pair(acs::AcsMode::PTG_TARGET_GS, FixedArrayList()); +auto ACS_TABLE_PTG_TARGET_GS_TGT = + std::make_pair((acs::AcsMode::PTG_TARGET_GS << 24) | 1, FixedArrayList()); +auto ACS_TABLE_PTG_TARGET_GS_TRANS_1 = + std::make_pair((acs::AcsMode::PTG_TARGET_GS << 24) | 3, FixedArrayList()); + +auto ACS_SEQUENCE_PTG_TARGET_NADIR = + std::make_pair(acs::AcsMode::PTG_NADIR, FixedArrayList()); +auto ACS_TABLE_PTG_TARGET_NADIR_TGT = + std::make_pair((acs::AcsMode::PTG_NADIR << 24) | 1, FixedArrayList()); +auto ACS_TABLE_PTG_TARGET_NADIR_TRANS_1 = + std::make_pair((acs::AcsMode::PTG_NADIR << 24) | 3, FixedArrayList()); + +auto ACS_SEQUENCE_PTG_TARGET_INERTIAL = + std::make_pair(acs::AcsMode::PTG_INERTIAL, FixedArrayList()); +auto ACS_TABLE_PTG_TARGET_INERTIAL_TGT = + std::make_pair((acs::AcsMode::PTG_INERTIAL << 24) | 1, FixedArrayList()); +auto ACS_TABLE_PTG_TARGET_INERTIAL_TRANS_1 = + std::make_pair((acs::AcsMode::PTG_INERTIAL << 24) | 3, FixedArrayList()); + +Subsystem& satsystem::acs::init() { + ModeListEntry entry; + const char* ctxc = "satsystem::acs::init: generic target"; + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table, + bool allowAllSubmodes = false) { + entry.setObject(obj); + entry.setMode(mode); + entry.setSubmode(submode); + if (allowAllSubmodes) { + entry.allowAllSubmodes(); + } + check(table.insert(entry), "satsystem::acs::init: generic target"); + }; + // Build TARGET PT transition 0 + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_PTG_TRANS_0.second); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TRANS_0.second, true); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TRANS_0.second, true); + iht(objects::RW_ASSY, NML, 0, ACS_TABLE_PTG_TRANS_0.second); + iht(objects::STR_ASSY, NML, 0, ACS_TABLE_PTG_TRANS_0.second); + check(ACS_SUBSYSTEM.addTable( + TableEntry(ACS_TABLE_PTG_TRANS_0.first, &ACS_TABLE_PTG_TRANS_0.second)), + ctxc); + + // Build SUS board transition + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, SUS_BOARD_NML_TRANS.second, true); + check(ACS_SUBSYSTEM.addTable(TableEntry(SUS_BOARD_NML_TRANS.first, &SUS_BOARD_NML_TRANS.second)), + ctxc); + + buildOffSequence(ACS_SUBSYSTEM, entry); + buildSafeSequence(ACS_SUBSYSTEM, entry); + buildIdleSequence(ACS_SUBSYSTEM, entry); + buildTargetPtSequence(ACS_SUBSYSTEM, entry); + buildTargetPtGsSequence(ACS_SUBSYSTEM, entry); + buildTargetPtNadirSequence(ACS_SUBSYSTEM, entry); + buildTargetPtInertialSequence(ACS_SUBSYSTEM, entry); + ACS_SUBSYSTEM.setInitialMode(::acs::AcsMode::SAFE); + return ACS_SUBSYSTEM; +} + +namespace { + +void buildOffSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::acs::buildOffSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // OFF Target table is empty + check(ss.addTable(TableEntry(ACS_TABLE_OFF_TGT.first, &ACS_TABLE_OFF_TGT.second)), ctxc); + + // Build OFF transition 0 + iht(objects::ACS_CONTROLLER, OFF, 0, ACS_TABLE_OFF_TRANS_0.second); + check(ss.addTable(TableEntry(ACS_TABLE_OFF_TRANS_0.first, &ACS_TABLE_OFF_TRANS_0.second)), ctxc); + + // Build OFF transition 1 + iht(objects::IMTQ_ASSY, OFF, 0, ACS_TABLE_OFF_TRANS_1.second); + iht(objects::STR_ASSY, OFF, 0, ACS_TABLE_OFF_TRANS_1.second); + iht(objects::ACS_BOARD_ASS, OFF, 0, ACS_TABLE_OFF_TRANS_1.second); + iht(objects::SUS_BOARD_ASS, OFF, 0, ACS_TABLE_OFF_TRANS_1.second); + iht(objects::RW_ASSY, OFF, 0, ACS_TABLE_OFF_TRANS_1.second); + check(ss.addTable(TableEntry(ACS_TABLE_OFF_TRANS_1.first, &ACS_TABLE_OFF_TRANS_1.second)), ctxc); + + // Build OFF sequence + ihs(ACS_SEQUENCE_OFF.second, ACS_TABLE_OFF_TGT.first, 0, false); + ihs(ACS_SEQUENCE_OFF.second, ACS_TABLE_OFF_TRANS_0.first, 0, false); + ihs(ACS_SEQUENCE_OFF.second, ACS_TABLE_OFF_TRANS_1.first, 0, false); + check(ss.addSequence(&ACS_SEQUENCE_OFF.second, ACS_SEQUENCE_OFF.first, ACS_SEQUENCE_OFF.first, + false, true), + ctxc); +} + +void buildSafeSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::acs::buildSafeSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence, bool allowAllSubmodes = false) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + if (allowAllSubmodes) { + eh.allowAllSubmodes(); + } + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + // Build SAFE target. Allow detumble submode. + iht(objects::ACS_CONTROLLER, acs::AcsMode::SAFE, acs::SafeSubmode::DEFAULT, + ACS_TABLE_SAFE_TGT.second, true); + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_SAFE_TGT.second); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_SAFE_TGT.second, true); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_SAFE_TGT.second, true); + check(ss.addTable(&ACS_TABLE_SAFE_TGT.second, ACS_TABLE_SAFE_TGT.first, false, true), ctxc); + + // Build SAFE transition 0 + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_SAFE_TRANS_0.second); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_SAFE_TRANS_0.second, true); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_SAFE_TRANS_0.second, true); + iht(objects::STR_ASSY, OFF, 0, ACS_TABLE_SAFE_TRANS_0.second); + iht(objects::RW_ASSY, OFF, 0, ACS_TABLE_SAFE_TRANS_0.second); + check(ss.addTable(&ACS_TABLE_SAFE_TRANS_0.second, ACS_TABLE_SAFE_TRANS_0.first, false, true), + ctxc); + + // SUS board transition table is defined above + + // Build SAFE transition 1 + iht(objects::ACS_CONTROLLER, acs::AcsMode::SAFE, acs::SafeSubmode::DEFAULT, + ACS_TABLE_SAFE_TRANS_1.second); + check(ss.addTable(&ACS_TABLE_SAFE_TRANS_1.second, ACS_TABLE_SAFE_TRANS_1.first, false, true), + ctxc); + + // Build SAFE sequence + ihs(ACS_SEQUENCE_SAFE.second, ACS_TABLE_SAFE_TGT.first, 0, true); + ihs(ACS_SEQUENCE_SAFE.second, ACS_TABLE_SAFE_TRANS_0.first, 0, false); + ihs(ACS_SEQUENCE_SAFE.second, ACS_TABLE_SAFE_TRANS_1.first, 0, false); + check(ss.addSequence(&ACS_SEQUENCE_SAFE.second, ACS_SEQUENCE_SAFE.first, ACS_SEQUENCE_SAFE.first, + false, true), + ctxc); +} + +void buildIdleSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::acs::buildIdleSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence, bool allowAllSubmodes = false) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + if (allowAllSubmodes) { + eh.allowAllSubmodes(); + } + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + // Build IDLE target + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_IDLE, 0, ACS_TABLE_IDLE_TGT.second); + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_IDLE_TGT.second); + iht(objects::RW_ASSY, NML, 0, ACS_TABLE_IDLE_TGT.second); + iht(objects::STR_ASSY, NML, 0, ACS_TABLE_IDLE_TGT.second); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_IDLE_TGT.second, true); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_IDLE_TGT.second, true); + ss.addTable(&ACS_TABLE_IDLE_TGT.second, ACS_TABLE_IDLE_TGT.first, false, true); + + // Build IDLE transition 0 + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_IDLE_TRANS_0.second); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_IDLE_TRANS_0.second, true); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_IDLE_TRANS_0.second, true); + iht(objects::RW_ASSY, NML, 0, ACS_TABLE_IDLE_TRANS_0.second); + iht(objects::STR_ASSY, NML, 0, ACS_TABLE_IDLE_TRANS_0.second); + ss.addTable(&ACS_TABLE_IDLE_TRANS_0.second, ACS_TABLE_IDLE_TRANS_0.first, false, true); + + // Build IDLE transition 1 + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_IDLE, 0, ACS_TABLE_IDLE_TRANS_1.second); + ss.addTable(&ACS_TABLE_IDLE_TRANS_1.second, ACS_TABLE_IDLE_TRANS_1.first, false, true); + + // Build IDLE sequence + ihs(ACS_SEQUENCE_IDLE.second, ACS_TABLE_IDLE_TGT.first, 0, true); + ihs(ACS_SEQUENCE_IDLE.second, ACS_TABLE_IDLE_TRANS_0.first, 0, true); + ihs(ACS_SEQUENCE_IDLE.second, ACS_TABLE_IDLE_TRANS_1.first, 0, true); + ss.addSequence(&ACS_SEQUENCE_IDLE.second, ACS_SEQUENCE_IDLE.first, ACS_SEQUENCE_SAFE.first, false, + true); +} + +void buildTargetPtSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::acs::buildTargetPtSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence, bool allowAllSubmodes = false) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + if (allowAllSubmodes) { + eh.allowAllSubmodes(); + } + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build TARGET PT table + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_TARGET, 0, ACS_TABLE_PTG_TARGET_TGT.second); + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_TGT.second); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TARGET_TGT.second, true); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TARGET_TGT.second, true); + iht(objects::RW_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_TGT.second); + iht(objects::STR_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_TGT.second); + check(ss.addTable(&ACS_TABLE_PTG_TARGET_TGT.second, ACS_TABLE_PTG_TARGET_TGT.first, false, true), + ctxc); + + // Transition 0 already built + // Build TARGET PT transition 1 + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_TARGET, 0, ACS_TABLE_PTG_TARGET_TRANS_1.second); + check(ss.addTable(&ACS_TABLE_PTG_TARGET_TRANS_1.second, ACS_TABLE_PTG_TARGET_TRANS_1.first, false, + true), + ctxc); + + // Build IDLE sequence + ihs(ACS_SEQUENCE_PTG_TARGET.second, ACS_TABLE_PTG_TARGET_TGT.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET.second, ACS_TABLE_PTG_TRANS_0.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET.second, ACS_TABLE_PTG_TARGET_TRANS_1.first, 0, true); + check(ss.addSequence(&ACS_SEQUENCE_PTG_TARGET.second, ACS_SEQUENCE_PTG_TARGET.first, + ACS_SEQUENCE_IDLE.first, false, true), + ctxc); +} + +void buildTargetPtNadirSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::acs::buildTargetPtNadirSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence, bool allowAllSubmodes = false) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + if (allowAllSubmodes) { + eh.allowAllSubmodes(); + } + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build TARGET PT table + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_NADIR, 0, ACS_TABLE_PTG_TARGET_NADIR_TGT.second); + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_NADIR_TGT.second); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TARGET_NADIR_TGT.second, true); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TARGET_NADIR_TGT.second, true); + iht(objects::RW_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_NADIR_TGT.second); + iht(objects::STR_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_NADIR_TGT.second); + check(ss.addTable(TableEntry(ACS_TABLE_PTG_TARGET_NADIR_TGT.first, + &ACS_TABLE_PTG_TARGET_NADIR_TGT.second)), + ctxc); + + // Transition 0 already built + // Build TARGET PT transition 1 + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_NADIR, 0, + ACS_TABLE_PTG_TARGET_NADIR_TRANS_1.second); + check(ss.addTable(TableEntry(ACS_TABLE_PTG_TARGET_NADIR_TRANS_1.first, + &ACS_TABLE_PTG_TARGET_NADIR_TRANS_1.second)), + ctxc); + + // Build IDLE sequence + ihs(ACS_SEQUENCE_PTG_TARGET_NADIR.second, ACS_TABLE_PTG_TARGET_NADIR_TGT.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_NADIR.second, SUS_BOARD_NML_TRANS.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_NADIR.second, ACS_TABLE_PTG_TRANS_0.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_NADIR.second, ACS_TABLE_PTG_TARGET_NADIR_TRANS_1.first, 0, true); + check( + ss.addSequence(SequenceEntry(ACS_SEQUENCE_PTG_TARGET_NADIR.first, + &ACS_SEQUENCE_PTG_TARGET_NADIR.second, ACS_SEQUENCE_IDLE.first)), + ctxc); +} + +void buildTargetPtGsSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::acs::buildTargetPtGsSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence, bool allowAllSubmodes = false) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + if (allowAllSubmodes) { + eh.allowAllSubmodes(); + } + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build TARGET PT table + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_TARGET_GS, 0, ACS_TABLE_PTG_TARGET_GS_TGT.second); + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_GS_TGT.second); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TARGET_GS_TGT.second, true); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TARGET_GS_TGT.second, true); + iht(objects::RW_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_GS_TGT.second); + iht(objects::STR_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_GS_TGT.second); + check(ss.addTable( + TableEntry(ACS_TABLE_PTG_TARGET_GS_TGT.first, &ACS_TABLE_PTG_TARGET_GS_TGT.second)), + ctxc); + + // Transition 0 already built + // Build TARGET PT transition 1 + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_TARGET_GS, 0, + ACS_TABLE_PTG_TARGET_GS_TRANS_1.second); + check(ss.addTable(TableEntry(ACS_TABLE_PTG_TARGET_GS_TRANS_1.first, + &ACS_TABLE_PTG_TARGET_GS_TRANS_1.second)), + ctxc); + + // Build IDLE sequence + ihs(ACS_SEQUENCE_PTG_TARGET_GS.second, ACS_TABLE_PTG_TARGET_GS_TGT.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_GS.second, SUS_BOARD_NML_TRANS.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_GS.second, ACS_TABLE_PTG_TRANS_0.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_GS.second, ACS_TABLE_PTG_TARGET_GS_TRANS_1.first, 0, true); + check(ss.addSequence(SequenceEntry(ACS_SEQUENCE_PTG_TARGET_GS.first, + &ACS_SEQUENCE_PTG_TARGET_GS.second, ACS_SEQUENCE_IDLE.first)), + ctxc); +} + +void buildTargetPtInertialSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::acs::buildTargetPtInertialSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence, bool allowAllSubmodes = false) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + if (allowAllSubmodes) { + eh.allowAllSubmodes(); + } + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build TARGET PT table + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_INERTIAL, 0, + ACS_TABLE_PTG_TARGET_INERTIAL_TGT.second); + iht(objects::IMTQ_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_INERTIAL_TGT.second); + iht(objects::SUS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TARGET_INERTIAL_TGT.second, + true); + iht(objects::ACS_BOARD_ASS, NML, duallane::B_SIDE, ACS_TABLE_PTG_TARGET_INERTIAL_TGT.second, + true); + iht(objects::RW_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_INERTIAL_TGT.second); + iht(objects::STR_ASSY, NML, 0, ACS_TABLE_PTG_TARGET_INERTIAL_TGT.second); + check(ss.addTable(TableEntry(ACS_TABLE_PTG_TARGET_INERTIAL_TGT.first, + &ACS_TABLE_PTG_TARGET_INERTIAL_TGT.second)), + ctxc); + + // Transition 0 already built + // Build TARGET PT transition 1 + iht(objects::ACS_CONTROLLER, acs::AcsMode::PTG_INERTIAL, 0, + ACS_TABLE_PTG_TARGET_INERTIAL_TRANS_1.second); + check(ss.addTable(TableEntry(ACS_TABLE_PTG_TARGET_INERTIAL_TRANS_1.first, + &ACS_TABLE_PTG_TARGET_INERTIAL_TRANS_1.second)), + ctxc); + + // Build IDLE sequence + ihs(ACS_SEQUENCE_PTG_TARGET_INERTIAL.second, ACS_TABLE_PTG_TARGET_INERTIAL_TGT.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_INERTIAL.second, SUS_BOARD_NML_TRANS.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_INERTIAL.second, ACS_TABLE_PTG_TRANS_0.first, 0, true); + ihs(ACS_SEQUENCE_PTG_TARGET_INERTIAL.second, ACS_TABLE_PTG_TARGET_INERTIAL_TRANS_1.first, 0, + true); + check(ss.addSequence(SequenceEntry(ACS_SEQUENCE_PTG_TARGET_INERTIAL.first, + &ACS_SEQUENCE_PTG_TARGET_INERTIAL.second, + ACS_SEQUENCE_IDLE.first)), + ctxc); +} + +} // namespace diff --git a/mission/system/acs/acsModeTree.h b/mission/system/acs/acsModeTree.h new file mode 100644 index 0000000..da0374e --- /dev/null +++ b/mission/system/acs/acsModeTree.h @@ -0,0 +1,10 @@ +#include + +namespace satsystem { +namespace acs { + +extern AcsSubsystem ACS_SUBSYSTEM; +Subsystem& init(); + +} // namespace acs +} // namespace satsystem diff --git a/mission/system/com/CMakeLists.txt b/mission/system/com/CMakeLists.txt new file mode 100644 index 0000000..6d31da8 --- /dev/null +++ b/mission/system/com/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources( + ${LIB_EIVE_MISSION} PRIVATE comModeTree.cpp ComSubsystem.cpp + SyrlinksAssembly.cpp SyrlinksFdir.cpp) diff --git a/mission/system/com/ComSubsystem.cpp b/mission/system/com/ComSubsystem.cpp new file mode 100644 index 0000000..dfcbbd4 --- /dev/null +++ b/mission/system/com/ComSubsystem.cpp @@ -0,0 +1,254 @@ +#include "ComSubsystem.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +ComSubsystem::ComSubsystem(object_id_t setObjectId, uint32_t maxNumberOfSequences, + uint32_t maxNumberOfTables, uint32_t transmitterTimeout) + : Subsystem(setObjectId, maxNumberOfSequences, maxNumberOfTables), paramHelper(this) { + com::setCurrentDatarate(com::Datarate::LOW_RATE_MODULATION_BPSK); + auto mqArgs = MqArgs(setObjectId, static_cast(this)); + eventQueue = + QueueFactory::instance()->createMessageQueue(10, EventMessage::EVENT_MESSAGE_SIZE, &mqArgs); + transmitterCountdown.setTimeout(transmitterTimeout); +} + +void ComSubsystem::performChildOperation() { + Subsystem::performChildOperation(); + readEventQueue(); + if (performRecoveryToRxOnly and not isInTransition) { + startRxOnlyRecovery(true); + // To avoid immediately enabling TX after falling back. + rememberBitLock = false; + } + // Execute default rate sequence after transition has been completed + if (rememberBitLock and not isInTransition) { + startRxAndTxLowRateSeq(); + rememberBitLock = false; + } + if (countdownActive) { + checkTransmitterCountdown(); + } +} + +MessageQueueId_t ComSubsystem::getCommandQueue() const { return Subsystem::getCommandQueue(); } + +ReturnValue_t ComSubsystem::getParameter(uint8_t domainId, uint8_t uniqueIdentifier, + ParameterWrapper *parameterWrapper, + const ParameterWrapper *newValues, uint16_t startAtIndex) { + if ((domainId == 0) and (uniqueIdentifier == static_cast(com::ParameterId::DATARATE))) { + uint8_t newVal = 0; + ReturnValue_t result = newValues->getElement(&newVal); + if (result != returnvalue::OK) { + return result; + } + if (newVal >= static_cast(com::Datarate::NUM_DATARATES)) { + return HasParametersIF::INVALID_VALUE; + } + parameterWrapper->set(datarateCfg); + com::setCurrentDatarate(static_cast(newVal)); + return returnvalue::OK; + } else if ((domainId == 0) and + (uniqueIdentifier == static_cast(com::ParameterId::TRANSMITTER_TIMEOUT))) { + uint32_t newVal = 0; + ReturnValue_t result = newValues->getElement(&newVal); + if (result != returnvalue::OK) { + return result; + } + parameterWrapper->set(transmitterTimeout); + transmitterTimeout = newVal; + transmitterCountdown.setTimeout(transmitterTimeout); + return returnvalue::OK; + } + return returnvalue::OK; +} + +ReturnValue_t ComSubsystem::handleCommandMessage(CommandMessage *message) { + ReturnValue_t result = paramHelper.handleParameterMessage(message); + if (result == returnvalue::OK) { + return result; + } + return Subsystem::handleCommandMessage(message); +} + +ReturnValue_t ComSubsystem::initialize() { + ReturnValue_t result = paramHelper.initialize(); + if (result != returnvalue::OK) { + return result; + } + EventManagerIF *manager = ObjectManager::instance()->get(objects::EVENT_MANAGER); + if (manager == nullptr) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "ComSubsystem::initialize: Invalid event manager" << std::endl; +#endif + return ObjectManagerIF::CHILD_INIT_FAILED; + } + result = manager->registerListener(eventQueue->getId()); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "ComSubsystem::initialize: Failed to register Com Subsystem as event " + "listener" + << std::endl; +#endif + } + result = manager->subscribeToEvent(eventQueue->getId(), + event::getEventId(tcsCtrl::SYRLINKS_OVERHEATING)); + if (result != returnvalue::OK) { + return ObjectManager::CHILD_INIT_FAILED; + } + result = + manager->subscribeToEventRange(eventQueue->getId(), event::getEventId(pdec::CARRIER_LOCK), + event::getEventId(pdec::BIT_LOCK_PDEC)); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "ComSubsystem::initialize: Failed to subscribe to events from PDEC " + "handler" + << std::endl; +#endif + return result; + } + return Subsystem::initialize(); +} + +void ComSubsystem::startTransition(Mode_t mode, Submode_t submode) { + // Depending on the submode the transmitter timeout is enabled or + // disabled here + if (mode == com::Submode::RX_ONLY) { + transmitterCountdown.timeOut(); + countdownActive = false; + } else if (isTxMode(mode)) { + // Only start transmitter countdown if transmitter is not already on + if (not isTxMode(this->mode)) { + transmitterCountdown.resetTimer(); + countdownActive = true; + } + } + Subsystem::startTransition(mode, submode); +} + +void ComSubsystem::readEventQueue() { + EventMessage event; + for (ReturnValue_t result = eventQueue->receiveMessage(&event); result == returnvalue::OK; + result = eventQueue->receiveMessage(&event)) { + switch (event.getMessageId()) { + case EventMessage::EVENT_MESSAGE: + handleEventMessage(&event); + break; + default: + sif::debug << "CcsdsHandler::checkEvents: Did not subscribe to this event message" + << std::endl; + break; + } + } +} + +void ComSubsystem::handleEventMessage(EventMessage *eventMessage) { + Event event = eventMessage->getEvent(); + switch (event) { + case tcsCtrl::SYRLINKS_OVERHEATING: { + // This event overrides the bit lock. + rememberBitLock = false; + if (mode == com::RX_ONLY) { + return; + } + if (isInTransition) { + performRecoveryToRxOnly = true; + return; + } + startRxOnlyRecovery(true); + break; + } + case pdec::BIT_LOCK_PDEC: { + handleBitLockEvent(); + break; + } + case pdec::CARRIER_LOCK: { + handleCarrierLockEvent(); + break; + } + default: + sif::debug << "ComSubsystem::handleEvent: Did not subscribe to this event" << std::endl; + break; + } +} + +void ComSubsystem::handleBitLockEvent() { + if (isTxMode(mode)) { + // Tx already on + return; + } + if (isInTransition) { + rememberBitLock = true; + return; + } + triggerEvent(BIT_LOCK_TX_ON); + startRxAndTxLowRateSeq(); +} + +void ComSubsystem::handleCarrierLockEvent() { + if (!enableTxWhenCarrierLock) { + return; + } + startRxAndTxLowRateSeq(); +} + +void ComSubsystem::startRxAndTxLowRateSeq() { + // Turns transmitter on + startTransition(com::Submode::RX_AND_TX_LOW_DATARATE, SUBMODE_NONE); +} + +void ComSubsystem::checkTransmitterCountdown() { + if (transmitterCountdown.hasTimedOut()) { + triggerEvent(TX_TIMER_EXPIRED, transmitterTimeout); + startTransition(com::Submode::RX_ONLY, SUBMODE_NONE); + countdownActive = false; + } +} + +void ComSubsystem::startRxOnlyRecovery(bool forced) { + modeHelper.setForced(forced); + startTransition(com::RX_ONLY, 0); + performRecoveryToRxOnly = false; +} + +bool ComSubsystem::isTxMode(Mode_t mode) { + if ((mode == com::Submode::RX_AND_TX_DEFAULT_DATARATE) || + (mode == com::Submode::RX_AND_TX_LOW_DATARATE) || + (mode == com::Submode::RX_AND_TX_HIGH_DATARATE)) { + return true; + } + return false; +} + +void ComSubsystem::announceMode(bool recursive) { + const char *modeStr = "UNKNOWN"; + switch (mode) { + case (com::RX_ONLY): { + modeStr = "RX_ONLY"; + break; + } + case (com::RX_AND_TX_LOW_DATARATE): { + modeStr = "RX_AND_TX_LOW_DATARATE"; + break; + } + case (com::RX_AND_TX_HIGH_DATARATE): { + modeStr = "RX_AND_TX_HIGH_DATARATE"; + break; + } + case (com::RX_AND_TX_DEFAULT_DATARATE): { + modeStr = "RX_AND_TX_DEFAULT_DATARATE"; + break; + } + } + sif::info << "COM subsystem is now in " << modeStr << " mode" << std::endl; + return Subsystem::announceMode(recursive); +} diff --git a/mission/system/com/ComSubsystem.h b/mission/system/com/ComSubsystem.h new file mode 100644 index 0000000..529e28f --- /dev/null +++ b/mission/system/com/ComSubsystem.h @@ -0,0 +1,93 @@ +#ifndef MISSION_SYSTEM_COMSUBSYSTEM_H_ +#define MISSION_SYSTEM_COMSUBSYSTEM_H_ + +#include +#include +#include +#include +#include + +#include "mission/com/defs.h" + +class ComSubsystem : public Subsystem, public ReceivesParameterMessagesIF { + public: + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::COM_SUBSYSTEM; + + //! [EXPORT] : [COMMENT] The transmit timer to protect the Syrlinks expired + //! P1: The current timer value + static const Event TX_TIMER_EXPIRED = MAKE_EVENT(1, severity::INFO); + //! [EXPORT] : [COMMENT] Transmitter will be turned on due to detection of bitlock + static const Event BIT_LOCK_TX_ON = MAKE_EVENT(2, severity::INFO); + + /** + * @brief Constructor + * + * @param setObjectId + * @param maxNumberOfSequences + * @param maxNumberOfTables + * @param transmitterTimeout Maximum time the transmitter of the syrlinks + * will + * be enabled + */ + ComSubsystem(object_id_t setObjectId, uint32_t maxNumberOfSequences, uint32_t maxNumberOfTables, + uint32_t transmitterTimeout); + virtual ~ComSubsystem() = default; + + MessageQueueId_t getCommandQueue() const override; + ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueIdentifier, + ParameterWrapper *parameterWrapper, const ParameterWrapper *newValues, + uint16_t startAtIndex) override; + virtual void performChildOperation() override; + + private: + static const Mode_t INITIAL_MODE = 0; + + ReturnValue_t handleCommandMessage(CommandMessage *message) override; + + ReturnValue_t initialize() override; + + void startTransition(Mode_t mode, Submode_t submode) override; + void announceMode(bool recursive) override; + + void readEventQueue(); + void handleEventMessage(EventMessage *eventMessage); + void handleBitLockEvent(); + void handleCarrierLockEvent(); + void checkTransmitterCountdown(); + /** + * @brief Enables transmitter in low rate mode + */ + void startRxAndTxLowRateSeq(); + void startRxOnlyRecovery(bool forced); + + /** + * @brief Returns true if mode is a mode where the transmitter is on + */ + bool isTxMode(Mode_t mode); + + uint8_t datarateCfg = static_cast(com::Datarate::LOW_RATE_MODULATION_BPSK); + + // Maximum time after which the transmitter will be turned of. This is a + // protection mechanism due prevent the syrlinks from overheating + uint32_t transmitterTimeout = 0; + ParameterHelper paramHelper; + + MessageQueueIF *eventQueue = nullptr; + + bool enableTxWhenCarrierLock = false; + bool performRecoveryToRxOnly = false; + + // Countdown will be started as soon as the transmitter was enabled + Countdown transmitterCountdown; + + // Transmitter countdown only active when sysrlinks transmitter is on (modes: + // rx and tx low rate, rx and tx high rate, rx and tx default rate) + bool countdownActive = false; + + // True when bit lock occurred while COM subsystem is in a transition. This + // variable is used to remember the bit lock and execute the default rate + // sequence after the active transition has been completed + bool rememberBitLock = false; +}; + +#endif /* MISSION_SYSTEM_COMSUBSYSTEM_H_ */ diff --git a/mission/system/com/SyrlinksAssembly.cpp b/mission/system/com/SyrlinksAssembly.cpp new file mode 100644 index 0000000..b8063bb --- /dev/null +++ b/mission/system/com/SyrlinksAssembly.cpp @@ -0,0 +1,64 @@ +#include "SyrlinksAssembly.h" + +#include +#include + +using namespace returnvalue; + +SyrlinksAssembly::SyrlinksAssembly(object_id_t objectId) : AssemblyBase(objectId) { + ModeListEntry entry; + entry.setObject(objects::SYRLINKS_HANDLER); + entry.setMode(MODE_OFF); + entry.setSubmode(SUBMODE_NONE); + commandTable.insert(entry); +} + +ReturnValue_t SyrlinksAssembly::commandChildren(Mode_t mode, Submode_t submode) { + commandTable[0].setMode(mode); + commandTable[0].setSubmode(submode); + HybridIterator iter(commandTable.begin(), commandTable.end()); + if (recoveryState == RECOVERY_IDLE) { + ReturnValue_t result = checkAndHandleHealthState(mode, submode); + if (result == NEED_TO_CHANGE_HEALTH) { + return result; + } + } + executeTable(iter); + return returnvalue::OK; +} + +ReturnValue_t SyrlinksAssembly::checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) { + if (childrenMap[objects::SYRLINKS_HANDLER].mode != wantedMode) { + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + return returnvalue::OK; +} + +ReturnValue_t SyrlinksAssembly::isModeCombinationValid(Mode_t mode, Submode_t submode) { + if (mode == MODE_ON or mode == DeviceHandlerIF::MODE_NORMAL) { + if (submode >= com::Submode::NUM_SUBMODES or submode < com::Submode::RX_ONLY) { + return HasModesIF::INVALID_SUBMODE; + } + return returnvalue::OK; + } + if (mode == MODE_OFF and submode != SUBMODE_NONE) { + return HasModesIF::INVALID_SUBMODE; + } + return returnvalue::OK; +} + +ReturnValue_t SyrlinksAssembly::checkAndHandleHealthState(Mode_t deviceMode, + Submode_t deviceSubmode) { + HealthState health = healthHelper.healthTable->getHealth(objects::SYRLINKS_HANDLER); + if (health == FAULTY or health == PERMANENT_FAULTY) { + overwriteDeviceHealth(objects::SYRLINKS_HANDLER, health); + return NEED_TO_CHANGE_HEALTH; + } else if (health == EXTERNAL_CONTROL) { + modeHelper.setForced(true); + } + return OK; +} + +void SyrlinksAssembly::handleChildrenLostMode(ReturnValue_t result) { + startTransition(mode, submode); +} diff --git a/mission/system/com/SyrlinksAssembly.h b/mission/system/com/SyrlinksAssembly.h new file mode 100644 index 0000000..314474d --- /dev/null +++ b/mission/system/com/SyrlinksAssembly.h @@ -0,0 +1,20 @@ +#ifndef MISSION_SYSTEM_OBJECTS_SYRLINKSASSEMBLY_H_ +#define MISSION_SYSTEM_OBJECTS_SYRLINKSASSEMBLY_H_ +#include + +class SyrlinksAssembly : public AssemblyBase { + public: + SyrlinksAssembly(object_id_t objectId); + + private: + FixedArrayList commandTable; + + ReturnValue_t commandChildren(Mode_t mode, Submode_t submode) override; + ReturnValue_t checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) override; + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; + void handleChildrenLostMode(ReturnValue_t result) override; + + ReturnValue_t checkAndHandleHealthState(Mode_t deviceMode, Submode_t deviceSubmode); +}; + +#endif /* MISSION_SYSTEM_OBJECTS_SYRLINKSASSEMBLY_H_ */ diff --git a/mission/system/com/SyrlinksFdir.cpp b/mission/system/com/SyrlinksFdir.cpp new file mode 100644 index 0000000..a8e2549 --- /dev/null +++ b/mission/system/com/SyrlinksFdir.cpp @@ -0,0 +1,122 @@ +#include "SyrlinksFdir.h" + +#include "fsfw/devicehandlers/DeviceHandlerIF.h" +#include "fsfw/health/HealthTableIF.h" +#include "fsfw/modes/HasModesIF.h" +#include "fsfw/objectmanager/ObjectManager.h" +#include "fsfw/power/Fuse.h" +#include "fsfw/serviceinterface/ServiceInterfaceStream.h" +#include "fsfw/thermal/ThermalComponentIF.h" +#include "mission/com/syrlinksDefs.h" + +SyrlinksFdir::SyrlinksFdir(object_id_t syrlinksId) + : DeviceHandlerFailureIsolation(syrlinksId, objects::NO_OBJECT) {} + +ReturnValue_t SyrlinksFdir::eventReceived(EventMessage* event) { + if (isFdirInActionOrAreWeFaulty(event)) { + return returnvalue::OK; + } + ReturnValue_t result = returnvalue::FAILED; + switch (event->getEvent()) { + case HasModesIF::MODE_TRANSITION_FAILED: + case HasModesIF::OBJECT_IN_INVALID_MODE: + case DeviceHandlerIF::DEVICE_WANTS_HARD_REBOOT: + // We'll try a recovery as long as defined in MAX_REBOOT. + // Might cause some AssemblyBase cycles, so keep number low. + handleRecovery(event->getEvent()); + break; + case DeviceHandlerIF::DEVICE_INTERPRETING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_READING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_UNREQUESTED_REPLY: + case DeviceHandlerIF::DEVICE_UNKNOWN_REPLY: // Some DH's generate generic reply-ids. + case DeviceHandlerIF::DEVICE_BUILDING_COMMAND_FAILED: + // These faults all mean that there were stupid replies from a device. + if (strangeReplyCount.incrementAndCheck()) { + handleRecovery(event->getEvent()); + } + break; + case DeviceHandlerIF::DEVICE_SENDING_COMMAND_FAILED: + case DeviceHandlerIF::DEVICE_REQUESTING_REPLY_FAILED: + // The two above should never be confirmed. + case DeviceHandlerIF::DEVICE_MISSED_REPLY: + result = sendConfirmationRequest(event); + if (result == returnvalue::OK) { + break; + } + // else + if (missedReplyCount.incrementAndCheck()) { + handleRecovery(event->getEvent()); + } + break; + case StorageManagerIF::GET_DATA_FAILED: + case StorageManagerIF::STORE_DATA_FAILED: + // Rather strange bugs, occur in RAW mode only. Ignore. + break; + case DeviceHandlerIF::INVALID_DEVICE_COMMAND: + // Ignore, is bad configuration. We can't do anything in flight. + break; + case HasHealthIF::HEALTH_INFO: + case HasModesIF::MODE_INFO: + case HasModesIF::CHANGING_MODE: + // Do nothing, but mark as handled. + break; + //****Power***** + case PowerSwitchIF::SWITCH_WENT_OFF: + if (powerConfirmation != MessageQueueIF::NO_QUEUE) { + result = sendConfirmationRequest(event, powerConfirmation); + if (result == returnvalue::OK) { + setFdirState(DEVICE_MIGHT_BE_OFF); + } + } + break; + case Fuse::FUSE_WENT_OFF: + // Not so good, because PCDU reacted. + case Fuse::POWER_ABOVE_HIGH_LIMIT: + // Better, because software detected over-current. + setFaulty(event->getEvent()); + break; + case Fuse::POWER_BELOW_LOW_LIMIT: + // Device might got stuck during boot, retry. + handleRecovery(event->getEvent()); + break; + //****Thermal***** + case ThermalComponentIF::COMPONENT_TEMP_LOW: + case ThermalComponentIF::COMPONENT_TEMP_HIGH: + case ThermalComponentIF::COMPONENT_TEMP_OOL_LOW: + case ThermalComponentIF::COMPONENT_TEMP_OOL_HIGH: + // Well, the device is not really faulty, but it is required to stay off as long as possible. + setFaulty(event->getEvent()); + break; + case ThermalComponentIF::TEMP_NOT_IN_OP_RANGE: + // Ignore, is information only. + break; + //*******Default monitoring variables. Are currently not used.***** + // case DeviceHandlerIF::MONITORING_LIMIT_EXCEEDED: + // setFaulty(event->getEvent()); + // break; + // case DeviceHandlerIF::MONITORING_AMBIGUOUS: + // break; + default: + // We don't know the event, someone else should handle it. + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void SyrlinksFdir::eventConfirmed(EventMessage* event) { + switch (event->getEvent()) { + case DeviceHandlerIF::DEVICE_SENDING_COMMAND_FAILED: + case DeviceHandlerIF::DEVICE_REQUESTING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_MISSED_REPLY: + if (missedReplyCount.incrementAndCheck()) { + handleRecovery(event->getEvent()); + } + break; + case PowerSwitchIF::SWITCH_WENT_OFF: + // This means the switch went off only for one device. + handleRecovery(event->getEvent()); + break; + default: + break; + } +} diff --git a/mission/system/com/SyrlinksFdir.h b/mission/system/com/SyrlinksFdir.h new file mode 100644 index 0000000..e56714e --- /dev/null +++ b/mission/system/com/SyrlinksFdir.h @@ -0,0 +1,15 @@ +#ifndef MISSION_DEVICES_SYRLINKSFDIR_H_ +#define MISSION_DEVICES_SYRLINKSFDIR_H_ + +#include + +class SyrlinksFdir : public DeviceHandlerFailureIsolation { + public: + SyrlinksFdir(object_id_t syrlinksId); + + private: + ReturnValue_t eventReceived(EventMessage* event) override; + void eventConfirmed(EventMessage* event) override; +}; + +#endif /* MISSION_DEVICES_SYRLINKSFDIR_H_ */ diff --git a/mission/system/com/comModeTree.cpp b/mission/system/com/comModeTree.cpp new file mode 100644 index 0000000..23611ea --- /dev/null +++ b/mission/system/com/comModeTree.cpp @@ -0,0 +1,307 @@ +#include "comModeTree.h" + +#include +#include +#include + +#include "eive/objects.h" +#include "mission/com/defs.h" +#include "mission/system/treeUtil.h" + +const auto check = subsystem::checkInsert; + +ComSubsystem satsystem::com::SUBSYSTEM = + ComSubsystem(objects::COM_SUBSYSTEM, 12, 24, TRANSMITTER_TIMEOUT); + +static const auto OFF = HasModesIF::MODE_OFF; +static const auto ON = HasModesIF::MODE_ON; +static const auto NML = DeviceHandlerIF::MODE_NORMAL; + +auto COM_SEQUENCE_RX_ONLY = + std::make_pair(::com::Submode::RX_ONLY, FixedArrayList()); +auto COM_TABLE_RX_ONLY_TGT = std::make_pair( + static_cast(::com::Submode::RX_ONLY << 24) | 1, FixedArrayList()); +auto COM_TABLE_RX_ONLY_TRANS_0 = std::make_pair( + static_cast(::com::Submode::RX_ONLY << 24) | 2, FixedArrayList()); +auto COM_TABLE_RX_ONLY_TRANS_1 = std::make_pair( + static_cast(::com::Submode::RX_ONLY << 24) | 3, FixedArrayList()); + +auto COM_SEQUENCE_RX_AND_TX_LOW_RATE = + std::make_pair(::com::Submode::RX_AND_TX_LOW_DATARATE, FixedArrayList()); +auto COM_TABLE_RX_AND_TX_LOW_RATE_TGT = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_LOW_DATARATE << 24) | 1, + FixedArrayList()); +auto COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_0 = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_LOW_DATARATE << 24) | 2, + FixedArrayList()); +auto COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1 = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_LOW_DATARATE << 24) | 3, + FixedArrayList()); + +auto COM_SEQUENCE_RX_AND_TX_HIGH_RATE = + std::make_pair(::com::Submode::RX_AND_TX_HIGH_DATARATE, FixedArrayList()); +auto COM_TABLE_RX_AND_TX_HIGH_RATE_TGT = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_HIGH_DATARATE << 24) | 1, + FixedArrayList()); +auto COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_0 = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_HIGH_DATARATE << 24) | 2, + FixedArrayList()); +auto COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1 = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_HIGH_DATARATE << 24) | 3, + FixedArrayList()); + +auto COM_SEQUENCE_RX_AND_TX_DEFAULT_RATE = + std::make_pair(::com::Submode::RX_AND_TX_DEFAULT_DATARATE, FixedArrayList()); +auto COM_TABLE_RX_AND_TX_DEFAULT_RATE_TGT = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_DEFAULT_DATARATE << 24) | 1, + FixedArrayList()); +auto COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_0 = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_DEFAULT_DATARATE << 24) | 2, + FixedArrayList()); +auto COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1 = + std::make_pair(static_cast(::com::Submode::RX_AND_TX_DEFAULT_DATARATE << 24) | 3, + FixedArrayList()); + +namespace { + +void buildRxOnlySequence(Subsystem& ss, ModeListEntry& eh); +void buildTxAndRxLowRateSequence(Subsystem& ss, ModeListEntry& eh); +void buildTxAndRxHighRateSequence(Subsystem& ss, ModeListEntry& eh); +void buildTxAndRxDefaultRateSequence(Subsystem& ss, ModeListEntry& eh); + +} // namespace + +Subsystem& satsystem::com::init() { + ModeListEntry entry; + buildRxOnlySequence(SUBSYSTEM, entry); + buildTxAndRxLowRateSequence(SUBSYSTEM, entry); + buildTxAndRxHighRateSequence(SUBSYSTEM, entry); + buildTxAndRxDefaultRateSequence(SUBSYSTEM, entry); + SUBSYSTEM.setInitialMode(COM_SEQUENCE_RX_ONLY.first); + return SUBSYSTEM; +} + +namespace { + +void buildRxOnlySequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::com::buildRxOnlySequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build RX Only table. We could track the state of the CCSDS IP core handler + // as well but I do not think this is necessary because enabling that should + // not interfere with the Syrlinks Handler. + iht(objects::SYRLINKS_ASSY, NML, ::com::Submode::RX_ONLY, COM_TABLE_RX_ONLY_TGT.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_ONLY_TGT.first, &COM_TABLE_RX_ONLY_TGT.second)), ctxc); + + // Build RX Only transition 0 + iht(objects::SYRLINKS_ASSY, NML, ::com::Submode::RX_ONLY, COM_TABLE_RX_ONLY_TRANS_0.second); + iht(objects::LOG_STORE_AND_TM_TASK, OFF, 0, COM_TABLE_RX_ONLY_TRANS_0.second); + iht(objects::HK_STORE_AND_TM_TASK, OFF, 0, COM_TABLE_RX_ONLY_TRANS_0.second); + iht(objects::CFDP_STORE_AND_TM_TASK, OFF, 0, COM_TABLE_RX_ONLY_TRANS_0.second); + iht(objects::LIVE_TM_TASK, OFF, 0, COM_TABLE_RX_ONLY_TRANS_0.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_ONLY_TRANS_0.first, &COM_TABLE_RX_ONLY_TRANS_0.second)), + ctxc); + + // Build RX Only transition 1 + iht(objects::CCSDS_HANDLER, OFF, 0, COM_TABLE_RX_ONLY_TRANS_1.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_ONLY_TRANS_1.first, &COM_TABLE_RX_ONLY_TRANS_1.second)), + ctxc); + + // Build TX OFF sequence + ihs(COM_SEQUENCE_RX_ONLY.second, COM_TABLE_RX_ONLY_TGT.first, 0, true); + ihs(COM_SEQUENCE_RX_ONLY.second, COM_TABLE_RX_ONLY_TRANS_0.first, 0, false); + ihs(COM_SEQUENCE_RX_ONLY.second, COM_TABLE_RX_ONLY_TRANS_1.first, 0, false); + check(ss.addSequence(SequenceEntry(COM_SEQUENCE_RX_ONLY.first, &COM_SEQUENCE_RX_ONLY.second, + COM_SEQUENCE_RX_ONLY.first)), + ctxc); +} + +void buildTxAndRxLowRateSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::com::buildTxAndRxLowRateSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build RX and TX low datarate table. + iht(objects::SYRLINKS_ASSY, NML, ::com::Submode::RX_AND_TX_LOW_DATARATE, + COM_TABLE_RX_AND_TX_LOW_RATE_TGT.second); + iht(objects::CCSDS_HANDLER, ON, static_cast(::com::CcsdsSubmode::DATARATE_LOW), + COM_TABLE_RX_AND_TX_LOW_RATE_TGT.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_LOW_RATE_TGT.first, + &COM_TABLE_RX_AND_TX_LOW_RATE_TGT.second)), + ctxc); + + // Build TX and RX low datarate transition 0, switch CCSDS handler first + iht(objects::CCSDS_HANDLER, ON, static_cast(::com::CcsdsSubmode::DATARATE_LOW), + COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_0.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_0.first, + &COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_0.second)), + ctxc); + + // Build TX and RX low transition 1 + iht(objects::SYRLINKS_ASSY, NML, ::com::Submode::RX_AND_TX_LOW_DATARATE, + COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1.second); + iht(objects::LOG_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1.second); + iht(objects::HK_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1.second); + iht(objects::CFDP_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1.second); + iht(objects::LIVE_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1.first, + &COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1.second)), + ctxc); + + // Build TX and RX low datarate sequence + ihs(COM_SEQUENCE_RX_AND_TX_LOW_RATE.second, COM_TABLE_RX_AND_TX_LOW_RATE_TGT.first, 0, true); + ihs(COM_SEQUENCE_RX_AND_TX_LOW_RATE.second, COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_0.first, 0, false); + ihs(COM_SEQUENCE_RX_AND_TX_LOW_RATE.second, COM_TABLE_RX_AND_TX_LOW_RATE_TRANS_1.first, 0, false); + check(ss.addSequence(SequenceEntry(COM_SEQUENCE_RX_AND_TX_LOW_RATE.first, + &COM_SEQUENCE_RX_AND_TX_LOW_RATE.second, + COM_SEQUENCE_RX_ONLY.first)), + ctxc); +} + +void buildTxAndRxHighRateSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::com::buildTxAndRxHighRateSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build RX and TX high datarate table. + iht(objects::SYRLINKS_ASSY, NML, ::com::Submode::RX_AND_TX_HIGH_DATARATE, + COM_TABLE_RX_AND_TX_HIGH_RATE_TGT.second); + iht(objects::CCSDS_HANDLER, ON, static_cast(::com::CcsdsSubmode::DATARATE_HIGH), + COM_TABLE_RX_AND_TX_HIGH_RATE_TGT.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_HIGH_RATE_TGT.first, + &COM_TABLE_RX_AND_TX_HIGH_RATE_TGT.second)), + ctxc); + + // Build TX and RX high datarate transition 0, switch CCSDS handler first + iht(objects::CCSDS_HANDLER, ON, static_cast(::com::CcsdsSubmode::DATARATE_HIGH), + COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_0.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_0.first, + &COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_0.second)), + ctxc); + + // Build TX and RX high transition 1 + iht(objects::SYRLINKS_ASSY, NML, ::com::Submode::RX_AND_TX_HIGH_DATARATE, + COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1.second); + iht(objects::LOG_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1.second); + iht(objects::HK_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1.second); + iht(objects::CFDP_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1.second); + iht(objects::LIVE_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1.first, + &COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1.second)), + ctxc); + + // Build TX and RX low datarate sequence + ihs(COM_SEQUENCE_RX_AND_TX_HIGH_RATE.second, COM_TABLE_RX_AND_TX_HIGH_RATE_TGT.first, 0, true); + ihs(COM_SEQUENCE_RX_AND_TX_HIGH_RATE.second, COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_0.first, 0, + false); + ihs(COM_SEQUENCE_RX_AND_TX_HIGH_RATE.second, COM_TABLE_RX_AND_TX_HIGH_RATE_TRANS_1.first, 0, + false); + check(ss.addSequence(SequenceEntry(COM_SEQUENCE_RX_AND_TX_HIGH_RATE.first, + &COM_SEQUENCE_RX_AND_TX_HIGH_RATE.second, + COM_SEQUENCE_RX_ONLY.first)), + ctxc); +} + +void buildTxAndRxDefaultRateSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::com::buildTxAndRxDefaultRateSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build RX and TX default datarate table. + iht(objects::SYRLINKS_ASSY, NML, ::com::Submode::RX_AND_TX_DEFAULT_DATARATE, + COM_TABLE_RX_AND_TX_DEFAULT_RATE_TGT.second); + iht(objects::CCSDS_HANDLER, ON, static_cast(::com::CcsdsSubmode::DATARATE_DEFAULT), + COM_TABLE_RX_AND_TX_DEFAULT_RATE_TGT.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_DEFAULT_RATE_TGT.first, + &COM_TABLE_RX_AND_TX_DEFAULT_RATE_TGT.second)), + ctxc); + + // Build TX and RX low datarate transition 0, switch CCSDS handler first + iht(objects::CCSDS_HANDLER, ON, static_cast(::com::CcsdsSubmode::DATARATE_DEFAULT), + COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_0.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_0.first, + &COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_0.second)), + ctxc); + + // Build TX and RX default transition 1 + iht(objects::SYRLINKS_ASSY, NML, ::com::Submode::RX_AND_TX_DEFAULT_DATARATE, + COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1.second); + iht(objects::LOG_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1.second); + iht(objects::HK_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1.second); + iht(objects::CFDP_STORE_AND_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1.second); + iht(objects::LIVE_TM_TASK, ON, 0, COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1.second); + check(ss.addTable(TableEntry(COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1.first, + &COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1.second)), + ctxc); + + // Build TX and RX default datarate sequence + ihs(COM_SEQUENCE_RX_AND_TX_DEFAULT_RATE.second, COM_TABLE_RX_AND_TX_DEFAULT_RATE_TGT.first, 0, + true); + ihs(COM_SEQUENCE_RX_AND_TX_DEFAULT_RATE.second, COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_0.first, 0, + false); + ihs(COM_SEQUENCE_RX_AND_TX_DEFAULT_RATE.second, COM_TABLE_RX_AND_TX_DEFAULT_RATE_TRANS_1.first, 0, + false); + check(ss.addSequence(SequenceEntry(COM_SEQUENCE_RX_AND_TX_DEFAULT_RATE.first, + &COM_SEQUENCE_RX_AND_TX_DEFAULT_RATE.second, + COM_SEQUENCE_RX_ONLY.first)), + ctxc); +} + +} // namespace diff --git a/mission/system/com/comModeTree.h b/mission/system/com/comModeTree.h new file mode 100644 index 0000000..bf03bfb --- /dev/null +++ b/mission/system/com/comModeTree.h @@ -0,0 +1,24 @@ +#ifndef MISSION_SYSTEM_TREE_COMMODETREE_H_ +#define MISSION_SYSTEM_TREE_COMMODETREE_H_ + +#include + +#include "ComSubsystem.h" + +namespace satsystem { + +namespace com { +extern ComSubsystem SUBSYSTEM; + +// The syrlinks must not transmitting longer then 15 minutes otherwise the +// transceiver might be damaged due to overheating +// This is the initial timeout of 2 minutes. The timeout needs to be incremented +// before each overpass +static const uint32_t TRANSMITTER_TIMEOUT = 120000; + +Subsystem& init(); +} // namespace com + +} // namespace satsystem + +#endif /* MISSION_SYSTEM_TREE_COMMODETREE_H_ */ diff --git a/mission/system/objects/CMakeLists.txt b/mission/system/objects/CMakeLists.txt new file mode 100644 index 0000000..e7c2b6f --- /dev/null +++ b/mission/system/objects/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources( + ${LIB_EIVE_MISSION} PRIVATE CamSwitcher.cpp PayloadSubsystem.cpp + Stack5VHandler.cpp PowerStateMachineBase.cpp) diff --git a/mission/system/objects/CamSwitcher.cpp b/mission/system/objects/CamSwitcher.cpp new file mode 100644 index 0000000..93b6764 --- /dev/null +++ b/mission/system/objects/CamSwitcher.cpp @@ -0,0 +1,24 @@ +#include "CamSwitcher.h" + +CamSwitcher::CamSwitcher(object_id_t objectId, PowerSwitchIF& pwrSwitcher, + power::Switch_t pwrSwitch) + : PowerSwitcherComponent(objectId, &pwrSwitcher, pwrSwitch) {} +void CamSwitcher::performFaultyOperation() { + if (not switcher.active() and switcher.getState() != PowerSwitcher::SWITCH_IS_OFF) { + switcher.turnOff(); + } +} + +ReturnValue_t CamSwitcher::checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t* msToReachTheMode) { + if (commandedMode != MODE_OFF) { + PoolReadGuard pg(&enablePl); + if (pg.getReadResult() == returnvalue::OK) { + if (enablePl.plUseAllowed.isValid() and not enablePl.plUseAllowed.value) { + return TRANS_NOT_ALLOWED; + } + } + } + return PowerSwitcherComponent::checkModeCommand(commandedMode, commandedSubmode, + msToReachTheMode); +} diff --git a/mission/system/objects/CamSwitcher.h b/mission/system/objects/CamSwitcher.h new file mode 100644 index 0000000..a0d1b24 --- /dev/null +++ b/mission/system/objects/CamSwitcher.h @@ -0,0 +1,20 @@ +#ifndef MISSION_SYSTEM_OBJECTS_CAMSWITCHER_H_ +#define MISSION_SYSTEM_OBJECTS_CAMSWITCHER_H_ + +#include +#include +#include + +class CamSwitcher : public PowerSwitcherComponent { + public: + CamSwitcher(object_id_t objectId, PowerSwitchIF& pwrSwitcher, power::Switch_t pwrSwitch); + + private: + pwrctrl::EnablePl enablePl = pwrctrl::EnablePl(objects::POWER_CONTROLLER); + ReturnValue_t checkModeCommand(Mode_t commandedMode, Submode_t commandedSubmode, + uint32_t* msToReachTheMode) override; + + void performFaultyOperation() override; +}; + +#endif /* MISSION_SYSTEM_OBJECTS_CAMSWITCHER_H_ */ diff --git a/mission/system/objects/PayloadSubsystem.cpp b/mission/system/objects/PayloadSubsystem.cpp new file mode 100644 index 0000000..f8e3b08 --- /dev/null +++ b/mission/system/objects/PayloadSubsystem.cpp @@ -0,0 +1,13 @@ +#include "PayloadSubsystem.h" + +#include + +PayloadSubsystem::PayloadSubsystem(object_id_t setObjectId, uint32_t maxNumberOfSequences, + uint32_t maxNumberOfTables) + : Subsystem(setObjectId, maxNumberOfSequences, maxNumberOfTables) {} + +void PayloadSubsystem::announceMode(bool recursive) { + const char* modeStr = payload::getModeStr(static_cast(mode)); + sif::info << "PAYLOAD subsystem is now in " << modeStr << " mode" << std::endl; + Subsystem::announceMode(recursive); +} diff --git a/mission/system/objects/PayloadSubsystem.h b/mission/system/objects/PayloadSubsystem.h new file mode 100644 index 0000000..a4203f4 --- /dev/null +++ b/mission/system/objects/PayloadSubsystem.h @@ -0,0 +1,15 @@ +#ifndef MISSION_SYSTEM_PAYLOADSUBSYSTEM_H_ +#define MISSION_SYSTEM_PAYLOADSUBSYSTEM_H_ + +#include + +class PayloadSubsystem : public Subsystem { + public: + PayloadSubsystem(object_id_t setObjectId, uint32_t maxNumberOfSequences, + uint32_t maxNumberOfTables); + + private: + void announceMode(bool recursive) override; +}; + +#endif /* MISSION_SYSTEM_PAYLOADSUBSYSTEM_H_ */ diff --git a/mission/system/objects/PowerStateMachineBase.cpp b/mission/system/objects/PowerStateMachineBase.cpp new file mode 100644 index 0000000..510f6e8 --- /dev/null +++ b/mission/system/objects/PowerStateMachineBase.cpp @@ -0,0 +1,35 @@ +#include "PowerStateMachineBase.h" + +#include "fsfw/serviceinterface.h" + +PowerStateMachineBase::PowerStateMachineBase(PowerSwitchIF *pwrSwitcher, dur_millis_t checkTimeout) + : pwrSwitcher(pwrSwitcher), checkTimeout(checkTimeout) {} + +void PowerStateMachineBase::reset() { + state = power::States::IDLE; + opResult = power::OpCodes::NONE; + targetMode = HasModesIF::MODE_OFF; + targetSubmode = 0; + checkTimeout.resetTimer(); +} + +void PowerStateMachineBase::setCheckTimeout(dur_millis_t timeout) { + checkTimeout.setTimeout(timeout); +} + +void PowerStateMachineBase::start(Mode_t mode, Submode_t submode) { + reset(); + checkTimeout.resetTimer(); + targetMode = mode; + targetSubmode = submode; + state = power::States::SWITCHING_POWER; +} + +power::States PowerStateMachineBase::getState() const { return state; } + +bool PowerStateMachineBase::active() { + if (state == power::States::IDLE or state == power::States::MODE_COMMANDING) { + return false; + } + return true; +} diff --git a/mission/system/objects/PowerStateMachineBase.h b/mission/system/objects/PowerStateMachineBase.h new file mode 100644 index 0000000..bc917b2 --- /dev/null +++ b/mission/system/objects/PowerStateMachineBase.h @@ -0,0 +1,31 @@ +#ifndef MISSION_SYSTEM_POWERSTATEMACHINE_H_ +#define MISSION_SYSTEM_POWERSTATEMACHINE_H_ + +#include +#include +#include + +#include "mission/power/defs.h" + +class PowerStateMachineBase { + public: + PowerStateMachineBase(PowerSwitchIF* pwrSwitcher, dur_millis_t checkTimeout); + + virtual power::OpCodes fsm() = 0; + + void setCheckTimeout(dur_millis_t timeout); + void reset(); + void start(Mode_t mode, Submode_t submode); + bool active(); + power::States getState() const; + + protected: + power::OpCodes opResult = power::OpCodes::NONE; + power::States state = power::States::IDLE; + PowerSwitchIF* pwrSwitcher = nullptr; + Mode_t targetMode = HasModesIF::MODE_OFF; + Submode_t targetSubmode = 0; + Countdown checkTimeout; +}; + +#endif /* MISSION_SYSTEM_POWERSTATEMACHINE_H_ */ diff --git a/mission/system/objects/Stack5VHandler.cpp b/mission/system/objects/Stack5VHandler.cpp new file mode 100644 index 0000000..6c3c94d --- /dev/null +++ b/mission/system/objects/Stack5VHandler.cpp @@ -0,0 +1,81 @@ +#include "Stack5VHandler.h" + +Stack5VHandler::Stack5VHandler(PowerSwitchIF& switcher) : switcher(switcher) { + stackLock = MutexFactory::instance()->createMutex(); +} + +ReturnValue_t Stack5VHandler::deviceToOn(StackCommander commander, bool updateStates) { + MutexGuard mg(stackLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (updateStates) { + updateInternalStates(); + } + if (handlerState == HandlerState::SWITCH_PENDING) { + return BUSY; + } + if (switchIsOn) { + if (commander == StackCommander::PL_PCDU) { + plPcduIsOn = true; + } else { + radSensorIsOn = true; + } + return returnvalue::OK; + } + + handlerState = HandlerState::SWITCH_PENDING; + targetState = true; + return switcher.sendSwitchCommand(stackSwitch, PowerSwitchIF::SWITCH_ON); +} + +ReturnValue_t Stack5VHandler::deviceToOff(StackCommander commander, bool updateStates) { + MutexGuard mg(stackLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (updateStates) { + updateInternalStates(); + } + // wait for our turn + if (handlerState == HandlerState::SWITCH_PENDING) { + return BUSY; + } + // If the switch is already off, we are done + if (not switchIsOn) { + if (commander == StackCommander::PL_PCDU) { + plPcduIsOn = false; + } else { + radSensorIsOn = false; + } + return returnvalue::OK; + } + // If one device is still on, do not turn off the switch + if ((commander == StackCommander::PL_PCDU and radSensorIsOn) or + (commander == StackCommander::RAD_SENSOR and plPcduIsOn)) { + return returnvalue::OK; + } + handlerState = HandlerState::SWITCH_PENDING; + targetState = false; + return switcher.sendSwitchCommand(stackSwitch, PowerSwitchIF::SWITCH_OFF); +} + +bool Stack5VHandler::isSwitchOn() { + MutexGuard mg(stackLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + return updateInternalStates(); +} + +void Stack5VHandler::update() { + MutexGuard mg(stackLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + updateInternalStates(); +} + +bool Stack5VHandler::updateInternalStates() { + if (switcher.getSwitchState(stackSwitch) == PowerSwitchIF::SWITCH_ON) { + if (handlerState == HandlerState::SWITCH_PENDING and targetState) { + handlerState = HandlerState::IDLE; + switchIsOn = true; + } + return true; + } else if (handlerState == HandlerState::SWITCH_PENDING and not targetState) { + handlerState = HandlerState::IDLE; + switchIsOn = false; + radSensorIsOn = false; + plPcduIsOn = false; + } + return false; +} diff --git a/mission/system/objects/Stack5VHandler.h b/mission/system/objects/Stack5VHandler.h new file mode 100644 index 0000000..8781fc5 --- /dev/null +++ b/mission/system/objects/Stack5VHandler.h @@ -0,0 +1,39 @@ +#ifndef MISSION_SYSTEM_OBJECTS_STACK5VHANDLER_H_ +#define MISSION_SYSTEM_OBJECTS_STACK5VHANDLER_H_ + +#include +#include + +enum class StackCommander { RAD_SENSOR = 0, PL_PCDU = 1 }; +enum class HandlerState { SWITCH_PENDING, IDLE }; + +class Stack5VHandler { + public: + //! [EXPORT] : [SKIP] + static constexpr ReturnValue_t BUSY = returnvalue::makeCode(1, 0); + Stack5VHandler(PowerSwitchIF& switcher); + + ReturnValue_t deviceToOn(StackCommander commander, bool updateStates); + ReturnValue_t deviceToOff(StackCommander commander, bool updateStates); + + bool isSwitchOn(); + void update(); + + private: + static constexpr MutexIF::TimeoutType LOCK_TYPE = MutexIF::TimeoutType::WAITING; + static constexpr uint32_t LOCK_TIMEOUT = 20; + + MutexIF* stackLock; + static constexpr char LOCK_CTX[] = "Stack5VHandler"; + PowerSwitchIF& switcher; + bool switchIsOn = false; + bool targetState = false; + HandlerState handlerState = HandlerState::IDLE; + bool radSensorIsOn = false; + bool plPcduIsOn = false; + power::Switches stackSwitch = power::Switches::P60_DOCK_5V_STACK; + + bool updateInternalStates(); +}; + +#endif /* MISSION_SYSTEM_OBJECTS_STACK5VHANDLER_H_ */ diff --git a/mission/system/payload/CMakeLists.txt b/mission/system/payload/CMakeLists.txt new file mode 100644 index 0000000..7b54684 --- /dev/null +++ b/mission/system/payload/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${LIB_EIVE_MISSION} PRIVATE payloadModeTree.cpp) diff --git a/mission/system/payload/payloadModeTree.cpp b/mission/system/payload/payloadModeTree.cpp new file mode 100644 index 0000000..b2d74dd --- /dev/null +++ b/mission/system/payload/payloadModeTree.cpp @@ -0,0 +1,376 @@ +#include "payloadModeTree.h" + +#include +#include +#include +#include +#include +#include + +#include "eive/objects.h" +#include "mission/power/defs.h" +#include "mission/system/objects/PayloadSubsystem.h" +#include "mission/system/treeUtil.h" + +namespace { +void initOffSequence(Subsystem& ss, ModeListEntry& eh); +void initPlMpsocStreamSequence(Subsystem& ss, ModeListEntry& eh); +void initPlCamStreamSequence(Subsystem& ss, ModeListEntry& eh); +void initPlSpvSequence(Subsystem& ss, ModeListEntry& eh); +void initEarthObsvSequence(Subsystem& ss, ModeListEntry& eh); +void initScexSequence(Subsystem& ss, ModeListEntry& eh); +} // namespace + +PayloadSubsystem satsystem::payload::SUBSYSTEM = PayloadSubsystem(objects::PL_SUBSYSTEM, 12, 24); + +const auto check = subsystem::checkInsert; +static const auto OFF = HasModesIF::MODE_OFF; +static const auto ON = HasModesIF::MODE_ON; +static const auto NML = DeviceHandlerIF::MODE_NORMAL; + +auto PL_SEQUENCE_OFF = std::make_pair(payload::Mode::OFF, FixedArrayList()); +auto PL_TABLE_OFF_TGT = + std::make_pair((payload::Mode::OFF << 24) | 1, FixedArrayList()); +auto PL_TABLE_OFF_TRANS_0 = + std::make_pair((payload::Mode::OFF << 24) | 2, FixedArrayList()); +auto PL_TABLE_OFF_TRANS_1 = + std::make_pair((payload::Mode::OFF << 24) | 3, FixedArrayList()); + +auto PL_SEQUENCE_MPSOC_STREAM = + std::make_pair(payload::Mode::MPSOC_STREAM, FixedArrayList()); +auto PL_TABLE_MPSOC_STREAM_TGT = + std::make_pair((payload::Mode::MPSOC_STREAM << 24) | 1, FixedArrayList()); +auto PL_TABLE_MPSOC_STREAM_TRANS_0 = + std::make_pair((payload::Mode::MPSOC_STREAM << 24) | 2, FixedArrayList()); +auto PL_TABLE_MPSOC_STREAM_TRANS_1 = + std::make_pair((payload::Mode::MPSOC_STREAM << 24) | 3, FixedArrayList()); + +auto PL_SEQUENCE_CAM_STREAM = + std::make_pair(payload::Mode::CAM_STREAM, FixedArrayList()); +auto PL_TABLE_CAM_STREAM_TGT = + std::make_pair((payload::Mode::CAM_STREAM << 24) | 1, FixedArrayList()); +auto PL_TABLE_CAM_STREAM_TRANS_0 = + std::make_pair((payload::Mode::CAM_STREAM << 24) | 2, FixedArrayList()); +auto PL_TABLE_CAM_STREAM_TRANS_1 = + std::make_pair((payload::Mode::CAM_STREAM << 24) | 3, FixedArrayList()); + +auto PL_SEQUENCE_SUPV_ONLY = + std::make_pair(payload::Mode::SUPV_ONLY, FixedArrayList()); +auto PL_TABLE_SUPV_ONLY_TGT = + std::make_pair((payload::Mode::SUPV_ONLY << 24) | 1, FixedArrayList()); +auto PL_TABLE_SUPV_ONLY_TRANS_0 = + std::make_pair((payload::Mode::SUPV_ONLY << 24) | 2, FixedArrayList()); +auto PL_TABLE_SUPV_ONLY_TRANS_1 = + std::make_pair((payload::Mode::SUPV_ONLY << 24) | 3, FixedArrayList()); + +auto PL_SEQUENCE_EARTH_OBSV = + std::make_pair(payload::Mode::EARTH_OBSV, FixedArrayList()); +auto PL_TABLE_EARTH_OBSV_TGT = + std::make_pair((payload::Mode::EARTH_OBSV << 24) | 1, FixedArrayList()); +auto PL_TABLE_EARTH_OBSV_TRANS_0 = + std::make_pair((payload::Mode::EARTH_OBSV << 24) | 2, FixedArrayList()); +auto PL_TABLE_EARTH_OBSV_TRANS_1 = + std::make_pair((payload::Mode::EARTH_OBSV << 24) | 3, FixedArrayList()); + +auto PL_SEQUENCE_SCEX = std::make_pair(payload::Mode::SCEX, FixedArrayList()); +auto PL_TABLE_SCEX_TGT = + std::make_pair((payload::Mode::SCEX << 24) | 1, FixedArrayList()); +auto PL_TABLE_SCEX_TRANS_0 = + std::make_pair((payload::Mode::SCEX << 24) | 2, FixedArrayList()); + +Subsystem& satsystem::payload::init() { + ModeListEntry entry; + initOffSequence(SUBSYSTEM, entry); + initPlMpsocStreamSequence(SUBSYSTEM, entry); + initPlCamStreamSequence(SUBSYSTEM, entry); + initPlSpvSequence(SUBSYSTEM, entry); + initEarthObsvSequence(SUBSYSTEM, entry); + initScexSequence(SUBSYSTEM, entry); + SUBSYSTEM.setInitialMode(OFF); + return SUBSYSTEM; +} + +namespace { + +void initOffSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::payload::buildOffSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build OFF target. Is empty + check(ss.addTable(TableEntry(PL_TABLE_OFF_TGT.first, &PL_TABLE_OFF_TGT.second)), ctxc); + + // Build OFF transition 0 + iht(objects::CAM_SWITCHER, OFF, 0, PL_TABLE_OFF_TRANS_0.second); + iht(objects::SCEX, OFF, 0, PL_TABLE_OFF_TRANS_0.second); + iht(objects::PLPCDU_HANDLER, OFF, 0, PL_TABLE_OFF_TRANS_0.second); + iht(objects::PLOC_MPSOC_HANDLER, OFF, 0, PL_TABLE_OFF_TRANS_0.second); + check(ss.addTable(TableEntry(PL_TABLE_OFF_TRANS_0.first, &PL_TABLE_OFF_TRANS_0.second)), ctxc); + + // Build OFF transition 1 + iht(objects::PLOC_SUPERVISOR_HANDLER, OFF, 0, PL_TABLE_OFF_TRANS_1.second); + check(ss.addTable(TableEntry(PL_TABLE_OFF_TRANS_1.first, &PL_TABLE_OFF_TRANS_1.second)), ctxc); + + // Build OFF sequence + ihs(PL_SEQUENCE_OFF.second, PL_TABLE_OFF_TGT.first, 0, false); + ihs(PL_SEQUENCE_OFF.second, PL_TABLE_OFF_TRANS_0.first, 0, false); + ihs(PL_SEQUENCE_OFF.second, PL_TABLE_OFF_TRANS_1.first, 0, false); + check(ss.addSequence( + SequenceEntry(PL_SEQUENCE_OFF.first, &PL_SEQUENCE_OFF.second, PL_SEQUENCE_OFF.first)), + ctxc); +} + +void initPlMpsocStreamSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::payload::initPlMpsocStreamSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build MPSoC stream target + // Camera should always be off to prevent a conflict with the MPSoC streaming + // PL PCDU must be on and in normal mode, but this is commanded separately because of the + // number of commands invovled + iht(objects::PLPCDU_HANDLER, NML, plpcdu::ALL_ON_SUBMODE, PL_TABLE_MPSOC_STREAM_TGT.second); + iht(objects::CAM_SWITCHER, OFF, 0, PL_TABLE_MPSOC_STREAM_TGT.second); + iht(objects::PLOC_MPSOC_HANDLER, NML, 0, PL_TABLE_MPSOC_STREAM_TGT.second); + iht(objects::PLOC_SUPERVISOR_HANDLER, NML, 0, PL_TABLE_MPSOC_STREAM_TGT.second); + check(ss.addTable(TableEntry(PL_TABLE_MPSOC_STREAM_TGT.first, &PL_TABLE_MPSOC_STREAM_TGT.second)), + ctxc); + + // Build MPSoC stream transition 0 + iht(objects::CAM_SWITCHER, OFF, 0, PL_TABLE_MPSOC_STREAM_TRANS_0.second); + iht(objects::SCEX, OFF, 0, PL_TABLE_MPSOC_STREAM_TRANS_0.second); + iht(objects::PLOC_SUPERVISOR_HANDLER, NML, 0, PL_TABLE_MPSOC_STREAM_TRANS_0.second); + check(ss.addTable( + TableEntry(PL_TABLE_MPSOC_STREAM_TRANS_0.first, &PL_TABLE_MPSOC_STREAM_TRANS_0.second)), + ctxc); + + // Build MPSoC stream transition 1 + iht(objects::PLOC_MPSOC_HANDLER, NML, 0, PL_TABLE_MPSOC_STREAM_TRANS_1.second); + check(ss.addTable( + TableEntry(PL_TABLE_MPSOC_STREAM_TRANS_1.first, &PL_TABLE_MPSOC_STREAM_TRANS_1.second)), + ctxc); + + // Build MPSoC stream sequence + ihs(PL_SEQUENCE_MPSOC_STREAM.second, PL_TABLE_MPSOC_STREAM_TGT.first, 0, true); + ihs(PL_SEQUENCE_MPSOC_STREAM.second, PL_TABLE_MPSOC_STREAM_TRANS_0.first, 0, true); + ihs(PL_SEQUENCE_MPSOC_STREAM.second, PL_TABLE_MPSOC_STREAM_TRANS_1.first, 0, false); + check(ss.addSequence(SequenceEntry(PL_SEQUENCE_MPSOC_STREAM.first, + &PL_SEQUENCE_MPSOC_STREAM.second, PL_SEQUENCE_OFF.first)), + ctxc); +} + +void initPlCamStreamSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::payload::initPlCamStreamSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build CAM target + // Only check that the PL PCDU is on for now. It might later become necessary to switch on + // the PLOC, so we ignore its state. + iht(objects::PLPCDU_HANDLER, NML, plpcdu::ALL_ON_SUBMODE, PL_TABLE_CAM_STREAM_TGT.second); + check(ss.addTable(TableEntry(PL_TABLE_CAM_STREAM_TGT.first, &PL_TABLE_CAM_STREAM_TGT.second)), + ctxc); + + // Build CAM transition 0 + // PLOC is actively commanded off here + iht(objects::PLOC_MPSOC_HANDLER, OFF, 0, PL_TABLE_CAM_STREAM_TRANS_0.second); + iht(objects::CAM_SWITCHER, ON, 0, PL_TABLE_CAM_STREAM_TRANS_0.second); + iht(objects::SCEX, OFF, 0, PL_TABLE_CAM_STREAM_TRANS_0.second); + check(ss.addTable( + TableEntry(PL_TABLE_CAM_STREAM_TRANS_0.first, &PL_TABLE_CAM_STREAM_TRANS_0.second)), + ctxc); + + // Build CAM transition 1 + iht(objects::PLOC_SUPERVISOR_HANDLER, OFF, 0, PL_TABLE_CAM_STREAM_TRANS_1.second); + check(ss.addTable( + TableEntry(PL_TABLE_CAM_STREAM_TRANS_1.first, &PL_TABLE_CAM_STREAM_TRANS_1.second)), + ctxc); + + // Build CAM stream sequence + ihs(PL_SEQUENCE_CAM_STREAM.second, PL_TABLE_CAM_STREAM_TGT.first, 0, true); + ihs(PL_SEQUENCE_CAM_STREAM.second, PL_TABLE_CAM_STREAM_TRANS_0.first, 0, true); + ihs(PL_SEQUENCE_CAM_STREAM.second, PL_TABLE_CAM_STREAM_TRANS_1.first, 0, false); + check(ss.addSequence(SequenceEntry(PL_SEQUENCE_CAM_STREAM.first, &PL_SEQUENCE_CAM_STREAM.second, + PL_SEQUENCE_OFF.first)), + ctxc); +} + +void initPlSpvSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::payload::initPlSupvSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build Payload Supervisor Only target + iht(objects::PLOC_SUPERVISOR_HANDLER, NML, 0, PL_TABLE_SUPV_ONLY_TGT.second); + check(ss.addTable(TableEntry(PL_TABLE_SUPV_ONLY_TGT.first, &PL_TABLE_SUPV_ONLY_TGT.second)), + ctxc); + + // Build Payload Supervisor Only transition 0 + iht(objects::PLOC_SUPERVISOR_HANDLER, NML, 0, PL_TABLE_SUPV_ONLY_TRANS_0.second); + iht(objects::CAM_SWITCHER, OFF, 0, PL_TABLE_SUPV_ONLY_TRANS_0.second); + check( + ss.addTable(TableEntry(PL_TABLE_SUPV_ONLY_TRANS_0.first, &PL_TABLE_SUPV_ONLY_TRANS_0.second)), + ctxc); + + // Build Payload Supervisor Only transition 1 + iht(objects::PLOC_MPSOC_HANDLER, OFF, 0, PL_TABLE_SUPV_ONLY_TRANS_1.second); + check( + ss.addTable(TableEntry(PL_TABLE_SUPV_ONLY_TRANS_1.first, &PL_TABLE_SUPV_ONLY_TRANS_1.second)), + ctxc); + + // Build Payload Supervisor Only Sequence + ihs(PL_SEQUENCE_SUPV_ONLY.second, PL_TABLE_SUPV_ONLY_TGT.first, 0, true); + ihs(PL_SEQUENCE_SUPV_ONLY.second, PL_TABLE_SUPV_ONLY_TRANS_0.first, 0, true); + ihs(PL_SEQUENCE_SUPV_ONLY.second, PL_TABLE_SUPV_ONLY_TRANS_1.first, 0, false); + check(ss.addSequence(SequenceEntry(PL_SEQUENCE_SUPV_ONLY.first, &PL_SEQUENCE_SUPV_ONLY.second, + PL_SEQUENCE_OFF.first)), + ctxc); +} + +void initEarthObsvSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::payload::initEarthObsvSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build Earth Observation target + iht(objects::PLOC_MPSOC_HANDLER, NML, 0, PL_TABLE_EARTH_OBSV_TGT.second); + iht(objects::PLOC_SUPERVISOR_HANDLER, NML, 0, PL_TABLE_EARTH_OBSV_TGT.second); + iht(objects::CAM_SWITCHER, ON, 0, PL_TABLE_EARTH_OBSV_TGT.second); + iht(objects::PLPCDU_HANDLER, OFF, 0, PL_TABLE_EARTH_OBSV_TGT.second); + check(ss.addTable(TableEntry(PL_TABLE_EARTH_OBSV_TGT.first, &PL_TABLE_EARTH_OBSV_TGT.second)), + ctxc); + + // Build Earth Observation transition 0 + iht(objects::PLOC_SUPERVISOR_HANDLER, NML, 0, PL_TABLE_EARTH_OBSV_TRANS_0.second); + iht(objects::CAM_SWITCHER, ON, 0, PL_TABLE_EARTH_OBSV_TRANS_0.second); + iht(objects::PLPCDU_HANDLER, OFF, 0, PL_TABLE_EARTH_OBSV_TRANS_0.second); + check(ss.addTable( + TableEntry(PL_TABLE_EARTH_OBSV_TRANS_0.first, &PL_TABLE_EARTH_OBSV_TRANS_0.second)), + ctxc); + + // Build Earth Observation transition 1 + iht(objects::PLOC_MPSOC_HANDLER, NML, 0, PL_TABLE_CAM_STREAM_TRANS_1.second); + check(ss.addTable( + TableEntry(PL_TABLE_EARTH_OBSV_TRANS_1.first, &PL_TABLE_EARTH_OBSV_TRANS_1.second)), + ctxc); + + ihs(PL_SEQUENCE_EARTH_OBSV.second, PL_TABLE_EARTH_OBSV_TGT.first, 0, true); + ihs(PL_SEQUENCE_EARTH_OBSV.second, PL_TABLE_EARTH_OBSV_TRANS_0.first, 0, true); + ihs(PL_SEQUENCE_EARTH_OBSV.second, PL_TABLE_EARTH_OBSV_TRANS_1.first, 0, false); + check(ss.addSequence(SequenceEntry(PL_SEQUENCE_EARTH_OBSV.first, &PL_SEQUENCE_EARTH_OBSV.second, + PL_SEQUENCE_OFF.first)), + ctxc); +} + +void initScexSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::payload::initScexSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, + ArrayList& sequence) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(sequence.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Build SCEX target + iht(objects::SCEX, NML, 0, PL_TABLE_SCEX_TGT.second); + check(ss.addTable(TableEntry(PL_TABLE_SCEX_TGT.first, &PL_TABLE_SCEX_TGT.second)), ctxc); + + // Build SCEX transition 0 + iht(objects::SCEX, NML, 0, PL_TABLE_SCEX_TRANS_0.second); + check(ss.addTable(TableEntry(PL_TABLE_SCEX_TRANS_0.first, &PL_TABLE_SCEX_TRANS_0.second)), ctxc); + + // Build SCEX sequence + ihs(PL_SEQUENCE_SCEX.second, PL_TABLE_SCEX_TGT.first, 0, true); + ihs(PL_SEQUENCE_SCEX.second, PL_TABLE_SCEX_TRANS_0.first, 0, false); + check(ss.addSequence( + SequenceEntry(PL_SEQUENCE_SCEX.first, &PL_SEQUENCE_SCEX.second, PL_SEQUENCE_OFF.first)), + ctxc); +} + +} // namespace diff --git a/mission/system/payload/payloadModeTree.h b/mission/system/payload/payloadModeTree.h new file mode 100644 index 0000000..842746e --- /dev/null +++ b/mission/system/payload/payloadModeTree.h @@ -0,0 +1,17 @@ +#ifndef MISSION_SYSTEM_TREE_PAYLOADMODETREE_H_ +#define MISSION_SYSTEM_TREE_PAYLOADMODETREE_H_ + +#include + +namespace satsystem { + +namespace payload { + +extern PayloadSubsystem SUBSYSTEM; + +Subsystem& init(); +} // namespace payload + +} // namespace satsystem + +#endif /* MISSION_SYSTEM_TREE_PAYLOADMODETREE_H_ */ diff --git a/mission/system/power/CMakeLists.txt b/mission/system/power/CMakeLists.txt new file mode 100644 index 0000000..2c72097 --- /dev/null +++ b/mission/system/power/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(${LIB_EIVE_MISSION} PRIVATE epsModeTree.cpp EpsSubsystem.cpp + GomspacePowerFdir.cpp) diff --git a/mission/system/power/EpsSubsystem.cpp b/mission/system/power/EpsSubsystem.cpp new file mode 100644 index 0000000..2ec4155 --- /dev/null +++ b/mission/system/power/EpsSubsystem.cpp @@ -0,0 +1,27 @@ +#include + +#include "fsfw/devicehandlers/DeviceHandlerIF.h" + +EpsSubsystem::EpsSubsystem(object_id_t objectId, uint32_t maxNumberOfSequences, + uint32_t maxNumberOfTables) + : Subsystem(objectId, maxNumberOfSequences, maxNumberOfTables) {} + +void EpsSubsystem::announceMode(bool recursive) { + const char* modeStr = "UNKNOWN"; + switch (mode) { + case (HasModesIF::MODE_OFF): { + modeStr = "OFF"; + break; + } + case (HasModesIF::MODE_ON): { + modeStr = "ON"; + break; + } + case (DeviceHandlerIF::MODE_NORMAL): { + modeStr = "NORMAL"; + break; + } + } + sif::info << "EPS subsystem is now in " << modeStr << " mode" << std::endl; + return Subsystem::announceMode(recursive); +} diff --git a/mission/system/power/EpsSubsystem.h b/mission/system/power/EpsSubsystem.h new file mode 100644 index 0000000..33f24d4 --- /dev/null +++ b/mission/system/power/EpsSubsystem.h @@ -0,0 +1,13 @@ +#ifndef MISSION_SYSTEM_OBJECTS_EPSSUBSYSTEM_H_ +#define MISSION_SYSTEM_OBJECTS_EPSSUBSYSTEM_H_ +#include + +class EpsSubsystem : public Subsystem { + public: + EpsSubsystem(object_id_t objectId, uint32_t maxNumberOfSequences, uint32_t maxNumberOfTables); + + private: + void announceMode(bool recursive) override; +}; + +#endif /* MISSION_SYSTEM_OBJECTS_EPSSUBSYSTEM_H_ */ diff --git a/mission/system/power/GomspacePowerFdir.cpp b/mission/system/power/GomspacePowerFdir.cpp new file mode 100644 index 0000000..98e10f5 --- /dev/null +++ b/mission/system/power/GomspacePowerFdir.cpp @@ -0,0 +1,129 @@ +#include "GomspacePowerFdir.h" + +#include + +#include "fsfw/devicehandlers/DeviceHandlerIF.h" +#include "fsfw/health/HealthTableIF.h" +#include "fsfw/modes/HasModesIF.h" +#include "fsfw/objectmanager/ObjectManager.h" +#include "fsfw/power/Fuse.h" +#include "fsfw/serviceinterface/ServiceInterfaceStream.h" +#include "fsfw/thermal/ThermalComponentIF.h" + +GomspacePowerFdir::GomspacePowerFdir(object_id_t devId, object_id_t parentId) + : DeviceHandlerFailureIsolation(devId, parentId) {} + +ReturnValue_t GomspacePowerFdir::eventReceived(EventMessage* event) { + if (isFdirInActionOrAreWeFaulty(event)) { + return returnvalue::OK; + } + ReturnValue_t result = returnvalue::FAILED; + switch (event->getEvent()) { + case HasModesIF::MODE_TRANSITION_FAILED: + case HasModesIF::OBJECT_IN_INVALID_MODE: + case DeviceHandlerIF::DEVICE_WANTS_HARD_REBOOT: + // We'll try a recovery as long as defined in MAX_REBOOT. + // Might cause some AssemblyBase cycles, so keep number low. + // handleRecovery(event->getEvent()); + triggerEvent(power::FDIR_REACTION_IGNORED, event->getEvent(), 0); + break; + case DeviceHandlerIF::DEVICE_INTERPRETING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_READING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_UNREQUESTED_REPLY: + case DeviceHandlerIF::DEVICE_UNKNOWN_REPLY: // Some DH's generate generic reply-ids. + case DeviceHandlerIF::DEVICE_BUILDING_COMMAND_FAILED: + // These faults all mean that there were stupid replies from a device. + if (strangeReplyCount.incrementAndCheck()) { + // handleRecovery(event->getEvent()); + triggerEvent(power::FDIR_REACTION_IGNORED, event->getEvent(), 0); + } + break; + case DeviceHandlerIF::DEVICE_SENDING_COMMAND_FAILED: + case DeviceHandlerIF::DEVICE_REQUESTING_REPLY_FAILED: + // The two above should never be confirmed. + case DeviceHandlerIF::DEVICE_MISSED_REPLY: + result = sendConfirmationRequest(event); + if (result == returnvalue::OK) { + break; + } + // else + if (missedReplyCount.incrementAndCheck()) { + // handleRecovery(event->getEvent()); + triggerEvent(power::FDIR_REACTION_IGNORED, event->getEvent(), 0); + } + break; + case StorageManagerIF::GET_DATA_FAILED: + case StorageManagerIF::STORE_DATA_FAILED: + // Rather strange bugs, occur in RAW mode only. Ignore. + break; + case DeviceHandlerIF::INVALID_DEVICE_COMMAND: + // Ignore, is bad configuration. We can't do anything in flight. + break; + case HasHealthIF::HEALTH_INFO: + case HasModesIF::MODE_INFO: + case HasModesIF::CHANGING_MODE: + // Do nothing, but mark as handled. + break; + //****Power***** + case PowerSwitchIF::SWITCH_WENT_OFF: + if (powerConfirmation != MessageQueueIF::NO_QUEUE) { + result = sendConfirmationRequest(event, powerConfirmation); + if (result == returnvalue::OK) { + setFdirState(DEVICE_MIGHT_BE_OFF); + } + } + break; + case Fuse::FUSE_WENT_OFF: + // Not so good, because PCDU reacted. + case Fuse::POWER_ABOVE_HIGH_LIMIT: + // Better, because software detected over-current. + setFaulty(event->getEvent()); + break; + case Fuse::POWER_BELOW_LOW_LIMIT: + // Device might got stuck during boot, retry. + // handleRecovery(event->getEvent()); + triggerEvent(power::FDIR_REACTION_IGNORED, event->getEvent(), 0); + break; + //****Thermal***** + case ThermalComponentIF::COMPONENT_TEMP_LOW: + case ThermalComponentIF::COMPONENT_TEMP_HIGH: + case ThermalComponentIF::COMPONENT_TEMP_OOL_LOW: + case ThermalComponentIF::COMPONENT_TEMP_OOL_HIGH: + // Well, the device is not really faulty, but it is required to stay off as long as possible. + setFaulty(event->getEvent()); + break; + case ThermalComponentIF::TEMP_NOT_IN_OP_RANGE: + // Ignore, is information only. + break; + //*******Default monitoring variables. Are currently not used.***** + // case DeviceHandlerIF::MONITORING_LIMIT_EXCEEDED: + // setFaulty(event->getEvent()); + // break; + // case DeviceHandlerIF::MONITORING_AMBIGUOUS: + // break; + default: + // We don't know the event, someone else should handle it. + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void GomspacePowerFdir::eventConfirmed(EventMessage* event) { + switch (event->getEvent()) { + case DeviceHandlerIF::DEVICE_SENDING_COMMAND_FAILED: + case DeviceHandlerIF::DEVICE_REQUESTING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_MISSED_REPLY: + if (missedReplyCount.incrementAndCheck()) { + // handleRecovery(event->getEvent()); + triggerEvent(power::FDIR_REACTION_IGNORED, event->getEvent(), 0); + } + break; + case PowerSwitchIF::SWITCH_WENT_OFF: + // This means the switch went off only for one device. + // handleRecovery(event->getEvent()); + triggerEvent(power::FDIR_REACTION_IGNORED, event->getEvent(), 0); + break; + default: + break; + } +} diff --git a/mission/system/power/GomspacePowerFdir.h b/mission/system/power/GomspacePowerFdir.h new file mode 100644 index 0000000..1a9b4b9 --- /dev/null +++ b/mission/system/power/GomspacePowerFdir.h @@ -0,0 +1,15 @@ +#ifndef MISSION_SYSTEM_FDIR_GOMSPACEPOWERFDIR_H_ +#define MISSION_SYSTEM_FDIR_GOMSPACEPOWERFDIR_H_ + +#include + +class GomspacePowerFdir : public DeviceHandlerFailureIsolation { + public: + GomspacePowerFdir(object_id_t devId, object_id_t parentId = objects::NO_OBJECT); + + private: + ReturnValue_t eventReceived(EventMessage* event) override; + void eventConfirmed(EventMessage* event) override; +}; + +#endif /* MISSION_SYSTEM_FDIR_GOMSPACEPOWERFDIR_H_ */ diff --git a/mission/system/power/epsModeTree.cpp b/mission/system/power/epsModeTree.cpp new file mode 100644 index 0000000..83f04ac --- /dev/null +++ b/mission/system/power/epsModeTree.cpp @@ -0,0 +1,104 @@ +#include + +#include "eive/objects.h" +#include "fsfw/devicehandlers/DeviceHandlerIF.h" +#include "fsfw/subsystem/Subsystem.h" +#include "mission/system/treeUtil.h" + +EpsSubsystem satsystem::eps::EPS_SUBSYSTEM(objects::EPS_SUBSYSTEM, 12, 24); + +namespace { +// Alias for checker function +const auto check = subsystem::checkInsert; +void buildOffSequence(Subsystem& ss, ModeListEntry& eh); +void buildNormalSequence(Subsystem& ss, ModeListEntry& eh); +} // namespace + +static const auto OFF = HasModesIF::MODE_OFF; +static const auto NML = DeviceHandlerIF::MODE_NORMAL; + +auto EPS_SEQUENCE_OFF = std::make_pair(OFF, FixedArrayList()); +auto EPS_TABLE_OFF_TGT = std::make_pair((OFF << 24) | 1, FixedArrayList()); +auto EPS_TABLE_OFF_TRANS_0 = std::make_pair((OFF << 24) | 2, FixedArrayList()); + +auto EPS_SEQUENCE_NORMAL = std::make_pair(NML, FixedArrayList()); +auto EPS_TABLE_NORMAL_TGT = std::make_pair((NML << 24) | 1, FixedArrayList()); +auto EPS_TABLE_NORMAL_TRANS_0 = std::make_pair((NML << 24) | 2, FixedArrayList()); + +Subsystem& satsystem::eps::init() { + ModeListEntry entry; + buildOffSequence(EPS_SUBSYSTEM, entry); + buildNormalSequence(EPS_SUBSYSTEM, entry); + EPS_SUBSYSTEM.setInitialMode(NML); + return EPS_SUBSYSTEM; +} + +namespace { + +void buildOffSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::eps::buildOffSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // OFF target table is empty + check(ss.addTable(TableEntry(EPS_TABLE_OFF_TGT.first, &EPS_TABLE_OFF_TGT.second)), ctxc); + + // Transition 0 + iht(objects::POWER_CONTROLLER, OFF, 0, EPS_TABLE_OFF_TRANS_0.second); + check(ss.addTable(TableEntry(EPS_TABLE_OFF_TRANS_0.first, &EPS_TABLE_OFF_TRANS_0.second)), ctxc); + + ihs(EPS_SEQUENCE_OFF.second, EPS_TABLE_OFF_TGT.first, 0, false); + ihs(EPS_SEQUENCE_OFF.second, EPS_TABLE_OFF_TRANS_0.first, 0, false); + check(ss.addSequence(SequenceEntry(EPS_SEQUENCE_OFF.first, &EPS_SEQUENCE_OFF.second, + EPS_SEQUENCE_OFF.first)), + ctxc); +} + +void buildNormalSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::tcs::buildNormalSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Normal table target table is empty + check(ss.addTable(TableEntry(EPS_TABLE_NORMAL_TGT.first, &EPS_TABLE_NORMAL_TGT.second)), ctxc); + + // Transition 0 + iht(objects::POWER_CONTROLLER, NML, 0, EPS_TABLE_NORMAL_TRANS_0.second); + check(ss.addTable(TableEntry(EPS_TABLE_NORMAL_TRANS_0.first, &EPS_TABLE_NORMAL_TRANS_0.second)), + ctxc); + + ihs(EPS_SEQUENCE_NORMAL.second, EPS_TABLE_NORMAL_TGT.first, 0, false); + ihs(EPS_SEQUENCE_NORMAL.second, EPS_TABLE_NORMAL_TRANS_0.first, 0, false); + check(ss.addSequence(SequenceEntry(EPS_SEQUENCE_NORMAL.first, &EPS_SEQUENCE_NORMAL.second, + EPS_SEQUENCE_NORMAL.first)), + ctxc); +} +} // namespace diff --git a/mission/system/power/epsModeTree.h b/mission/system/power/epsModeTree.h new file mode 100644 index 0000000..852c2c1 --- /dev/null +++ b/mission/system/power/epsModeTree.h @@ -0,0 +1,15 @@ +#ifndef MISSION_SYSTEM_TREE_EPSMODETREE_H_ +#define MISSION_SYSTEM_TREE_EPSMODETREE_H_ + +#include + +namespace satsystem { +namespace eps { + +extern EpsSubsystem EPS_SUBSYSTEM; +Subsystem& init(); + +} // namespace eps +} // namespace satsystem + +#endif /* MISSION_SYSTEM_TREE_EPSMODETREE_H_ */ diff --git a/mission/system/systemTree.cpp b/mission/system/systemTree.cpp new file mode 100644 index 0000000..ba44957 --- /dev/null +++ b/mission/system/systemTree.cpp @@ -0,0 +1,408 @@ +#include "systemTree.h" + +#include +#include +#include +#include +#include + +#include "eive/objects.h" +#include "mission/com/defs.h" +#include "mission/sysDefs.h" +#include "mission/system/acs/acsModeTree.h" +#include "mission/system/payload/payloadModeTree.h" +#include "mission/system/power/epsModeTree.h" +#include "mission/system/tcs/tcsModeTree.h" +#include "treeUtil.h" + +namespace { +// Alias for checker function +const auto check = subsystem::checkInsert; + +void buildBootSequence(Subsystem& ss, ModeListEntry& eh); +void buildSafeSequence(Subsystem& ss, ModeListEntry& eh); +void buildIdleSequence(Subsystem& ss, ModeListEntry& eh); +void buildPtgNadirSequence(Subsystem& ss, ModeListEntry& eh); +void buildPtgTargetSequence(Subsystem& ss, ModeListEntry& eh); +void buildPtgTargetGsSequence(Subsystem& ss, ModeListEntry& eh); +void buildPtgInertialSequence(Subsystem& ss, ModeListEntry& eh); +} // namespace + +static const auto OFF = HasModesIF::MODE_OFF; +static const auto NML = DeviceHandlerIF::MODE_NORMAL; + +void satsystem::init(bool commandPlPcdu1) { + auto& acsSubsystem = acs::init(); + acsSubsystem.connectModeTreeParent(EIVE_SYSTEM); + auto& payloadSubsystem = payload::init(); + payloadSubsystem.connectModeTreeParent(EIVE_SYSTEM); + auto& tcsSubsystem = tcs::init(commandPlPcdu1); + tcsSubsystem.connectModeTreeParent(EIVE_SYSTEM); + auto& comSubsystem = com::init(); + comSubsystem.connectModeTreeParent(EIVE_SYSTEM); + auto& epsSubsystem = eps::init(); + epsSubsystem.connectModeTreeParent(EIVE_SYSTEM); + ModeListEntry entry; + buildBootSequence(EIVE_SYSTEM, entry); + buildSafeSequence(EIVE_SYSTEM, entry); + buildIdleSequence(EIVE_SYSTEM, entry); + buildPtgNadirSequence(EIVE_SYSTEM, entry); + buildPtgTargetSequence(EIVE_SYSTEM, entry); + buildPtgTargetGsSequence(EIVE_SYSTEM, entry); + buildPtgInertialSequence(EIVE_SYSTEM, entry); + EIVE_SYSTEM.setInitialMode(satsystem::Mode::BOOT, 0); +} + +EiveSystem satsystem::EIVE_SYSTEM = + EiveSystem(objects::EIVE_SYSTEM, 12, 24, signals::I2C_FATAL_ERRORS); + +auto EIVE_SEQUENCE_BOOT = std::make_pair(satsystem::Mode::BOOT, FixedArrayList()); +auto EIVE_TABLE_BOOT_TGT = + std::make_pair((satsystem::Mode::BOOT << 24) | 1, FixedArrayList()); +auto EIVE_TABLE_BOOT_TRANS_0 = + std::make_pair((satsystem::Mode::BOOT << 24) | 2, FixedArrayList()); + +auto EIVE_SEQUENCE_SAFE = std::make_pair(satsystem::Mode::SAFE, FixedArrayList()); +auto EIVE_TABLE_SAFE_TGT = + std::make_pair((satsystem::Mode::SAFE << 24) | 1, FixedArrayList()); +auto EIVE_TABLE_SAFE_TRANS_0 = + std::make_pair((satsystem::Mode::SAFE << 24) | 2, FixedArrayList()); +auto EIVE_TABLE_SAFE_TRANS_1 = + std::make_pair((satsystem::Mode::SAFE << 24) | 3, FixedArrayList()); + +auto EIVE_SEQUENCE_IDLE = + std::make_pair(satsystem::Mode::PTG_IDLE, FixedArrayList()); +auto EIVE_TABLE_IDLE_TGT = + std::make_pair((satsystem::Mode::PTG_IDLE << 24) | 1, FixedArrayList()); +auto EIVE_TABLE_IDLE_TRANS_0 = + std::make_pair((satsystem::Mode::PTG_IDLE << 24) | 2, FixedArrayList()); +auto EIVE_TABLE_IDLE_TRANS_1 = + std::make_pair((satsystem::Mode::PTG_IDLE << 24) | 3, FixedArrayList()); + +auto EIVE_SEQUENCE_PTG_NADIR = + std::make_pair(satsystem::Mode::PTG_NADIR, FixedArrayList()); +auto EIVE_TABLE_PTG_NADIR_TGT = + std::make_pair((satsystem::Mode::PTG_NADIR << 24) | 1, FixedArrayList()); +auto EIVE_TABLE_PTG_NADIR_TRANS_0 = + std::make_pair((satsystem::Mode::PTG_NADIR << 24) | 2, FixedArrayList()); +auto EIVE_TABLE_PTG_NADIR_TRANS_1 = + std::make_pair((satsystem::Mode::PTG_NADIR << 24) | 3, FixedArrayList()); + +auto EIVE_SEQUENCE_PTG_TARGET = + std::make_pair(satsystem::Mode::PTG_TARGET, FixedArrayList()); +auto EIVE_TABLE_PTG_TARGET_TGT = + std::make_pair((satsystem::Mode::PTG_TARGET << 24) | 1, FixedArrayList()); +auto EIVE_TABLE_PTG_TARGET_TRANS_0 = + std::make_pair((satsystem::Mode::PTG_TARGET << 24) | 2, FixedArrayList()); +auto EIVE_TABLE_PTG_TARGET_TRANS_1 = + std::make_pair((satsystem::Mode::PTG_TARGET << 24) | 3, FixedArrayList()); + +auto EIVE_SEQUENCE_PTG_TARGET_GS = + std::make_pair(satsystem::Mode::PTG_TARGET_GS, FixedArrayList()); +auto EIVE_TABLE_PTG_TARGET_GS_TGT = + std::make_pair((satsystem::Mode::PTG_TARGET_GS << 24) | 1, FixedArrayList()); +auto EIVE_TABLE_PTG_TARGET_GS_TRANS_0 = + std::make_pair((satsystem::Mode::PTG_TARGET_GS << 24) | 2, FixedArrayList()); +auto EIVE_TABLE_PTG_TARGET_GS_TRANS_1 = + std::make_pair((satsystem::Mode::PTG_TARGET_GS << 24) | 3, FixedArrayList()); + +auto EIVE_SEQUENCE_PTG_INERTIAL = + std::make_pair(satsystem::Mode::PTG_INERTIAL, FixedArrayList()); +auto EIVE_TABLE_PTG_INERTIAL_TGT = + std::make_pair((satsystem::Mode::PTG_INERTIAL << 24) | 1, FixedArrayList()); +auto EIVE_TABLE_PTG_INERTIAL_TRANS_0 = + std::make_pair((satsystem::Mode::PTG_INERTIAL << 24) | 2, FixedArrayList()); +auto EIVE_TABLE_PTG_INERTIAL_TRANS_1 = + std::make_pair((satsystem::Mode::PTG_INERTIAL << 24) | 3, FixedArrayList()); + +namespace { + +void buildBootSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::buildBootSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table, + bool allowAllSubmodes = false) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + if (allowAllSubmodes) { + eh.allowAllSubmodes(); + } + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::OFF, 0, EIVE_TABLE_BOOT_TGT.second, true); + iht(objects::PL_SUBSYSTEM, OFF, 0, EIVE_TABLE_BOOT_TGT.second); + iht(objects::COM_SUBSYSTEM, com::RX_ONLY, 0, EIVE_TABLE_BOOT_TGT.second); + iht(objects::TCS_SUBSYSTEM, OFF, 0, EIVE_TABLE_BOOT_TGT.second); + iht(objects::EPS_SUBSYSTEM, OFF, 0, EIVE_TABLE_BOOT_TGT.second); + check(ss.addTable(TableEntry(EIVE_TABLE_BOOT_TGT.first, &EIVE_TABLE_BOOT_TGT.second)), ctxc); + + // Build BOOT transition 0. + iht(objects::EPS_SUBSYSTEM, OFF, 0, EIVE_TABLE_BOOT_TRANS_0.second); + iht(objects::TCS_SUBSYSTEM, OFF, 0, EIVE_TABLE_BOOT_TRANS_0.second); + iht(objects::COM_SUBSYSTEM, com::RX_ONLY, 0, EIVE_TABLE_BOOT_TRANS_0.second); + iht(objects::PL_SUBSYSTEM, OFF, 0, EIVE_TABLE_BOOT_TRANS_0.second); + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::OFF, 0, EIVE_TABLE_BOOT_TRANS_0.second, true); + check(ss.addTable(TableEntry(EIVE_TABLE_BOOT_TRANS_0.first, &EIVE_TABLE_BOOT_TRANS_0.second)), + ctxc); + + // Build BOOT sequence + ihs(EIVE_SEQUENCE_BOOT.second, EIVE_TABLE_BOOT_TGT.first, 0, false); + ihs(EIVE_SEQUENCE_BOOT.second, EIVE_TABLE_BOOT_TRANS_0.first, 0, false); + check(ss.addSequence(SequenceEntry(EIVE_SEQUENCE_BOOT.first, &EIVE_SEQUENCE_BOOT.second, + EIVE_SEQUENCE_SAFE.first)), + ctxc); +} + +void buildSafeSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::buildSafeSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table, + bool allowAllSubmodes = false) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + if (allowAllSubmodes) { + eh.allowAllSubmodes(); + } + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Do no track submode to allow transitions to DETUMBLE submode. + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::SAFE, 0, EIVE_TABLE_SAFE_TGT.second, true); + check(ss.addTable(TableEntry(EIVE_TABLE_SAFE_TGT.first, &EIVE_TABLE_SAFE_TGT.second)), ctxc); + + // Build SAFE transition 0. + iht(objects::EPS_SUBSYSTEM, NML, 0, EIVE_TABLE_SAFE_TRANS_0.second); + iht(objects::TCS_SUBSYSTEM, NML, 0, EIVE_TABLE_SAFE_TRANS_0.second); + iht(objects::PL_SUBSYSTEM, OFF, 0, EIVE_TABLE_SAFE_TRANS_0.second); + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::SAFE, 0, EIVE_TABLE_SAFE_TRANS_0.second, true); + check(ss.addTable(TableEntry(EIVE_TABLE_SAFE_TRANS_0.first, &EIVE_TABLE_SAFE_TRANS_0.second)), + ctxc); + + // Build SAFE sequence + ihs(EIVE_SEQUENCE_SAFE.second, EIVE_TABLE_SAFE_TGT.first, 0, false); + ihs(EIVE_SEQUENCE_SAFE.second, EIVE_TABLE_SAFE_TRANS_0.first, 0, false); + check(ss.addSequence(SequenceEntry(EIVE_SEQUENCE_SAFE.first, &EIVE_SEQUENCE_SAFE.second, + EIVE_SEQUENCE_SAFE.first)), + ctxc); +} + +void buildIdleSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::buildIdleSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_IDLE, 0, EIVE_TABLE_IDLE_TGT.second); + check(ss.addTable(TableEntry(EIVE_TABLE_IDLE_TGT.first, &EIVE_TABLE_IDLE_TGT.second)), ctxc); + + // Build IDLE transition 0 + iht(objects::EPS_SUBSYSTEM, NML, 0, EIVE_TABLE_IDLE_TRANS_0.second); + iht(objects::TCS_SUBSYSTEM, NML, 0, EIVE_TABLE_IDLE_TRANS_0.second); + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_IDLE, 0, EIVE_TABLE_IDLE_TRANS_0.second); + check(ss.addTable(TableEntry(EIVE_TABLE_IDLE_TRANS_0.first, &EIVE_TABLE_IDLE_TRANS_0.second)), + ctxc); + + // Build IDLE sequence + ihs(EIVE_SEQUENCE_IDLE.second, EIVE_TABLE_IDLE_TGT.first, 0, false); + ihs(EIVE_SEQUENCE_IDLE.second, EIVE_TABLE_IDLE_TRANS_0.first, 0, false); + check(ss.addSequence(SequenceEntry(EIVE_SEQUENCE_IDLE.first, &EIVE_SEQUENCE_IDLE.second, + EIVE_SEQUENCE_SAFE.first)), + ctxc); +} + +void buildPtgNadirSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::buildPtgNadirSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_NADIR, 0, EIVE_TABLE_PTG_NADIR_TGT.second); + check(ss.addTable(TableEntry(EIVE_TABLE_PTG_NADIR_TGT.first, &EIVE_TABLE_PTG_NADIR_TGT.second)), + ctxc); + + // Build PTG_NADIR transition 0 + iht(objects::EPS_SUBSYSTEM, NML, 0, EIVE_TABLE_PTG_NADIR_TRANS_0.second); + iht(objects::TCS_SUBSYSTEM, NML, 0, EIVE_TABLE_PTG_NADIR_TRANS_0.second); + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_NADIR, 0, EIVE_TABLE_PTG_NADIR_TRANS_0.second); + check(ss.addTable( + TableEntry(EIVE_TABLE_PTG_NADIR_TRANS_0.first, &EIVE_TABLE_PTG_NADIR_TRANS_0.second)), + ctxc); + + // Build PTG_NADIR sequence + ihs(EIVE_SEQUENCE_PTG_NADIR.second, EIVE_TABLE_PTG_NADIR_TGT.first, 0, false); + ihs(EIVE_SEQUENCE_PTG_NADIR.second, EIVE_TABLE_PTG_NADIR_TRANS_0.first, 0, false); + check(ss.addSequence(SequenceEntry(EIVE_SEQUENCE_PTG_NADIR.first, &EIVE_SEQUENCE_PTG_NADIR.second, + EIVE_SEQUENCE_IDLE.first)), + ctxc); +} + +void buildPtgTargetSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::buildPtgTargetSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_TARGET, 0, EIVE_TABLE_PTG_TARGET_TGT.second); + check(ss.addTable(TableEntry(EIVE_TABLE_PTG_TARGET_TGT.first, &EIVE_TABLE_PTG_TARGET_TGT.second)), + ctxc); + + // Build PTG_TARGET transition 0 + iht(objects::EPS_SUBSYSTEM, NML, 0, EIVE_TABLE_PTG_TARGET_TRANS_0.second); + iht(objects::TCS_SUBSYSTEM, NML, 0, EIVE_TABLE_PTG_TARGET_TRANS_0.second); + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_TARGET, 0, EIVE_TABLE_PTG_TARGET_TRANS_0.second); + check(ss.addTable( + TableEntry(EIVE_TABLE_PTG_TARGET_TRANS_0.first, &EIVE_TABLE_PTG_TARGET_TRANS_0.second)), + ctxc); + + // Build PTG_TARGET sequence + ihs(EIVE_SEQUENCE_PTG_TARGET.second, EIVE_TABLE_PTG_TARGET_TGT.first, 0, false); + ihs(EIVE_SEQUENCE_PTG_TARGET.second, EIVE_TABLE_PTG_TARGET_TRANS_0.first, 0, false); + check(ss.addSequence(SequenceEntry(EIVE_SEQUENCE_PTG_TARGET.first, + &EIVE_SEQUENCE_PTG_TARGET.second, EIVE_SEQUENCE_IDLE.first)), + ctxc); +} + +void buildPtgTargetGsSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::buildPtgTargetGsSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_TARGET_GS, 0, EIVE_TABLE_PTG_TARGET_GS_TGT.second); + check(ss.addTable( + TableEntry(EIVE_TABLE_PTG_TARGET_GS_TGT.first, &EIVE_TABLE_PTG_TARGET_GS_TGT.second)), + ctxc); + + // Build PTG_TARGET_GS transition 0 + iht(objects::EPS_SUBSYSTEM, NML, 0, EIVE_TABLE_PTG_TARGET_GS_TRANS_0.second); + iht(objects::TCS_SUBSYSTEM, NML, 0, EIVE_TABLE_PTG_TARGET_GS_TRANS_0.second); + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_TARGET_GS, 0, + EIVE_TABLE_PTG_TARGET_GS_TRANS_0.second); + check(ss.addTable(TableEntry(EIVE_TABLE_PTG_TARGET_GS_TRANS_0.first, + &EIVE_TABLE_PTG_TARGET_GS_TRANS_0.second)), + ctxc); + + // Build PTG_TARGET_GS sequence + ihs(EIVE_SEQUENCE_PTG_TARGET_GS.second, EIVE_TABLE_PTG_TARGET_GS_TGT.first, 0, false); + ihs(EIVE_SEQUENCE_PTG_TARGET_GS.second, EIVE_TABLE_PTG_TARGET_GS_TRANS_0.first, 0, false); + check( + ss.addSequence(SequenceEntry(EIVE_SEQUENCE_PTG_TARGET_GS.first, + &EIVE_SEQUENCE_PTG_TARGET_GS.second, EIVE_SEQUENCE_IDLE.first)), + ctxc); +} + +void buildPtgInertialSequence(Subsystem& ss, ModeListEntry& eh) { + std::string context = "satsystem::buildPtgInertialSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_INERTIAL, 0, EIVE_TABLE_PTG_INERTIAL_TGT.second); + check(ss.addTable( + TableEntry(EIVE_TABLE_PTG_INERTIAL_TGT.first, &EIVE_TABLE_PTG_INERTIAL_TGT.second)), + ctxc); + + // Build PTG_INERTIAL transition 0 + iht(objects::EPS_SUBSYSTEM, NML, 0, EIVE_TABLE_PTG_INERTIAL_TRANS_0.second); + iht(objects::TCS_SUBSYSTEM, NML, 0, EIVE_TABLE_PTG_INERTIAL_TRANS_0.second); + iht(objects::ACS_SUBSYSTEM, acs::AcsMode::PTG_INERTIAL, 0, + EIVE_TABLE_PTG_INERTIAL_TRANS_0.second); + check(ss.addTable(TableEntry(EIVE_TABLE_PTG_INERTIAL_TRANS_0.first, + &EIVE_TABLE_PTG_INERTIAL_TRANS_0.second)), + ctxc); + + // Build PTG_INERTIAL sequence + ihs(EIVE_SEQUENCE_PTG_INERTIAL.second, EIVE_TABLE_PTG_INERTIAL_TGT.first, 0, false); + ihs(EIVE_SEQUENCE_PTG_INERTIAL.second, EIVE_TABLE_PTG_INERTIAL_TRANS_0.first, 0, false); + check(ss.addSequence(SequenceEntry(EIVE_SEQUENCE_PTG_INERTIAL.first, + &EIVE_SEQUENCE_PTG_INERTIAL.second, EIVE_SEQUENCE_IDLE.first)), + ctxc); +} + +} // namespace diff --git a/mission/system/systemTree.h b/mission/system/systemTree.h new file mode 100644 index 0000000..0ed73a8 --- /dev/null +++ b/mission/system/systemTree.h @@ -0,0 +1,14 @@ +#ifndef MISSION_SYSTEM_TREE_SYSTEM_H_ +#define MISSION_SYSTEM_TREE_SYSTEM_H_ + +#include + +namespace satsystem { + +void init(bool commandPlPcdu1); + +extern EiveSystem EIVE_SYSTEM; + +} // namespace satsystem + +#endif /* MISSION_SYSTEM_TREE_SYSTEM_H_ */ diff --git a/mission/system/tcs/CMakeLists.txt b/mission/system/tcs/CMakeLists.txt new file mode 100644 index 0000000..475a2dd --- /dev/null +++ b/mission/system/tcs/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources( + ${LIB_EIVE_MISSION} PRIVATE tcsModeTree.cpp TcsSubsystem.cpp + TcsBoardAssembly.cpp RtdFdir.cpp TmpDevFdir.cpp) diff --git a/mission/system/tcs/RtdFdir.cpp b/mission/system/tcs/RtdFdir.cpp new file mode 100644 index 0000000..24ebc2a --- /dev/null +++ b/mission/system/tcs/RtdFdir.cpp @@ -0,0 +1,6 @@ +#include "RtdFdir.h" + +#include "eive/objects.h" + +RtdFdir::RtdFdir(object_id_t sensorId) + : DeviceHandlerFailureIsolation(sensorId, objects::NO_OBJECT) {} diff --git a/mission/system/tcs/RtdFdir.h b/mission/system/tcs/RtdFdir.h new file mode 100644 index 0000000..1dedbb8 --- /dev/null +++ b/mission/system/tcs/RtdFdir.h @@ -0,0 +1,11 @@ +#ifndef MISSION_SYSTEM_RTDFDIR_H_ +#define MISSION_SYSTEM_RTDFDIR_H_ + +#include + +class RtdFdir : public DeviceHandlerFailureIsolation { + public: + RtdFdir(object_id_t sensorId); +}; + +#endif /* MISSION_SYSTEM_RTDFDIR_H_ */ diff --git a/mission/system/tcs/TcsBoardAssembly.cpp b/mission/system/tcs/TcsBoardAssembly.cpp new file mode 100644 index 0000000..2a5a3e7 --- /dev/null +++ b/mission/system/tcs/TcsBoardAssembly.cpp @@ -0,0 +1,179 @@ +#include "TcsBoardAssembly.h" + +#include +#include + +TcsBoardAssembly::TcsBoardAssembly(object_id_t objectId, PowerSwitchIF* pwrSwitcher, + power::Switch_t theSwitch, TcsBoardHelper helper, + std::atomic_bool& tcsShortlyUnavailable) + : SharedPowerAssemblyBase(objectId, pwrSwitcher, theSwitch, 16), + helper(helper), + tcsShortlyUnavailable(tcsShortlyUnavailable) { + ModeListEntry entry; + for (uint8_t idx = 0; idx < NUMBER_RTDS; idx++) { + entry.setObject(helper.rtdInfos[idx].first); + entry.setMode(MODE_OFF); + entry.setSubmode(SUBMODE_NONE); + modeTable.insert(entry); + } +} + +ReturnValue_t TcsBoardAssembly::commandChildren(Mode_t mode, Submode_t submode) { + ReturnValue_t result = returnvalue::OK; + // Initialize the mode table to ensure all devices are in a defined state + for (uint8_t idx = 0; idx < NUMBER_RTDS; idx++) { + modeTable[idx].setMode(MODE_OFF); + modeTable[idx].setSubmode(SUBMODE_NONE); + } + if (recoveryState == RecoveryState::RECOVERY_IDLE) { + result = checkAndHandleHealthStates(mode, submode); + if (result == NEED_TO_CHANGE_HEALTH) { + return returnvalue::OK; + } + } + if (recoveryState != RecoveryState::RECOVERY_STARTED) { + if (mode == DeviceHandlerIF::MODE_NORMAL or mode == MODE_ON) { + result = handleNormalOrOnModeCmd(mode, submode); + } + } else { + tcsShortlyUnavailable = true; + } + HybridIterator tableIter(modeTable.begin(), modeTable.end()); + executeTable(tableIter); + return result; +} + +ReturnValue_t TcsBoardAssembly::checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) { + int devsInWrongMode = 0; + try { + for (uint8_t idx = 0; idx < NUMBER_RTDS; idx++) { + if (childrenMap.at(helper.rtdInfos[idx].first).mode != wantedMode) { + devsInWrongMode++; + } + } + } catch (const std::out_of_range& e) { + sif::error << "TcsBoardAssembly: Invalid children map: " << e.what() << std::endl; + } + if (devsInWrongMode == NUMBER_RTDS) { + if (warningSwitch) { + sif::warning << "TcsBoardAssembly::checkChildrenStateOn: All devices in wrong mode" + << std::endl; + warningSwitch = false; + } + return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE; + } + // TODO: Can't really do something other than power cycling if devices in wrong mode. + // Might attempt one power-cycle. In any case, trigger an event + if (devsInWrongMode > 0) { + if (warningSwitch) { + sif::warning << "TcsBoardAssembly::checkChildrenStateOn: " << devsInWrongMode << " devices in" + << " wrong mode" << std::endl; + warningSwitch = false; + } + } + return returnvalue::OK; +} + +ReturnValue_t TcsBoardAssembly::isModeCombinationValid(Mode_t mode, Submode_t submode) { + if (mode == MODE_ON or mode == MODE_OFF or mode == DeviceHandlerIF::MODE_NORMAL) { + return returnvalue::OK; + } + return HasModesIF::INVALID_MODE; +} + +ReturnValue_t TcsBoardAssembly::handleNormalOrOnModeCmd(Mode_t mode, Submode_t submode) { + ReturnValue_t result = returnvalue::OK; + bool needsSecondStep = false; + Mode_t devMode = 0; + object_id_t objId = 0; + try { + for (uint8_t idx = 0; idx < NUMBER_RTDS; idx++) { + devMode = childrenMap.at(helper.rtdInfos[idx].first).mode; + objId = helper.rtdInfos[idx].first; + if (mode == devMode) { + modeTable[idx].setMode(mode); + } else if (mode == DeviceHandlerIF::MODE_NORMAL) { + if (isUseable(objId, devMode)) { + if (devMode == MODE_ON) { + modeTable[idx].setMode(mode); + modeTable[idx].setSubmode(SUBMODE_NONE); + } else { + modeTable[idx].setMode(MODE_ON); + modeTable[idx].setSubmode(SUBMODE_NONE); + if (internalState != STATE_SECOND_STEP) { + needsSecondStep = true; + } + } + } + } else if (mode == MODE_ON) { + if (isUseable(objId, devMode)) { + modeTable[idx].setMode(MODE_ON); + modeTable[idx].setSubmode(SUBMODE_NONE); + } + } + } + } catch (const std::out_of_range& e) { + sif::error << "TcsBoardAssembly: Invalid children map: " << e.what() << std::endl; + } + if (needsSecondStep) { + result = NEED_SECOND_STEP; + } + return result; +} + +bool TcsBoardAssembly::isUseable(object_id_t object, Mode_t mode) { + if (healthHelper.healthTable->isFaulty(object)) { + return false; + } + + // Check if device is already in target mode + if (childrenMap[object].mode == mode) { + return true; + } + + if (healthHelper.healthTable->isCommandable(object)) { + return true; + } + return false; +} + +void TcsBoardAssembly::handleChildrenLostMode(ReturnValue_t result) { + triggerEvent(CHILDREN_LOST_MODE, result); + startTransition(mode, submode); +} + +ReturnValue_t TcsBoardAssembly::checkAndHandleHealthStates(Mode_t deviceMode, + Submode_t deviceSubmode) { + ReturnValue_t status = returnvalue::OK; + for (const auto& dev : helper.rtdInfos) { + HealthState health = healthHelper.healthTable->getHealth(dev.first); + if (health == HealthState::HEALTHY) { + return returnvalue::OK; + } + } + + for (const auto& dev : helper.rtdInfos) { + HealthState health = healthHelper.healthTable->getHealth(dev.first); + if (health == FAULTY or health == PERMANENT_FAULTY) { + status = NEED_TO_CHANGE_HEALTH; + } else if (health == EXTERNAL_CONTROL) { + modeHelper.setForced(true); + } + } + return status; +} + +bool TcsBoardAssembly::checkAndHandleRecovery() { + bool recoveryPending = SharedPowerAssemblyBase::checkAndHandleRecovery(); + tcsShortlyUnavailable = recoveryPending; + return recoveryPending; +} + +void TcsBoardAssembly::handleModeTransitionFailed(ReturnValue_t result) { + if (targetMode == MODE_OFF) { + AssemblyBase::handleModeTransitionFailed(result); + } else { + // To avoid transitioning back to off + triggerEvent(MODE_TRANSITION_FAILED, result); + } +} diff --git a/mission/system/tcs/TcsBoardAssembly.h b/mission/system/tcs/TcsBoardAssembly.h new file mode 100644 index 0000000..da10999 --- /dev/null +++ b/mission/system/tcs/TcsBoardAssembly.h @@ -0,0 +1,59 @@ +#ifndef MISSION_SYSTEM_TCSSUBSYSTEM_H_ +#define MISSION_SYSTEM_TCSSUBSYSTEM_H_ + +#include +#include +#include +#include +#include + +#include + +#include "events/subsystemIdRanges.h" +#include "returnvalues/classIds.h" + +struct TcsBoardHelper { + TcsBoardHelper(std::array, 16> rtdInfos) + : rtdInfos(std::move(rtdInfos)) {} + + std::array, 16> rtdInfos = {}; +}; + +class TcsBoardAssembly : public SharedPowerAssemblyBase { + public: + static constexpr uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::TCS_BOARD_ASS; + static constexpr Event CHILDREN_LOST_MODE = event::makeEvent(SUBSYSTEM_ID, 0, severity::MEDIUM); + + TcsBoardAssembly(object_id_t objectId, PowerSwitchIF* pwrSwitcher, power::Switch_t switcher, + TcsBoardHelper helper, std::atomic_bool& tcsShortlyUnavailable); + + private: + static constexpr uint8_t NUMBER_RTDS = 16; + bool warningSwitch = true; + TcsBoardHelper helper; + FixedArrayList modeTable; + std::atomic_bool& tcsShortlyUnavailable; + + ReturnValue_t handleNormalOrOnModeCmd(Mode_t mode, Submode_t submode); + /** + * Check whether it makes sense to send mode commands to the device + * @param object + * @param mode + * @return + */ + bool isUseable(object_id_t object, Mode_t mode); + + // AssemblyBase implementation + ReturnValue_t commandChildren(Mode_t mode, Submode_t submode) override; + ReturnValue_t checkChildrenStateOn(Mode_t wantedMode, Submode_t wantedSubmode) override; + ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode) override; + ReturnValue_t checkAndHandleHealthStates(Mode_t deviceMode, Submode_t deviceSubmode); + bool checkAndHandleRecovery() override; + + // These two overrides prevent a transition of the whole assembly back to off just because + // some devices are not working + void handleChildrenLostMode(ReturnValue_t result) override; + void handleModeTransitionFailed(ReturnValue_t result) override; +}; + +#endif /* MISSION_SYSTEM_TCSSUBSYSTEM_H_ */ diff --git a/mission/system/tcs/TcsSubsystem.cpp b/mission/system/tcs/TcsSubsystem.cpp new file mode 100644 index 0000000..85cf644 --- /dev/null +++ b/mission/system/tcs/TcsSubsystem.cpp @@ -0,0 +1,27 @@ +#include "TcsSubsystem.h" + +#include "fsfw/devicehandlers/DeviceHandlerIF.h" + +TcsSubsystem::TcsSubsystem(object_id_t objectId, uint32_t maxNumberOfSequences, + uint32_t maxNumberOfTables) + : Subsystem(objectId, maxNumberOfSequences, maxNumberOfTables) {} + +void TcsSubsystem::announceMode(bool recursive) { + const char* modeStr = "UNKNOWN"; + switch (mode) { + case (HasModesIF::MODE_OFF): { + modeStr = "OFF"; + break; + } + case (HasModesIF::MODE_ON): { + modeStr = "ON"; + break; + } + case (DeviceHandlerIF::MODE_NORMAL): { + modeStr = "NORMAL"; + break; + } + } + sif::info << "TCS subsystem is now in " << modeStr << " mode" << std::endl; + return Subsystem::announceMode(recursive); +} diff --git a/mission/system/tcs/TcsSubsystem.h b/mission/system/tcs/TcsSubsystem.h new file mode 100644 index 0000000..4218f9b --- /dev/null +++ b/mission/system/tcs/TcsSubsystem.h @@ -0,0 +1,13 @@ +#ifndef MISSION_SYSTEM_OBJECTS_TCSSUBSYSTEM_H_ +#define MISSION_SYSTEM_OBJECTS_TCSSUBSYSTEM_H_ +#include + +class TcsSubsystem : public Subsystem { + public: + TcsSubsystem(object_id_t objectId, uint32_t maxNumberOfSequences, uint32_t maxNumberOfTables); + + private: + void announceMode(bool recursive) override; +}; + +#endif /* MISSION_SYSTEM_OBJECTS_TCSSUBSYSTEM_H_ */ diff --git a/mission/system/tcs/TmpDevFdir.cpp b/mission/system/tcs/TmpDevFdir.cpp new file mode 100644 index 0000000..8c2b170 --- /dev/null +++ b/mission/system/tcs/TmpDevFdir.cpp @@ -0,0 +1,95 @@ +#include "TmpDevFdir.h" + +#include +#include +#include +#include + +TmpDevFdir::TmpDevFdir(object_id_t sensorId) + : DeviceHandlerFailureIsolation(sensorId, objects::NO_OBJECT) {} + +ReturnValue_t TmpDevFdir::eventReceived(EventMessage* event) { + if (isFdirInActionOrAreWeFaulty(event)) { + return returnvalue::OK; + } + ReturnValue_t result = returnvalue::FAILED; + switch (event->getEvent()) { + case HasModesIF::MODE_TRANSITION_FAILED: + case HasModesIF::OBJECT_IN_INVALID_MODE: + case DeviceHandlerIF::DEVICE_WANTS_HARD_REBOOT: + // We'll try a recovery as long as defined in MAX_REBOOT. + // Might cause some AssemblyBase cycles, so keep number low. + // Ignored for TMP device, no way to power cycle it without going to OFF/BOOT mode. + setFaulty(event->getEvent()); + break; + case DeviceHandlerIF::DEVICE_INTERPRETING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_READING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_UNREQUESTED_REPLY: + case DeviceHandlerIF::DEVICE_UNKNOWN_REPLY: // Some DH's generate generic reply-ids. + case DeviceHandlerIF::DEVICE_BUILDING_COMMAND_FAILED: + // These faults all mean that there were stupid replies from a device. + // With no way to do a recovery, set the device to faulty instead of trying a recovery. + if (strangeReplyCount.incrementAndCheck()) { + setFaulty(event->getEvent()); + } + break; + case DeviceHandlerIF::DEVICE_SENDING_COMMAND_FAILED: + case DeviceHandlerIF::DEVICE_REQUESTING_REPLY_FAILED: + // The two above should never be confirmed. + case DeviceHandlerIF::DEVICE_MISSED_REPLY: + result = sendConfirmationRequest(event); + if (result == returnvalue::OK) { + break; + } + // else + if (missedReplyCount.incrementAndCheck()) { + setFaulty(event->getEvent()); + } + break; + case StorageManagerIF::GET_DATA_FAILED: + case StorageManagerIF::STORE_DATA_FAILED: + // Rather strange bugs, occur in RAW mode only. Ignore. + break; + case DeviceHandlerIF::INVALID_DEVICE_COMMAND: + // Ignore, is bad configuration. We can't do anything in flight. + break; + case HasHealthIF::HEALTH_INFO: + case HasModesIF::MODE_INFO: + case HasModesIF::CHANGING_MODE: + // Do nothing, but mark as handled. + break; + //****Thermal***** + case ThermalComponentIF::COMPONENT_TEMP_LOW: + case ThermalComponentIF::COMPONENT_TEMP_HIGH: + case ThermalComponentIF::COMPONENT_TEMP_OOL_LOW: + case ThermalComponentIF::COMPONENT_TEMP_OOL_HIGH: + // Well, the device is not really faulty, but it is required to stay off as long as possible. + setFaulty(event->getEvent()); + break; + case ThermalComponentIF::TEMP_NOT_IN_OP_RANGE: + // Ignore, is information only. + break; + //*******Default monitoring variables. Are currently not used.***** + // case DeviceHandlerIF::MONITORING_LIMIT_EXCEEDED: + // setFaulty(event->getEvent()); + // break; + // case DeviceHandlerIF::MONITORING_AMBIGUOUS: + // break; + default: + // We don't know the event, someone else should handle it. + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +void TmpDevFdir::eventConfirmed(EventMessage* event) { + switch (event->getEvent()) { + case DeviceHandlerIF::DEVICE_SENDING_COMMAND_FAILED: + case DeviceHandlerIF::DEVICE_REQUESTING_REPLY_FAILED: + case DeviceHandlerIF::DEVICE_MISSED_REPLY: + setFaulty(event->getEvent()); + break; + default: + break; + } +} diff --git a/mission/system/tcs/TmpDevFdir.h b/mission/system/tcs/TmpDevFdir.h new file mode 100644 index 0000000..38cb73b --- /dev/null +++ b/mission/system/tcs/TmpDevFdir.h @@ -0,0 +1,20 @@ +#ifndef MISSION_SYSTEM_TCS_TMPDEVFDIR_H_ +#define MISSION_SYSTEM_TCS_TMPDEVFDIR_H_ +#include + +/** + * Special FDIR because we can not simply power cycle the TMP devices which are powered by the + * 3.3 V stack and there is also no way to logically reset or re-configure the TMP devices in + * any way. In general, instead of doing a recovery, the TMP devices should be set faulty + * immediately for the EIVE project. + */ +class TmpDevFdir : public DeviceHandlerFailureIsolation { + public: + TmpDevFdir(object_id_t sensorId); + + private: + ReturnValue_t eventReceived(EventMessage* event) override; + void eventConfirmed(EventMessage* event) override; +}; + +#endif /* MISSION_SYSTEM_TCS_TMPDEVFDIR_H_ */ diff --git a/mission/system/tcs/tcsModeTree.cpp b/mission/system/tcs/tcsModeTree.cpp new file mode 100644 index 0000000..09a4fa6 --- /dev/null +++ b/mission/system/tcs/tcsModeTree.cpp @@ -0,0 +1,130 @@ +#include "tcsModeTree.h" + +#include "eive/objects.h" +#include "fsfw/devicehandlers/DeviceHandlerIF.h" +#include "fsfw/subsystem/Subsystem.h" +#include "mission/system/treeUtil.h" + +TcsSubsystem satsystem::tcs::SUBSYSTEM(objects::TCS_SUBSYSTEM, 12, 24); + +namespace { +// Alias for checker function +const auto check = subsystem::checkInsert; +void buildOffSequence(Subsystem& ss, ModeListEntry& eh, bool commandPlPcdu1); +void buildNormalSequence(Subsystem& ss, ModeListEntry& eh, bool commandPlPcdu1); +} // namespace + +static const auto OFF = HasModesIF::MODE_OFF; +static const auto NML = DeviceHandlerIF::MODE_NORMAL; + +auto TCS_SEQUENCE_OFF = std::make_pair(OFF, FixedArrayList()); +auto TCS_TABLE_OFF_TGT = std::make_pair((OFF << 24) | 1, FixedArrayList()); +auto TCS_TABLE_OFF_TRANS_0 = std::make_pair((OFF << 24) | 2, FixedArrayList()); +auto TCS_TABLE_OFF_TRANS_1 = std::make_pair((OFF << 24) | 3, FixedArrayList()); + +auto TCS_SEQUENCE_NORMAL = std::make_pair(NML, FixedArrayList()); +auto TCS_TABLE_NORMAL_TGT = std::make_pair((NML << 24) | 1, FixedArrayList()); +auto TCS_TABLE_NORMAL_TRANS_0 = std::make_pair((NML << 24) | 2, FixedArrayList()); +auto TCS_TABLE_NORMAL_TRANS_1 = std::make_pair((NML << 24) | 3, FixedArrayList()); + +Subsystem& satsystem::tcs::init(bool commandPlPcdu1) { + ModeListEntry entry; + buildOffSequence(SUBSYSTEM, entry, commandPlPcdu1); + buildNormalSequence(SUBSYSTEM, entry, commandPlPcdu1); + SUBSYSTEM.setInitialMode(OFF); + return SUBSYSTEM; +} + +namespace { + +void buildOffSequence(Subsystem& ss, ModeListEntry& eh, bool commandPlPcdu1) { + std::string context = "satsystem::tcs::buildOffSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // OFF target table is empty + check(ss.addTable(TableEntry(TCS_TABLE_OFF_TGT.first, &TCS_TABLE_OFF_TGT.second)), ctxc); + + // Transition 1 + iht(objects::THERMAL_CONTROLLER, OFF, 0, TCS_TABLE_OFF_TRANS_0.second); + check(ss.addTable(TableEntry(TCS_TABLE_OFF_TRANS_0.first, &TCS_TABLE_OFF_TRANS_0.second)), ctxc); + + iht(objects::TCS_BOARD_ASS, OFF, 0, TCS_TABLE_OFF_TRANS_1.second); + iht(objects::TMP1075_HANDLER_TCS_0, OFF, 0, TCS_TABLE_OFF_TRANS_1.second); + iht(objects::TMP1075_HANDLER_TCS_1, OFF, 0, TCS_TABLE_OFF_TRANS_1.second); + iht(objects::TMP1075_HANDLER_PLPCDU_0, OFF, 0, TCS_TABLE_OFF_TRANS_1.second); + if (commandPlPcdu1) { + iht(objects::TMP1075_HANDLER_PLPCDU_1, OFF, 0, TCS_TABLE_OFF_TRANS_1.second); + } + iht(objects::TMP1075_HANDLER_IF_BOARD, OFF, 0, TCS_TABLE_OFF_TRANS_1.second); + check(ss.addTable(TableEntry(TCS_TABLE_OFF_TRANS_1.first, &TCS_TABLE_OFF_TRANS_1.second)), ctxc); + + ihs(TCS_SEQUENCE_OFF.second, TCS_TABLE_OFF_TGT.first, 0, false); + ihs(TCS_SEQUENCE_OFF.second, TCS_TABLE_OFF_TRANS_0.first, 0, false); + ihs(TCS_SEQUENCE_OFF.second, TCS_TABLE_OFF_TRANS_1.first, 0, false); + check(ss.addSequence(SequenceEntry(TCS_SEQUENCE_OFF.first, &TCS_SEQUENCE_OFF.second, + TCS_SEQUENCE_OFF.first)), + ctxc); +} + +void buildNormalSequence(Subsystem& ss, ModeListEntry& eh, bool commandPlPcdu1) { + std::string context = "satsystem::tcs::buildNormalSequence"; + auto ctxc = context.c_str(); + // Insert Helper Table + auto iht = [&](object_id_t obj, Mode_t mode, Submode_t submode, ArrayList& table) { + eh.setObject(obj); + eh.setMode(mode); + eh.setSubmode(submode); + check(table.insert(eh), ctxc); + }; + // Insert Helper Sequence + auto ihs = [&](ArrayList& sequence, Mode_t tableId, uint32_t waitSeconds, + bool checkSuccess) { + eh.setTableId(tableId); + eh.setWaitSeconds(waitSeconds); + eh.setCheckSuccess(checkSuccess); + check(sequence.insert(eh), ctxc); + }; + + // Normal target table is empty + check(ss.addTable(TableEntry(TCS_TABLE_NORMAL_TGT.first, &TCS_TABLE_NORMAL_TGT.second)), ctxc); + + iht(objects::TCS_BOARD_ASS, NML, 0, TCS_TABLE_NORMAL_TRANS_0.second); + iht(objects::TMP1075_HANDLER_TCS_0, NML, 0, TCS_TABLE_NORMAL_TRANS_0.second); + iht(objects::TMP1075_HANDLER_TCS_1, NML, 0, TCS_TABLE_NORMAL_TRANS_0.second); + iht(objects::TMP1075_HANDLER_PLPCDU_0, NML, 0, TCS_TABLE_NORMAL_TRANS_0.second); + if (commandPlPcdu1) { + iht(objects::TMP1075_HANDLER_PLPCDU_1, NML, 0, TCS_TABLE_NORMAL_TRANS_0.second); + } + iht(objects::TMP1075_HANDLER_IF_BOARD, NML, 0, TCS_TABLE_NORMAL_TRANS_0.second); + check(ss.addTable(TableEntry(TCS_TABLE_NORMAL_TRANS_0.first, &TCS_TABLE_NORMAL_TRANS_0.second)), + ctxc); + + // Transition 1 + iht(objects::THERMAL_CONTROLLER, HasModesIF::MODE_ON, 0, TCS_TABLE_NORMAL_TRANS_1.second); + check(ss.addTable(TableEntry(TCS_TABLE_NORMAL_TRANS_1.first, &TCS_TABLE_NORMAL_TRANS_1.second)), + ctxc); + + ihs(TCS_SEQUENCE_NORMAL.second, TCS_TABLE_NORMAL_TGT.first, 0, false); + ihs(TCS_SEQUENCE_NORMAL.second, TCS_TABLE_NORMAL_TRANS_0.first, 0, false); + ihs(TCS_SEQUENCE_NORMAL.second, TCS_TABLE_NORMAL_TRANS_1.first, 0, false); + check(ss.addSequence(SequenceEntry(TCS_SEQUENCE_NORMAL.first, &TCS_SEQUENCE_NORMAL.second, + TCS_SEQUENCE_NORMAL.first)), + ctxc); +} + +} // namespace diff --git a/mission/system/tcs/tcsModeTree.h b/mission/system/tcs/tcsModeTree.h new file mode 100644 index 0000000..4370ae7 --- /dev/null +++ b/mission/system/tcs/tcsModeTree.h @@ -0,0 +1,15 @@ +#ifndef MISSION_SYSTEM_TREE_TCSMODETREE_H_ +#define MISSION_SYSTEM_TREE_TCSMODETREE_H_ + +#include + +namespace satsystem { +namespace tcs { + +extern TcsSubsystem SUBSYSTEM; +Subsystem& init(bool commandPlPcdu1); + +} // namespace tcs +} // namespace satsystem + +#endif /* MISSION_SYSTEM_TREE_TCSMODETREE_H_ */ diff --git a/mission/system/treeUtil.cpp b/mission/system/treeUtil.cpp new file mode 100644 index 0000000..0e95f27 --- /dev/null +++ b/mission/system/treeUtil.cpp @@ -0,0 +1,19 @@ +#include "treeUtil.h" + +#include "fsfw/container/FixedMap.h" +#include "fsfw/serviceinterface.h" + +void subsystem::checkInsert(ReturnValue_t result, const char* ctx) { + if (result != returnvalue::OK) { + sif::warning << "satsystem::checkInsert: Insertion failed at " << ctx; + if (result == containers::KEY_ALREADY_EXISTS) { + sif::warning << ": Key already exists" << std::endl; + } else if (result == containers::MAP_FULL) { + sif::warning << ": Map full" << std::endl; + } else if (result == containers::LIST_FULL) { + sif::warning << ": List full" << std::endl; + } else { + sif::warning << std::endl; + } + } +} diff --git a/mission/system/treeUtil.h b/mission/system/treeUtil.h new file mode 100644 index 0000000..da73e3b --- /dev/null +++ b/mission/system/treeUtil.h @@ -0,0 +1,12 @@ +#ifndef MISSION_SYSTEM_TREE_UTIL_H_ +#define MISSION_SYSTEM_TREE_UTIL_H_ + +#include + +namespace subsystem { + +void checkInsert(ReturnValue_t result, const char* ctx); + +} + +#endif /* MISSION_SYSTEM_TREE_UTIL_H_ */ diff --git a/mission/tcs/CMakeLists.txt b/mission/tcs/CMakeLists.txt new file mode 100644 index 0000000..c23e9c1 --- /dev/null +++ b/mission/tcs/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE HeaterHandler.cpp max1227.cpp Max31865EiveHandler.cpp + Tmp1075Handler.cpp HeaterHealthDev.cpp) diff --git a/mission/tcs/HeaterHandler.cpp b/mission/tcs/HeaterHandler.cpp new file mode 100644 index 0000000..b88dfe7 --- /dev/null +++ b/mission/tcs/HeaterHandler.cpp @@ -0,0 +1,460 @@ +#include +#include +#include +#include +#include + +#include + +#include "devices/powerSwitcherList.h" +#include "fsfw/subsystem/helper.h" + +HeaterHandler::HeaterHandler(object_id_t setObjectId_, GpioIF* gpioInterface_, HeaterHelper helper, + PowerSwitchIF* mainLineSwitcher_, power::Switch_t mainLineSwitch_) + : SystemObject(setObjectId_), + helper(helper), + modeHelper(this), + gpioInterface(gpioInterface_), + mainLineSwitcher(mainLineSwitcher_), + mainLineSwitch(mainLineSwitch_), + actionHelper(this, nullptr) { + for (const auto& heater : helper.heaters) { + if (heater.first == nullptr) { + throw std::invalid_argument("HeaterHandler::HeaterHandler: Invalid Heater Object"); + } + } + if (gpioInterface_ == nullptr) { + throw std::invalid_argument("HeaterHandler::HeaterHandler: Invalid Gpio IF"); + } + if (mainLineSwitcher == nullptr) { + throw std::invalid_argument("HeaterHandler::HeaterHandler: Invalid PowerSwitchIF"); + } + handlerLock = MutexFactory::instance()->createMutex(); + if (handlerLock == nullptr) { + throw std::runtime_error("HeaterHandler::HeaterHandler: Creating Mutex failed"); + } + auto mqArgs = MqArgs(setObjectId_, static_cast(this)); + commandQueue = QueueFactory::instance()->createMessageQueue( + cmdQueueSize, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); +} + +HeaterHandler::~HeaterHandler() = default; + +ReturnValue_t HeaterHandler::performOperation(uint8_t operationCode) { + try { + readCommandQueue(); + for (const auto& heater : helper.heaters) { + heater.first->performOperation(0); + } + handleSwitchHandling(); + if (waitForSwitchOff) { + if (mainLineSwitcher->getSwitchState(mainLineSwitch) == SWITCH_OFF) { + waitForSwitchOff = false; + mode = MODE_OFF; + busyWithSwitchCommanding = false; + modeHelper.modeChanged(mode, submode); + } + } + if (busyWithSwitchCommanding and heaterCmdBusyCd.hasTimedOut()) { + busyWithSwitchCommanding = false; + } + } catch (const std::out_of_range& e) { + sif::warning << "HeaterHandler::performOperation: " + "Out of range error | " + << e.what() << std::endl; + } + return returnvalue::OK; +} + +ReturnValue_t HeaterHandler::initialize() { + ReturnValue_t result = SystemObject::initialize(); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + result = initializeHeaterMap(); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + ipcStore = ObjectManager::instance()->get(objects::IPC_STORE); + if (ipcStore == nullptr) { + sif::error << "HeaterHandler::initialize: IPC store not set up in factory." << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + result = actionHelper.initialize(commandQueue); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + + result = modeHelper.initialize(); + if (result != returnvalue::OK) { + return ObjectManagerIF::CHILD_INIT_FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t HeaterHandler::initializeHeaterMap() { + for (power::Switch_t switchNr = 0; switchNr < heater::NUMBER_OF_SWITCHES; switchNr++) { + heaterVec.push_back(HeaterWrapper(helper.heaters[switchNr], heater::SwitchState::OFF)); + } + return returnvalue::OK; +} + +void HeaterHandler::readCommandQueue() { + ReturnValue_t result = returnvalue::OK; + CommandMessage command; + if (not busyWithSwitchCommanding) { + result = commandQueue->receiveMessage(&command); + if (result == MessageQueueIF::EMPTY) { + return; + } else if (result != returnvalue::OK) { + sif::warning << "HeaterHandler::readCommandQueue: Message reception error" << std::endl; + return; + } + result = modeHelper.handleModeCommand(&command); + if (result == returnvalue::OK) { + return; + } + result = actionHelper.handleActionMessage(&command); + if (result == returnvalue::OK) { + return; + } + } +} + +ReturnValue_t HeaterHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) { + if (data == nullptr or size < 3) { + return HasActionsIF::INVALID_PARAMETERS; + } + if (actionId != SWITCH_HEATER) { + return COMMAND_NOT_SUPPORTED; + } + auto switchNr = data[0]; + if (switchNr > 7) { + return HasActionsIF::INVALID_PARAMETERS; + } + auto& heater = heaterVec.at(switchNr); + + auto actionRaw = data[1]; + if (actionRaw != 0 and actionRaw != 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + auto action = static_cast(data[1]); + // Always accepts OFF commands + if (action == SwitchAction::SET_SWITCH_ON) { + HasHealthIF::HealthState health; + { + MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + health = heater.healthDevice->getHealth(); + } + if (health == HasHealthIF::FAULTY or health == HasHealthIF::PERMANENT_FAULTY or + health == HasHealthIF::NEEDS_RECOVERY) { + return HasHealthIF::OBJECT_NOT_HEALTHY; + } + CmdSourceParam cmdSource = CmdSourceParam::EXTERNAL; + uint8_t cmdSourceRaw = data[2]; + if (cmdSourceRaw > 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + cmdSource = static_cast(data[2]); + if (health == HasHealthIF::EXTERNAL_CONTROL and cmdSource == CmdSourceParam::INTERNAL) { + return HasHealthIF::IS_EXTERNALLY_CONTROLLED; + } + } + + if (heater.cmdActive) { + return COMMAND_ALREADY_WAITING; + } + heater.action = action; + heater.cmdActive = true; + heater.replyQueue = commandedBy; + busyWithSwitchCommanding = true; + heaterCmdBusyCd.resetTimer(); + return returnvalue::OK; +} + +ReturnValue_t HeaterHandler::sendSwitchCommand(uint8_t switchNr, ReturnValue_t onOff) { + ReturnValue_t result; + store_address_t storeAddress; + uint8_t commandData[3] = {}; + + switch (onOff) { + case PowerSwitchIF::SWITCH_ON: + commandData[0] = switchNr; + commandData[1] = SET_SWITCH_ON; + commandData[2] = CmdSourceParam::INTERNAL; + break; + case PowerSwitchIF::SWITCH_OFF: + commandData[0] = switchNr; + commandData[1] = SET_SWITCH_OFF; + commandData[2] = CmdSourceParam::INTERNAL; + break; + default: + sif::error << "HeaterHandler::sendSwitchCommand: Invalid switch request" << std::endl; + break; + } + + result = ipcStore->addData(&storeAddress, commandData, sizeof(commandData)); + if (result == returnvalue::OK) { + CommandMessage message; + ActionMessage::setCommand(&message, SWITCH_HEATER, storeAddress); + /* Send heater command to own command queue */ + result = commandQueue->sendMessage(commandQueue->getId(), &message, 0); + if (result != returnvalue::OK) { + sif::debug << "HeaterHandler::sendSwitchCommand: Failed to send switch" + << "message" << std::endl; + } + } + return result; +} + +void HeaterHandler::handleSwitchHandling() { + for (uint8_t idx = 0; idx < heater::NUMBER_OF_SWITCHES; idx++) { + auto health = heaterVec[idx].healthDevice->getHealth(); + if (heaterVec[idx].switchState == heater::SwitchState::ON) { + // If a heater is still on but the device was marked faulty by the operator, the SW + // will shut down the heater + if (health == HasHealthIF::FAULTY or health == HasHealthIF::PERMANENT_FAULTY) { + heaterVec[idx].cmdActive = true; + heaterVec[idx].action = SET_SWITCH_OFF; + triggerEvent(FAULTY_HEATER_WAS_ON, idx, 0); + handleSwitchOffCommand(static_cast(idx)); + continue; + } + } + if (heaterVec[idx].cmdActive) { + switch (heaterVec[idx].action) { + case SET_SWITCH_ON: + handleSwitchOnCommand(static_cast(idx)); + break; + case SET_SWITCH_OFF: + handleSwitchOffCommand(static_cast(idx)); + break; + default: + sif::error << "HeaterHandler::handleActiveCommands: Invalid action commanded" + << std::endl; + break; + } + } + } +} + +void HeaterHandler::handleSwitchOnCommand(heater::Switch heaterIdx) { + ReturnValue_t result = returnvalue::OK; + auto& heater = heaterVec.at(heaterIdx); + if (waitForSwitchOff) { + waitForSwitchOff = false; + } + /* Check if command waits for main switch being set on and whether the timeout has expired */ + if (heater.waitMainSwitchOn && heater.mainSwitchCountdown.hasTimedOut()) { + // TODO - This requires the initiation of an FDIR procedure + triggerEvent(MAIN_SWITCH_TIMEOUT); + sif::error << "HeaterHandler::handleSwitchOnCommand: Main switch setting on timeout" + << std::endl; + heater.cmdActive = false; + busyWithSwitchCommanding = false; + heater.waitMainSwitchOn = false; + if (heater.replyQueue != commandQueue->getId()) { + actionHelper.finish(false, heater.replyQueue, heater.action, MAIN_SWITCH_SET_TIMEOUT); + } + return; + } + + // Check state of main line switch + ReturnValue_t mainSwitchState = mainLineSwitcher->getSwitchState(mainLineSwitch); + if (mainSwitchState == PowerSwitchIF::SWITCH_ON) { + gpioId_t gpioId = heater.gpioId; + result = gpioInterface->pullHigh(gpioId); + if (result != returnvalue::OK) { + sif::error << "HeaterHandler::handleSwitchOnCommand: Failed to pull GPIO with ID " << gpioId + << " high" << std::endl; + triggerEvent(GPIO_PULL_HIGH_FAILED, result); + } + if (result == returnvalue::OK) { + triggerEvent(HEATER_WENT_ON, heaterIdx, 0); + { + MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + heater.switchState = heater::SwitchState::ON; + } + EventManagerIF::triggerEvent(helper.heaters[heaterIdx].first->getObjectId(), MODE_INFO, + MODE_ON, 0); + busyWithSwitchCommanding = false; + mode = HasModesIF::MODE_ON; + modeHelper.modeChanged(mode, submode); + } + // There is no need to send action finish replies if the sender was the + // HeaterHandler itself + if (heater.replyQueue != commandQueue->getId()) { + if (result == returnvalue::OK) { + actionHelper.finish(true, heater.replyQueue, heater.action, result); + } else { + actionHelper.finish(false, heater.replyQueue, heater.action, result); + } + } + heater.cmdActive = false; + heater.waitMainSwitchOn = false; + } else if (mainSwitchState == PowerSwitchIF::SWITCH_OFF && heater.waitMainSwitchOn) { + // Just waiting for the main switch being set on + return; + } else if (mainSwitchState == PowerSwitchIF::SWITCH_OFF or + mainSwitchState == PowerSwitchIF::SWITCH_UNKNOWN) { + mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_ON); + heater.mainSwitchCountdown.setTimeout(mainLineSwitcher->getSwitchDelayMs()); + heater.waitMainSwitchOn = true; + } else { + sif::debug << "HeaterHandler::handleSwitchOnCommand: Failed to get state of" + << " main line switch" << std::endl; + if (heater.replyQueue != commandQueue->getId()) { + actionHelper.finish(false, heater.replyQueue, heater.action, mainSwitchState); + } + heater.cmdActive = false; + } +} + +void HeaterHandler::handleSwitchOffCommand(heater::Switch heaterIdx) { + ReturnValue_t result = returnvalue::OK; + auto& heater = heaterVec.at(heaterIdx); + gpioId_t gpioId = heater.gpioId; + result = gpioInterface->pullLow(gpioId); + if (result != returnvalue::OK) { + sif::error << "HeaterHandler::handleSwitchOffCommand: Failed to pull gpio with id" << gpioId + << " low" << std::endl; + triggerEvent(GPIO_PULL_LOW_FAILED, result); + } + if (result == returnvalue::OK) { + // Check whether switch is already off + if (getSwitchState(heaterIdx) == heater::SwitchState::ON) { + { + MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + heater.switchState = heater::SwitchState::OFF; + } + triggerEvent(HEATER_WENT_OFF, heaterIdx, 0); + } else { + triggerEvent(SWITCH_ALREADY_OFF, heaterIdx); + } + EventManagerIF::triggerEvent(helper.heaters[heaterIdx].first->getObjectId(), MODE_INFO, + MODE_OFF, 0); + // When all switches are off, also main line switch will be turned off + if (allSwitchesOff()) { + mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF); + waitForSwitchOff = true; + } else { + busyWithSwitchCommanding = false; + } + } + if (heater.replyQueue != NO_COMMANDER) { + // Report back switch command reply if necessary + if (result == returnvalue::OK) { + actionHelper.finish(true, heater.replyQueue, heater.action, result); + } else { + actionHelper.finish(false, heater.replyQueue, heater.action, result); + } + } + heater.cmdActive = false; +} + +heater::SwitchState HeaterHandler::getSwitchState(heater::Switch switchNr) const { + MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + return heaterVec.at(switchNr).switchState; +} + +ReturnValue_t HeaterHandler::switchHeater(heater::Switch heater, heater::SwitchState switchState) { + if (switchState == heater::SwitchState::ON) { + return sendSwitchCommand(heater, PowerSwitchIF::SWITCH_ON); + } else if (switchState == heater::SwitchState::OFF) { + return sendSwitchCommand(heater, PowerSwitchIF::SWITCH_OFF); + } + return returnvalue::FAILED; +} + +void HeaterHandler::announceMode(bool recursive) { + triggerEvent(MODE_INFO, mode, submode); + + std::array states; + getAllSwitchStates(states); + for (unsigned idx = 0; idx < helper.heaters.size(); idx++) { + if (states[idx] == heater::SwitchState::ON) { + EventManagerIF::triggerEvent(helper.heaters[idx].first->getObjectId(), MODE_INFO, MODE_ON, 0); + } else { + EventManagerIF::triggerEvent(helper.heaters[idx].first->getObjectId(), MODE_INFO, MODE_OFF, + 0); + } + } +} + +void HeaterHandler::getMode(Mode_t* mode, Submode_t* submode) { + if (!mode || !submode) { + return; + } + *mode = this->mode; + *submode = this->submode; +} + +const HasHealthIF* HeaterHandler::getOptHealthIF() const { return nullptr; } + +const HasModesIF& HeaterHandler::getModeIF() const { return *this; } + +ReturnValue_t HeaterHandler::connectModeTreeParent(HasModeTreeChildrenIF& parent) { + return modetree::connectModeTreeParent(parent, *this, nullptr, modeHelper); +} + +ModeTreeChildIF& HeaterHandler::getModeTreeChildIF() { return *this; } + +object_id_t HeaterHandler::getObjectId() const { return SystemObject::getObjectId(); } + +ReturnValue_t HeaterHandler::getAllSwitchStates(std::array& statesBuf) { + { + MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + if (mg.getLockResult() != returnvalue::OK) { + return returnvalue::FAILED; + } + for (unsigned idx = 0; idx < helper.heaters.size(); idx++) { + statesBuf[idx] = heaterVec[idx].switchState; + } + } + return returnvalue::OK; +} + +bool HeaterHandler::allSwitchesOff() { + MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + for (power::Switch_t switchNr = 0; switchNr < heater::NUMBER_OF_SWITCHES; switchNr++) { + if (heaterVec.at(switchNr).switchState == heater::SwitchState::ON) { + return false; + } + } + return true; +} + +MessageQueueId_t HeaterHandler::getCommandQueue() const { return commandQueue->getId(); } + +ReturnValue_t HeaterHandler::sendFuseOnCommand(uint8_t fuseNr) { return returnvalue::OK; } + +ReturnValue_t HeaterHandler::getSwitchState(uint8_t switchNr) const { + ReturnValue_t mainSwitchState = mainLineSwitcher->getSwitchState(mainLineSwitch); + if (mainSwitchState == PowerSwitchIF::SWITCH_OFF) { + return PowerSwitchIF::SWITCH_OFF; + } + if (switchNr > 7) { + return returnvalue::FAILED; + } + if (getSwitchState(static_cast(switchNr)) == heater::SwitchState::ON) { + return PowerSwitchIF::SWITCH_ON; + } + return PowerSwitchIF::SWITCH_OFF; +} + +ReturnValue_t HeaterHandler::getFuseState(uint8_t fuseNr) const { return 0; } + +uint32_t HeaterHandler::getSwitchDelayMs(void) const { return 2000; } + +HasHealthIF::HealthState HeaterHandler::getHealth(heater::Switch heater) { + auto* healthDev = heaterVec.at(heater).healthDevice; + if (healthDev != nullptr) { + MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); + return healthDev->getHealth(); + } + return HasHealthIF::HealthState::FAULTY; +} diff --git a/mission/tcs/HeaterHandler.h b/mission/tcs/HeaterHandler.h new file mode 100644 index 0000000..4901c50 --- /dev/null +++ b/mission/tcs/HeaterHandler.h @@ -0,0 +1,214 @@ +#ifndef MISSION_TCS_HEATERHANDLER_H_ +#define MISSION_TCS_HEATERHANDLER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "events/subsystemIdRanges.h" +#include "mission/tcs/defs.h" +#include "returnvalues/classIds.h" + +class PowerSwitchIF; +class HealthTableIF; + +using HeaterPair = std::pair; + +struct HeaterHelper { + public: + HeaterHelper(std::array heaters) + : heaters(std::move(heaters)) {} + std::array heaters = {}; +}; +/** + * @brief This class intends the control of heaters. + * + * @author J. Meier + */ +class HeaterHandler : public ExecutableObjectIF, + public PowerSwitchIF, + public HasModesIF, + public ModeTreeChildIF, + public ModeTreeConnectionIF, + public SystemObject, + public HasActionsIF { + friend class ThermalController; + + public: + static const uint8_t INTERFACE_ID = CLASS_ID::HEATER_HANDLER; + + static const ReturnValue_t COMMAND_NOT_SUPPORTED = MAKE_RETURN_CODE(0xA1); + static const ReturnValue_t INIT_FAILED = MAKE_RETURN_CODE(0xA2); + static const ReturnValue_t INVALID_SWITCH_NR = MAKE_RETURN_CODE(0xA3); + static const ReturnValue_t MAIN_SWITCH_SET_TIMEOUT = MAKE_RETURN_CODE(0xA4); + static const ReturnValue_t COMMAND_ALREADY_WAITING = MAKE_RETURN_CODE(0xA5); + + enum CmdSourceParam : uint8_t { INTERNAL = 0, EXTERNAL = 1 }; + + /** Device command IDs */ + static const DeviceCommandId_t SWITCH_HEATER = 0x0; + + HeaterHandler(object_id_t setObjectId, GpioIF* gpioInterface_, HeaterHelper helper, + PowerSwitchIF* mainLineSwitcherObjectId, power::Switch_t mainLineSwitch); + + ReturnValue_t connectModeTreeParent(HasModeTreeChildrenIF& parent) override; + ReturnValue_t getAllSwitchStates(std::array& statesBuf); + + virtual ~HeaterHandler(); + + protected: + enum SwitchAction : uint8_t { SET_SWITCH_OFF, SET_SWITCH_ON, NONE }; + + ReturnValue_t switchHeater(heater::Switch heater, heater::SwitchState switchState); + HasHealthIF::HealthState getHealth(heater::Switch heater); + + ReturnValue_t performOperation(uint8_t operationCode = 0) override; + + ReturnValue_t sendSwitchCommand(uint8_t switchNr, ReturnValue_t onOff) override; + ReturnValue_t sendFuseOnCommand(uint8_t fuseNr) override; + /** + * @brief This function will be called from the Heater object to check + * the current switch state. + */ + ReturnValue_t getSwitchState(uint8_t switchNr) const override; + ReturnValue_t getFuseState(uint8_t fuseNr) const override; + uint32_t getSwitchDelayMs(void) const override; + + MessageQueueId_t getCommandQueue() const override; + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size) override; + ReturnValue_t initialize() override; + + private: + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::HEATER_HANDLER; + static constexpr Event GPIO_PULL_HIGH_FAILED = MAKE_EVENT(0, severity::LOW); + static constexpr Event GPIO_PULL_LOW_FAILED = MAKE_EVENT(1, severity::LOW); + static constexpr Event HEATER_WENT_ON = event::makeEvent(SUBSYSTEM_ID, 2, severity::INFO); + static constexpr Event HEATER_WENT_OFF = event::makeEvent(SUBSYSTEM_ID, 3, severity::INFO); + static constexpr Event SWITCH_ALREADY_ON = MAKE_EVENT(4, severity::INFO); + static constexpr Event SWITCH_ALREADY_OFF = MAKE_EVENT(5, severity::INFO); + static constexpr Event MAIN_SWITCH_TIMEOUT = MAKE_EVENT(6, severity::MEDIUM); + //! A faulty heater was one. The SW will autonomously attempt to shut it down. P1: Heater Index + static constexpr Event FAULTY_HEATER_WAS_ON = event::makeEvent(SUBSYSTEM_ID, 7, severity::LOW); + + static const MessageQueueId_t NO_COMMANDER = 0; + + /** + * @brief Struct holding information about a heater command to execute. + * + * @param action The action to perform. + * @param replyQueue The queue of the commander to which status replies + * will be sent. + * @param active True if command is waiting for execution, otherwise false. + * @param waitSwitchOn True if the command is waiting for the main switch being set on. + * @param mainSwitchCountdown Sets timeout to wait for main switch being set on. + */ + struct HeaterWrapper { + HeaterWrapper(HeaterPair pair, heater::SwitchState initState) + : healthDevice(pair.first), gpioId(pair.second), switchState(initState) {} + HealthDevice* healthDevice = nullptr; + gpioId_t gpioId = gpio::NO_GPIO; + SwitchAction action = SwitchAction::NONE; + MessageQueueId_t replyQueue = MessageQueueIF::NO_QUEUE; + bool cmdActive = false; + heater::SwitchState switchState = heater::SwitchState::OFF; + bool waitMainSwitchOn = false; + Countdown mainSwitchCountdown; + }; + + using HeaterMap = std::vector; + + HeaterMap heaterVec = {}; + + MutexIF* handlerLock = nullptr; + static constexpr MutexIF::TimeoutType LOCK_TYPE = MutexIF::TimeoutType::WAITING; + static constexpr uint32_t LOCK_TIMEOUT = 20; + static constexpr char LOCK_CTX[] = "HeaterHandler"; + + HeaterHelper helper; + ModeHelper modeHelper; + + /** Size of command queue */ + size_t cmdQueueSize = 20; + bool waitForSwitchOff = true; + bool busyWithSwitchCommanding = false; + + GpioIF* gpioInterface = nullptr; + + /** Queue to receive messages from other objects. */ + MessageQueueIF* commandQueue = nullptr; + + /** + * Power switcher object which controls the 8V main line of the heater + * logic on the TCS board. + */ + PowerSwitchIF* mainLineSwitcher = nullptr; + /** Switch number of the heater power supply switch */ + power::Switch_t mainLineSwitch; + + ActionHelper actionHelper; + Countdown heaterCmdBusyCd = Countdown(2000); + + StorageManagerIF* ipcStore = nullptr; + + Mode_t mode = HasModesIF::MODE_OFF; + Submode_t submode = 0; + + void readCommandQueue(); + + /** + * @brief Returns the state of a switch (ON - true, or OFF - false). + * @param switchNr The number of the switch to check. + */ + heater::SwitchState getSwitchState(heater::Switch switchNr) const; + + /** + * @brief This function runs commands waiting for execution. + */ + void handleSwitchHandling(); + + ReturnValue_t initializeHeaterMap(); + + /** + * @brief Sets all switches to OFF. + */ + void setInitialSwitchStates(); + + // HasModesIF implementation + void announceMode(bool recursive) override; + void getMode(Mode_t* mode, Submode_t* submode) override; + + // Mode Tree helper overrides + object_id_t getObjectId() const override; + const HasHealthIF* getOptHealthIF() const override; + const HasModesIF& getModeIF() const override; + ModeTreeChildIF& getModeTreeChildIF() override; + + void handleSwitchOnCommand(heater::Switch heaterIdx); + + void handleSwitchOffCommand(heater::Switch heaterIdx); + + /** + * @brief Checks if all switches are off. + * @return True if all switches are off, otherwise false. + */ + bool allSwitchesOff(); +}; + +#endif /* MISSION_TCS_HEATERHANDLER_H_ */ diff --git a/mission/tcs/HeaterHealthDev.cpp b/mission/tcs/HeaterHealthDev.cpp new file mode 100644 index 0000000..6be56a0 --- /dev/null +++ b/mission/tcs/HeaterHealthDev.cpp @@ -0,0 +1,12 @@ +#include "HeaterHealthDev.h" + +HeaterHealthDev::HeaterHealthDev(object_id_t setObjectId, MessageQueueId_t parentQueue) + : HealthDevice(setObjectId, parentQueue) {} + +ReturnValue_t HeaterHealthDev::setHealth(HealthState health) { + // Does not make sense for a simple heater. + if (health == HealthState::NEEDS_RECOVERY) { + return returnvalue::FAILED; + } + return HealthDevice::setHealth(health); +} diff --git a/mission/tcs/HeaterHealthDev.h b/mission/tcs/HeaterHealthDev.h new file mode 100644 index 0000000..8d15836 --- /dev/null +++ b/mission/tcs/HeaterHealthDev.h @@ -0,0 +1,12 @@ +#ifndef MISSION_TCS_HEATERHEALTHDEV_H_ +#define MISSION_TCS_HEATERHEALTHDEV_H_ +#include + +class HeaterHealthDev : public HealthDevice { + public: + HeaterHealthDev(object_id_t setObjectId, MessageQueueId_t parentQueue); + + ReturnValue_t setHealth(HealthState health) override; +}; + +#endif /* MISSION_TCS_HEATERHEALTHDEV_H_ */ diff --git a/mission/tcs/Max31865Definitions.h b/mission/tcs/Max31865Definitions.h new file mode 100644 index 0000000..057b289 --- /dev/null +++ b/mission/tcs/Max31865Definitions.h @@ -0,0 +1,137 @@ +#ifndef MISSION_DEVICES_DEVICEDEFINITIONS_MAX13865DEFINITIONS_H_ +#define MISSION_DEVICES_DEVICEDEFINITIONS_MAX13865DEFINITIONS_H_ + +#include +#include +#include + +#include "objects/systemObjectList.h" + +namespace MAX31865 { + +enum class PoolIds : lp_id_t { RTD_VALUE, TEMPERATURE_C, LAST_FAULT_BYTE, FAULT_BYTE }; +enum Wires : unsigned int { TWO_WIRE = 0, THREE_WIRE = 1, FOUR_WIRE = 0 }; + +enum ConvMode : unsigned int { NORM_OFF = 0, AUTO = 1 }; + +enum Bias : unsigned int { OFF = 0, ON = 1 }; + +enum FilterSel : unsigned int { FIFTY_HERTZ = 1, SIXTY_HERTZ = 0 }; + +enum CfgBitPos { + FILTER_SEL = 0, + FAULT_STATUS_CLEAR = 1, + FDCC = 2, + WIRE_SEL = 4, + ONE_SHOT = 5, + CONV_MODE = 6, + BIAS_SEL = 7 +}; + +static constexpr uint32_t WARMUP_MS = 100; + +static constexpr uint8_t WRITE_BIT = 0b10000000; + +enum Regs : uint8_t { + CONFIG = 0x00, + RTD = 0x01, + HIGH_THRESHOLD = 0x03, + LOW_THRESHOLD = 0x05, + FAULT_BYTE = 0x07 +}; + +static constexpr DeviceCommandId_t CONFIG_CMD = 0x80; +static constexpr DeviceCommandId_t WRITE_HIGH_THRESHOLD = 0x83; +static constexpr DeviceCommandId_t WRITE_LOW_THRESHOLD = 0x85; + +static constexpr DeviceCommandId_t REQUEST_CONFIG = CONFIG; +static constexpr DeviceCommandId_t REQUEST_RTD = RTD; +static constexpr DeviceCommandId_t REQUEST_HIGH_THRESHOLD = HIGH_THRESHOLD; +static constexpr DeviceCommandId_t REQUEST_LOW_THRESHOLD = LOW_THRESHOLD; +static constexpr DeviceCommandId_t REQUEST_FAULT_BYTE = FAULT_BYTE; +static constexpr DeviceCommandId_t CLEAR_FAULT_BYTE = 0x08; + +static constexpr uint32_t MAX31865_SET_ID = REQUEST_RTD; +static constexpr uint8_t CLEAR_FAULT_BIT_VAL = 0b0000'0010; + +static constexpr size_t MAX_REPLY_SIZE = 5; + +class PrimarySet : public StaticLocalDataSet<4> { + public: + /** + * Constructor used by owner and data creators like device handlers. + * @param owner + * @param setId + */ + PrimarySet(HasLocalDataPoolIF* owner, uint32_t setId) : StaticLocalDataSet(owner, setId) {} + + /** + * Constructor used by data users like controllers. + * @param sid + */ + PrimarySet(object_id_t objectId, uint32_t setId) : StaticLocalDataSet(sid_t(objectId, setId)) {} + + lp_var_t rtdValue = + lp_var_t(sid.objectId, static_cast(PoolIds::RTD_VALUE), this); + lp_var_t temperatureCelcius = + lp_var_t(sid.objectId, static_cast(PoolIds::TEMPERATURE_C), this); + lp_var_t lastErrorByte = + lp_var_t(sid.objectId, static_cast(PoolIds::LAST_FAULT_BYTE), this); + lp_var_t errorByte = + lp_var_t(sid.objectId, static_cast(PoolIds::FAULT_BYTE), this); +}; + +} // namespace MAX31865 + +namespace EiveMax31855 { + +static constexpr float RTD_RREF_PT1000 = 4020.0; //!< Ohm +static constexpr uint8_t NUM_RTDS = 16; + +enum RtdCommands : DeviceCommandId_t { + ON = 0, + EXCHANGE_SET_ID = MAX31865::REQUEST_RTD, + ACTIVE = 2, + LOW_THRESHOLD = 3, + HIGH_TRESHOLD = 4, + OFF = 5, + CFG = 6, + NUM_CMDS +}; + +class ReadOutStruct : public SerialLinkedListAdapter { + public: + ReadOutStruct() { setLinks(); } + ReadOutStruct(bool active, uint32_t spiErrCnt, bool faultBitSet, uint8_t faultVal, + uint16_t rtdVal) + : active(active), + adcCode(rtdVal), + faultBitSet(faultBitSet), + faultValue(faultVal), + spiErrorCount(spiErrCnt) { + setLinks(); + } + + //! RTD was set on and is configured, but is not periodically polled + SerializeElement configured = false; + //! RTD is active and polled periodically + SerializeElement active = false; + SerializeElement adcCode = 0; + SerializeElement faultBitSet = false; + SerializeElement faultValue = 0; + SerializeElement spiErrorCount = 0; + + private: + void setLinks() { + setStart(&configured); + configured.setNext(&active); + active.setNext(&adcCode); + adcCode.setNext(&faultBitSet); + faultBitSet.setNext(&faultValue); + faultValue.setNext(&spiErrorCount); + }; +}; + +}; // namespace EiveMax31855 + +#endif /* MISSION_DEVICES_DEVICEDEFINITIONS_MAX13865DEFINITIONS_H_ */ diff --git a/mission/tcs/Max31865EiveHandler.cpp b/mission/tcs/Max31865EiveHandler.cpp new file mode 100644 index 0000000..a3c1dce --- /dev/null +++ b/mission/tcs/Max31865EiveHandler.cpp @@ -0,0 +1,212 @@ +#include +#include + +#include "fsfw/thermal/tcsDefinitions.h" + +Max31865EiveHandler::Max31865EiveHandler(object_id_t objectId, object_id_t comIF, + CookieIF* comCookie) + : DeviceHandlerBase(objectId, comIF, comCookie, nullptr), + sensorDataset(this, EiveMax31855::RtdCommands::EXCHANGE_SET_ID), + debugDivider(5) { + structLen = exchangeStruct.getSerializedSize(); +} + +void Max31865EiveHandler::doStartUp() { + updatePeriodicReply(true, EiveMax31855::RtdCommands::EXCHANGE_SET_ID); + if (state == InternalState::NONE or state == InternalState::INACTIVE) { + if (instantNormal) { + state = InternalState::ACTIVE; + } else { + state = InternalState::ON; + } + transitionOk = false; + } + if ((state == InternalState::ON or state == InternalState::ACTIVE) and transitionOk) { + if (instantNormal) { + setMode(MODE_NORMAL); + } else { + setMode(MODE_ON); + } + } +} + +void Max31865EiveHandler::doShutDown() { + if (state == InternalState::NONE or state == InternalState::ACTIVE or + state == InternalState::ON) { + state = InternalState::INACTIVE; + transitionOk = false; + } + if (state == InternalState::INACTIVE and transitionOk) { + sensorDataset.temperatureCelcius = thermal::INVALID_TEMPERATURE; + sensorDataset.setValidity(false, true); + updatePeriodicReply(false, EiveMax31855::RtdCommands::EXCHANGE_SET_ID); + setMode(MODE_OFF); + } +} + +ReturnValue_t Max31865EiveHandler::buildNormalDeviceCommand(DeviceCommandId_t* id) { + //*id = EiveMax31855::RtdCommands::EXCHANGE_SET_ID; + return NOTHING_TO_SEND; +} + +ReturnValue_t Max31865EiveHandler::buildTransitionDeviceCommand(DeviceCommandId_t* id) { + ReturnValue_t result = NOTHING_TO_SEND; + if (state == InternalState::ON) { + *id = EiveMax31855::RtdCommands::ON; + result = buildCommandFromCommand(*id, nullptr, 0); + } + if (state == InternalState::ACTIVE) { + *id = EiveMax31855::RtdCommands::ACTIVE; + result = buildCommandFromCommand(*id, nullptr, 0); + } + if (state == InternalState::INACTIVE) { + *id = EiveMax31855::RtdCommands::OFF; + result = buildCommandFromCommand(*id, nullptr, 0); + } + return result; +} + +ReturnValue_t Max31865EiveHandler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t* commandData, + size_t commandDataLen) { + auto cmdTyped = static_cast(deviceCommand); + switch (cmdTyped) { + case (EiveMax31855::RtdCommands::ON): + case (EiveMax31855::RtdCommands::ACTIVE): + case (EiveMax31855::RtdCommands::OFF): + case (EiveMax31855::RtdCommands::CFG): { + simpleCommand(cmdTyped); + break; + } + case (EiveMax31855::RtdCommands::LOW_THRESHOLD): + case (EiveMax31855::RtdCommands::HIGH_TRESHOLD): { + break; + } + default: + return NOTHING_TO_SEND; + } + return returnvalue::OK; +} + +void Max31865EiveHandler::setInstantNormal(bool instantNormal) { + this->instantNormal = instantNormal; +} + +void Max31865EiveHandler::setDebugMode(bool enable, uint32_t divider) { + this->debugMode = enable; + debugDivider.setDivider(divider); +} + +void Max31865EiveHandler::simpleCommand(EiveMax31855::RtdCommands cmd) { + cmdBuf[0] = static_cast(cmd); + rawPacket = cmdBuf.data(); + rawPacketLen = 1; +} + +void Max31865EiveHandler::doTransition(Mode_t modeFrom, Submode_t subModeFrom) { + if (getMode() == _MODE_TO_NORMAL) { + if (state != InternalState::ACTIVE) { + state = InternalState::ACTIVE; + transitionOk = false; + } else if (transitionOk) { + setMode(MODE_NORMAL); + } + } else { + DeviceHandlerBase::doTransition(modeFrom, subModeFrom); + } +} + +void Max31865EiveHandler::fillCommandAndReplyMap() { + insertInCommandMap(EiveMax31855::RtdCommands::ON); + insertInCommandMap(EiveMax31855::RtdCommands::ACTIVE); + insertInCommandMap(EiveMax31855::RtdCommands::OFF); + insertInCommandMap(EiveMax31855::RtdCommands::CFG); + insertInReplyMap(EiveMax31855::RtdCommands::EXCHANGE_SET_ID, 200, &sensorDataset, 0, true); +} + +ReturnValue_t Max31865EiveHandler::scanForReply(const uint8_t* start, size_t remainingSize, + DeviceCommandId_t* foundId, size_t* foundLen) { + if (getMode() == _MODE_POWER_ON or getMode() == _MODE_WAIT_ON) { + return IGNORE_FULL_PACKET; + } + if (remainingSize != structLen) { + sif::error << "Invalid reply from RTD reader detected, reply size " << remainingSize + << " not equal to exchange struct size " << structLen << std::endl; + return DeviceHandlerIF::INVALID_DATA; + } + *foundId = EiveMax31855::RtdCommands::EXCHANGE_SET_ID; + *foundLen = remainingSize; + return returnvalue::OK; +} + +ReturnValue_t Max31865EiveHandler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t* packet) { + size_t deserTmp = structLen; + auto result = exchangeStruct.deSerialize(&packet, &deserTmp, SerializeIF::Endianness::MACHINE); + if (result != returnvalue::OK) { + return result; + } + if (getMode() == _MODE_TO_NORMAL and exchangeStruct.active and state == InternalState::ACTIVE) { + transitionOk = true; + } + if (getMode() == _MODE_START_UP and exchangeStruct.configured and state == InternalState::ON) { + transitionOk = true; + } + if (getMode() == _MODE_SHUT_DOWN and not exchangeStruct.active) { + transitionOk = true; + return returnvalue::OK; + } + // If the 15 received bits are all ones, this is considered a case where the device does not + // work because it does not drive the MISO line. This can happens if the sensor is broken + // or off. + if (exchangeStruct.adcCode == 0x7fff) { + PoolReadGuard pg(&sensorDataset); + sensorDataset.temperatureCelcius.setValid(false); + sensorDataset.temperatureCelcius = thermal::INVALID_TEMPERATURE; + return returnvalue::FAILED; + } + + // Calculate resistance + float rtdValue = exchangeStruct.adcCode * EiveMax31855::RTD_RREF_PT1000 / INT16_MAX; + // calculate approximation + float approxTemp = exchangeStruct.adcCode / 32.0 - 256.0; + + PoolReadGuard pg(&sensorDataset); + if (pg.getReadResult() != returnvalue::OK) { + sif::warning << "Max31865EiveHandler: Failed to read sensor dataset" << std::endl; + sensorDataset.temperatureCelcius.setValid(false); + return returnvalue::OK; + } + sensorDataset.temperatureCelcius = approxTemp; + sensorDataset.temperatureCelcius.setValid(true); + + if (debugMode) { + if (debugDivider.checkAndIncrement()) { + sif::info << "Max31865: " << std::setw(20) << std::left << locString << std::right + << " | R[Ohm] " << rtdValue << " Ohms | Approx T[C]: " << approxTemp << std::endl; + } + } + return returnvalue::OK; +} + +uint32_t Max31865EiveHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 2000; } + +ReturnValue_t Max31865EiveHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + using namespace MAX31865; + localDataPoolMap.emplace(static_cast(PoolIds::RTD_VALUE), new PoolEntry({0})); + localDataPoolMap.emplace(static_cast(PoolIds::TEMPERATURE_C), new PoolEntry({0})); + localDataPoolMap.emplace(static_cast(PoolIds::LAST_FAULT_BYTE), + new PoolEntry({0})); + localDataPoolMap.emplace(static_cast(PoolIds::FAULT_BYTE), new PoolEntry({0})); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(sensorDataset.getSid(), false, 30.0)); + return returnvalue::OK; +} + +void Max31865EiveHandler::setDeviceInfo(uint8_t idx_, std::string location_) { + idx = idx_; + locString = std::move(location_); +} + +ReturnValue_t Max31865EiveHandler::initialize() { return DeviceHandlerBase::initialize(); } diff --git a/mission/tcs/Max31865EiveHandler.h b/mission/tcs/Max31865EiveHandler.h new file mode 100644 index 0000000..aa159ba --- /dev/null +++ b/mission/tcs/Max31865EiveHandler.h @@ -0,0 +1,46 @@ +#ifndef MISSION_TCS_MAX31865EIVEHANDLER_H_ +#define MISSION_TCS_MAX31865EIVEHANDLER_H_ + +#include +#include +#include + +class Max31865EiveHandler : public DeviceHandlerBase { + public: + Max31865EiveHandler(object_id_t objectId, object_id_t comIF, CookieIF* comCookie); + void setInstantNormal(bool instantNormal); + void setDebugMode(bool enable, uint32_t divider); + void setDeviceInfo(uint8_t idx, std::string location); + + private: + void doStartUp() override; + void doShutDown() override; + void doTransition(Mode_t modeFrom, Submode_t subModeFrom) override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t* id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t* id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t* commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t* start, size_t remainingSize, DeviceCommandId_t* foundId, + size_t* foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t* packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + ReturnValue_t initialize() override; + + void simpleCommand(EiveMax31855::RtdCommands cmd); + std::array cmdBuf = {}; + uint8_t idx = 0; + std::string locString = "Unknown"; + EiveMax31855::ReadOutStruct exchangeStruct; + bool debugMode = false; + size_t structLen = 0; + bool instantNormal = false; + MAX31865::PrimarySet sensorDataset; + PeriodicOperationDivider debugDivider; + enum class InternalState { NONE, ON, ACTIVE, INACTIVE } state = InternalState::NONE; + bool transitionOk = false; +}; + +#endif /* MISSION_TCS_MAX31865EIVEHANDLER_H_ */ diff --git a/mission/tcs/Max31865PT1000Handler.cpp b/mission/tcs/Max31865PT1000Handler.cpp new file mode 100644 index 0000000..7dcb493 --- /dev/null +++ b/mission/tcs/Max31865PT1000Handler.cpp @@ -0,0 +1,547 @@ +#include + +#include +#include + +#include "fsfw/datapool/PoolReadGuard.h" + +Max31865PT1000Handler::Max31865PT1000Handler(object_id_t objectId, object_id_t comIF, + CookieIF *comCookie) + : DeviceHandlerBase(objectId, comIF, comCookie), + sensorDataset(this, MAX31865::REQUEST_RTD), + sensorDatasetSid(sensorDataset.getSid()) { +#if OBSW_VERBOSE_LEVEL >= 1 + debugDivider = new PeriodicOperationDivider(10); +#endif +} + +Max31865PT1000Handler::~Max31865PT1000Handler() {} + +void Max31865PT1000Handler::doStartUp() { + if (internalState == InternalState::NONE) { + internalState = InternalState::WARMUP; + Clock::getUptime(&startTime); + } + + if (internalState == InternalState::WARMUP) { + dur_millis_t timeNow = 0; + Clock::getUptime(&timeNow); + if (timeNow - startTime >= 100) { + internalState = InternalState::CONFIGURE; + } + } + + if (internalState == InternalState::CONFIGURE) { + if (commandExecuted) { + commandExecuted = false; + internalState = InternalState::REQUEST_CONFIG; + } + } + + if (internalState == InternalState::REQUEST_CONFIG) { + if (commandExecuted) { + commandExecuted = false; + internalState = InternalState::CONFIG_HIGH_THRESHOLD; + } + } + + if (internalState == InternalState::CONFIG_HIGH_THRESHOLD) { + if (commandExecuted) { + internalState = InternalState::REQUEST_HIGH_THRESHOLD; + commandExecuted = false; + } + } + + if (internalState == InternalState::REQUEST_HIGH_THRESHOLD) { + if (commandExecuted) { + internalState = InternalState::CONFIG_LOW_THRESHOLD; + commandExecuted = false; + } + } + + if (internalState == InternalState::CONFIG_LOW_THRESHOLD) { + if (commandExecuted) { + internalState = InternalState::REQUEST_LOW_THRESHOLD; + commandExecuted = false; + } + } + + if (internalState == InternalState::REQUEST_LOW_THRESHOLD) { + if (commandExecuted) { + internalState = InternalState::CLEAR_FAULT_BYTE; + commandExecuted = false; + } + } + if (internalState == InternalState::CLEAR_FAULT_BYTE) { + if (commandExecuted) { + commandExecuted = false; + internalState = InternalState::RUNNING; + if (instantNormal) { + setMode(MODE_NORMAL); + } else { + setMode(_MODE_TO_ON); + } + } + } +} + +void Max31865PT1000Handler::doShutDown() { + commandExecuted = false; + warningSwitch = true; + setMode(_MODE_POWER_DOWN); +} + +ReturnValue_t Max31865PT1000Handler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + if (internalState == InternalState::RUNNING) { + *id = MAX31865::REQUEST_RTD; + return buildCommandFromCommand(*id, nullptr, 0); + } else if (internalState == InternalState::REQUEST_FAULT_BYTE) { + *id = MAX31865::REQUEST_FAULT_BYTE; + return buildCommandFromCommand(*id, nullptr, 0); + } else if (internalState == InternalState::CLEAR_FAULT_BYTE) { + *id = MAX31865::CLEAR_FAULT_BYTE; + return buildCommandFromCommand(*id, nullptr, 0); + } else { + return DeviceHandlerBase::NOTHING_TO_SEND; + } +} + +ReturnValue_t Max31865PT1000Handler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + switch (internalState) { + case (InternalState::NONE): + case (InternalState::WARMUP): + case (InternalState::RUNNING): + return DeviceHandlerBase::NOTHING_TO_SEND; + case (InternalState::CONFIGURE): { + *id = MAX31865::CONFIG_CMD; + uint8_t config[1] = {DEFAULT_CONFIG}; + return buildCommandFromCommand(*id, config, 1); + } + case (InternalState::REQUEST_CONFIG): { + *id = MAX31865::REQUEST_CONFIG; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (InternalState::CONFIG_HIGH_THRESHOLD): { + *id = MAX31865::WRITE_HIGH_THRESHOLD; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (InternalState::REQUEST_HIGH_THRESHOLD): { + *id = MAX31865::REQUEST_HIGH_THRESHOLD; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (InternalState::CONFIG_LOW_THRESHOLD): { + *id = MAX31865::WRITE_LOW_THRESHOLD; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (InternalState::REQUEST_LOW_THRESHOLD): { + *id = MAX31865::REQUEST_LOW_THRESHOLD; + return buildCommandFromCommand(*id, nullptr, 0); + } + case (InternalState::CLEAR_FAULT_BYTE): { + *id = MAX31865::CLEAR_FAULT_BYTE; + return buildCommandFromCommand(*id, nullptr, 0); + } + + default: +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "Max31865PT1000Handler: Invalid internal state" << std::endl; +#else + sif::printError("Max31865PT1000Handler: Invalid internal state\n"); +#endif + return returnvalue::FAILED; + } +} + +ReturnValue_t Max31865PT1000Handler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + switch (deviceCommand) { + case (MAX31865::CONFIG_CMD): { + commandBuffer[0] = static_cast(MAX31865::CONFIG_CMD); + if (commandDataLen == 1) { + commandBuffer[1] = commandData[0]; + currentCfg = commandData[0]; + DeviceHandlerBase::rawPacketLen = 2; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } else { + return DeviceHandlerIF::NO_COMMAND_DATA; + } + } + case (MAX31865::CLEAR_FAULT_BYTE): { + commandBuffer[0] = static_cast(MAX31865::CONFIG_CMD); + commandBuffer[1] = currentCfg | MAX31865::CLEAR_FAULT_BIT_VAL; + DeviceHandlerBase::rawPacketLen = 2; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } + case (MAX31865::REQUEST_CONFIG): { + commandBuffer[0] = static_cast(MAX31865::REQUEST_CONFIG); + commandBuffer[1] = 0x00; // dummy byte + DeviceHandlerBase::rawPacketLen = 2; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } + case (MAX31865::WRITE_HIGH_THRESHOLD): { + commandBuffer[0] = static_cast(MAX31865::WRITE_HIGH_THRESHOLD); + commandBuffer[1] = static_cast(HIGH_THRESHOLD >> 8); + commandBuffer[2] = static_cast(HIGH_THRESHOLD & 0xFF); + DeviceHandlerBase::rawPacketLen = 3; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } + case (MAX31865::REQUEST_HIGH_THRESHOLD): { + commandBuffer[0] = static_cast(MAX31865::REQUEST_HIGH_THRESHOLD); + commandBuffer[1] = 0x00; // dummy byte + commandBuffer[2] = 0x00; // dummy byte + DeviceHandlerBase::rawPacketLen = 3; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } + case (MAX31865::WRITE_LOW_THRESHOLD): { + commandBuffer[0] = static_cast(MAX31865::WRITE_LOW_THRESHOLD); + commandBuffer[1] = static_cast(LOW_THRESHOLD >> 8); + commandBuffer[2] = static_cast(LOW_THRESHOLD & 0xFF); + DeviceHandlerBase::rawPacketLen = 3; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } + case (MAX31865::REQUEST_LOW_THRESHOLD): { + commandBuffer[0] = static_cast(MAX31865::REQUEST_LOW_THRESHOLD); + commandBuffer[1] = 0x00; // dummy byte + commandBuffer[2] = 0x00; // dummy byte + DeviceHandlerBase::rawPacketLen = 3; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } + case (MAX31865::REQUEST_RTD): { + commandBuffer[0] = static_cast(MAX31865::REQUEST_RTD); + // two dummy bytes + commandBuffer[1] = 0x00; + commandBuffer[2] = 0x00; + DeviceHandlerBase::rawPacketLen = 3; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } + case (MAX31865::REQUEST_FAULT_BYTE): { + commandBuffer[0] = static_cast(MAX31865::REQUEST_FAULT_BYTE); + commandBuffer[1] = 0x00; + DeviceHandlerBase::rawPacketLen = 2; + DeviceHandlerBase::rawPacket = commandBuffer.data(); + return returnvalue::OK; + } + default: + // Unknown DeviceCommand + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } +} + +void Max31865PT1000Handler::fillCommandAndReplyMap() { + insertInCommandAndReplyMap(MAX31865::CONFIG_CMD, 3); + insertInCommandAndReplyMap(MAX31865::REQUEST_CONFIG, 3); + insertInCommandAndReplyMap(MAX31865::WRITE_LOW_THRESHOLD, 3); + insertInCommandAndReplyMap(MAX31865::REQUEST_LOW_THRESHOLD, 3); + insertInCommandAndReplyMap(MAX31865::WRITE_HIGH_THRESHOLD, 3); + insertInCommandAndReplyMap(MAX31865::REQUEST_HIGH_THRESHOLD, 3); + insertInCommandAndReplyMap(MAX31865::REQUEST_RTD, 3, &sensorDataset); + insertInCommandAndReplyMap(MAX31865::REQUEST_FAULT_BYTE, 3); + insertInCommandAndReplyMap(MAX31865::CLEAR_FAULT_BYTE, 3); +} + +ReturnValue_t Max31865PT1000Handler::scanForReply(const uint8_t *start, size_t remainingSize, + DeviceCommandId_t *foundId, size_t *foundLen) { + size_t rtdReplySize = 3; + size_t configReplySize = 2; + + if (remainingSize == rtdReplySize and internalState == InternalState::RUNNING) { + *foundId = MAX31865::REQUEST_RTD; + *foundLen = rtdReplySize; + return returnvalue::OK; + } + + if (remainingSize == 3) { + switch (internalState) { + case (InternalState::CONFIG_HIGH_THRESHOLD): { + *foundLen = 3; + *foundId = MAX31865::WRITE_HIGH_THRESHOLD; + commandExecuted = true; + return returnvalue::OK; + } + case (InternalState::REQUEST_HIGH_THRESHOLD): { + *foundLen = 3; + *foundId = MAX31865::REQUEST_HIGH_THRESHOLD; + return returnvalue::OK; + } + case (InternalState::CONFIG_LOW_THRESHOLD): { + *foundLen = 3; + *foundId = MAX31865::WRITE_LOW_THRESHOLD; + commandExecuted = true; + return returnvalue::OK; + } + case (InternalState::REQUEST_LOW_THRESHOLD): { + *foundLen = 3; + *foundId = MAX31865::REQUEST_LOW_THRESHOLD; + return returnvalue::OK; + } + default: { + sif::debug << "Max31865PT1000Handler::scanForReply: Unknown internal state" << std::endl; + return returnvalue::OK; + } + } + } + + if (remainingSize == configReplySize) { + if (internalState == InternalState::CONFIGURE) { + commandExecuted = true; + *foundLen = configReplySize; + *foundId = MAX31865::CONFIG_CMD; + } else if (internalState == InternalState::REQUEST_FAULT_BYTE) { + *foundId = MAX31865::REQUEST_FAULT_BYTE; + *foundLen = 2; + internalState = InternalState::RUNNING; + } else if (internalState == InternalState::CLEAR_FAULT_BYTE) { + *foundId = MAX31865::CLEAR_FAULT_BYTE; + *foundLen = 2; + if (getMode() == _MODE_START_UP) { + commandExecuted = true; + } else { + internalState = InternalState::RUNNING; + } + } else { + *foundId = MAX31865::REQUEST_CONFIG; + *foundLen = configReplySize; + } + } + + return returnvalue::OK; +} + +ReturnValue_t Max31865PT1000Handler::interpretDeviceReply(DeviceCommandId_t id, + const uint8_t *packet) { + switch (id) { + case (MAX31865::REQUEST_CONFIG): { + if (packet[1] != DEFAULT_CONFIG) { + if (warningSwitch) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + // it propably would be better if we at least try one restart.. + sif::warning << "Max31865PT1000Handler: 0x" << std::hex << this->getObjectId() + << ": Invalid configuration reply" << std::endl; +#else + sif::printWarning("Max31865PT1000Handler: %04x: Invalid configuration reply!\n", + this->getObjectId()); +#endif + warningSwitch = false; + } + return returnvalue::OK; + } + // set to true for invalid configs too for now. + if (internalState == InternalState::REQUEST_CONFIG) { + commandExecuted = true; + } else if (internalState == InternalState::RUNNING) { + // we should propably generate a telemetry with the config byte + // as payload here. + } + break; + } + case (MAX31865::REQUEST_LOW_THRESHOLD): { + uint16_t readLowThreshold = packet[1] << 8 | packet[2]; + if (readLowThreshold != LOW_THRESHOLD) { +#if FSFW_VERBOSE_LEVEL >= 1 +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "Max31865PT1000Handler::interpretDeviceReply: Object ID: " << std::hex + << this->getObjectId() << ": Missmatch between " + << "written and readback value of low threshold register" << std::endl; +#else + sif::printWarning( + "Max31865PT1000Handler::interpretDeviceReply: Missmatch between " + "written and readback value of low threshold register\n"); +#endif +#endif + } + commandExecuted = true; + break; + } + case (MAX31865::REQUEST_HIGH_THRESHOLD): { + uint16_t readHighThreshold = (packet[1] << 8) | packet[2]; + if (readHighThreshold != HIGH_THRESHOLD) { +#if FSFW_VERBOSE_LEVEL >= 1 +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "Max31865PT1000Handler::interpretDeviceReply: Object ID: " << std::hex + << this->getObjectId() << ": Missmatch between " + << "written and readback value of high threshold register" << std::endl; +#else + sif::printWarning( + "Max31865PT1000Handler::interpretDeviceReply: Missmatch between " + "written and readback value of high threshold register\n"); +#endif +#endif + } + commandExecuted = true; + break; + } + case (MAX31865::REQUEST_RTD): { + // first bit of LSB reply byte is the fault bit + bool faultBit = packet[2] & 0b0000'0001; + if (resetFaultBit) { + internalState = InternalState::CLEAR_FAULT_BYTE; + resetFaultBit = false; + } else if (shouldFaultStatusBeRequested(faultBit)) { + // Maybe we should attempt to restart it? + internalState = InternalState::REQUEST_FAULT_BYTE; + resetFaultBit = true; + } + + // RTD value consists of last seven bits of the LSB reply byte and + // the MSB reply byte + uint16_t adcCode = ((packet[1] << 8) | packet[2]) >> 1; + // Calculate resistance + float rtdValue = adcCode * EiveMax31855::RTD_RREF_PT1000 / INT16_MAX; + // calculate approximation + float approxTemp = adcCode / 32.0 - 256.0; + + if (debugMode) { +#if OBSW_VERBOSE_LEVEL >= 1 + if (debugDivider->checkAndIncrement()) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::info << "Max31865: " << std::setw(24) << std::left << locString << std::right + << " | R[Ohm] " << rtdValue << " Ohms | Approx T[C]: " << approxTemp + << std::endl; +#else + sif::printInfo("Max31685: Measured resistance is %f Ohms\n", rtdValue); + sif::printInfo("Approximated temperature is %f C\n", approxTemp); +#endif + } +#endif + } + + PoolReadGuard pg(&sensorDataset); + if (pg.getReadResult() != returnvalue::OK) { + // Configuration error +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "Max31865PT1000Handler::interpretDeviceReply: Object ID: " << std::hex + << this->getObjectId() << ": Error reading dataset!" << std::endl; +#else + sif::printWarning( + "Max31865PT1000Handler::interpretDeviceReply: " + "Error reading dataset!\n"); +#endif + return pg.getReadResult(); + } + + if (not sensorDataset.isValid()) { + sensorDataset.setValidity(true, false); + sensorDataset.rtdValue.setValid(true); + sensorDataset.temperatureCelcius.setValid(true); + } + + sensorDataset.rtdValue = rtdValue; + sensorDataset.temperatureCelcius = approxTemp; + break; + } + case (MAX31865::REQUEST_FAULT_BYTE): { + currentFaultStatus = packet[1]; + bool faultStatusChanged = (currentFaultStatus != lastFaultStatus); + // Spam protection + if (faultStatusChanged or + ((currentFaultStatus == lastFaultStatus) and (sameFaultStatusCounter < 3))) { + // TODO: Think about triggering an event here +#if OBSW_VERBOSE_LEVEL >= 1 +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "Max31865PT1000Handler::interpretDeviceReply: Object ID: " << std::hex + << this->getObjectId() << ": Fault byte is: 0b" + << std::bitset<8>(currentFaultStatus) << std::endl; +#else + sif::printWarning( + "Max31865PT1000Handler::interpretDeviceReply: Fault byte" + " is: 0b" BYTE_TO_BINARY_PATTERN "\n", + BYTE_TO_BINARY(faultByte)); +#endif +#endif + if (faultStatusChanged) { + sameFaultStatusCounter = 0; + } else { + sameFaultStatusCounter++; + } + } + if (faultStatusChanged) { + lastFaultStatus = currentFaultStatus; + } + + PoolReadGuard pg(&sensorDataset); + auto result = pg.getReadResult(); + if (result != returnvalue::OK) { + // Configuration error +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "Max31865PT1000Handler::interpretDeviceReply: Object ID: " << std::hex + << this->getObjectId() << ": Error reading dataset" << std::endl; +#else + sif::printWarning("Max31865PT1000Handler::interpretDeviceReply: Error reading dataset\n"); +#endif + return result; + } + if (faultStatusChanged) { + sensorDataset.lastErrorByte.setValid(true); + sensorDataset.lastErrorByte = lastFaultStatus; + } + sensorDataset.errorByte.setValid(true); + sensorDataset.errorByte = currentFaultStatus; + + if (currentFaultStatus != 0) { + sensorDataset.temperatureCelcius.setValid(false); + } + break; + } + default: + break; + } + return returnvalue::OK; +} + +uint32_t Max31865PT1000Handler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { + return 5000; +} + +ReturnValue_t Max31865PT1000Handler::getSwitches(const uint8_t **switches, + uint8_t *numberOfSwitches) { + return DeviceHandlerBase::NO_SWITCH; +} + +ReturnValue_t Max31865PT1000Handler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + using namespace MAX31865; + localDataPoolMap.emplace(static_cast(PoolIds::RTD_VALUE), new PoolEntry({0})); + localDataPoolMap.emplace(static_cast(PoolIds::TEMPERATURE_C), new PoolEntry({0})); + localDataPoolMap.emplace(static_cast(PoolIds::LAST_FAULT_BYTE), + new PoolEntry({0})); + localDataPoolMap.emplace(static_cast(PoolIds::FAULT_BYTE), new PoolEntry({0})); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(sensorDataset.getSid(), false, 30.0)); + return returnvalue::OK; +} + +void Max31865PT1000Handler::setInstantNormal(bool instantNormal) { + this->instantNormal = instantNormal; +} + +void Max31865PT1000Handler::modeChanged() { + if (getMode() == MODE_OFF) { + lastFaultStatus = 0; + currentFaultStatus = 0; + sameFaultStatusCounter = 0; + internalState = InternalState::NONE; + } +} + +void Max31865PT1000Handler::setDeviceInfo(uint8_t idx, std::string locString_) { + deviceIdx = idx; + locString = std::move(locString_); +} + +void Max31865PT1000Handler::setDebugMode(bool enable) { this->debugMode = enable; } + +bool Max31865PT1000Handler::shouldFaultStatusBeRequested(bool faultBit) { + if ((sameFaultStatusCounter < 3) and faultBit) { + return true; + } + return false; +} diff --git a/mission/tcs/Max31865PT1000Handler.h b/mission/tcs/Max31865PT1000Handler.h new file mode 100644 index 0000000..3c7eac2 --- /dev/null +++ b/mission/tcs/Max31865PT1000Handler.h @@ -0,0 +1,124 @@ +#ifndef MISSION_TCS_MAX31865PT1000HANDLER_H_ +#define MISSION_TCS_MAX31865PT1000HANDLER_H_ + +#include +#include +#include +#include + +#include +#include + +/** + * @brief Device Handler for the thermal sensors + * @details + * Documentation of devices: + * MAX31865 RTD converter: + * https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf + * Pt1000 platinum resistance thermometers OMEGA F2020-1000-1/3B: + * https://br.omega.com/omegaFiles/temperature/pdf/F1500_F2000_F4000.pdf + * + * The thermal sensor values are measured using the MAX31865 RTD converter IC + * which creates digital values from the measured resistance of the Pt1000 + * devices which can be read with the SPI interface. + * Refer to the SOURCE system schematic for the exact setup and number + * of devices. + * + * @author R. Mueller + * @ingroup devices + */ +class Max31865PT1000Handler : public DeviceHandlerBase { + public: + Max31865PT1000Handler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie); + virtual ~Max31865PT1000Handler(); + + void setDebugMode(bool enable); + + // Configuration in 8 digit code: + // 1. 1 for V_BIAS enabled, 0 for disabled + // 2. 1 for Auto-conversion, 0 for off + // 3. 1 for 1-shot enabled, 0 for disabled (self-clearing bit) + // 4. 1 for 3-wire enabled, 0 for disabled (two and four wired RTD) + // 5./6. Fault detection: 00 for no action, 01 for automatic delay, 1 + // 0 for run fault detection with manual delay, + // 11 for finish fault detection with manual delay + // 7. Fault status: Write 1 to reset fault status register (Bit is self cleared afterwards) + // 8. 1 for 50 Hz filter, 0 for 60 Hz filter (noise rejection filter) + static constexpr uint8_t DEFAULT_CONFIG = 0b11000001; + + void setInstantNormal(bool instantNormal); + void setDeviceInfo(uint8_t idx, std::string locString); + + /** + * Expected temperature range is -100 C and 100 C. + * If there are temperatures beyond this range there must be a fault. + * The threshold variables cause the MAX1385 to signal an error in case the measured resistance + * indicates a temperature higher than 100 C or lower than -100 C. + * Default settings of MAX13865 are 0xFFFF for the high threshold register and 0x0 for the + * low threshold register. + */ + static constexpr uint16_t HIGH_THRESHOLD = 11298; // = 100 C + static constexpr uint16_t LOW_THRESHOLD = 4902; // = -100 C + + static constexpr float RTD_RESISTANCE0_PT1000 = 1000.0; //!< Ohm + protected: + // DeviceHandlerBase abstract function implementation + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + void fillCommandAndReplyMap() override; + ReturnValue_t scanForReply(const uint8_t *start, size_t remainingSize, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t getSwitches(const uint8_t **switches, uint8_t *numberOfSwitches) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + void modeChanged() override; + bool shouldFaultStatusBeRequested(bool faultBit); + + private: + uint8_t switchId = 0; + bool instantNormal = false; + bool debugMode = false; + bool warningSwitch = true; + + enum class InternalState { + NONE, + WARMUP, + CONFIGURE, + CONFIG_HIGH_THRESHOLD, + REQUEST_HIGH_THRESHOLD, + CONFIG_LOW_THRESHOLD, + REQUEST_LOW_THRESHOLD, + REQUEST_CONFIG, + RUNNING, + REQUEST_FAULT_BYTE, + CLEAR_FAULT_BYTE + }; + + InternalState internalState = InternalState::NONE; + bool commandExecuted = false; + + bool resetFaultBit = false; + dur_millis_t startTime = 0; + uint8_t currentCfg = 0; + uint8_t currentFaultStatus = 0; + uint8_t lastFaultStatus = 0; + uint16_t sameFaultStatusCounter = 0; + std::string locString; + uint8_t deviceIdx = 0; + std::array commandBuffer{0}; + + MAX31865::PrimarySet sensorDataset; + sid_t sensorDatasetSid; + +#if OBSW_VERBOSE_LEVEL >= 1 + PeriodicOperationDivider *debugDivider; +#endif +}; + +#endif /* MISSION_TCS_MAX31865PT1000HANDLER_H_ */ diff --git a/mission/tcs/Tmp1075Definitions.h b/mission/tcs/Tmp1075Definitions.h new file mode 100644 index 0000000..345146e --- /dev/null +++ b/mission/tcs/Tmp1075Definitions.h @@ -0,0 +1,42 @@ +#ifndef MISSION_TCS_TMP1075DEFINITIONS_H_ +#define MISSION_TCS_TMP1075DEFINITIONS_H_ + +#include + +#include + +#include "fsfw/devicehandlers/DeviceHandlerIF.h" + +namespace TMP1075 { +static const uint8_t TEMP_REG_ADDR = 0x0; +static const uint8_t CFGR_ADDR = 0x1; + +/* Writing this information to the configuration register sets the tmp1075 + * to shutdown mode and starts a single temperature conversion */ +static const uint16_t ONE_SHOT_MODE = 0x8100; + +static const DeviceCommandId_t NONE = 0x0; // Set when no command is pending +static const DeviceCommandId_t GET_TEMP = 0x1; +static const DeviceCommandId_t START_ADC_CONVERSION = 0x2; + +static const uint8_t GET_TEMP_REPLY_SIZE = 2; +static const uint8_t CFGR_CMD_SIZE = 3; +static const uint8_t POINTER_REG_SIZE = 1; + +static const uint32_t TMP1075_DATA_SET_ID = GET_TEMP; + +static const uint8_t MAX_REPLY_LENGTH = GET_TEMP_REPLY_SIZE; + +enum Tmp1075PoolIds : lp_id_t { TEMPERATURE_C_TMP1075 }; + +class Tmp1075Dataset : public StaticLocalDataSet<2> { + public: + Tmp1075Dataset(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, TMP1075_DATA_SET_ID) {} + + Tmp1075Dataset(object_id_t objectId) : StaticLocalDataSet(sid_t(objectId, TMP1075_DATA_SET_ID)) {} + + lp_var_t temperatureCelcius = lp_var_t(sid.objectId, TEMPERATURE_C_TMP1075, this); +}; +} // namespace TMP1075 + +#endif /* MISSION_TCS_TMP1075DEFINITIONS_H_ */ diff --git a/mission/tcs/Tmp1075Handler.cpp b/mission/tcs/Tmp1075Handler.cpp new file mode 100644 index 0000000..7e8569b --- /dev/null +++ b/mission/tcs/Tmp1075Handler.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include + +Tmp1075Handler::Tmp1075Handler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie) + : DeviceHandlerBase(objectId, comIF, comCookie), dataset(this) { + if (comCookie == NULL) { + sif::error << "Tmp1075Handler: Invalid com cookie" << std::endl; + } +} + +Tmp1075Handler::~Tmp1075Handler() {} + +void Tmp1075Handler::doStartUp() { setMode(MODE_ON); } + +void Tmp1075Handler::doShutDown() { + communicationStep = CommunicationStep::START_ADC_CONVERSION; + PoolReadGuard pg(&dataset); + dataset.setValidity(false, true); + setMode(_MODE_POWER_DOWN); +} + +ReturnValue_t Tmp1075Handler::buildNormalDeviceCommand(DeviceCommandId_t *id) { + if (communicationStep == CommunicationStep::START_ADC_CONVERSION) { + *id = TMP1075::START_ADC_CONVERSION; + communicationStep = CommunicationStep::GET_TEMPERATURE; + return buildCommandFromCommand(*id, NULL, 0); + } else { + *id = TMP1075::GET_TEMP; + communicationStep = CommunicationStep::START_ADC_CONVERSION; + return buildCommandFromCommand(*id, NULL, 0); + } + return returnvalue::OK; +} + +ReturnValue_t Tmp1075Handler::buildTransitionDeviceCommand(DeviceCommandId_t *id) { + return NOTHING_TO_SEND; +} + +ReturnValue_t Tmp1075Handler::buildCommandFromCommand(DeviceCommandId_t deviceCommand, + const uint8_t *commandData, + size_t commandDataLen) { + switch (deviceCommand) { + case (TMP1075::START_ADC_CONVERSION): { + std::memset(cmdBuffer, 0, sizeof(cmdBuffer)); + prepareAdcConversionCommand(); + rawPacket = cmdBuffer; + rawPacketLen = TMP1075::CFGR_CMD_SIZE; + return returnvalue::OK; + } + case (TMP1075::GET_TEMP): { + std::memset(cmdBuffer, 0, sizeof(cmdBuffer)); + prepareGetTempCommand(); + rawPacket = cmdBuffer; + rawPacketLen = TMP1075::POINTER_REG_SIZE; + rememberCommandId = TMP1075::GET_TEMP; + return returnvalue::OK; + } + default: + return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED; + } + return returnvalue::FAILED; +} + +void Tmp1075Handler::fillCommandAndReplyMap() { + this->insertInCommandMap(TMP1075::START_ADC_CONVERSION); + this->insertInCommandAndReplyMap(TMP1075::GET_TEMP, 1, &dataset, TMP1075::GET_TEMP_REPLY_SIZE); +} + +ReturnValue_t Tmp1075Handler::scanForReply(const uint8_t *start, size_t remainingSize, + DeviceCommandId_t *foundId, size_t *foundLen) { + switch (rememberCommandId) { + case (TMP1075::GET_TEMP): + *foundId = TMP1075::GET_TEMP; + *foundLen = TMP1075::GET_TEMP_REPLY_SIZE; + rememberCommandId = TMP1075::NONE; + break; + default: + return IGNORE_REPLY_DATA; + } + return returnvalue::OK; +} + +ReturnValue_t Tmp1075Handler::interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) { + switch (id) { + case TMP1075::GET_TEMP: { + // Convert 12 bit MSB first raw temperature to 16 bit first. + int16_t tempValueRaw = static_cast((packet[0] << 8) | packet[1]) >> 4; + // Sign extension to 16 bits: If the sign bit is set, fill up with ones on the left. + tempValueRaw = (packet[0] & 0x80) ? (tempValueRaw | 0xF000) : tempValueRaw; + // 0.0625 is the sensor sensitivity. + float tempValue = ((static_cast(tempValueRaw)) * 0.0625); +#if OBSW_DEBUG_TMP1075 == 1 + sif::info << "Tmp1075 with object id: 0x" << std::hex << getObjectId() + << ": Temperature: " << tempValue << " °C" << std::endl; +#endif + ReturnValue_t result = dataset.read(); + if (result == returnvalue::OK) { + dataset.temperatureCelcius = tempValue; + dataset.setValidity(true, true); + dataset.commit(); + } else { + dataset.setValidity(false, true); + } + break; + } + + default: { + return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY; + } + } + return returnvalue::OK; +} + +void Tmp1075Handler::setNormalDatapoolEntriesInvalid() {} + +void Tmp1075Handler::prepareAdcConversionCommand() { + cmdBuffer[0] = TMP1075::CFGR_ADDR; + cmdBuffer[1] = TMP1075::ONE_SHOT_MODE >> 8; + cmdBuffer[2] = TMP1075::ONE_SHOT_MODE & 0xFF; +} + +void Tmp1075Handler::prepareGetTempCommand() { cmdBuffer[0] = TMP1075::TEMP_REG_ADDR; } + +uint32_t Tmp1075Handler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) { return 500; } + +ReturnValue_t Tmp1075Handler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) { + localDataPoolMap.emplace(TMP1075::TEMPERATURE_C_TMP1075, new PoolEntry({0.0})); + poolManager.subscribeForRegularPeriodicPacket( + subdp::RegularHkPeriodicParams(dataset.getSid(), false, 30.0)); + return returnvalue::OK; +} + +ReturnValue_t Tmp1075Handler::setHealth(HealthState health) { + if (health != FAULTY and health != PERMANENT_FAULTY and health != HEALTHY and + health != EXTERNAL_CONTROL) { + return returnvalue::FAILED; + } + // Required because we do not have an assembly. + if (health == FAULTY or health == PERMANENT_FAULTY) { + setMode(_MODE_SHUT_DOWN); + } + return DeviceHandlerBase::setHealth(health); +} diff --git a/mission/tcs/Tmp1075Handler.h b/mission/tcs/Tmp1075Handler.h new file mode 100644 index 0000000..4badca3 --- /dev/null +++ b/mission/tcs/Tmp1075Handler.h @@ -0,0 +1,61 @@ +#ifndef MISSION_TCS_TMP1075HANDLER_H_ +#define MISSION_TCS_TMP1075HANDLER_H_ + +#include +#include + +/** + * @brief This is the device handler class for the tmp1075 temperature sensor. + * + * @details The tmp1075 communicates via I2C. After the address byte and the + * read and write bit, a pointer address must be sent to the device. + * This pointer address is stored in the pointer register of the + * tmp1075 and defines to which address following data bytes will + * be written or from which address data will be read. + * + * @author J. Meier + */ +class Tmp1075Handler : public DeviceHandlerBase { + public: + Tmp1075Handler(object_id_t objectId, object_id_t comIF, CookieIF *comCookie); + virtual ~Tmp1075Handler(); + + protected: + void doStartUp() override; + void doShutDown() override; + ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; + ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) override; + void fillCommandAndReplyMap() override; + ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand, const uint8_t *commandData, + size_t commandDataLen) override; + ReturnValue_t scanForReply(const uint8_t *start, size_t remainingSize, DeviceCommandId_t *foundId, + size_t *foundLen) override; + ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) override; + void setNormalDatapoolEntriesInvalid() override; + uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) override; + ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap, + LocalDataPoolManager &poolManager) override; + ReturnValue_t setHealth(HealthState health) override; + + private: + /** + * @brief Function fills cmdBuffer with command to start the adc + * conversion for a new temperature value. + */ + void prepareAdcConversionCommand(); + + void prepareGetTempCommand(); + + enum class CommunicationStep { START_ADC_CONVERSION, GET_TEMPERATURE }; + + TMP1075::Tmp1075Dataset dataset; + + static const uint8_t MAX_CMD_LEN = 3; + + uint8_t rememberRequestedSize = 0; + uint8_t rememberCommandId = TMP1075::NONE; + uint8_t cmdBuffer[MAX_CMD_LEN]; + CommunicationStep communicationStep = CommunicationStep::START_ADC_CONVERSION; +}; + +#endif /* MISSION_TCS_TMP1075HANDLER_H_ */ diff --git a/mission/tcs/defs.h b/mission/tcs/defs.h new file mode 100644 index 0000000..e284357 --- /dev/null +++ b/mission/tcs/defs.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +namespace heater { + +enum Switch : uint8_t { + HEATER_0_PLOC_PROC_BRD, + HEATER_1_PCDU_PDU, + HEATER_2_ACS_BRD, + HEATER_3_OBC_BRD, + HEATER_4_CAMERA, + HEATER_5_STR, + HEATER_6_DRO, + HEATER_7_S_BAND, + NUMBER_OF_SWITCHES = 8, + HEATER_NONE = 0xff +}; + +enum SwitchState : uint8_t { ON = 1, OFF = 0 }; + +} // namespace heater + +namespace tcs { + +extern std::atomic_bool TCS_BOARD_SHORTLY_UNAVAILABLE; + +} diff --git a/mission/tcs/max1227.cpp b/mission/tcs/max1227.cpp new file mode 100644 index 0000000..8b49bb5 --- /dev/null +++ b/mission/tcs/max1227.cpp @@ -0,0 +1,31 @@ +#include + +#include + +void max1227::prepareExternallyClockedSingleChannelRead(uint8_t *spiBuf, uint8_t channel, + size_t &sz) { + spiBuf[0] = buildConvByte(ScanModes::N_ONCE, channel, false); + spiBuf[1] = 0x00; + spiBuf[2] = 0x00; + sz = 3; +} + +void max1227::prepareExternallyClockedRead0ToN(uint8_t *spiBuf, uint8_t n, size_t &sz) { + for (uint8_t idx = 0; idx <= n; idx++) { + spiBuf[idx * 2] = buildConvByte(ScanModes::N_ONCE, idx, false); + spiBuf[idx * 2 + 1] = 0x00; + } + spiBuf[(n + 1) * 2] = 0x00; + sz += (n + 1) * 2 + 1; +} + +void max1227::prepareExternallyClockedTemperatureRead(uint8_t *spiBuf, size_t &sz) { + spiBuf[0] = buildConvByte(ScanModes::N_ONCE, 0, true); + std::memset(spiBuf + 1, 0, 24); + sz += 25; +} + +float max1227::getTemperature(uint16_t raw) { + float temp = static_cast((-2048 * (raw >> 11)) + (raw & 0x7FF)) * 0.125; + return temp; +} diff --git a/mission/tcs/max1227.h b/mission/tcs/max1227.h new file mode 100644 index 0000000..5a0e768 --- /dev/null +++ b/mission/tcs/max1227.h @@ -0,0 +1,84 @@ +#ifndef MISSION_TCS_MAX1227_H_ +#define MISSION_TCS_MAX1227_H_ + +#include +#include + +namespace max1227 { + +enum ScanModes : uint8_t { + CHANNELS_0_TO_N = 0b00, + CHANNEL_N_TO_HIGHEST = 0b01, + N_REPEATEDLY = 0b10, + N_ONCE = 0b11 +}; + +enum ClkSel : uint8_t { + INT_CONV_INT_TIMED_CNVST_AS_CNVST = 0b00, + INT_CONV_EXT_TIMED_CNVST = 0b01, + // Default mode upon power-up + INT_CONV_INT_TIMED_CNVST_AS_AIN = 0b10, + // Use external SPI clock for conversion and timing + EXT_CONV_EXT_TIMED = 0b11 +}; + +enum RefSel : uint8_t { + INT_REF_WITH_WAKEUP = 0b00, + // No wakeup delay needed + EXT_REF_SINGLE_ENDED = 0b01, + INT_REF_NO_WAKEUP = 0b10, + // No wakeup delay needed + EXT_REF_DIFFERENTIAL = 0b11 +}; + +enum DiffSel : uint8_t { + NONE_0 = 0b00, + NONE_1 = 0b01, + // One unipolar config byte follows the setup byte + UNIPOLAR_CFG = 0b10, + // One bipolar config byte follows the setup byte + BIPOLAR_CFG = 0b11 +}; + +constexpr uint8_t buildResetByte(bool fifoOnly) { return (1 << 4) | (fifoOnly << 3); } + +constexpr uint8_t buildConvByte(ScanModes scanMode, uint8_t channel, bool readTemp) { + return (1 << 7) | (channel << 3) | (scanMode << 1) | readTemp; +} + +constexpr uint8_t buildSetupByte(ClkSel clkSel, RefSel refSel, DiffSel diffSel) { + return (1 << 6) | (clkSel << 4) | (refSel << 2) | diffSel; +} + +/** + * If there is a wakeup delay, there needs to be a 65 us delay between sending + * the first byte (conversion byte) and the the rest of the SPI buffer. + * The raw ADC value will be located in the first and second reply byte. + * @param spiBuf + * @param n + * @param sz + */ +void prepareExternallyClockedSingleChannelRead(uint8_t* spiBuf, uint8_t channel, size_t& sz); + +/** + * If there is a wakeup delay, there needs to be a 65 us delay between sending + * the first byte (first conversion byte) the the rest of the SPI buffer. + * @param spiBuf + * @param n Channel number. Example: If the ADC has 6 channels, n will be 5 + * @param sz Will be incremented by amount which should be sent + */ +void prepareExternallyClockedRead0ToN(uint8_t* spiBuf, uint8_t n, size_t& sz); + +/** + * Prepare an externally clocked temperature read. 25 bytes have to be sent + * and the raw temperature value will appear on the last 2 bytes of the reply. + * @param spiBuf + * @param sz Will be incremented by amount which should be sent + */ +void prepareExternallyClockedTemperatureRead(uint8_t* spiBuf, size_t& sz); + +float getTemperature(uint16_t raw); + +} // namespace max1227 + +#endif /* MISSION_TCS_MAX1227_H_ */ diff --git a/mission/tmtc/CMakeLists.txt b/mission/tmtc/CMakeLists.txt new file mode 100644 index 0000000..a8388d8 --- /dev/null +++ b/mission/tmtc/CMakeLists.txt @@ -0,0 +1,13 @@ +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE PersistentTmStoreWithTmQueue.cpp + TmFunnelHandler.cpp + TmFunnelBase.cpp + CfdpTmFunnel.cpp + tmFilters.cpp + PusLiveDemux.cpp + PusPacketFilter.cpp + PusTmRouteByFilterHelper.cpp + Service15TmStorage.cpp + PersistentTmStore.cpp + PusTmFunnel.cpp) diff --git a/mission/tmtc/CfdpTmFunnel.cpp b/mission/tmtc/CfdpTmFunnel.cpp new file mode 100644 index 0000000..daf5392 --- /dev/null +++ b/mission/tmtc/CfdpTmFunnel.cpp @@ -0,0 +1,118 @@ +#include "CfdpTmFunnel.h" + +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/tmtcpacket/ccsds/SpacePacketCreator.h" +#include "fsfw/tmtcservices/TmTcMessage.h" +#include "mission/sysDefs.h" + +CfdpTmFunnel::CfdpTmFunnel(TmFunnelBase::FunnelCfg cfg, + std::optional fileStoreDest, + StorageManagerIF& ramToFileStore, uint16_t cfdpInCcsdsApid) + : TmFunnelBase(cfg), + fileStoreDest(fileStoreDest), + ramToFileStore(ramToFileStore), + cfdpInCcsdsApid(cfdpInCcsdsApid) {} + +const char* CfdpTmFunnel::getName() const { return "CFDP TM Funnel"; } + +ReturnValue_t CfdpTmFunnel::performOperation(uint8_t) { + TmTcMessage currentMessage; + ReturnValue_t status; + unsigned int count = 0; + if (saveSequenceCount) { + status = saveSequenceCountToFile(); + if (status != returnvalue::OK) { + sif::error << "CfdpTmFunnel: Storing sequence count to file has failed" << std::endl; + } + saveSequenceCount = false; + } + status = tmQueue->receiveMessage(¤tMessage); + uint32_t handledPackets = 0; + while (status == returnvalue::OK) { + handledPackets++; + status = handlePacket(currentMessage); + if (status != returnvalue::OK) { + sif::warning << "CfdpTmFunnel packet handling failed" << std::endl; + break; + } + count++; + if (count == 500) { + sif::error << "CfdpTmFunnel: Possible message storm detected" << std::endl; + break; + } + status = tmQueue->receiveMessage(¤tMessage); + } + if (handledPackets > 0) { + // Very useful for profiling and debugging + // sif::debug << "CfdpFunnel: Handled " << handledPackets << " packets in one cycle" << + // std::endl; + } + + if (status == MessageQueueIF::EMPTY) { + return returnvalue::OK; + } + return status; +} + +ReturnValue_t CfdpTmFunnel::initialize() { return returnvalue::OK; } + +ReturnValue_t CfdpTmFunnel::handlePacket(TmTcMessage& msg) { + const uint8_t* cfdpPacket = nullptr; + size_t cfdpPacketLen = 0; + ReturnValue_t result = tmStore.getData(msg.getStorageId(), &cfdpPacket, &cfdpPacketLen); + if (result != returnvalue::OK) { + return result; + } + auto spacePacketHeader = + SpacePacketCreator(ccsds::PacketType::TM, false, cfdpInCcsdsApid, + ccsds::SequenceFlags::UNSEGMENTED, sourceSequenceCount++, 0); + sourceSequenceCount = sourceSequenceCount % ccsds::LIMIT_SEQUENCE_COUNT; + spacePacketHeader.setCcsdsLenFromTotalDataFieldLen(cfdpPacketLen); + uint8_t* newPacketData = nullptr; + store_address_t newStoreId{}; + result = + tmStore.getFreeElement(&newStoreId, spacePacketHeader.getFullPacketLen(), &newPacketData); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "CfdpTmFunnel::handlePacket: Error getting TM store element of size " + << spacePacketHeader.getFullPacketLen() << std::endl; +#endif + return result; + } + + size_t packetLen = 0; + uint8_t* serPtr = newPacketData; + result = spacePacketHeader.serializeBe(&serPtr, &packetLen, spacePacketHeader.getFullPacketLen()); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "CfdpTmFunnel::handlePacket: Error serializing packet" << std::endl; +#endif + return result; + } + std::memcpy(serPtr, cfdpPacket, cfdpPacketLen); + packetLen += cfdpPacketLen; + + // Delete old packet + tmStore.deleteData(msg.getStorageId()); + msg.setStorageId(newStoreId); + store_address_t origStoreId = newStoreId; + + if (fileStoreDest.has_value()) { + store_address_t storageId; + result = ramToFileStore.addData(&storageId, newPacketData, packetLen); + TmTcMessage fileMsg(storageId); + if (result == returnvalue::OK) { + tmQueue->sendMessage(fileStoreDest.value(), &fileMsg); + } else if (result == StorageManagerIF::DATA_STORAGE_FULL) { + sif::error << "CfdpTmFunnel::handlePacket: RAM to File Store too full to create data copy" + << std::endl; + } + } + return demultiplexLivePackets(origStoreId, newPacketData, packetLen); +} + +uint32_t CfdpTmFunnel::addLiveDestination(const char* name, + const AcceptsTelemetryIF& downlinkDestination, + uint8_t vcid) { + return TmFunnelBase::addLiveDestination(name, downlinkDestination, vcid); +} diff --git a/mission/tmtc/CfdpTmFunnel.h b/mission/tmtc/CfdpTmFunnel.h new file mode 100644 index 0000000..b3f0948 --- /dev/null +++ b/mission/tmtc/CfdpTmFunnel.h @@ -0,0 +1,31 @@ +#ifndef FSFW_EXAMPLE_COMMON_CFDPTMFUNNEL_H +#define FSFW_EXAMPLE_COMMON_CFDPTMFUNNEL_H + +#include + +#include +#include + +#include "fsfw/objectmanager/SystemObject.h" +#include "fsfw/storagemanager/StorageManagerIF.h" +#include "fsfw/tmtcservices/AcceptsTelemetryIF.h" +#include "fsfw/tmtcservices/TmTcMessage.h" + +class CfdpTmFunnel : public TmFunnelBase { + public: + CfdpTmFunnel(TmFunnelBase::FunnelCfg cfg, std::optional fileStoreDest, + StorageManagerIF& ramToFileStore, uint16_t cfdpInCcsdsApid); + [[nodiscard]] const char* getName() const override; + uint32_t addLiveDestination(const char* name, const AcceptsTelemetryIF& downlinkDestination, + uint8_t vcid = 0) override; + ReturnValue_t performOperation(uint8_t opCode); + ReturnValue_t initialize() override; + + private: + ReturnValue_t handlePacket(TmTcMessage& msg); + + std::optional fileStoreDest; + StorageManagerIF& ramToFileStore; + uint16_t cfdpInCcsdsApid; +}; +#endif // FSFW_EXAMPLE_COMMON_CFDPTMFUNNEL_H diff --git a/mission/tmtc/DirectTmSinkIF.h b/mission/tmtc/DirectTmSinkIF.h new file mode 100644 index 0000000..546b293 --- /dev/null +++ b/mission/tmtc/DirectTmSinkIF.h @@ -0,0 +1,58 @@ +#ifndef MISSION_TMTC_DIRECTTMSINKIF_H_ +#define MISSION_TMTC_DIRECTTMSINKIF_H_ + +#include + +#include "eive/resultClassIds.h" +#include "fsfw/retval.h" + +class DirectTmSinkIF { + public: + virtual ~DirectTmSinkIF() = default; + + static constexpr uint8_t CLASS_ID = CLASS_ID::TM_SINK; + + static constexpr ReturnValue_t IS_BUSY = returnvalue::makeCode(CLASS_ID, 0); + static constexpr ReturnValue_t PARTIALLY_WRITTEN = returnvalue::makeCode(CLASS_ID, 1); + static constexpr ReturnValue_t NO_WRITE_ACTIVE = returnvalue::makeCode(CLASS_ID, 2); + static constexpr ReturnValue_t TIMEOUT = returnvalue::makeCode(CLASS_ID, 3); + + /** + * @brief Implements the functionality to write to a TM sink directly. + * + * The write might not be completed immediately! If PARTIALLY_WRITTEN is returned, the user + * should poll the ready for packet status bit and call @advanceWrite continuously until + * the transfer is completed. + * + * @param data Pointer to buffer holding the data to write + * @param size Number of bytes to write + * @param writtenSize Size written during write call. + * @return returnvalue::OK on full write success, IS_BUSY if a previous write transfer has not + * been completed yet or the PAPB interface is not ready for a packet, PARTIALLY_WRITTEN + * if some bytes were written, but the transfer has not been completed yet. + */ + virtual ReturnValue_t write(const uint8_t* data, size_t size, size_t& writtenSize) = 0; + + /** + * Advances a active file transfer. + * @param writtenSize + * @return returnvalue::OK if the packet write process is complete, PARTIALLY_WRITTEN if + * some bytes were written but the transfer is not complete yet. + * NO_WRITE_ACTIVE if this is called without a valid previous write call. + */ + virtual ReturnValue_t advanceWrite(size_t& writtenSize) = 0; + + /** + * Is busy, so no write operation can not be started and write advancement + * is not possible. + * @return + */ + virtual bool isBusy() const = 0; + /** + * The PAPB interface is currently busy writing a packet and a new packet can not be written yet. + * @return + */ + virtual bool writeActive() const = 0; +}; + +#endif /* MISSION_TMTC_DIRECTTMSINKIF_H_ */ diff --git a/mission/tmtc/PersistentTmStore.cpp b/mission/tmtc/PersistentTmStore.cpp new file mode 100644 index 0000000..099f644 --- /dev/null +++ b/mission/tmtc/PersistentTmStore.cpp @@ -0,0 +1,500 @@ +#include "PersistentTmStore.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "eive/definitions.h" +#include "fsfw/ipc/CommandMessage.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/tmstorage/TmStoreMessage.h" +#include "mission/persistentTmStoreDefs.h" + +using namespace returnvalue; + +static constexpr bool DEBUG_DUMPS = false; + +PersistentTmStore::PersistentTmStore(PersistentTmStoreArgs args) + : SystemObject(args.objectId), + tmStore(args.tmStore), + baseDir(args.baseDir), + baseName(std::move(args.baseName)), + sdcMan(args.sdcMan) { + tcQueue = QueueFactory::instance()->createMessageQueue(); + calcDiffSeconds(args.intervalUnit, args.intervalCount); +} + +ReturnValue_t PersistentTmStore::cancelDump() { + state = State::IDLE; + return returnvalue::OK; +} + +ReturnValue_t PersistentTmStore::buildDumpSet(uint32_t fromUnixSeconds, uint32_t upToUnixSeconds) { + using namespace std::filesystem; + std::error_code e; + dumpParams.orderedDumpFilestamps.clear(); + for (auto const& fileOrDir : directory_iterator(basePath)) { + if (not fileOrDir.is_regular_file(e)) { + continue; + } + dumpParams.fileSize = std::filesystem::file_size(fileOrDir.path(), e); + if (e) { + sif::error << "PersistentTmStore: Could not retrieve file size: " << e.message() << std::endl; + continue; + } + + // File empty or can't even read CCSDS header. + if (dumpParams.fileSize <= 6) { + continue; + } + if (dumpParams.fileSize > fileBuf.size()) { + sif::error << "PersistentTmStore: File too large, is deleted" << std::endl; + triggerEvent(persTmStore::FILE_TOO_LARGE, dumpParams.fileSize, fileBuf.size()); + std::filesystem::remove(fileOrDir.path(), e); + continue; + } + const path& file = fileOrDir.path(); + struct tm fileTime{}; + if (pathToTime(file, fileTime) != returnvalue::OK) { + sif::error << "Time extraction for file " << file << "failed" << std::endl; + continue; + } + auto fileEpoch = static_cast(timegm(&fileTime)); + if ((fileEpoch > dumpParams.fromUnixTime) and + (fileEpoch + rolloverDiffSeconds <= dumpParams.untilUnixTime)) { + std::ifstream ifile(file, std::ios::binary); + if (ifile.bad()) { + sif::error << "PersistentTmStore: File is bad" << std::endl; + // TODO: Consider deleting file here? + continue; + } + + if (DEBUG_DUMPS) { + sif::debug << "Inserting file " << fileOrDir.path() << std::endl; + } + DumpIndex dumpIndex; + dumpIndex.epoch = fileEpoch; + // Multiple files for the same time are supported via a special suffix. We simply count the + // number of copies and later try to dump the same number of files with the additional + // suffixes + auto iter = dumpParams.orderedDumpFilestamps.find(dumpIndex); + if (iter != dumpParams.orderedDumpFilestamps.end()) { + dumpIndex.epoch = iter->epoch; + dumpIndex.additionalFiles = iter->additionalFiles + 1; + dumpParams.orderedDumpFilestamps.erase(dumpIndex); + } else { + dumpIndex.additionalFiles = 0; + } + dumpParams.orderedDumpFilestamps.emplace(dumpIndex); + } + } + return returnvalue::OK; +} + +std::optional PersistentTmStore::extractSuffix(const std::string& pathStr) { + std::string numberStr; + // Find the position of the dot at the end of the file path + size_t dotPos = pathStr.find_last_of('.'); + if ((dotPos < pathStr.length()) and not std::isdigit(pathStr[dotPos + 1])) { + return std::nullopt; + } + // Extract the substring after the dot + numberStr = pathStr.substr(dotPos + 1); + std::optional number; + try { + number = std::stoi(numberStr); + if (number.value() > std::numeric_limits::max()) { + return std::nullopt; + } + + } catch (std::invalid_argument& exception) { + sif::error << "PersistentTmStore::extractSuffix: Exception " << exception.what() + << ", invald input string: " << numberStr << std::endl; + } + return number; +} + +ReturnValue_t PersistentTmStore::assignAndOrCreateMostRecentFile() { + if (not activeFile.has_value()) { + return createMostRecentFile(std::nullopt); + } + return returnvalue::OK; +} + +ReturnValue_t PersistentTmStore::handleCommandQueue(StorageManagerIF& ipcStore, + Command_t& execCmd) { + execCmd = CommandMessageIF::CMD_NONE; + CommandMessage cmdMessage; + ReturnValue_t result = tcQueue->receiveMessage(&cmdMessage); + if (result != returnvalue::OK) { + return result; + } + if (cmdMessage.getMessageType() == messagetypes::TM_STORE) { + Command_t cmd = cmdMessage.getCommand(); + if (cmd == TmStoreMessage::DELETE_STORE_CONTENT_TIME) { + result = handleDeletionCmd(ipcStore, cmdMessage); + execCmd = cmd; + } else if (cmd == TmStoreMessage::DOWNLINK_STORE_CONTENT_TIME) { + result = handleDumpCmd(ipcStore, cmdMessage); + execCmd = cmd; + } + } + return result; +} + +ReturnValue_t PersistentTmStore::handleDeletionCmd(StorageManagerIF& ipcStore, + CommandMessage& cmdMessage) { + Clock::getClock_timeval(¤tTv); + store_address_t storeId = TmStoreMessage::getStoreId(&cmdMessage); + auto accessor = ipcStore.getData(storeId); + size_t size = accessor.second.size(); + if (size < 4) { + return returnvalue::FAILED; + } + const uint8_t* data = accessor.second.data(); + uint32_t deleteUpToUnixSeconds = 0; + if (size == 4) { + SerializeAdapter::deSerialize(&deleteUpToUnixSeconds, &data, &size, + SerializeIF::Endianness::NETWORK); + deleteUpTo(deleteUpToUnixSeconds); + } else if (size == 8) { + uint32_t deleteFromUnixSeconds = 0; + SerializeAdapter::deSerialize(&deleteFromUnixSeconds, &data, &size, + SerializeIF::Endianness::NETWORK); + SerializeAdapter::deSerialize(&deleteUpToUnixSeconds, &data, &size, + SerializeIF::Endianness::NETWORK); + deleteFromUpTo(deleteFromUnixSeconds, deleteUpToUnixSeconds); + } else { + sif::warning << "PersistentTmStore: Unknown deletion time specification" << std::endl; + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t PersistentTmStore::handleDumpCmd(StorageManagerIF& ipcStore, + CommandMessage& cmdMessage) { + Clock::getClock_timeval(¤tTv); + store_address_t storeId = TmStoreMessage::getStoreId(&cmdMessage); + auto accessor = ipcStore.getData(storeId); + if (accessor.second.size() < 8) { + return returnvalue::FAILED; + } + uint32_t dumpFromUnixSeconds = 0; + uint32_t dumpUntilUnixSeconds = 0; + size_t size = 8; + SerializeAdapter::deSerialize(&dumpFromUnixSeconds, accessor.second.data(), &size, + SerializeIF::Endianness::NETWORK); + SerializeAdapter::deSerialize(&dumpUntilUnixSeconds, accessor.second.data() + 4, &size, + SerializeIF::Endianness::NETWORK); + ReturnValue_t result = startDumpFromUpTo(dumpFromUnixSeconds, dumpUntilUnixSeconds); + if (result == BUSY_DUMPING) { + triggerEvent(persTmStore::BUSY_DUMPING_EVENT); + return result; + } + return returnvalue::OK; +} + +ReturnValue_t PersistentTmStore::startDumpFrom(uint32_t fromUnixSeconds) { + return startDumpFromUpTo(fromUnixSeconds, currentTv.tv_sec); +} + +ReturnValue_t PersistentTmStore::storePacket(PusTmReader& reader) { + using namespace std::filesystem; + if (baseDirUninitialized) { + updateBaseDir(); + } + Clock::getClock_timeval(¤tTv); + // It is assumed here that the filesystem is usable. + if (not activeFile.has_value()) { + ReturnValue_t result = assignAndOrCreateMostRecentFile(); + if (result != returnvalue::OK) { + return result; + } + } + + bool createNewFile = false; + std::optional suffix = std::nullopt; + std::error_code e; + size_t fileSize = file_size(activeFile.value(), e); + if (e) { + sif::error << "PersistentTmStore: Could not retrieve file size, " + "error " + << e.message() << std::endl; + return returnvalue::FAILED; + } + if (currentTv.tv_sec > activeFileTv.tv_sec + static_cast(rolloverDiffSeconds)) { + createNewFile = true; + currentSameSecNumber = 0; + } else if (fileSize + reader.getFullPacketLen() > fileBuf.size()) { + createNewFile = true; + if (currentSameSecNumber >= MAX_FILES_IN_ONE_SECOND) { + currentSameSecNumber = 0; + } + if (currentTv.tv_sec == activeFileTv.tv_sec) { + suffix = currentSameSecNumber++; + } else { + currentSameSecNumber = 0; + } + } + if (createNewFile) { + createMostRecentFile(suffix); + } + + // Rollover conditions were handled, write to file now + std::ofstream of(activeFile.value(), std::ios::app | std::ios::binary); + of.write(reinterpret_cast(reader.getFullData()), + static_cast(reader.getFullPacketLen())); + return returnvalue::OK; +} + +MessageQueueId_t PersistentTmStore::getCommandQueue() const { return tcQueue->getId(); } + +void PersistentTmStore::calcDiffSeconds(RolloverInterval intervalUnit, uint32_t intervalCount) { + if (intervalUnit == RolloverInterval::MINUTELY) { + rolloverDiffSeconds = 60 * intervalCount; + } else if (intervalUnit == RolloverInterval::HOURLY) { + rolloverDiffSeconds = 60 * 60 * intervalCount; + } else if (intervalUnit == RolloverInterval::DAILY) { + rolloverDiffSeconds = 60 * 60 * 24 * intervalCount; + } +} + +bool PersistentTmStore::updateBaseDir() { + using namespace std::filesystem; + const char* currentPrefix = sdcMan.getCurrentMountPrefix(); + if (currentPrefix == nullptr) { + return false; + } + basePath = path(currentPrefix) / baseDir / baseName; + std::error_code e; + if (not exists(basePath, e)) { + create_directories(basePath); + } + // Each file will have the base name as a prefix again + path preparedFullFilePath = basePath / baseName; + basePathSize = preparedFullFilePath.string().length(); + std::memcpy(filePathBuf.data(), preparedFullFilePath.c_str(), basePathSize); + filePathBuf[basePathSize] = '_'; + basePathSize += 1; + baseDirUninitialized = false; + return true; +} + +void PersistentTmStore::deleteUpTo(uint32_t unixSeconds) { deleteFromUpTo(0, unixSeconds); } + +void PersistentTmStore::deleteFromUpTo(uint32_t startUnixTime, uint32_t endUnixTime) { + using namespace std::filesystem; + for (auto const& file : directory_iterator(basePath)) { + if (file.is_directory() or (activeFile.has_value() and (activeFile.value() == file.path()))) { + continue; + } + // Convert file time to the UNIX epoch + struct tm fileTime{}; + if (pathToTime(file.path(), fileTime) != returnvalue::OK) { + sif::error << "Time extraction for " << file << " failed" << std::endl; + continue; + } + time_t fileEpoch = timegm(&fileTime); + if (fileEpoch + rolloverDiffSeconds < endUnixTime and + static_cast(fileEpoch) >= startUnixTime) { + std::error_code e; + std::filesystem::remove(file.path(), e); + } + } +} + +ReturnValue_t PersistentTmStore::startDumpFromUpTo(uint32_t fromUnixSeconds, + uint32_t upToUnixSeconds) { + using namespace std::filesystem; + if (state == State::DUMPING) { + return returnvalue::FAILED; + } + auto dirIter = directory_iterator(basePath); + // Directory empty case. + if (dirIter == directory_iterator()) { + return returnvalue::FAILED; + } + dumpParams.fromUnixTime = fromUnixSeconds; + dumpParams.untilUnixTime = upToUnixSeconds; + buildDumpSet(fromUnixSeconds, upToUnixSeconds); + // No files in time window found. + if (dumpParams.orderedDumpFilestamps.empty()) { + return DUMP_DONE; + } + dumpParams.dumpIter = dumpParams.orderedDumpFilestamps.begin(); + dumpParams.currentSameFileIdx = std::nullopt; + state = State::DUMPING; + return loadNextDumpFile(); +} + +ReturnValue_t PersistentTmStore::loadNextDumpFile() { + using namespace std::filesystem; + dumpParams.currentSize = 0; + std::error_code e; + // Handle iteration, which does not necessarily have to increment the set iterator + // if there are multiple files for a given timestamp. + auto handleIteration = [&](DumpIndex& dumpIndex) { + if (dumpIndex.additionalFiles > 0) { + if (not dumpParams.currentSameFileIdx.has_value()) { + // Initialize the file index and stay on same file + dumpParams.currentSameFileIdx = 0; + return; + } else if (dumpParams.currentSameFileIdx.value() < dumpIndex.additionalFiles - 1) { + dumpParams.currentSameFileIdx = dumpParams.currentSameFileIdx.value() + 1; + return; + } + } + // File will change, reset this field for correct state-keeping. + dumpParams.currentSameFileIdx = std::nullopt; + dumpParams.currentFileUnixStamp = dumpParams.dumpIter->epoch; + // Increment iterator for next cycle. + dumpParams.dumpIter++; + }; + while (dumpParams.dumpIter != dumpParams.orderedDumpFilestamps.end()) { + DumpIndex dumpIndex = *dumpParams.dumpIter; + timeval tv{}; + tv.tv_sec = dumpIndex.epoch; + size_t fullPathLength = 0; + createFileName(tv, dumpParams.currentSameFileIdx, fullPathLength); + dumpParams.currentFile = + path(std::string(reinterpret_cast(filePathBuf.data()), fullPathLength)); + if (DEBUG_DUMPS) { + sif::debug << baseName << " dump: Loading " << dumpParams.currentFile << std::endl; + } + dumpParams.fileSize = std::filesystem::file_size(dumpParams.currentFile, e); + if (e) { + // TODO: Event? + sif::error << "PersistentTmStore: Could not load next dump file: " << e.message() + << std::endl; + handleIteration(dumpIndex); + continue; + } + std::ifstream ifile(dumpParams.currentFile, std::ios::binary); + if (ifile.bad()) { + sif::error << "PersistentTmStore: File is bad. Loading next file" << std::endl; + handleIteration(dumpIndex); + continue; + } + ifile.read(reinterpret_cast(fileBuf.data()), + static_cast(dumpParams.fileSize)); + // Next file is loaded, exit the loop. + handleIteration(dumpIndex); + return returnvalue::OK; + } + // Directory iterator was consumed and we are done. + state = State::IDLE; + return DUMP_DONE; +} + +ReturnValue_t PersistentTmStore::getNextDumpPacket(PusTmReader& reader, bool& fileHasSwapped) { + if (state == State::IDLE) { + return DUMP_DONE; + } + if (dumpParams.pendingPacketDump) { + return returnvalue::FAILED; + } + fileHasSwapped = false; + reader.setReadOnlyData(fileBuf.data() + dumpParams.currentSize, + fileBuf.size() - dumpParams.currentSize); + // CRC check to fully ensure this is a valid TM + ReturnValue_t result = reader.parseDataWithCrcCheck(); + if (result != returnvalue::OK) { + sif::error << "PersistentTmStore: Parsing of PUS TM failed with code " << result << std::endl; + triggerEvent(persTmStore::POSSIBLE_FILE_CORRUPTION, result, dumpParams.currentFileUnixStamp); + // Delete the file and load next. Could use better algorithm to partially + // restore the file dump, but for now do not trust the file. + std::error_code e; + std::filesystem::remove(dumpParams.currentFile.c_str(), e); + if (dumpParams.currentFile == activeFile) { + activeFile == std::nullopt; + assignAndOrCreateMostRecentFile(); + } + fileHasSwapped = true; + return loadNextDumpFile(); + } + dumpParams.pendingPacketDump = true; + return returnvalue::OK; +} + +ReturnValue_t PersistentTmStore::confirmDump(const PusTmReader& reader, bool& fileHasSwapped) { + dumpParams.pendingPacketDump = false; + dumpParams.currentSize += reader.getFullPacketLen(); + if (dumpParams.currentSize >= dumpParams.fileSize) { + fileHasSwapped = true; + return loadNextDumpFile(); + } + fileHasSwapped = false; + return returnvalue::OK; +} + +ReturnValue_t PersistentTmStore::pathToTime(const std::filesystem::path& path, struct tm& time) { + auto pathStr = path.string(); + size_t splitChar = pathStr.find('_'); + auto timeOnlyStr = pathStr.substr(splitChar + 1); + if (nullptr == strptime(timeOnlyStr.c_str(), config::FILE_DATE_FORMAT, &time)) { + return returnvalue::FAILED; + } + return returnvalue::OK; +} + +ReturnValue_t PersistentTmStore::createMostRecentFile(std::optional suffix) { + using namespace std::filesystem; + size_t currentIdx; + createFileName(currentTv, suffix, currentIdx); + path newPath(std::string(reinterpret_cast(filePathBuf.data()), currentIdx)); + std::ofstream of(newPath, std::ios::binary); + activeFile = newPath; + activeFileTv = currentTv; + return returnvalue::OK; +} + +ReturnValue_t PersistentTmStore::initializeTmStore() { + Clock::getClock_timeval(¤tTv); + updateBaseDir(); + // Reset active file, base directory might have changed. + activeFile = std::nullopt; + return assignAndOrCreateMostRecentFile(); +} + +PersistentTmStore::State PersistentTmStore::getState() const { return state; } + +void PersistentTmStore::getStartAndEndTimeCurrentOrLastDump(uint32_t& startTime, + uint32_t& endTime) const { + startTime = dumpParams.fromUnixTime; + endTime = dumpParams.untilUnixTime; +} + +ReturnValue_t PersistentTmStore::createFileName(timeval& tv, std::optional suffix, + size_t& fullPathLength) { + unsigned currentIdx = basePathSize; + time_t epoch = tv.tv_sec; + struct tm* time = gmtime(&epoch); + size_t writtenBytes = strftime(reinterpret_cast(filePathBuf.data() + currentIdx), + filePathBuf.size(), config::FILE_DATE_FORMAT, time); + if (writtenBytes == 0) { + sif::error << "PersistentTmStore::createMostRecentFile: Could not create file timestamp" + << std::endl; + return returnvalue::FAILED; + } + currentIdx += writtenBytes; + char* res = strcpy(reinterpret_cast(filePathBuf.data() + currentIdx), ".bin"); + if (res == nullptr) { + return returnvalue::FAILED; + } + currentIdx += 4; + if (suffix.has_value()) { + std::string fullSuffix = "." + std::to_string(suffix.value()); + res = strcpy(reinterpret_cast(filePathBuf.data() + currentIdx), fullSuffix.c_str()); + if (res == nullptr) { + return returnvalue::FAILED; + } + currentIdx += fullSuffix.size(); + } + fullPathLength = currentIdx; + return returnvalue::OK; +} diff --git a/mission/tmtc/PersistentTmStore.h b/mission/tmtc/PersistentTmStore.h new file mode 100644 index 0000000..ccd6cbe --- /dev/null +++ b/mission/tmtc/PersistentTmStore.h @@ -0,0 +1,154 @@ +#ifndef MISSION_TMTC_TMSTOREBACKEND_H_ +#define MISSION_TMTC_TMSTOREBACKEND_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "eive/eventSubsystemIds.h" +#include "eive/resultClassIds.h" +#include "fsfw/ipc/CommandMessage.h" + +enum class RolloverInterval { MINUTELY, HOURLY, DAILY }; + +struct PersistentTmStoreArgs { + PersistentTmStoreArgs(object_id_t objectId, const char* baseDir, std::string baseName, + RolloverInterval intervalUnit, uint32_t intervalCount, + StorageManagerIF& tmStore, SdCardMountedIF& sdcMan) + : objectId(objectId), + baseDir(baseDir), + baseName(baseName), + intervalUnit(intervalUnit), + intervalCount(intervalCount), + tmStore(tmStore), + sdcMan(sdcMan) {} + + object_id_t objectId; + const char* baseDir; + std::string baseName; + RolloverInterval intervalUnit; + uint32_t intervalCount; + StorageManagerIF& tmStore; + SdCardMountedIF& sdcMan; +}; + +struct DumpIndex { + uint32_t epoch = 0; + // Number of additional files with a suffix like .0, .1 etc. + uint8_t additionalFiles = 0; + // Define a custom comparison function based on the epoch variable + bool operator<(const DumpIndex& other) const { return epoch < other.epoch; } +}; + +class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject { + public: + enum class State { IDLE, DUMPING }; + static constexpr uint8_t INTERFACE_ID = CLASS_ID::PERSISTENT_TM_STORE; + static constexpr ReturnValue_t DUMP_DONE = returnvalue::makeCode(INTERFACE_ID, 0); + static constexpr ReturnValue_t BUSY_DUMPING = returnvalue::makeCode(INTERFACE_ID, 1); + + PersistentTmStore(PersistentTmStoreArgs args); + + ReturnValue_t initializeTmStore(); + State getState() const; + ReturnValue_t handleCommandQueue(StorageManagerIF& ipcStore, Command_t& execCmd); + + void deleteUpTo(uint32_t unixSeconds); + void deleteFromUpTo(uint32_t startUnixTime, uint32_t endUnixTime); + ReturnValue_t startDumpFrom(uint32_t fromUnixSeconds); + ReturnValue_t startDumpFromUpTo(uint32_t fromUnixSeconds, uint32_t upToUnixSeconds); + /** + * + * @param tmReader: Next packet will be loaded into the PUS TM reader. A CRC check will be + * performed on the packet. If that check fails, the file is considered corrupted and will + * be deleted for now. + * @param fileHasSwapped: If the CRC check fails, the file will be deleted and a new one has to + * be loaded. The dump can reach completion during that process. If a file is swapped, this + * boolean is set to true + * @return DUMP_DONE if dump is finished, returnvalue::OK if the next packet was loaded into the + * TM reader, and the returnvalue of the file swap operation if the CRC check failed and + * a new file was loaded. + */ + ReturnValue_t getNextDumpPacket(PusTmReader& tmReader, bool& fileHasSwapped); + /** + * Confirm the dump to advance the dump state machine. + * @param tmReader + * @param fileHasSwapped: If the confirmed dumps completes the current file, a new file will + * be loaded and this parameter will be set to true. + * @return If a file is swapped, the retrunvalue of the file swap operation. + */ + ReturnValue_t confirmDump(const PusTmReader& tmReader, bool& fileHasSwapped); + + void getStartAndEndTimeCurrentOrLastDump(uint32_t& startTime, uint32_t& endTime) const; + ReturnValue_t storePacket(PusTmReader& reader); + ReturnValue_t cancelDump(); + + protected: + StorageManagerIF& tmStore; + + private: + static constexpr uint8_t MAX_FILES_IN_ONE_SECOND = 10; + static constexpr size_t MAX_FILESIZE = 8192; + + //! [EXPORT] : [SKIP] + static constexpr ReturnValue_t INVALID_FILE_DETECTED_AND_DELETED = returnvalue::makeCode(2, 1); + + MessageQueueIF* tcQueue; + State state = State::IDLE; + bool baseDirUninitialized = true; + const char* baseDir; + std::string baseName; + uint8_t currentSameSecNumber = 0; + std::filesystem::path basePath; + // std::filesystem::path pathStart = basePath / baseName; + uint32_t rolloverDiffSeconds = 0; + std::array filePathBuf{}; + size_t basePathSize; + std::array fileBuf{}; + timeval currentTv; + timeval activeFileTv{}; + + struct ActiveDumpParams { + bool pendingPacketDump = false; + uint32_t fromUnixTime = 0; + uint32_t untilUnixTime = 0; + uint32_t currentFileUnixStamp = 0; + std::filesystem::path currentFile; + std::set orderedDumpFilestamps{}; + std::set::iterator dumpIter; + std::optional currentSameFileIdx = 0; + size_t fileSize = 0; + size_t currentSize = 0; + }; + ActiveDumpParams dumpParams; + std::optional activeFile; + SdCardMountedIF& sdcMan; + + /** + * To get the queue where commands shall be sent. + * @return Id of command queue. + */ + [[nodiscard]] MessageQueueId_t getCommandQueue() const override; + + void calcDiffSeconds(RolloverInterval intervalUnit, uint32_t intervalCount); + ReturnValue_t buildDumpSet(uint32_t fromUnixSeconds, uint32_t upToUnixSeconds); + ReturnValue_t createMostRecentFile(std::optional suffix); + static ReturnValue_t pathToTime(const std::filesystem::path& path, struct tm& time); + void fileToPackets(const std::filesystem::path& path, uint32_t unixStamp); + ReturnValue_t loadNextDumpFile(); + ReturnValue_t createFileName(timeval& tv, std::optional suffix, size_t& fullPathLength); + std::optional extractSuffix(const std::string& pathStr); + bool updateBaseDir(); + ReturnValue_t assignAndOrCreateMostRecentFile(); + ReturnValue_t handleDeletionCmd(StorageManagerIF& ipcStore, CommandMessage& cmdMessage); + ReturnValue_t handleDumpCmd(StorageManagerIF& ipcStore, CommandMessage& cmdMessage); +}; + +#endif /* MISSION_TMTC_TMSTOREBACKEND_H_ */ diff --git a/mission/tmtc/PersistentTmStoreWithTmQueue.cpp b/mission/tmtc/PersistentTmStoreWithTmQueue.cpp new file mode 100644 index 0000000..df4960d --- /dev/null +++ b/mission/tmtc/PersistentTmStoreWithTmQueue.cpp @@ -0,0 +1,33 @@ +#include "PersistentTmStoreWithTmQueue.h" + +#include +#include + +PersistentTmStoreWithTmQueue::PersistentTmStoreWithTmQueue(PersistentTmStoreArgs args, + const char* storeName, + uint32_t tmQueueDepth) + : PersistentTmStore(args), storeName(storeName) { + tmQueue = QueueFactory::instance()->createMessageQueue(tmQueueDepth); +} + +ReturnValue_t PersistentTmStoreWithTmQueue::handleNextTm() { + TmTcMessage msg; + ReturnValue_t result = tmQueue->receiveMessage(&msg); + if (result == MessageQueueIF::EMPTY) { + return result; + } + auto accessor = tmStore.getData(msg.getStorageId()); + if (accessor.first != returnvalue::OK) { + return result; + } + PusTmReader reader(accessor.second.data(), accessor.second.size()); + storePacket(reader); + return returnvalue::OK; +} + +const char* PersistentTmStoreWithTmQueue::getName() const { return storeName; } + +MessageQueueId_t PersistentTmStoreWithTmQueue::getReportReceptionQueue( + uint8_t virtualChannel) const { + return tmQueue->getId(); +} diff --git a/mission/tmtc/PersistentTmStoreWithTmQueue.h b/mission/tmtc/PersistentTmStoreWithTmQueue.h new file mode 100644 index 0000000..be1026f --- /dev/null +++ b/mission/tmtc/PersistentTmStoreWithTmQueue.h @@ -0,0 +1,20 @@ +#ifndef MISSION_TMTC_PERSISTENTTMSTOREWITHTMQUEUE_H_ +#define MISSION_TMTC_PERSISTENTTMSTOREWITHTMQUEUE_H_ +#include + +class PersistentTmStoreWithTmQueue : public PersistentTmStore, public AcceptsTelemetryIF { + public: + PersistentTmStoreWithTmQueue(PersistentTmStoreArgs args, const char* storeName, + uint32_t tmQueueDepth); + + ReturnValue_t handleNextTm(); + + [[nodiscard]] const char* getName() const override; + [[nodiscard]] MessageQueueId_t getReportReceptionQueue(uint8_t virtualChannel) const override; + + private: + const char* storeName; + MessageQueueIF* tmQueue; +}; + +#endif /* MISSION_TMTC_PERSISTENTTMSTOREWITHTMQUEUE_H_ */ diff --git a/mission/tmtc/PusLiveDemux.cpp b/mission/tmtc/PusLiveDemux.cpp new file mode 100644 index 0000000..6a30d78 --- /dev/null +++ b/mission/tmtc/PusLiveDemux.cpp @@ -0,0 +1,79 @@ +#include "PusLiveDemux.h" + +#include +#include + +PusLiveDemux::PusLiveDemux(MessageQueueIF& ownerQueue) : ownerQueue(ownerQueue) {} + +ReturnValue_t PusLiveDemux::demultiplexPackets(StorageManagerIF& tmStore, + store_address_t origStoreId, const uint8_t* tmData, + size_t tmSize) { + ReturnValue_t result = returnvalue::OK; + // sif::debug << "tm size: " << tmSize << " for " << destinations.size() << " destinations" << + // std::endl; + for (unsigned int idx = 0; idx < destinations.size(); idx++) { + const auto& dest = destinations[idx]; + if (dest.isFull) { + continue; + } + if ((destinations.size() > 1) and (idx < (destinations.size() - 1))) { + // Create copy of data to ensure each TM recipient has its own copy. That way, we don't need + // to bother with send order and where the data is deleted. + store_address_t storeId; + result = tmStore.addData(&storeId, tmData, tmSize); + if (result == returnvalue::OK) { + message.setStorageId(storeId); + } else if (result == StorageManagerIF::DATA_STORAGE_FULL) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "PusLiveDemux::handlePacket: Store too full to create data copy" << std::endl; + uint8_t fillCounts[16]; + uint8_t written = 0; + tmStore.getFillCount(fillCounts, &written); + sif::error << "Fill counts: ["; + for (uint8_t fillIdx = 0; fillIdx < written; fillIdx++) { + sif::error << fillCounts[fillIdx]; + if (fillIdx < written - 1) { + sif::error << ", "; + } + } + sif::error << "]" << std::endl; +#endif + } + } else { + message.setStorageId(origStoreId); + } + result = ownerQueue.sendMessage(dest.queueId, &message); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "PusLiveDemux::handlePacket: Error sending TM to downlink handler " << dest.name + << ", failed with code 0x" << std::hex << std::setw(4) << result << std::dec + << std::endl; +#endif + tmStore.deleteData(message.getStorageId()); + } + } + return result; +} + +uint32_t PusLiveDemux::addDestination(const char* name, + const AcceptsTelemetryIF& downlinkDestination, uint8_t vcid) { + return addDestinationByRawId(name, downlinkDestination.getReportReceptionQueue(vcid), vcid); +} + +void PusLiveDemux::setDestFull(uint32_t listIndex) { + if (destinations.size() > 0 and listIndex <= destinations.size() - 1) { + destinations[listIndex].isFull = true; + } +} + +void PusLiveDemux::setDestAvailable(uint32_t listIndex) { + if (destinations.size() > 0 and listIndex <= destinations.size() - 1) { + destinations[listIndex].isFull = false; + } +} + +uint32_t PusLiveDemux::addDestinationByRawId(const char* name, MessageQueueId_t downlinkDestination, + uint8_t vcid) { + destinations.emplace_back(name, downlinkDestination, vcid); + return destinations.size() - 1; +} diff --git a/mission/tmtc/PusLiveDemux.h b/mission/tmtc/PusLiveDemux.h new file mode 100644 index 0000000..d565c19 --- /dev/null +++ b/mission/tmtc/PusLiveDemux.h @@ -0,0 +1,40 @@ +#ifndef MISSION_TMTC_PUSLIVEDEMUX_H_ +#define MISSION_TMTC_PUSLIVEDEMUX_H_ + +#include +#include +#include +#include + +#include + +class PusLiveDemux { + public: + PusLiveDemux(MessageQueueIF& ownerQueue); + ReturnValue_t demultiplexPackets(StorageManagerIF& tmStore, store_address_t origStoreId, + const uint8_t* tmData, size_t tmSize); + + uint32_t addDestinationByRawId(const char* name, MessageQueueId_t downlinkDestination, + uint8_t vcid = 0); + uint32_t addDestination(const char* name, const AcceptsTelemetryIF& downlinkDestination, + uint8_t vcid = 0); + void setDestFull(uint32_t listIndex); + void setDestAvailable(uint32_t listIndex); + + private: + struct Destination { + Destination(const char* name, MessageQueueId_t queueId, uint8_t virtualChannel) + : name(name), queueId(queueId), virtualChannel(virtualChannel) {} + + const char* name; + MessageQueueId_t queueId; + bool isFull = false; + uint8_t virtualChannel = 0; + }; + + MessageQueueIF& ownerQueue; + TmTcMessage message; + std::vector destinations; +}; + +#endif /* MISSION_TMTC_PUSLIVEDEMUX_H_ */ diff --git a/mission/tmtc/PusPacketFilter.cpp b/mission/tmtc/PusPacketFilter.cpp new file mode 100644 index 0000000..51abc02 --- /dev/null +++ b/mission/tmtc/PusPacketFilter.cpp @@ -0,0 +1,64 @@ +#include + +#include + +PusPacketFilter::PusPacketFilter() {} + +void PusPacketFilter::addApid(uint16_t apid) { + if (not this->apid) { + this->apid = std::vector({apid}); + return; + } + this->apid.value().push_back(apid); +} + +void PusPacketFilter::addService(uint8_t service) { + if (not this->services) { + this->services = std::vector({service}); + return; + } + this->services.value().push_back(service); +} + +void PusPacketFilter::addServiceSubservice(uint8_t service, uint8_t subservice) { + if (not serviceSubservices) { + serviceSubservices = std::vector>({std::pair(service, subservice)}); + return; + } + serviceSubservices.value().emplace_back(service, subservice); +} + +bool PusPacketFilter::packetMatches(PusTmReader& reader) const { + bool inApidList = false; + if (apid) { + auto& apidFilter = apid.value(); + if (std::find(apidFilter.begin(), apidFilter.end(), reader.getApid()) != apidFilter.end()) { + if (not serviceSubservices and not services) { + return true; + } + inApidList = true; + } + } + std::pair serviceSubservice; + serviceSubservice.first = reader.getService(); + serviceSubservice.second = reader.getSubService(); + if (services) { + auto& serviceFilter = services.value(); + if (std::find(serviceFilter.begin(), serviceFilter.end(), serviceSubservice.first) != + serviceFilter.end()) { + if (apid and inApidList) { + return true; + } + } + } + if (serviceSubservices) { + auto& serviceSubserviceFilter = serviceSubservices.value(); + if (std::find(serviceSubserviceFilter.begin(), serviceSubserviceFilter.end(), + serviceSubservice) != serviceSubserviceFilter.end()) { + if (apid and inApidList) { + return true; + } + } + } + return false; +} diff --git a/mission/tmtc/PusPacketFilter.h b/mission/tmtc/PusPacketFilter.h new file mode 100644 index 0000000..78d9cc1 --- /dev/null +++ b/mission/tmtc/PusPacketFilter.h @@ -0,0 +1,24 @@ +#ifndef MISSION_TMTC_PUSPACKETFILTER_H_ +#define MISSION_TMTC_PUSPACKETFILTER_H_ + +#include + +#include +#include + +class PusPacketFilter { + public: + PusPacketFilter(); + + bool packetMatches(PusTmReader& reader) const; + void addApid(uint16_t apid); + void addService(uint8_t service); + void addServiceSubservice(uint8_t service, uint8_t subservice); + + private: + std::optional> apid; + std::optional> services; + std::optional>> serviceSubservices; +}; + +#endif /* MISSION_TMTC_PUSPACKETFILTER_H_ */ diff --git a/mission/tmtc/PusTmFunnel.cpp b/mission/tmtc/PusTmFunnel.cpp new file mode 100644 index 0000000..87648e5 --- /dev/null +++ b/mission/tmtc/PusTmFunnel.cpp @@ -0,0 +1,121 @@ +#include "PusTmFunnel.h" + +#include + +#include "eive/definitions.h" +#include "eive/objects.h" +#include "fsfw/ipc/CommandMessage.h" +#include "fsfw/ipc/QueueFactory.h" +#include "fsfw/objectmanager.h" +#include "fsfw/pus/Service5EventReporting.h" +#include "fsfw/tmstorage/TmStoreMessage.h" +#include "fsfw/tmtcpacket/pus/tm/PusTmZcWriter.h" +#include "tmtc/pusIds.h" + +PusTmFunnel::PusTmFunnel(TmFunnelBase::FunnelCfg cfg, StorageManagerIF &ramToFileStore, + TimeReaderIF &timeReader) + : TmFunnelBase(cfg), ramToFileStore(ramToFileStore), timeReader(timeReader) {} + +PusTmFunnel::~PusTmFunnel() = default; + +ReturnValue_t PusTmFunnel::performOperation(uint8_t) { + CommandMessage cmdMessage; + ReturnValue_t result; + TmTcMessage currentMessage; + unsigned int count = 0; + if (saveSequenceCount) { + result = saveSequenceCountToFile(); + if (result != returnvalue::OK) { + sif::error << "PusTmFunnel: Storing sequence count to file has failed" << std::endl; + } + saveSequenceCount = false; + } + result = tmQueue->receiveMessage(¤tMessage); + while (result == returnvalue::OK) { + result = handleTmPacket(currentMessage); + if (result != returnvalue::OK) { + sif::warning << "TmFunnel packet handling failed" << std::endl; + break; + } + count++; + if (count == 1000) { + sif::error << "PusTmFunnel: Possible message storm detected" << std::endl; + break; + } + result = tmQueue->receiveMessage(¤tMessage); + } + + if (result == MessageQueueIF::EMPTY) { + return returnvalue::OK; + } + return result; +} + +ReturnValue_t PusTmFunnel::handleTmPacket(TmTcMessage &message) { + uint8_t *packetData = nullptr; + size_t size = 0; + store_address_t origStoreId = message.getStorageId(); + ReturnValue_t result = tmStore.modifyData(origStoreId, &packetData, &size); + if (result != returnvalue::OK) { + return result; + } + PusTmZeroCopyWriter packet(timeReader, packetData, size); + result = packet.parseDataWithoutCrcCheck(); + if (result != returnvalue::OK) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::warning << "PusTmFunnel::handlePacket: Error parsing received PUS packet" << std::endl; +#endif + return result; + } + packet.setSequenceCount(sourceSequenceCount++); + // NOTE: This only works because the limit value bit width is below 16 bits! + sourceSequenceCount = sourceSequenceCount % ccsds::LIMIT_SEQUENCE_COUNT; + + // Message type counter handling. + uint8_t service = packet.getService(); + bool insertionFailed = false; + auto mapIter = msgCounterMap.find(service); + if (mapIter == msgCounterMap.end()) { + auto iterPair = msgCounterMap.emplace(service, 0); + if (iterPair.second) { + mapIter = iterPair.first; + } else { + // Should really never never happen but you never know.. + insertionFailed = true; + } + } + if (not insertionFailed) { + packet.setMessageCount(mapIter->second); + // Sane overflow handling. + if (mapIter->second == std::numeric_limits::max()) { + mapIter->second = 0; + } else { + mapIter->second++; + } + } + + // Re-calculate CRC after changing the fields. This operation HAS to come last! + packet.updateErrorControl(); + + // Send to persistent TM store if the packet matches some filter. + MessageQueueId_t destination; + if (persistentTmMap.packetMatches(packet, destination)) { + store_address_t storageId; + result = ramToFileStore.addData(&storageId, packetData, size); + TmTcMessage msg(storageId); + if (result != returnvalue::OK) { + sif::error << "PusLiveDemux::handlePacket: Store too full to create data copy" << std::endl; + } else { + tmQueue->sendMessage(destination, &msg); + } + } + + // Send to registered live targets. + return demultiplexLivePackets(origStoreId, packetData, size); +} + +const char *PusTmFunnel::getName() const { return "PUS TM Funnel"; } + +void PusTmFunnel::addPersistentTmStoreRouting(PusPacketFilter filter, MessageQueueId_t dest) { + persistentTmMap.addRouting(filter, dest); +} diff --git a/mission/tmtc/PusTmFunnel.h b/mission/tmtc/PusTmFunnel.h new file mode 100644 index 0000000..3696fdc --- /dev/null +++ b/mission/tmtc/PusTmFunnel.h @@ -0,0 +1,50 @@ +#ifndef FSFW_EXAMPLE_COMMON_PUSTMFUNNEL_H +#define FSFW_EXAMPLE_COMMON_PUSTMFUNNEL_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "PersistentTmStore.h" +#include "fsfw/ipc/CommandMessage.h" +#include "fsfw/timemanager/TimeReaderIF.h" + +/** + * @brief TM Recipient. + * @details + * Main telemetry receiver. All generated telemetry is funneled into this object. + * @ingroup utility + * @author J. Meier, R. Mueller + */ +class PusTmFunnel : public TmFunnelBase { + public: + PusTmFunnel(TmFunnelBase::FunnelCfg cfg, StorageManagerIF &ramToFileStore, + TimeReaderIF &timeReader); + [[nodiscard]] const char *getName() const override; + ~PusTmFunnel() override; + + ReturnValue_t performOperation(uint8_t operationCode); + void addPersistentTmStoreRouting(PusPacketFilter filter, MessageQueueId_t dest); + + private: + // Update TV stamp every 5 minutes + static constexpr dur_millis_t TV_UPDATE_INTERVAL_SECS = 60 * 5; + + std::map msgCounterMap; + StorageManagerIF &ramToFileStore; + TimeReaderIF &timeReader; + bool storesInitialized = false; + PusTmRouteByFilterHelper persistentTmMap; + + ReturnValue_t handleTmPacket(TmTcMessage &message); +}; + +#endif // FSFW_EXAMPLE_COMMON_PUSTMFUNNEL_H diff --git a/mission/tmtc/PusTmRouteByFilterHelper.cpp b/mission/tmtc/PusTmRouteByFilterHelper.cpp new file mode 100644 index 0000000..15adde0 --- /dev/null +++ b/mission/tmtc/PusTmRouteByFilterHelper.cpp @@ -0,0 +1,19 @@ +#include "PusTmRouteByFilterHelper.h" + +#include + +PusTmRouteByFilterHelper::PusTmRouteByFilterHelper() = default; + +bool PusTmRouteByFilterHelper::packetMatches(PusTmReader& reader, MessageQueueId_t& destination) { + for (const auto& filterAndDest : routerMap) { + if (filterAndDest.first.packetMatches(reader)) { + destination = filterAndDest.second; + return true; + } + } + return false; +} + +void PusTmRouteByFilterHelper::addRouting(PusPacketFilter filter, MessageQueueId_t destination) { + routerMap.emplace_back(std::move(filter), destination); +} diff --git a/mission/tmtc/PusTmRouteByFilterHelper.h b/mission/tmtc/PusTmRouteByFilterHelper.h new file mode 100644 index 0000000..92bb0f6 --- /dev/null +++ b/mission/tmtc/PusTmRouteByFilterHelper.h @@ -0,0 +1,29 @@ +#ifndef MISSION_TMTC_PUSTMROUTER_H_ +#define MISSION_TMTC_PUSTMROUTER_H_ + +#include +#include + +/** + * Simple composition of concrecte @PusPacketFilters which also maps them to + * a destination ID. + */ +class PusTmRouteByFilterHelper { + public: + PusTmRouteByFilterHelper(); + + /** + * Checks whether the packet matches any of the inserted filters and returns + * the destination if it does. Currently only supports one destination. + * @param reader + * @param destination + * @return + */ + bool packetMatches(PusTmReader& reader, MessageQueueId_t& destination); + void addRouting(PusPacketFilter filter, MessageQueueId_t destination); + + private: + std::vector> routerMap; +}; + +#endif /* MISSION_TMTC_PUSTMROUTER_H_ */ diff --git a/mission/tmtc/Service15TmStorage.cpp b/mission/tmtc/Service15TmStorage.cpp new file mode 100644 index 0000000..efc9f2a --- /dev/null +++ b/mission/tmtc/Service15TmStorage.cpp @@ -0,0 +1,107 @@ +#include "Service15TmStorage.h" + +#include +#include + +#include "eive/objects.h" +#include "fsfw/tmstorage/TmStoreMessage.h" + +using namespace returnvalue; + +Service15TmStorage::Service15TmStorage(object_id_t objectId, uint16_t apid, + uint8_t numParallelCommands, uint16_t commandTimeoutSecs, + size_t queueDepth) + : CommandingServiceBase(objectId, apid, "PUS Service 15", 15, numParallelCommands, + commandTimeoutSecs, queueDepth) {} + +ReturnValue_t Service15TmStorage::isValidSubservice(uint8_t subservice) { + switch (subservice) { + case (Subservice::DELETE_UP_TO): + case (Subservice::DELETE_BY_TIME_RANGE): + case (Subservice::START_BY_TIME_RANGE_RETRIEVAL): { + return OK; + } + default: { + return FAILED; + } + } +} + +ReturnValue_t Service15TmStorage::getMessageQueueAndObject(uint8_t subservice, + const uint8_t *tcData, size_t tcDataLen, + MessageQueueId_t *id, + object_id_t *objectId) { + if (tcDataLen < 4) { + return CommandingServiceBase::INVALID_TC; + } + SerializeAdapter::deSerialize(objectId, &tcData, &tcDataLen, SerializeIF::Endianness::NETWORK); + auto *frontendIF = ObjectManager::instance()->get(*objectId); + if (frontendIF == nullptr) { + return CommandingServiceBase::INVALID_OBJECT; + } + *id = frontendIF->getCommandQueue(); + return OK; +} + +ReturnValue_t Service15TmStorage::prepareCommand(CommandMessage *message, uint8_t subservice, + const uint8_t *tcData, size_t tcDataLen, + uint32_t *state, object_id_t objectId) { + switch (subservice) { + case (Subservice::START_BY_TIME_RANGE_RETRIEVAL): { + // TODO: Hardcoded to UNIX timestamps.. Should allow arbitrary timestamp and let receiver + // to time reading and reply handling + if (tcDataLen != 12) { + return INVALID_TC; + } + store_address_t storeId; + ReturnValue_t result = ipcStore->addData(&storeId, tcData + 4, tcDataLen - 4); + if (result != OK) { + return result; + } + // Store timestamps + TmStoreMessage::setDownlinkContentTimeMessage(message, storeId); + return CommandingServiceBase::EXECUTION_COMPLETE; + } + case (Subservice::DELETE_UP_TO): { + // TODO: Hardcoded to UNIX timestamps.. Should allow arbitrary timestamp and let receiver + // to time reading and reply handling + if (tcDataLen != 8) { + return INVALID_TC; + } + store_address_t storeId; + ReturnValue_t result = ipcStore->addData(&storeId, tcData + 4, tcDataLen - 4); + if (result != OK) { + return result; + } + // Store timestamps + TmStoreMessage::setDeleteContentTimeMessage(message, storeId); + return CommandingServiceBase::EXECUTION_COMPLETE; + } + case (Subservice::DELETE_BY_TIME_RANGE): { + // TODO: Hardcoded two UNIX timestamps.. Should allow arbitrary timestamp and let receiver + // to time reading and reply handling + if (tcDataLen != 12) { + return INVALID_TC; + } + store_address_t storeId; + ReturnValue_t result = ipcStore->addData(&storeId, tcData + 4, tcDataLen - 4); + if (result != OK) { + return result; + } + // Store timestamps + TmStoreMessage::setDeleteContentTimeMessage(message, storeId); + return CommandingServiceBase::EXECUTION_COMPLETE; + } + default: { + return CommandingServiceBase::INVALID_SUBSERVICE; + } + } + return OK; +} + +ReturnValue_t Service15TmStorage::handleReply(const CommandMessage *reply, + Command_t previousCommand, uint32_t *state, + CommandMessage *optionalNextCommand, + object_id_t objectId, bool *isStep) { + return OK; +} diff --git a/mission/tmtc/Service15TmStorage.h b/mission/tmtc/Service15TmStorage.h new file mode 100644 index 0000000..6da381e --- /dev/null +++ b/mission/tmtc/Service15TmStorage.h @@ -0,0 +1,28 @@ +#ifndef MISSION_TMTC_SERVICE15TMSTORAGE_H_ +#define MISSION_TMTC_SERVICE15TMSTORAGE_H_ + +#include + +class Service15TmStorage : public CommandingServiceBase { + public: + enum Subservice : uint8_t { + START_BY_TIME_RANGE_RETRIEVAL = 9, + DELETE_UP_TO = 11, + DELETE_BY_TIME_RANGE = 128 + }; + explicit Service15TmStorage(object_id_t objectId, uint16_t apid, uint8_t numParallelCommands, + uint16_t commandTimeoutSecs = 60, size_t queueDepth = 10); + + private: + ReturnValue_t isValidSubservice(uint8_t subservice) override; + ReturnValue_t getMessageQueueAndObject(uint8_t subservice, const uint8_t* tcData, + size_t tcDataLen, MessageQueueId_t* id, + object_id_t* objectId) override; + ReturnValue_t prepareCommand(CommandMessage* message, uint8_t subservice, const uint8_t* tcData, + size_t tcDataLen, uint32_t* state, object_id_t objectId) override; + ReturnValue_t handleReply(const CommandMessage* reply, Command_t previousCommand, uint32_t* state, + CommandMessage* optionalNextCommand, object_id_t objectId, + bool* isStep) override; +}; + +#endif /* MISSION_TMTC_SERVICE15TMSTORAGE_H_ */ diff --git a/mission/tmtc/TmFunnelBase.cpp b/mission/tmtc/TmFunnelBase.cpp new file mode 100644 index 0000000..c19afd0 --- /dev/null +++ b/mission/tmtc/TmFunnelBase.cpp @@ -0,0 +1,78 @@ +#include "TmFunnelBase.h" + +#include + +#include +#include +#include + +#include "fsfw/ipc/QueueFactory.h" + +TmFunnelBase::TmFunnelBase(FunnelCfg cfg) + : SystemObject(cfg.objectId), + name(cfg.name), + tmStore(cfg.tmStore), + ipcStore(cfg.ipcStore), + tmQueue(QueueFactory::instance()->createMessageQueue(cfg.tmMsgDepth)), + liveDemux(*tmQueue), + sdcMan(cfg.sdcMan), + sequenceCounterFilename(cfg.sequenceCounterFilename), + saveSequenceCount(cfg.saveSequenceCount) {} + +ReturnValue_t TmFunnelBase::demultiplexLivePackets(store_address_t origStoreId, + const uint8_t *tmData, size_t tmSize) { + return liveDemux.demultiplexPackets(tmStore, origStoreId, tmData, tmSize); +} + +TmFunnelBase::~TmFunnelBase() { QueueFactory::instance()->deleteMessageQueue(tmQueue); } + +MessageQueueId_t TmFunnelBase::getReportReceptionQueue(uint8_t virtualChannel) const { + return tmQueue->getId(); +} + +uint32_t TmFunnelBase::addLiveDestination(const char *name, + const AcceptsTelemetryIF &downlinkDestination, + uint8_t vcid) { + return liveDemux.addDestination(name, downlinkDestination, vcid); +} + +ReturnValue_t TmFunnelBase::initialize() { + using namespace std::filesystem; + // The filesystem should always be available at the start.. Let's assume it always is, otherwise + // we just live with a regular 0 initialization. It simplifies a lot. + std::error_code e; + path filePath = + path(path(sdcMan.getCurrentMountPrefix()) / path("conf") / path(sequenceCounterFilename)); + if (exists(filePath, e)) { + std::ifstream ifile(filePath); + if (ifile.bad()) { + sif::error << "TmFunnelBase::initialize: Faulty file open for sequence counter initialization" + << std::endl; + return returnvalue::OK; + } + if (not(ifile >> sourceSequenceCount)) { + // Initialize to 0 in any case if reading a number failed. + sourceSequenceCount = 0; + } + } + return returnvalue::OK; +} + +ReturnValue_t TmFunnelBase::saveSequenceCountToFile() { + using namespace std::filesystem; + std::error_code e; + path filePath = + path(path(sdcMan.getCurrentMountPrefix()) / path("conf") / path(sequenceCounterFilename)); + std::ofstream ofile(filePath); + if (ofile.bad()) { + return returnvalue::FAILED; + } + ofile << sourceSequenceCount << "\n"; + return returnvalue::OK; +} + +uint32_t TmFunnelBase::addLiveDestinationByRawId(const char *name, + MessageQueueId_t downlinkDestination, + uint8_t vcid) { + return liveDemux.addDestinationByRawId(name, downlinkDestination, vcid); +} diff --git a/mission/tmtc/TmFunnelBase.h b/mission/tmtc/TmFunnelBase.h new file mode 100644 index 0000000..bc36d7f --- /dev/null +++ b/mission/tmtc/TmFunnelBase.h @@ -0,0 +1,65 @@ +#ifndef MISSION_TMTC_TMFUNNELBASE_H_ +#define MISSION_TMTC_TMFUNNELBASE_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class TmFunnelBase : public AcceptsTelemetryIF, public SystemObject { + public: + struct FunnelCfg { + FunnelCfg(object_id_t objId, const char* name, StorageManagerIF& tmStore, + StorageManagerIF& ipcStore, uint32_t tmMsgDepth, SdCardMountedIF& sdcMan, + const char* sequenceCounterFilename, std::atomic_bool& saveSequenceCount) + : objectId(objId), + name(name), + tmStore(tmStore), + ipcStore(ipcStore), + tmMsgDepth(tmMsgDepth), + sdcMan(sdcMan), + sequenceCounterFilename(sequenceCounterFilename), + saveSequenceCount(saveSequenceCount) {} + object_id_t objectId; + const char* name; + StorageManagerIF& tmStore; + StorageManagerIF& ipcStore; + uint32_t tmMsgDepth; + SdCardMountedIF& sdcMan; + const char* sequenceCounterFilename; + std::atomic_bool& saveSequenceCount; + }; + explicit TmFunnelBase(FunnelCfg cfg); + [[nodiscard]] MessageQueueId_t getReportReceptionQueue(uint8_t virtualChannel) const override; + virtual uint32_t addLiveDestinationByRawId(const char* name, MessageQueueId_t downlinkDestination, + uint8_t vcid = 0); + virtual uint32_t addLiveDestination(const char* name, + const AcceptsTelemetryIF& downlinkDestination, + uint8_t vcid = 0); + ReturnValue_t demultiplexLivePackets(store_address_t origStoreId, const uint8_t* tmData, + size_t tmSize); + ReturnValue_t initialize() override; + + ReturnValue_t saveSequenceCountToFile(); + + ~TmFunnelBase() override; + + protected: + const char* name; + StorageManagerIF& tmStore; + StorageManagerIF& ipcStore; + MessageQueueIF* tmQueue = nullptr; + PusLiveDemux liveDemux; + SdCardMountedIF& sdcMan; + const char* sequenceCounterFilename; + std::atomic_bool& saveSequenceCount; + uint16_t sourceSequenceCount = 0; +}; + +#endif /* MISSION_TMTC_TMFUNNELBASE_H_ */ diff --git a/mission/tmtc/TmFunnelHandler.cpp b/mission/tmtc/TmFunnelHandler.cpp new file mode 100644 index 0000000..33f8add --- /dev/null +++ b/mission/tmtc/TmFunnelHandler.cpp @@ -0,0 +1,17 @@ +#include "TmFunnelHandler.h" + +#include + +TmFunnelHandler::TmFunnelHandler(object_id_t objectId, PusTmFunnel& pusFunnel, + CfdpTmFunnel& cfdpFunnel) + : SystemObject(objectId), pusFunnel(pusFunnel), cfdpFunnel(cfdpFunnel) {} + +TmFunnelHandler::~TmFunnelHandler() = default; + +ReturnValue_t TmFunnelHandler::performOperation(uint8_t operationCode) { + pusFunnel.performOperation(operationCode); + cfdpFunnel.performOperation(operationCode); + return returnvalue::OK; +} + +ReturnValue_t TmFunnelHandler::initialize() { return returnvalue::OK; } diff --git a/mission/tmtc/TmFunnelHandler.h b/mission/tmtc/TmFunnelHandler.h new file mode 100644 index 0000000..a101f36 --- /dev/null +++ b/mission/tmtc/TmFunnelHandler.h @@ -0,0 +1,35 @@ +#ifndef MISSION_UTILITY_TMFUNNEL_H_ +#define MISSION_UTILITY_TMFUNNEL_H_ + +#include +#include +#include +#include +#include + +#include "CfdpTmFunnel.h" +#include "PusTmFunnel.h" +#include "fsfw/timemanager/TimeReaderIF.h" + +/** + * @brief TM Recipient. + * @details + * Main telemetry receiver. All generated telemetry is funneled into + * this object. + * @ingroup utility + * @author J. Meier, R. Mueller + */ +class TmFunnelHandler : public ExecutableObjectIF, public SystemObject { + public: + TmFunnelHandler(object_id_t objectId, PusTmFunnel& pusFunnel, CfdpTmFunnel& cfdpFunnel); + ~TmFunnelHandler() override; + + ReturnValue_t performOperation(uint8_t operationCode) override; + ReturnValue_t initialize() override; + + private: + PusTmFunnel& pusFunnel; + CfdpTmFunnel& cfdpFunnel; +}; + +#endif /* MISSION_UTILITY_TMFUNNEL_H_ */ diff --git a/mission/tmtc/tmFilters.cpp b/mission/tmtc/tmFilters.cpp new file mode 100644 index 0000000..944cd3b --- /dev/null +++ b/mission/tmtc/tmFilters.cpp @@ -0,0 +1,57 @@ +#include "tmFilters.h" + +#include +#include + +#include "eive/definitions.h" + +PusPacketFilter filters::hkFilter() { + PusPacketFilter hkFilter; + hkFilter.addApid(config::EIVE_PUS_APID); + hkFilter.addService(pus::PUS_SERVICE_3); + return hkFilter; +} + +PusPacketFilter filters::miscFilter() { + PusPacketFilter miscFilter; + miscFilter.addApid(config::EIVE_PUS_APID); + miscFilter.addService(pus::PUS_SERVICE_17); + miscFilter.addService(pus::PUS_SERVICE_2); + miscFilter.addService(pus::PUS_SERVICE_200); + miscFilter.addService(pus::PUS_SERVICE_201); + miscFilter.addService(pus::PUS_SERVICE_9); + miscFilter.addService(pus::PUS_SERVICE_20); + return miscFilter; +} + +PusPacketFilter filters::okFilter() { + PusPacketFilter okFilter; + okFilter.addApid(config::EIVE_PUS_APID); + okFilter.addServiceSubservice(pus::PUS_SERVICE_5, + Service5EventReporting::Subservice::NORMAL_REPORT); + okFilter.addService(pus::PUS_SERVICE_8); + okFilter.addServiceSubservice(pus::PUS_SERVICE_1, 1); + okFilter.addServiceSubservice(pus::PUS_SERVICE_1, 3); + okFilter.addServiceSubservice(pus::PUS_SERVICE_1, 5); + okFilter.addServiceSubservice(pus::PUS_SERVICE_1, 7); + return okFilter; +} + +PusPacketFilter filters::notOkFilter() { + PusPacketFilter notOkFilter; + notOkFilter.addApid(config::EIVE_PUS_APID); + notOkFilter.addServiceSubservice(pus::PUS_SERVICE_5, 2); + notOkFilter.addServiceSubservice(pus::PUS_SERVICE_5, 3); + notOkFilter.addServiceSubservice(pus::PUS_SERVICE_5, 4); + notOkFilter.addServiceSubservice(pus::PUS_SERVICE_1, 2); + notOkFilter.addServiceSubservice(pus::PUS_SERVICE_1, 4); + notOkFilter.addServiceSubservice(pus::PUS_SERVICE_1, 6); + notOkFilter.addServiceSubservice(pus::PUS_SERVICE_1, 8); + return notOkFilter; +} + +PusPacketFilter filters::cfdpFilter() { + PusPacketFilter cfdpFilter; + cfdpFilter.addApid(config::EIVE_CFDP_APID); + return cfdpFilter; +} diff --git a/mission/tmtc/tmFilters.h b/mission/tmtc/tmFilters.h new file mode 100644 index 0000000..a358c8f --- /dev/null +++ b/mission/tmtc/tmFilters.h @@ -0,0 +1,15 @@ +#ifndef MISSION_TMTC_FILTERS_H_ +#define MISSION_TMTC_FILTERS_H_ +#include + +namespace filters { + +PusPacketFilter cfdpFilter(); +PusPacketFilter hkFilter(); +PusPacketFilter miscFilter(); +PusPacketFilter okFilter(); +PusPacketFilter notOkFilter(); + +} // namespace filters + +#endif /* MISSION_TMTC_FILTERS_H_ */ diff --git a/mission/utility/CMakeLists.txt b/mission/utility/CMakeLists.txt new file mode 100644 index 0000000..0641f4a --- /dev/null +++ b/mission/utility/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources( + ${LIB_EIVE_MISSION} + PRIVATE Timestamp.cpp ProgressPrinter.cpp Filenaming.cpp + GlobalConfigHandler.cpp DummySdCardManager.cpp trace.cpp) diff --git a/mission/utility/DummySdCardManager.cpp b/mission/utility/DummySdCardManager.cpp new file mode 100644 index 0000000..17d7ba2 --- /dev/null +++ b/mission/utility/DummySdCardManager.cpp @@ -0,0 +1,13 @@ +#include "DummySdCardManager.h" + +DummySdCardManager::DummySdCardManager(std::string prefix) : prefix(std::move(prefix)) {} + +const char* DummySdCardManager::getCurrentMountPrefix() const { return prefix.c_str(); } + +bool DummySdCardManager::isSdCardUsable(std::optional sdCard) { return true; } + +std::optional DummySdCardManager::getPreferredSdCard() const { return std::nullopt; } + +void DummySdCardManager::setActiveSdCard(sd::SdCard sdCard) {} + +std::optional DummySdCardManager::getActiveSdCard() const { return std::nullopt; } diff --git a/mission/utility/DummySdCardManager.h b/mission/utility/DummySdCardManager.h new file mode 100644 index 0000000..4c98505 --- /dev/null +++ b/mission/utility/DummySdCardManager.h @@ -0,0 +1,18 @@ +#ifndef BSP_LINUX_BOARD_RPISDCARDMANAGER_H_ +#define BSP_LINUX_BOARD_RPISDCARDMANAGER_H_ +#include + +class DummySdCardManager : public SdCardMountedIF { + public: + DummySdCardManager(std::string prefix); + const char* getCurrentMountPrefix() const override; + bool isSdCardUsable(std::optional sdCard) override; + std::optional getPreferredSdCard() const override; + void setActiveSdCard(sd::SdCard sdCard) override; + std::optional getActiveSdCard() const override; + + private: + std::string prefix; +}; + +#endif /* BSP_LINUX_BOARD_RPISDCARDMANAGER_H_ */ diff --git a/mission/utility/Filenaming.cpp b/mission/utility/Filenaming.cpp new file mode 100644 index 0000000..3f4ec99 --- /dev/null +++ b/mission/utility/Filenaming.cpp @@ -0,0 +1,17 @@ +#include "Filenaming.h" + +#include "Timestamp.h" + +Filenaming::Filenaming() {} + +std::string Filenaming::generateAbsoluteFilename(std::string path, std::string filename, + bool addTimestamp) { + std::string absoluteName; + Timestamp timestamp; + if (addTimestamp) { + absoluteName = path + "/" + timestamp.str() + filename; + } else { + absoluteName = path + "/" + filename; + } + return absoluteName; +} diff --git a/mission/utility/Filenaming.h b/mission/utility/Filenaming.h new file mode 100644 index 0000000..9c7b1cb --- /dev/null +++ b/mission/utility/Filenaming.h @@ -0,0 +1,28 @@ +#ifndef MISSION_UTILITY_FILENAMING_H_ +#define MISSION_UTILITY_FILENAMING_H_ + +#include + +/** + * @brief Static functions related to file name creation + */ +class Filenaming { + public: + /** + * @brief Creates the absolute name of a file by merging the path name, the filename and adding + * an optional timestamp. + * + * @param path + * @param filename + * @param addTimestap Set to true if timestamp should be considered in name + * + * @return The absolute filename + */ + static std::string generateAbsoluteFilename(std::string path, std::string filename, + bool addTimestamp); + + private: + Filenaming(); +}; + +#endif /* MISSION_UTILITY_FILENAMING_H_ */ diff --git a/mission/utility/GlobalConfigFileDefinitions.h b/mission/utility/GlobalConfigFileDefinitions.h new file mode 100644 index 0000000..13d8cdf --- /dev/null +++ b/mission/utility/GlobalConfigFileDefinitions.h @@ -0,0 +1,16 @@ +/* + * GlobalConfigFileDefinitions.h + * + * Created on: July 05, 2022 + * Author: Jona Petri (IRS) + */ + +#ifndef MISSION_UTILITY_GLOBALCONFIGFILEDEFINITIONS_H_ +#define MISSION_UTILITY_GLOBALCONFIGFILEDEFINITIONS_H_ + +static constexpr double PARAM0_DEFAULT = 5.0; +static constexpr int PARAM1_DEFAULT = 905; + +enum ParamIds : uint8_t { PARAM0 = 0, PARAM1 = 1, PARAM2 = 2, PDEC_PW = 3, PDEC_NW = 4 }; + +#endif /* MISSION_UTILITY_GLOBALCONFIGFILEDEFINITIONS_H_ */ diff --git a/mission/utility/GlobalConfigHandler.cpp b/mission/utility/GlobalConfigHandler.cpp new file mode 100644 index 0000000..bb4b3d7 --- /dev/null +++ b/mission/utility/GlobalConfigHandler.cpp @@ -0,0 +1,269 @@ +/* + * GlobalConfigHandler.cpp + * + * Created on: May 3, 2022 + * Author: Jona Petri (IRS) + */ + +#include "GlobalConfigHandler.h" + +#include +#include +#include + +#include "fsfw/serviceinterface/ServiceInterface.h" + +MutexIF* GlobalConfigHandler::CONFIG_LOCK = nullptr; + +GlobalConfigHandler::GlobalConfigHandler(object_id_t objectId, std::string configFilePath) + : SystemObject(objectId), + NVMParameterBase(configFilePath), + commandQueue(QueueFactory::instance()->createMessageQueue(20)) { + if (CONFIG_LOCK == nullptr) { + CONFIG_LOCK = MutexFactory::instance()->createMutex(); + } +} +ReturnValue_t GlobalConfigHandler::initialize() { + ReturnValue_t result = SystemObject::initialize(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::initialize: SystemObject::initialize() failed with " + << result << std::endl; +#endif + return result; + } + + result = ReadConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::initialize: Creating JSON file at " << getFullName() + << std::endl; +#endif + result = ResetConfigFile(); + if (result != returnvalue::OK) { + return result; + } + } + +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::initialize success " << std::endl; +#endif + return result; +} +GlobalConfigHandler::~GlobalConfigHandler() {} + +ReturnValue_t GlobalConfigHandler::performOperation(uint8_t operationCode) { + ReturnValue_t result = returnvalue::OK; + sif::debug << "GlobalConfigHandler::performOperation" << std::endl; + return result; +} + +ReturnValue_t GlobalConfigHandler::lockConfigFile() { + ReturnValue_t result = returnvalue::OK; + result = CONFIG_LOCK->lockMutex(MutexIF::TimeoutType::WAITING, 10); + return result; +} +ReturnValue_t GlobalConfigHandler::unlockConfigFile() { + ReturnValue_t result = returnvalue::OK; + result = CONFIG_LOCK->unlockMutex(); + return result; +} + +template +ReturnValue_t GlobalConfigHandler::setConfigFileValue(ParamIds paramID, T data) { + ReturnValue_t result = returnvalue::OK; + ReturnValue_t resultSet = returnvalue::OK; + + result = lockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::setConfigFileValue lock mutex failed with " << result + << std::endl; +#endif + return result; + } + + std::string paramString; + paramString = PARAM_KEY_MAP[paramID]; + + // Check if key exists in map before setting value. No check is done in setValue! Somehow + // PARAM_KEY_MAP.count(paramID) == 0 does not work + if (paramString.empty() == true) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::setConfigFileValue ParamId " << PARAM_KEY_MAP[paramID] + << " not found!" << std::endl; +#endif + triggerEvent(SET_CONFIGFILEVALUE_FAILED, 1, 0); + return returnvalue::FAILED; + } + + resultSet = setValue(PARAM_KEY_MAP[paramID], data); + if (resultSet != returnvalue::OK) { + triggerEvent(SET_CONFIGFILEVALUE_FAILED, 0, 0); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::setConfigFileValue set json failed with " << resultSet + << std::endl; +#endif + } + + result = unlockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::setConfigFileValue unlock mutex failed with " << result + << std::endl; +#endif + return result; + } + + return resultSet; +} +template +ReturnValue_t GlobalConfigHandler::getConfigFileValue(ParamIds paramID, T& data) { + ReturnValue_t result = returnvalue::OK; + ReturnValue_t resultGet = returnvalue::OK; + + result = lockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::getConfigFileValue lock mutex failed with " << result + << std::endl; +#endif + return result; + } + + resultGet = getValue(PARAM_KEY_MAP[paramID], data); + if (resultGet != returnvalue::OK) { + triggerEvent(GET_CONFIGFILEVALUE_FAILED, 0, 0); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::getConfigFileValue getValue failed with " << resultGet + << std::endl; +#endif + } + result = unlockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::getConfigFileValue unlock mutex failed with " << result + << std::endl; +#endif + return result; + } + + return resultGet; +} + +ReturnValue_t GlobalConfigHandler::resetConfigFileValues() { + ReturnValue_t result = returnvalue::OK; + result = lockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::resetConfigFileValues lock mutex failed with " << result + << std::endl; +#endif + return result; + } + insertValue(PARAM_KEY_MAP[PARAM0], PARAM0_DEFAULT); + insertValue(PARAM_KEY_MAP[PARAM1], PARAM1_DEFAULT); + + result = unlockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::resetConfigFileValues unlock mutex failed with " << result + << std::endl; +#endif + return result; + } + return result; +} +ReturnValue_t GlobalConfigHandler::WriteConfigFile() { + ReturnValue_t result = returnvalue::OK; + ReturnValue_t resultWrite = returnvalue::OK; + result = lockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::WriteConfigFile lock mutex failed with " << result + << std::endl; +#endif + return result; + } + + resultWrite = writeJsonFile(); + if (resultWrite != returnvalue::OK) { + triggerEvent(WRITE_CONFIGFILE_FAILED, 0, 0); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::WriteConfigFile write json failed with " << resultWrite + << std::endl; +#endif + } + + result = unlockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::WriteConfigFile unlock mutex failed with " << result + << std::endl; +#endif + return result; + } + return resultWrite; +} +ReturnValue_t GlobalConfigHandler::ReadConfigFile() { + ReturnValue_t result = returnvalue::OK; + ReturnValue_t resultRead = returnvalue::OK; + result = lockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::ReadConfigFile lock mutex failed with " << result + << std::endl; +#endif + return result; + } + + resultRead = readJsonFile(); + if (resultRead != returnvalue::OK) { + triggerEvent(READ_CONFIGFILE_FAILED, 0, 0); +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::ReadConfigFile read json failed with " << resultRead + << std::endl; +#endif + } + + result = unlockConfigFile(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::ReadConfigFile unlock mutex failed with " << result + << std::endl; +#endif + return result; + } + + return resultRead; +} +ReturnValue_t GlobalConfigHandler::ResetConfigFile() { + ReturnValue_t result = returnvalue::OK; + result = resetConfigFileValues(); + if (result != returnvalue::OK) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "GlobalConfigHandler::ResetConfigFile failed with " << result << std::endl; +#endif + return result; + } + result = writeJsonFile(); + return result; +} + +ReturnValue_t GlobalConfigHandler::setConfigFileName(std::string configFileName) { + ReturnValue_t result = returnvalue::OK; + setFullName(configFileName); + result = ResetConfigFile(); + return result; +} +std::string GlobalConfigHandler::getConfigFileName() { return getFullName(); } + +template ReturnValue_t GlobalConfigHandler::getConfigFileValue(ParamIds paramID, + double& data); +template ReturnValue_t GlobalConfigHandler::getConfigFileValue(ParamIds paramID, + int32_t& data); + +template ReturnValue_t GlobalConfigHandler::setConfigFileValue(ParamIds paramID, + double data); +template ReturnValue_t GlobalConfigHandler::setConfigFileValue(ParamIds paramID, + int32_t data); diff --git a/mission/utility/GlobalConfigHandler.h b/mission/utility/GlobalConfigHandler.h new file mode 100644 index 0000000..bb3badf --- /dev/null +++ b/mission/utility/GlobalConfigHandler.h @@ -0,0 +1,79 @@ +/* + * GlobalConfigHandler.h + * + * Created on: May 3, 2022 + * Author: Jona Petri (IRS) + */ + +#ifndef MISSION_UTILITY_GLOBALCONFIGHANDLER_H_ +#define MISSION_UTILITY_GLOBALCONFIGHANDLER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "GlobalConfigFileDefinitions.h" +#include "OBSWConfig.h" +#include "fsfw/parameters/HasParametersIF.h" +#include "fsfw/parameters/ParameterHelper.h" + +static std::map PARAM_KEY_MAP = { + {PARAM0, "Parameter0"}, + {PARAM1, "Parameter1"}, +}; +/* + * Idea: This class is intended to be used as a subclass for the Core Controller. + * Its tasks is managing a configuration JSON file containing config values important for various + * object. If some function to read or write a config value is called, a mutex should be used so + * only one call is done at a time. + */ +class GlobalConfigHandler : public SystemObject, + public ExecutableObjectIF, + public NVMParameterBase { + public: + GlobalConfigHandler(object_id_t objectId, std::string configFilePath); + virtual ~GlobalConfigHandler(); + + static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::CONFIGHANDLER; + + static constexpr Event SET_CONFIGFILEVALUE_FAILED = MAKE_EVENT(1, severity::MEDIUM); + static constexpr Event GET_CONFIGFILEVALUE_FAILED = MAKE_EVENT(2, severity::MEDIUM); + static constexpr Event INSERT_CONFIGFILEVALUE_FAILED = MAKE_EVENT(3, severity::MEDIUM); + static constexpr Event WRITE_CONFIGFILE_FAILED = MAKE_EVENT(4, severity::MEDIUM); + static constexpr Event READ_CONFIGFILE_FAILED = MAKE_EVENT(5, severity::MEDIUM); + + ReturnValue_t performOperation(uint8_t operationCode); + + ReturnValue_t initialize(); + + template + ReturnValue_t setConfigFileValue(ParamIds paramID, T data); + template + ReturnValue_t getConfigFileValue(ParamIds paramID, T& data); + + ReturnValue_t ResetConfigFile(); + ReturnValue_t WriteConfigFile(); + std::string getConfigFileName(); + + private: + static MutexIF* CONFIG_LOCK; + + ReturnValue_t lockConfigFile(); + ReturnValue_t unlockConfigFile(); + ReturnValue_t resetConfigFileValues(); + + ReturnValue_t setConfigFileName(std::string configFileName); + + ReturnValue_t ReadConfigFile(); + + MessageQueueIF* commandQueue; +}; + +#endif /* MISSION_UTILITY_GLOBALCONFIGHANDLER_H_ */ diff --git a/mission/utility/InitMission.h b/mission/utility/InitMission.h new file mode 100644 index 0000000..85d8ae2 --- /dev/null +++ b/mission/utility/InitMission.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace scheduling { + +static void printAddObjectError(const char* name, object_id_t objectId) { +#if FSFW_CPP_OSTREAM_ENABLED == 1 + sif::error << "InitMission::printAddError: Adding object " << name << " with object ID 0x" + << std::hex << std::setfill('0') << std::setw(8) << objectId << " failed!" << std::dec + << std::endl; +#else + sif::printError("InitMission::printAddError: Adding object %s with object ID 0x%08x failed!\n", + name, objectId); +#endif /* FSFW_CPP_OSTREAM_ENABLED == 1 */ +} + +} // namespace scheduling diff --git a/mission/utility/ProgressPrinter.cpp b/mission/utility/ProgressPrinter.cpp new file mode 100644 index 0000000..124ae39 --- /dev/null +++ b/mission/utility/ProgressPrinter.cpp @@ -0,0 +1,25 @@ +#include "ProgressPrinter.h" + +#include +#include + +#include "fsfw/serviceinterface/ServiceInterfaceStream.h" + +ProgressPrinter::ProgressPrinter(std::string name, uint32_t numSteps, float percentageResolution) + : name(name), numSteps(numSteps), percentageResolution(percentageResolution) {} + +ProgressPrinter::~ProgressPrinter() {} + +void ProgressPrinter::print(uint32_t currentStep) { + float progressInPercent = static_cast(currentStep) / static_cast(numSteps) * 100; + if (progressInPercent >= nextProgressPrint) { + sif::info << name << " progress: " << std::setprecision(4) << progressInPercent << " %" + << std::endl; + nextProgressPrint += percentageResolution; + } + if (nextProgressPrint - progressInPercent < 0) { + nextProgressPrint = + (std::floor(progressInPercent / percentageResolution) * percentageResolution) + + percentageResolution; + } +} diff --git a/mission/utility/ProgressPrinter.h b/mission/utility/ProgressPrinter.h new file mode 100644 index 0000000..9db27c7 --- /dev/null +++ b/mission/utility/ProgressPrinter.h @@ -0,0 +1,42 @@ +#ifndef MISSION_UTILITY_PROGRESSPRINTER_H_ +#define MISSION_UTILITY_PROGRESSPRINTER_H_ + +#include + +/** + * @brief Class which can be used to print the progress of processes in percent. + * + * @author J. Meier + */ +class ProgressPrinter { + public: + static constexpr float HALF_PERCENT = 0.5; + static constexpr float ONE_PERCENT = 1; + static constexpr float FIVE_PERCENT = 5; + + /** + * @brief Constructor + * + * @param name Name of the process to monitor + * @param numSteps Number of steps to execute + * @param percentageResolution Distance between printed percentage steps. E.g. 5 means that + * a printout will be generated after 0%, 5%, 10% etc. + */ + ProgressPrinter(std::string name, uint32_t numSteps, float percentageResolution = FIVE_PERCENT); + virtual ~ProgressPrinter(); + + /** + * @brief Will print the progress + * + * @param currentStep Current step from which to derive progress + */ + void print(uint32_t step); + + private: + std::string name = ""; + uint32_t numSteps = 0; + float nextProgressPrint = 0; + float percentageResolution = 0; +}; + +#endif /* MISSION_UTILITY_PROGRESSPRINTER_H_ */ diff --git a/mission/utility/Timestamp.cpp b/mission/utility/Timestamp.cpp new file mode 100644 index 0000000..030be5c --- /dev/null +++ b/mission/utility/Timestamp.cpp @@ -0,0 +1,22 @@ +#include "Timestamp.h" + +#include "fsfw/serviceinterface/ServiceInterfaceStream.h" + +Timestamp::Timestamp() { + ReturnValue_t result = Clock::getDateAndTime(&time); + if (result != returnvalue::OK) { + sif::warning << "Timestamp::Timestamp: Failed to get time" << std::endl; + } +} + +Timestamp::~Timestamp() {} + +std::string Timestamp::str() { + timestamp << std::to_string(time.year) << "-" << std::setw(2) << std::setfill('0') + << std::to_string(time.month) << "-" << std::setw(2) << std::setfill('0') + << std::to_string(time.day) << "--" << std::setw(2) << std::setfill('0') + << std::to_string(time.hour) << "-" << std::setw(2) << std::setfill('0') + << std::to_string(time.minute) << "-" << std::setw(2) << std::setfill('0') + << std::to_string(time.second) << "--"; + return timestamp.str(); +} diff --git a/mission/utility/Timestamp.h b/mission/utility/Timestamp.h new file mode 100644 index 0000000..a83ef34 --- /dev/null +++ b/mission/utility/Timestamp.h @@ -0,0 +1,31 @@ +#ifndef MISSION_UTILITY_TIMESTAMP_H_ +#define MISSION_UTILITY_TIMESTAMP_H_ + +#include +#include +#include + +#include "fsfw/returnvalues/returnvalue.h" +#include "fsfw/timemanager/Clock.h" + +/** + * @brief This class generates timestamps for files. + * + * @author J. Meier + */ +class Timestamp { + public: + Timestamp(); + virtual ~Timestamp(); + + /** + * @brief Returns the timestamp string + */ + std::string str(); + + private: + std::stringstream timestamp; + Clock::TimeOfDay_t time; +}; + +#endif /* MISSION_UTILITY_TIMESTAMP_H_ */ diff --git a/mission/utility/compileTime.h b/mission/utility/compileTime.h new file mode 100644 index 0000000..0e36724 --- /dev/null +++ b/mission/utility/compileTime.h @@ -0,0 +1,90 @@ +/* + * + * Created: 29.03.2018 + * + * Authors: + * + * Assembled from the code released on Stackoverflow by: + * Dennis (instructable.com/member/nqtronix) | + * https://stackoverflow.com/questions/23032002/c-c-how-to-get-integer-unix-timestamp-of-build-time-not-string + * and + * Alexis Wilke | + * https://stackoverflow.com/questions/10538444/do-you-know-of-a-c-macro-to-compute-unix-time-and-date + * + * Assembled by Jean Rabault + * + * UNIX_TIMESTAMP gives the UNIX timestamp (unsigned long integer of seconds since 1st Jan 1970) of + * compilation from macros using the compiler defined __TIME__ macro. This should include Gregorian + * calendar leap days, in particular the 29ths of February, 100 and 400 years modulo leaps. + * + * Careful: __TIME__ is the local time of the computer, NOT the UTC time in general! + * + */ + +#ifndef COMPILE_TIME_H_ +#define COMPILE_TIME_H_ + +// Some definitions for calculation +#define SEC_PER_MIN 60UL +#define SEC_PER_HOUR 3600UL +#define SEC_PER_DAY 86400UL +#define SEC_PER_YEAR (SEC_PER_DAY * 365) + +// extracts 1..4 characters from a string and interprets it as a decimal value +#define CONV_STR2DEC_1(str, i) (str[i] > '0' ? str[i] - '0' : 0) +#define CONV_STR2DEC_2(str, i) (CONV_STR2DEC_1(str, i) * 10 + str[i + 1] - '0') +#define CONV_STR2DEC_3(str, i) (CONV_STR2DEC_2(str, i) * 10 + str[i + 2] - '0') +#define CONV_STR2DEC_4(str, i) (CONV_STR2DEC_3(str, i) * 10 + str[i + 3] - '0') + +// Custom "glue logic" to convert the month name to a usable number +#define GET_MONTH(str, i) \ + (str[i] == 'J' && str[i + 1] == 'a' && str[i + 2] == 'n' ? 1 \ + : str[i] == 'F' && str[i + 1] == 'e' && str[i + 2] == 'b' ? 2 \ + : str[i] == 'M' && str[i + 1] == 'a' && str[i + 2] == 'r' ? 3 \ + : str[i] == 'A' && str[i + 1] == 'p' && str[i + 2] == 'r' ? 4 \ + : str[i] == 'M' && str[i + 1] == 'a' && str[i + 2] == 'y' ? 5 \ + : str[i] == 'J' && str[i + 1] == 'u' && str[i + 2] == 'n' ? 6 \ + : str[i] == 'J' && str[i + 1] == 'u' && str[i + 2] == 'l' ? 7 \ + : str[i] == 'A' && str[i + 1] == 'u' && str[i + 2] == 'g' ? 8 \ + : str[i] == 'S' && str[i + 1] == 'e' && str[i + 2] == 'p' ? 9 \ + : str[i] == 'O' && str[i + 1] == 'c' && str[i + 2] == 't' ? 10 \ + : str[i] == 'N' && str[i + 1] == 'o' && str[i + 2] == 'v' ? 11 \ + : str[i] == 'D' && str[i + 1] == 'e' && str[i + 2] == 'c' ? 12 \ + : 0) + +// extract the information from the time string given by __TIME__ and __DATE__ +#define __TIME_SECONDS__ CONV_STR2DEC_2(__TIME__, 6) +#define __TIME_MINUTES__ CONV_STR2DEC_2(__TIME__, 3) +#define __TIME_HOURS__ CONV_STR2DEC_2(__TIME__, 0) +#define __TIME_DAYS__ CONV_STR2DEC_2(__DATE__, 4) +#define __TIME_MONTH__ GET_MONTH(__DATE__, 0) +#define __TIME_YEARS__ CONV_STR2DEC_4(__DATE__, 7) + +// Days in February +#define _UNIX_TIMESTAMP_FDAY(year) \ + (((year) % 400) == 0UL ? 29UL \ + : (((year) % 100) == 0UL ? 28UL : (((year) % 4) == 0UL ? 29UL : 28UL))) + +// Days in the year +#define _UNIX_TIMESTAMP_YDAY(year, month, day) \ + (/* January */ day /* February */ + (month >= 2 ? 31UL : 0UL) /* March */ + \ + (month >= 3 ? _UNIX_TIMESTAMP_FDAY(year) : 0UL) /* April */ + \ + (month >= 4 ? 31UL : 0UL) /* May */ + (month >= 5 ? 30UL : 0UL) /* June */ + \ + (month >= 6 ? 31UL : 0UL) /* July */ + (month >= 7 ? 30UL : 0UL) /* August */ + \ + (month >= 8 ? 31UL : 0UL) /* September */ + (month >= 9 ? 31UL : 0UL) /* October */ + \ + (month >= 10 ? 30UL : 0UL) /* November */ + (month >= 11 ? 31UL : 0UL) /* December */ + \ + (month >= 12 ? 30UL : 0UL)) + +// get the UNIX timestamp from a digits representation +#define _UNIX_TIMESTAMP(year, month, day, hour, minute, second) \ + (/* time */ second + minute * SEC_PER_MIN + hour * SEC_PER_HOUR + \ + /* year day (month + day) */ (_UNIX_TIMESTAMP_YDAY(year, month, day) - 1) * SEC_PER_DAY + \ + /* year */ (year - 1970UL) * SEC_PER_YEAR + ((year - 1969UL) / 4UL) * SEC_PER_DAY - \ + ((year - 1901UL) / 100UL) * SEC_PER_DAY + ((year - 1601UL) / 400UL) * SEC_PER_DAY) + +// the UNIX timestamp +#define UNIX_TIMESTAMP \ + (_UNIX_TIMESTAMP(__TIME_YEARS__, __TIME_MONTH__, __TIME_DAYS__, __TIME_HOURS__, \ + __TIME_MINUTES__, __TIME_SECONDS__)) + +#endif diff --git a/mission/utility/trace.cpp b/mission/utility/trace.cpp new file mode 100644 index 0000000..6641d08 --- /dev/null +++ b/mission/utility/trace.cpp @@ -0,0 +1,10 @@ +#include + +#include "fsfw/serviceinterface.h" + +void trace::threadTrace(uint32_t& counter, const char* name, unsigned div) { + counter++; + if (counter % div == 0) { + sif::debug << name << " running" << std::endl; + } +} diff --git a/mission/utility/trace.h b/mission/utility/trace.h new file mode 100644 index 0000000..52eb887 --- /dev/null +++ b/mission/utility/trace.h @@ -0,0 +1,14 @@ +#ifndef MISSION_UTILITY_TRACE_H_ +#define MISSION_UTILITY_TRACE_H_ + +#include + +#define OBSW_THREAD_TRACING 0 + +namespace trace { + +void threadTrace(uint32_t& counter, const char* name, unsigned div = 5); + +} + +#endif /* MISSION_UTILITY_TRACE_H_ */ diff --git a/q7s-env-em.sh b/q7s-env-em.sh new file mode 100755 index 0000000..698b6b2 --- /dev/null +++ b/q7s-env-em.sh @@ -0,0 +1,23 @@ +#!/bin/bash -i +# This script is intended to be copied outside of the source directory on the +# same folder level as the eive-obsw folder. This allows to tweak it to +# custom cross-compiler and sysroot path setups + +# Adapt the following two entries to your need +export CROSS_COMPILE_BIN_PATH="/opt/q7s-gcc/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin" +export ZYNQ_7020_SYSROOT="/opt/xiphos/sdk/ark/sysroots/cortexa9hf-neon-xiphos-linux-gnueabi" + +export PATH=${CROSS_COMPILE_BIN_PATH}:$PATH +export CROSS_COMPILE="arm-linux-gnueabihf" +export EIVE_Q7S_EM=1 + +if [[ -d "eive-obsw" ]]; then + echo "Detected EIVE OBSW root directory at $(pwd)/eive-obsw. Setting to EIVE_OBSW_ROOT" + export EIVE_OBSW_ROOT="$(pwd)/eive-obsw" + echo "Adding $(pwd)/eive-obsw/cmake/scripts/q7s helper script path to PATH" + export PATH=$PATH:"$(pwd)/eive-obsw/cmake/scripts/q7s" + export PATH=$PATH:"$(pwd)/eive-obsw/scripts" + cd "eive-obsw" +fi +export CONSOLE_PREFIX="[Q7S ENV EM]" +/bin/bash diff --git a/q7s-env.sh b/q7s-env.sh new file mode 100755 index 0000000..bf030f8 --- /dev/null +++ b/q7s-env.sh @@ -0,0 +1,23 @@ +#!/bin/bash -i +# This script is intended to be copied outside of the source directory on the +# same folder level as the eive-obsw folder. This allows to tweak it to +# custom cross-compiler and sysroot path setups + +# Adapt the following two entries to your need +export CROSS_COMPILE_BIN_PATH="/opt/q7s-gcc/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin" +export ZYNQ_7020_SYSROOT="/opt/xiphos/sdk/ark/sysroots/cortexa9hf-neon-xiphos-linux-gnueabi" + +export PATH=${CROSS_COMPILE_BIN_PATH}:$PATH +export CROSS_COMPILE="arm-linux-gnueabihf" +unset EIVE_Q7S_EM + +if [[ -d "eive-obsw" ]]; then + echo "Detected EIVE OBSW root directory at $(pwd)/eive-obsw. Setting to EIVE_OBSW_ROOT" + export EIVE_OBSW_ROOT="$(pwd)/eive-obsw" + echo "Adding $(pwd)/eive-obsw/cmake/scripts/q7s helper script path to PATH" + export PATH=$PATH:"$(pwd)/eive-obsw/cmake/scripts/q7s" + export PATH=$PATH:"$(pwd)/eive-obsw/scripts" + cd "eive-obsw" +fi +export CONSOLE_PREFIX="[Q7S ENV]" +/bin/bash diff --git a/release-checklist.md b/release-checklist.md new file mode 100644 index 0000000..04a0233 --- /dev/null +++ b/release-checklist.md @@ -0,0 +1,16 @@ +OBSW Release Checklist +========= + +# Pre-Release + +1. Update version in `CMakeLists.txt` +2. Re-run the generators with `generators/gen.py all` +3. Re-run the auto-formatters with the `scripts/auto-formatter.sh` script +4. Verify that the Q7S, Q7S EM and Host build are working +5. Update `CHANGELOG.md`: Add new `unreleased` section, convert old unreleased section to + header containing version number and release date. +6. Wait for CI/CD results + +# Post-Release + +1. Create a new release with tag on `EGit` diff --git a/scripts/auto-formatter.sh b/scripts/auto-formatter.sh new file mode 100755 index 0000000..dea617c --- /dev/null +++ b/scripts/auto-formatter.sh @@ -0,0 +1,44 @@ +#!/bin/bash +if [[ ! -f README.md ]]; then + cd .. +fi + +folder_list=( + "./watchdog" + "./mission" + "./linux" + "./bsp_q7s" + "./bsp_linux_board" + "./bsp_hosted" + "./bsp_egse" + "./test" + "./common" + "./dummies" +) + +cmake_fmt="cmake-format" +file_selectors="-iname CMakeLists.txt" +if command -v ${cmake_fmt} &> /dev/null; then + echo "Auto-formatting all CMakeLists.txt files" + ${cmake_fmt} -i CMakeLists.txt + for dir in ${folder_list[@]}; do + find ${dir} ${file_selectors} | xargs ${cmake_fmt} -i + done + ${cmake_fmt} -i ./thirdparty/gomspace-sw/CMakeLists.txt +else + echo "No ${cmake_fmt} tool found, not formatting CMake files" +fi + +cpp_format="clang-format-19" +file_selectors="( -iname *.h -o -iname *.cpp -o -iname *.c -o -iname *.tpp )" +file_excludes="( -not -iname translateObjects.cpp -not -iname translateEvents.cpp )" +if command -v ${cpp_format} &> /dev/null; then + for dir in ${folder_list[@]}; do + echo "Auto-formatting C/C++ files in ${dir} recursively" + find ${dir} ${file_excludes} -and ${file_selectors} | xargs ${cpp_format} --style=file -i + done + find ./unittest ${file_selectors} -o -type d -name build -prune | \ + xargs ${cpp_format} --style=file -i +else + echo "No ${cpp_format} tool found, not formatting C++/C files" +fi diff --git a/scripts/create-sw-update.sh b/scripts/create-sw-update.sh new file mode 100755 index 0000000..9f56fca --- /dev/null +++ b/scripts/create-sw-update.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +cmake --build . -j +source create-version-file.sh + +if [ ! -f eive-obsw-stripped ]; then + echo "No file eive-obsw-stripped found. Please ensure you are in the " + echo "build folder and the OBSW was built properly" + exit 1 +fi + +if [ ! -f obsw_version.txt ]; then + echo "No OBSW version file found." + echo "You can use the create-version-file.sh script to create it" + exit 1 +fi + +mkdir update-archive +cp eive-obsw-stripped update-archive +cp obsw_version.txt update-archive + +cd update-archive + +sudo chown root:root eive-obsw-stripped +sudo chown root:root obsw_version.txt + +cmd="tar -cJvf eive-sw-update.tar.xz eive-obsw-stripped obsw_version.txt" +echo "Running command ${cmd} to generate compressed SW update archive." +eval ${cmd} +cp eive-sw-update.tar.xz .. +cd .. +rm -rf update-archive + +echo "Generated eive-sw-update.tar.xz update archive." diff --git a/scripts/create-version-file.sh b/scripts/create-version-file.sh new file mode 100755 index 0000000..4a822dc --- /dev/null +++ b/scripts/create-version-file.sh @@ -0,0 +1,7 @@ +#!/bin/bash +obsw_version_filename="obsw_version.txt" +version_cmd="git describe --tags --always --exclude docker_*" +version_tag=$(${version_cmd}) +echo "-I- Running ${version_cmd} to retrieve OBSW version and store it into ${obsw_version_filename}" +echo "-I- Detected version tag ${version_tag}" +echo ${version_tag} > ${obsw_version_filename} diff --git a/scripts/egse-port.sh b/scripts/egse-port.sh new file mode 100755 index 0000000..b76cd0b --- /dev/null +++ b/scripts/egse-port.sh @@ -0,0 +1,6 @@ +#!/bin/bash +echo "-L 1534:localhost:1534 pi@192.168.18.31 portforwarding for tcf agent" +echo "-L 1537:localhost:7301 pi@192.168.18.31 for TMTC commanding using the TCP/IP IF" + +ssh -L 1534:localhost:1534 \ + -L 1537:localhost:7301 pi@192.168.18.31 \ No newline at end of file diff --git a/scripts/install-obsw-yocto.sh b/scripts/install-obsw-yocto.sh new file mode 100755 index 0000000..ae6fbee --- /dev/null +++ b/scripts/install-obsw-yocto.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# This is a helper script to install the compiles EIVE OBSW files +# into the yocto repository to re-generate the mission root filesystem +build_dir=cmake-build-release-q7s +if [[ ! -z ${1} && "${1}" == "em" ]] || [[ ${EIVE_Q7S_EM} == "1" ]]; then + echo "-I- Installing EM binaries" + em_install="1" + build_dir=cmake-build-release-q7s-em +fi + +init_dir=$(pwd) + +obsw_root="" +q7s_yocto_dir="yocto" +q7s_package_path="q7s-package/${q7s_yocto_dir}" + +obsw_version_filename="obsw_version.txt" +yocto_obsw_path="yocto/meta-eive/recipes-core/eive-obsw/files" +variant_specific_path="" +if [[ ${em_install} == "1" ]]; then + variant_specific_path="${yocto_obsw_path}/em" +else + variant_specific_path="${yocto_obsw_path}/fm" +fi + +yocto_watchdog_path="yocto/meta-eive/recipes-support/eive-obsw-watchdog/files" +obsw_bin_name="eive-obsw" +watchdog_bin_name="eive-watchdog" +obsw_target_name="eive-obsw" +watchdog_target_name="eive-watchdog" + +if [ ! -z ${EIVE_OBSW_ROOT} ]; then + cd ${EIVE_OBSW_ROOT} + obsw_root=$(pwd) +elif [ -d ${build_dir} ]; then + obsw_root=$(pwd) + : +elif [ -d ../${build_dir} ]; then + cd .. + obsw_root=$(pwd) +else + echo "-E- No way into the EIVE OBSW Root folder found. Exiting" + exit 1 +fi + +yocto_root="" +if [ -d ../${q7s_package_path} ]; then + cd ../${q7s_package_path} + yocto_root=$(pwd) +elif [ -d ../${q7s_yocto_dir} ]; then + cd ../${q7s_yocto_dir} + yocto_root=$(pwd) +fi +if [ -z ${yocto_root} ]; then + echo "-E- No yocto directory found. Exiting" + exit 1 +fi + +cd ${obsw_root} + +version_cmd="git describe --tags --always --exclude docker_*" +echo "-I- Running ${version_cmd} to retrieve OBSW version and store it into ${obsw_version_filename}" +version_tag=$(${version_cmd}) +echo "-I- Detected version tag ${version_tag}" +echo ${version_tag} > ${obsw_version_filename} + +if [ ! -d ${build_dir} ]; then + echo "No Q7S Release binary folder ${build_dir} found. Exiting" + exit 1 +fi + +if [ ! -f ${build_dir}/${obsw_bin_name} ]; then + echo "-W- No EIVE OBSW binary found to intall to yocto" +else + cp_cmd="cp $(pwd)/${build_dir}/${obsw_bin_name} ${yocto_root}/${variant_specific_path}/${obsw_target_name}" + echo "-I- Executing: ${cp_cmd}" + eval ${cp_cmd} + cp_ver_cmd="cp $(pwd)/${obsw_version_filename} ${yocto_root}/${variant_specific_path}" + echo "-I- Executing: ${cp_ver_cmd}" + eval ${cp_ver_cmd} + echo "-I- Installed EIVE OBSW into yocto repository successfully" +fi + +#if [ ! -f ${build_dir}/${watchdog_bin_name} ]; then +# echo "-W- No EIVE Watchdog found to intall to yocto" +#else +# cp_cmd="cp $(pwd)/${build_dir}/${watchdog_bin_name} ${yocto_root}/${yocto_watchdog_path}/${watchdog_target_name}" +# echo "-I- Executing: ${cp_cmd}" +# eval ${cp_cmd} +# cp_ver_cmd="cp $(pwd)/${obsw_version_filename} ${yocto_root}/${yocto_watchdog_path}" +# echo "-I- Executing: ${cp_ver_cmd}" +# eval ${cp_ver_cmd} +# echo "-I- Installed EIVE watchdog into yocto repository successfully" +#fi + +if [ -f $(pwd)/${obsw_version_filename} ]; then + rm $(pwd)/${obsw_version_filename} +fi + +cd ${init_dir} diff --git a/scripts/q7s-cp.py b/scripts/q7s-cp.py new file mode 100755 index 0000000..c3be26d --- /dev/null +++ b/scripts/q7s-cp.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +import argparse +import os +import sys + + +DEFAULT_PORT = 1539 + + +def main(): + args = handle_args() + cmd = build_cmd(args) + # Run the command + print(f"Running command: {cmd}") + result = os.system(cmd) + if result != 0: + prompt_ssh_key_removal() + print(f'Running command "{cmd}"') + result = os.system(cmd) + + +def prompt_ssh_key_removal(): + do_remove_key = input( + "Do you want to remove problematic keys on localhost ([Y]/n)?: " + ) + if do_remove_key.lower() not in ["y", "yes", "1", ""]: + sys.exit(1) + port = 0 + while True: + port = input("Enter port to remove: ") + if not port.isdecimal(): + print("Invalid port detected") + else: + break + cmd = f'ssh-keygen -f "$HOME/.ssh/known_hosts" -R "[localhost]:{port}"' + print(f"Removing problematic SSH key with command {cmd}..") + os.system(cmd) + + +def handle_args(): + help_string = ( + "This script copies files to the Q7S as long as port forwarding is active.\n" + ) + help_string += ( + "You can set up port forwarding with " + '"ssh -L 1535:192.168.133.10:22 " -t /bin/bash' + ) + parser = argparse.ArgumentParser(description=help_string) + # Optional arguments + parser.add_argument( + "-r", "--recursive", dest="recursive", default=False, action="store_true" + ) + parser.add_argument( + "-t", + "--target", + help="Target destination. If files are copied to Q7S, will be /tmp by default. " + "If files are copied back to host, will be current directory by default", + default="", + ) + parser.add_argument("-P", "--port", help="Target port", default=DEFAULT_PORT) + parser.add_argument( + "-i", + "--invert", + default=False, + action="store_true", + help="Copy from Q7S to host instead. Always copies to current directory.", + ) + parser.add_argument( + "-f", + "--flatsat", + default=False, + action="store_true", + help="Copy to flatsat instead", + ) + # Positional argument(s) + parser.add_argument( + "source", help="Source files to copy or target files to copy back to host" + ) + return parser.parse_args() + + +def build_cmd(args): + # Build run command + cmd = "scp " + if args.recursive: + cmd += "-r " + # Necessary to avoid some errors related to SFTP server of OBSW. + cmd += "-O " + address = "" + port_args = "" + target = args.target + if args.flatsat: + address = "eive@flatsat.eive.absatvirt.lw" + else: + address = "root@localhost" + port_args = f"-P {args.port}" + if args.invert: + if target == "": + target = "." + else: + target = args.target + else: + if target == "": + target = "/tmp" + else: + target = args.target + # accepted_key_rsa_args = "-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa" + accepted_key_rsa_args = "" + if args.invert: + cmd += f"{port_args} {accepted_key_rsa_args} {address}:{args.source} {target}" + else: + cmd += f"{port_args} {accepted_key_rsa_args} {args.source} {address}:{target}" + return cmd + + +if __name__ == "__main__": + main() diff --git a/scripts/q7s-em-udp-forwarding.sh b/scripts/q7s-em-udp-forwarding.sh new file mode 100755 index 0000000..284e788 --- /dev/null +++ b/scripts/q7s-em-udp-forwarding.sh @@ -0,0 +1,7 @@ +#!/bin/bash +echo "Q7S UDP connection from local port 18000 -> TCP ssh tunnel -> EM port 7301" + + +socat udp4-listen:18000,reuseaddr,fork tcp:localhost:18002 & +ssh -L 18002:localhost:18123 eive@flatsat.eive.absatvirt.lw \ + 'socat tcp4-listen:18123,reuseaddr udp:192.168.133.10:7301' diff --git a/scripts/q7s-fm-udp-forwarding.sh b/scripts/q7s-fm-udp-forwarding.sh new file mode 100755 index 0000000..34c8421 --- /dev/null +++ b/scripts/q7s-fm-udp-forwarding.sh @@ -0,0 +1,7 @@ +#!/bin/bash +echo "Q7S UDP connection from local port 18000 -> TCP ssh tunnel -> FM port 7301" + + +socat udp4-listen:18000,reuseaddr,fork tcp:localhost:18002 & +ssh -L 18002:localhost:18123 eive@flatsat.eive.absatvirt.lw \ + 'socat tcp4-listen:18123,reuseaddr udp:192.168.155.55:7301' diff --git a/scripts/q7s-port-em.sh b/scripts/q7s-port-em.sh new file mode 100755 index 0000000..e697063 --- /dev/null +++ b/scripts/q7s-port-em.sh @@ -0,0 +1,11 @@ +#!/bin/bash +echo "Setting up all Q7S ports for EM" +echo "-L 1538:192.168.133.10:1534 for connection to the TCF agent on the EM" +echo "-L 1539:192.168.133.10:22 for file transfers to the EM" +echo "-L 1540:192.168.133.10:7301 for TMTC commanding using the TCP/IP IF on the EM" + +ssh -L 1538:192.168.133.10:1534 \ + -L 1539:192.168.133.10:22 \ + -L 1540:192.168.133.10:7301 \ + eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 \ + -t 'CONSOLE_PREFIX="[Q7S Tunnel]" /bin/bash' diff --git a/scripts/q7s-port-local.sh b/scripts/q7s-port-local.sh new file mode 100755 index 0000000..0a9cc7e --- /dev/null +++ b/scripts/q7s-port-local.sh @@ -0,0 +1,8 @@ +#!/bin/bash +echo "Setting up all Q7S ports" +echo "-L 1534:localhost:1534 root@192.168.155.55 for connection to the TCF agent on the FM" +echo "-L 1560:localhost:7301 root@192.168.155.55 for TMTC commanding using the TCP/IP IF on the FM" + + +ssh -L 1534:localhost:1534 root@192.168.155.55 +ssh -L 1560:localhost:7301 root@192.168.155.55 \ No newline at end of file diff --git a/scripts/q7s-port.sh b/scripts/q7s-port.sh new file mode 100755 index 0000000..daa4ff4 --- /dev/null +++ b/scripts/q7s-port.sh @@ -0,0 +1,19 @@ +#!/bin/bash +echo "Setting up all Q7S ports" +echo "-L 1534:192.168.155.55:1534 for connection to the TCF agent on the FM" +echo "-L 1535:192.168.155.55:22 for file transfers to the FM" +echo "-L 1536:192.168.155.55:7301 for TMTC commanding using the TCP/IP IF on the FM" +echo "-L 1537:127.0.0.1:7100 for TMTC commanding using the CCSDS IF" +echo "-L 1538:192.168.133.10:1534 for connection to the TCF agent on the EM" +echo "-L 1539:192.168.133.10:22 for file transfers to the EM" +echo "-L 1540:192.168.133.10:7301 for TMTC commanding using the TCP/IP IF on the EM" + +ssh -L 1534:192.168.155.55:1534 \ + -L 1535:192.168.155.55:22 \ + -L 1536:192.168.155.55:7301 \ + -L 1537:127.0.0.1:7100 \ + -L 1538:192.168.133.10:1534 \ + -L 1539:192.168.133.10:22 \ + -L 1540:192.168.133.10:7301 \ + eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 \ + -t 'CONSOLE_PREFIX="[Q7S Tunnel]" /bin/bash' diff --git a/scripts/rpi-port.sh b/scripts/rpi-port.sh new file mode 100755 index 0000000..e8832c8 --- /dev/null +++ b/scripts/rpi-port.sh @@ -0,0 +1,10 @@ +#!/bin/bash +echo "-L 1538:raspberrypi.local:1538 for Raspberry Pi connect with TCF agent" +echo "-L 1539:raspberrypi.local:22 for Raspberry Pi file transfers" +echo "-L 7301:raspberrypi.local:7301 for Raspberry Pi TMTC Commands" + +ssh -L 1538:raspberrypi.local:1534 \ + -L 1539:raspberrypi.local:22 \ + -L 7301:raspberrypi.local:7301 \ + eive@2001:7c0:2018:1099:babe:0:e1fe:f1a5 \ + -t 'CONSOLE_PREFIX="[RPi Tunnel]" /bin/bash' diff --git a/scripts/win-q7s-env-em.sh b/scripts/win-q7s-env-em.sh new file mode 100644 index 0000000..5ff9bcf --- /dev/null +++ b/scripts/win-q7s-env-em.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# Run with: source win-q7s-env.sh [OPTIONS] +function help () { + echo "source win-q7s-env.sh [options] -t|--toolchain= -s|--sysroot=" +} + +TOOLCHAIN_PATH="/c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" +SYSROOT="/c/Users/${USER}/eive-software/eive-compile-rootfs" +export EIVE_Q7S_EM=1 + +for i in "$@"; do + case $i in + -t=*|--toolchain=*) + TOOLCHAIN_PATH="${i#*=}" + shift + ;; + -s=*|--sysroot=*) + SYSROOT="${i#*=}" + shift + ;; + -h|--help) + help + shift + ;; + -*|--*) + echo "Unknown option $i" + help + return + ;; + *) + ;; + esac +done + +if [ -d "$TOOLCHAIN_PATH" ]; then + export PATH=$PATH:"/c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" + export CROSS_COMPILE="arm-linux-gnueabihf" + echo "Set toolchain path to /c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" +else + echo "Toolchain path $TOOLCHAIN_PATH does not exist" + return +fi + +if [ -d "$SYSROOT" ]; then + export ZYNQ_7020_SYSROOT=$SYSROOT + echo "Set sysroot path to $SYSROOT" +else + echo "Sysroot path $SYSROOT does not exist" + return +fi + +if [ -d "eive-obsw" ]; then + echo "Detected EIVE OBSW root directory at $(pwd)/eive-obsw. Setting to EIVE_OBSW_ROOT" + export EIVE_OBSW_ROOT="$(pwd)/eive-obsw" + echo "Adding $(pwd)/eive-obsw/cmake/scripts/q7s helper script path to PATH" + export PATH=$PATH:"$(pwd)/eive-obsw/cmake/scripts/q7s" + export PATH=$PATH:"$(pwd)/eive-obsw/scripts" + cd "eive-obsw" +fi diff --git a/scripts/win-q7s-env.sh b/scripts/win-q7s-env.sh new file mode 100644 index 0000000..243ea31 --- /dev/null +++ b/scripts/win-q7s-env.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# Run with: source win-q7s-env.sh [OPTIONS] +function help () { + echo "source win-q7s-env.sh [options] -t|--toolchain= -s|--sysroot=" +} + +TOOLCHAIN_PATH="/c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" +SYSROOT="/c/Users/${USER}/eive-software/eive-compile-rootfs" +# export EIVE_Q7S_EM=1 + +for i in "$@"; do + case $i in + -t=*|--toolchain=*) + TOOLCHAIN_PATH="${i#*=}" + shift + ;; + -s=*|--sysroot=*) + SYSROOT="${i#*=}" + shift + ;; + -h|--help) + help + shift + ;; + -*|--*) + echo "Unknown option $i" + help + return + ;; + *) + ;; + esac +done + +if [ -d "$TOOLCHAIN_PATH" ]; then + export PATH=$PATH:"/c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" + export CROSS_COMPILE="arm-linux-gnueabihf" + echo "Set toolchain path to /c/Xilinx/Vitis/2019.2/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin" +else + echo "Toolchain path $TOOLCHAIN_PATH does not exist" + return +fi + +if [ -d "$SYSROOT" ]; then + export ZYNQ_7020_SYSROOT=$SYSROOT + echo "Set sysroot path to $SYSROOT" +else + echo "Sysroot path $SYSROOT does not exist" + return +fi + +if [ -d "eive-obsw" ]; then + echo "Detected EIVE OBSW root directory at $(pwd)/eive-obsw. Setting to EIVE_OBSW_ROOT" + export EIVE_OBSW_ROOT="$(pwd)/eive-obsw" + echo "Adding $(pwd)/eive-obsw/cmake/scripts/q7s helper script path to PATH" + export PATH=$PATH:"$(pwd)/eive-obsw/cmake/scripts/q7s" + export PATH=$PATH:"$(pwd)/eive-obsw/scripts" + cd "eive-obsw" +fi diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..25f9194 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(testtasks) +add_subdirectory(gpio) + +target_sources(${LIB_EIVE_MISSION} PUBLIC TestTask.cpp) diff --git a/test/DummyParameter.h b/test/DummyParameter.h new file mode 100644 index 0000000..fe9296b --- /dev/null +++ b/test/DummyParameter.h @@ -0,0 +1,24 @@ +#ifndef BSP_Q7S_CORE_NVMPARAMS_PARAMETERDEFINITIONS_H_ +#define BSP_Q7S_CORE_NVMPARAMS_PARAMETERDEFINITIONS_H_ + +#include + +#include +#include + +class DummyParameter : public NVMParameterBase { + public: + static constexpr char DUMMY_KEY_PARAM_1[] = "dummy1"; + static constexpr char DUMMY_KEY_PARAM_2[] = "dummy2"; + + DummyParameter(std::string mountPrefix, std::string jsonFileName) + : NVMParameterBase(mountPrefix + "/conf/" + jsonFileName), mountPrefix(mountPrefix) { + insertValue(DUMMY_KEY_PARAM_1, 1); + insertValue(DUMMY_KEY_PARAM_2, "blablub"); + } + + private: + std::string mountPrefix; +}; + +#endif /* BSP_Q7S_CORE_NVMPARAMS_PARAMETERDEFINITIONS_H_ */ diff --git a/test/TestTask.cpp b/test/TestTask.cpp new file mode 100644 index 0000000..2c6cb01 --- /dev/null +++ b/test/TestTask.cpp @@ -0,0 +1,119 @@ +#include "TestTask.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "OBSWConfig.h" + +EiveTestTask::EiveTestTask(object_id_t objectId_) : TestTask(objectId_), testMode(testModes::A) { + IPCStore = ObjectManager::instance()->get(objects::IPC_STORE); +} + +EiveTestTask::~EiveTestTask() {} + +ReturnValue_t EiveTestTask::performOperation(uint8_t operationCode) { + ReturnValue_t result = returnvalue::OK; + + if (oneShotAction) { + /* Add code here which should only be run once */ + performOneShotAction(); + oneShotAction = false; + } + + /* Add code here which should only be run once per performOperation */ + performPeriodicAction(); + + /* Add code here which should only be run on alternating cycles. */ + if (testMode == testModes::A) { + performActionA(); + testMode = testModes::B; + } else if (testMode == testModes::B) { + performActionB(); + testMode = testModes::A; + } + return result; +} + +#include + +// #include + +/** + * @brief Dummy data from GPS receiver. Will be replaced witgh hyperion data later. + */ + +const char gps_rx_data[] = + "" + "$GPRMC,183729,A,3907.356,N,12102.482,W,000.0,360.0,080301,015.5,E*6F\r\n" + "$GPRMB,A,,,,,,,,,,,,V*71\r\n" + "$GPGGA,183730,3907.356,N,12102.482,W,1,05,1.6,646.4,M,-24.1,M,,*75\r\n" + "$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D\r\n" + "$GPGSV,2,1,08,02,43,088,38,04,42,145,00,05,11,291,00,07,60,043,35*71\r\n" + "$GPGSV,2,2,08,08,02,145,00,09,46,303,47,24,16,178,32,26,18,231,43*77\r\n" + "$PGRME,22.0,M,52.9,M,51.0,M*14\r\n" + "$GPGLL,3907.360,N,12102.481,W,183730,A*33\r\n" + "$PGRMZ,2062,f,3*2D\r\n" + "$PGRMM,WGS84*06\r\n" + "$GPBOD,,T,,M,,*47\r\n" + "$GPRTE,1,1,c,0*07\r\n" + "$GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67\r\n" + "$GPRMB,A,,,,,,,,,,,,V*71\r\n"; + +const char hyperion_gps_data[] = + "" + "$GNGGA,173225.998892,4908.5596,N,00906.2765,E,1,05,2.1,215.0,M,48.2,M,,0000*74\r\n" + "$GNGLL,4908.5596,N,00906.2765,E,173225.998892,A,A*7F\r\n" + "$GPGSA,A,3,18,16,26,31,20,,,,,,,,3.2,2.1,2.4*3C\r\n" + "$GNRMC,173225.998892,A,4908.5596,N,00906.2765,E,000.0,040.7,270221,,,A*4F\r\n" + "$GNVTG,040.7,T,,M,000.0,N,000.0,K,A*10\r\n" + "$GNZDA,173225.998892,27,02,2021,00,00*75\r\n"; + +ReturnValue_t EiveTestTask::performOneShotAction() { +#if OBSW_ADD_TEST_CODE == 1 + // performLwgpsTest(); +#endif + return returnvalue::OK; +} + +ReturnValue_t EiveTestTask::performPeriodicAction() { + ReturnValue_t result = returnvalue::OK; + return result; +} + +ReturnValue_t EiveTestTask::performActionA() { + ReturnValue_t result = returnvalue::OK; + /* Add periodically executed code here */ + return result; +} + +ReturnValue_t EiveTestTask::performActionB() { + ReturnValue_t result = returnvalue::OK; + /* Add periodically executed code here */ + return result; +} + +/* +void EiveTestTask::performLwgpsTest() { + // Everything here will only be performed once. + sif::info << "Processing sample GPS output.." << std::endl; + + lwgps_t gpsStruct; + sif::info << "Size of GPS struct: " << sizeof(gpsStruct) << std::endl; + lwgps_init(&gpsStruct); + + // Process all input data + lwgps_process(&gpsStruct, hyperion_gps_data, strlen(hyperion_gps_data)); + + // Print messages + printf("Valid status: %d\n", gpsStruct.is_valid); + printf("Latitude: %f degrees\n", gpsStruct.latitude); + printf("Longitude: %f degrees\n", gpsStruct.longitude); + printf("Altitude: %f meters\n", gpsStruct.altitude); +} +*/ diff --git a/test/TestTask.h b/test/TestTask.h new file mode 100644 index 0000000..2040ffb --- /dev/null +++ b/test/TestTask.h @@ -0,0 +1,50 @@ +#ifndef TEST_TESTTASK_H_ +#define TEST_TESTTASK_H_ + +#include +#include +#include + +#include + +#include "fsfw_tests/integration/task/TestTask.h" + +/** + * @brief Test class for general C++ testing. + * @details + * Should not be used for board specific + * tests. Instead, a derived board test class should be used. + */ +class EiveTestTask : public TestTask { + public: + EiveTestTask(object_id_t objectId); + virtual ~EiveTestTask(); + virtual ReturnValue_t performOperation(uint8_t operationCode = 0) override; + + protected: + virtual ReturnValue_t performOneShotAction(); + virtual ReturnValue_t performPeriodicAction(); + virtual ReturnValue_t performActionA(); + virtual ReturnValue_t performActionB(); + + enum testModes : uint8_t { A, B }; + + testModes testMode; + + bool testFlag = false; + uint8_t counter{1}; + uint8_t counterTrigger{3}; + + void performPusInjectorTest(); + void examplePacketTest(); + + private: + // Actually, to be really thread-safe, a mutex should be used as well + // Let's keep it simple for now. + bool oneShotAction = true; + StorageManagerIF* IPCStore; + + void performLwgpsTest(); +}; + +#endif /* TESTTASK_H_ */ diff --git a/test/gpio/CMakeLists.txt b/test/gpio/CMakeLists.txt new file mode 100644 index 0000000..47ced20 --- /dev/null +++ b/test/gpio/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${LIB_EIVE_MISSION} PUBLIC DummyGpioIF.cpp) diff --git a/test/gpio/DummyGpioIF.cpp b/test/gpio/DummyGpioIF.cpp new file mode 100644 index 0000000..f9c28bf --- /dev/null +++ b/test/gpio/DummyGpioIF.cpp @@ -0,0 +1,16 @@ +#include "DummyGpioIF.h" + +DummyGpioIF::DummyGpioIF() {} + +DummyGpioIF::~DummyGpioIF() {} + +ReturnValue_t DummyGpioIF::addGpios(GpioCookie* cookie) { return returnvalue::OK; } + +ReturnValue_t DummyGpioIF::pullHigh(gpioId_t gpioId) { return returnvalue::OK; } + +ReturnValue_t DummyGpioIF::pullLow(gpioId_t gpioId) { return returnvalue::OK; } + +ReturnValue_t DummyGpioIF::readGpio(gpioId_t gpioId, gpio::Levels& gpioState) { + gpioState = gpio::Levels::LOW; + return returnvalue::OK; +} diff --git a/test/gpio/DummyGpioIF.h b/test/gpio/DummyGpioIF.h new file mode 100644 index 0000000..8a259f0 --- /dev/null +++ b/test/gpio/DummyGpioIF.h @@ -0,0 +1,16 @@ +#ifndef TEST_GPIO_DUMMYGPIOIF_H_ +#define TEST_GPIO_DUMMYGPIOIF_H_ + +#include "fsfw_hal/common/gpio/GpioIF.h" + +class DummyGpioIF : public GpioIF { + public: + DummyGpioIF(); + virtual ~DummyGpioIF(); + virtual ReturnValue_t addGpios(GpioCookie* cookie); + virtual ReturnValue_t pullHigh(gpioId_t gpioId); + virtual ReturnValue_t pullLow(gpioId_t gpioId); + virtual ReturnValue_t readGpio(gpioId_t gpioId, gpio::Levels& gpioState); +}; + +#endif /* TEST_GPIO_DUMMYGPIOIF_H_ */ diff --git a/test/gpio/GpioDummy.h b/test/gpio/GpioDummy.h new file mode 100644 index 0000000..a849bae --- /dev/null +++ b/test/gpio/GpioDummy.h @@ -0,0 +1,27 @@ +#ifndef TEST_GPIODUMMY_H_ +#define TEST_GPIODUMMY_H_ + +#include "fsfw_hal/common/gpio/GpioIF.h" +#include "fsfw_hal/common/gpio/gpioDefinitions.h" + +/** + * @brief Additional abstraction layer for handling GPIOs. + * + * @author J. Meier + */ +class Gpio { + public: + Gpio(gpioId_t gpioId, GpioIF* gpioIF) : gpioId(gpioId), gpioIF(gpioIF) { + if (gpioIF == nullptr) { + sif::error << "Gpio::Gpio: Invalid GpioIF" << std::endl; + } + } + ReturnValue_t pullHigh() { return gpioIF->pullHigh(gpioId); } + ReturnValue_t pullLow() { return gpioIF->pullLow(gpioId); } + + private: + gpioId_t gpioId = gpio::NO_GPIO; + GpioIF* gpioIF = nullptr; +}; + +#endif /* TEST_GPIODUMMY_H_ */ diff --git a/test/testtasks/CMakeLists.txt b/test/testtasks/CMakeLists.txt new file mode 100644 index 0000000..e48d3e4 --- /dev/null +++ b/test/testtasks/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(${LIB_EIVE_MISSION} PUBLIC) diff --git a/test/testtasks/PusTcInjector.cpp b/test/testtasks/PusTcInjector.cpp new file mode 100644 index 0000000..c0bff95 --- /dev/null +++ b/test/testtasks/PusTcInjector.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +PusTcInjector::PusTcInjector(object_id_t objectId, object_id_t destination, object_id_t tcStore, + uint16_t defaultApid) + : SystemObject(objectId), + defaultApid(defaultApid), + destination(destination), + tcStoreId(tcStore) {} + +PusTcInjector::~PusTcInjector() {} + +// ReturnValue_t PusTcInjector::injectPusTelecommand(uint8_t service, +// uint8_t subservice,const uint8_t* appData, size_t appDataLen) { +// return injectPusTelecommand(service, subservice, defaultApid, appData, +// appDataLen); +// } + +// TODO: ACK flags +// ReturnValue_t PusTcInjector::injectPusTelecommand(uint8_t service, +// uint8_t subservice,uint16_t apid, const uint8_t* appData, +// size_t appDataLen) { +// // Prepare TC packet. Store into TC store immediately. +// TcPacketStored tcPacket(service, subservice, apid, sequenceCount++); +// +// const uint8_t* packetPtr = nullptr; +// size_t packetSize = 0; +// tcPacket.getData(&packetPtr, &packetSize); +// //arrayprinter::print(packetPtr, packetSize, OutputType::BIN); +// +// // Send TC packet. +// TmTcMessage tcMessage(tcPacket.getStoreAddress()); +// ReturnValue_t result = injectionQueue->sendToDefault(&tcMessage); +// if(result != returnvalue::OK) { +// sif::warning << "PusTcInjector: Sending TMTC message failed!" << std::endl; +// } +// return result; +//} + +ReturnValue_t PusTcInjector::initialize() { + // Prepare message queue which is used to send telecommands. + injectionQueue = QueueFactory::instance()->createMessageQueue(INJECTION_QUEUE_DEPTH); + AcceptsTelecommandsIF* targetQueue = + ObjectManager::instance()->get(destination); + if (targetQueue == nullptr) { + sif::error << "PusTcInjector: CCSDS distributor not initialized yet!" << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + } else { + injectionQueue->setDefaultDestination(targetQueue->getRequestQueue()); + } + + // Prepare store used to store TC messages + tcStore = ObjectManager::instance()->get(tcStoreId); + if (tcStore == nullptr) { + sif::error << "PusTcInjector: TC Store not initialized!" << std::endl; + return ObjectManagerIF::CHILD_INIT_FAILED; + } + return returnvalue::OK; +} diff --git a/test/testtasks/PusTcInjector.h b/test/testtasks/PusTcInjector.h new file mode 100644 index 0000000..217f3b5 --- /dev/null +++ b/test/testtasks/PusTcInjector.h @@ -0,0 +1,73 @@ +#ifndef TEST_TESTTASKS_PUSTCINJECTOR_H_ +#define TEST_TESTTASKS_PUSTCINJECTOR_H_ +#include +#include +#include + +#include + +class PusTcInjector : public SystemObject { + public: + static constexpr uint8_t INJECTION_QUEUE_DEPTH = 10; + const uint16_t defaultApid; + + /** + * Initialize a software telecommand injector by supplying object IDs to + * various helper objects which must exist before calling initialiez() + * @param objectId ID of PUS telecommand injector + * @param destination ID of destination, which has to implement + * AcceptsTelecommandIF. + * @param tcStore ID of telecommand store, which has to implement + * StorageManagerIF. + * @param defaultApid Default APID which will be used if an injection + * without an APID is requested. + */ + PusTcInjector(object_id_t objectId, object_id_t destination, object_id_t tcStore, + uint16_t defaultApid); + /** + * This has to be called before using the PusTcInjector. + * Call Not necessary when using a factory and the object manager. + * @return -@c returnvalue::OK for successfull init + * -@c ObjectManagerIF::CHILD_INIT_FAILED otherwise + */ + ReturnValue_t initialize() override; + + virtual ~PusTcInjector(); + + /** + * Can be used to inject a telecommand by supplying service, subservice + * and optional application data and its length. + * Default APID will be used. + * @param service PUS service type + * @param subservice PUS subservice type + * @param appData Pointer to application data + * @param appDataLen Length of application data + * @return + */ + // ReturnValue_t injectPusTelecommand(uint8_t service, uint8_t subservice, + // const uint8_t* appData = nullptr, size_t appDataLen = 0); + + /** + * Provides the same functionality while also setting a user defined APID. + * @param service PUS service type + * @param subservice PUS subservice type + * @param apid Custom APID to, + * @param appData Pointer to application data + * @param appDataLen Length of application data + * @return + */ + // ReturnValue_t injectPusTelecommand(uint8_t service, uint8_t subservice, + // uint16_t apid, const uint8_t* appData = nullptr, + // size_t appDataLen = 0); + private: + MessageQueueIF* injectionQueue = nullptr; + StorageManagerIF* tcStore = nullptr; + + /* Cached for initialize function */ + object_id_t destination = 0; + object_id_t tcStoreId = 0; + + uint16_t sequenceCount = 0; +}; + +#endif /* TEST_TESTTASKS_PUSTCINJECTOR_H_ */ diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt new file mode 100644 index 0000000..3a5110c --- /dev/null +++ b/thirdparty/CMakeLists.txt @@ -0,0 +1,11 @@ +if(EIVE_ADD_LINUX_FILES) + add_subdirectory(tas) +endif() + +# Dependency on proprietary library +if(TGT_BSP MATCHES "arm/q7s") + # Only add required folder for wire library. + add_subdirectory(sagittactl/wire) +endif() + +add_subdirectory(rapidcsv) diff --git a/thirdparty/gomspace-sw b/thirdparty/gomspace-sw new file mode 160000 index 0000000..15d489b --- /dev/null +++ b/thirdparty/gomspace-sw @@ -0,0 +1 @@ +Subproject commit 15d489b884f46d232ec7d8879e7355d83873f06f diff --git a/thirdparty/json b/thirdparty/json new file mode 160000 index 0000000..bc889af --- /dev/null +++ b/thirdparty/json @@ -0,0 +1 @@ +Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d diff --git a/thirdparty/lwgps b/thirdparty/lwgps new file mode 160000 index 0000000..18ce34f --- /dev/null +++ b/thirdparty/lwgps @@ -0,0 +1 @@ +Subproject commit 18ce34faf729ed63c94517b2ae6a3d3741e0a054 diff --git a/thirdparty/rapidcsv b/thirdparty/rapidcsv new file mode 160000 index 0000000..d4e0bc1 --- /dev/null +++ b/thirdparty/rapidcsv @@ -0,0 +1 @@ +Subproject commit d4e0bc1047ca3965653463dc63f6d230a043a060 diff --git a/thirdparty/sagittactl b/thirdparty/sagittactl new file mode 160000 index 0000000..a558693 --- /dev/null +++ b/thirdparty/sagittactl @@ -0,0 +1 @@ +Subproject commit a558693675dae09aff7ef74242c25f826584c46a diff --git a/thirdparty/tas/CMakeLists.txt b/thirdparty/tas/CMakeLists.txt new file mode 100644 index 0000000..ab4e75e --- /dev/null +++ b/thirdparty/tas/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources(${OBSW_NAME} PRIVATE + hdlc.c + uart.c + crc.c +) + +target_include_directories(${OBSW_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/thirdparty/tas/crc.c b/thirdparty/tas/crc.c new file mode 100644 index 0000000..5a50a3a --- /dev/null +++ b/thirdparty/tas/crc.c @@ -0,0 +1,195 @@ +/*************************************************************************************** +* \copyright: 2020-2022 Thales Alenia Space Deutschland GmbH +* \project: multiMIND +* \file: crc.c +* \date: 22.02.2022 +* \author: David Woodward +* \brief: CRC algorithms +***************************************************************************************/ + +#include +#include "tas/crc.h" + + +const uint16_t crc16_0x1021_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +const uint16_t crc16_0x1021_table_reverse[256] = +{ + 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, + 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, + 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E, + 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, + 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD, + 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5, + 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C, + 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974, + 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB, + 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, + 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, + 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, + 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, + 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1, + 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738, + 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70, + 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7, + 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF, + 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, + 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, + 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, + 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, + 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134, + 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C, + 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3, + 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB, + 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232, + 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, + 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1, + 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, + 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, + 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78 +}; + +// CRC-32 calculation from original implementation (Sarthak) +// The used algorithm is (most likely) CRC32/BZIP2, as found here: +// https://www.cl.cam.ac.uk/research/srg/projects/fairisle/bluebook/21/crc/node6.html +uint32_t Crc32(const uint8_t *msg, int numBytes, uint32_t remainder) { + + int byte; + unsigned char bit; + + // Perform modulo-2 division, a byte at a time. + for (byte = 0; byte < numBytes; ++byte) + { + // Bring the next byte into the remainder. + remainder ^= (*(msg + byte) << 16); + + // Perform modulo-2 division, a bit at a time. + for (bit = 8; bit > 0; --bit) { + + // Try to divide the current data bit. + if (remainder & CRC32_TOPBIT) { + remainder = (remainder << 1) ^ CRC32_POLYNOMIAL; + } + else { + remainder = (remainder << 1); + } + } + } + + // The final remainder is the CRC result. + return remainder; +} + +// ref.: CRC-16/CCITT-FALSE, alias: CRC-16/AUTOSAR +// https://reveng.sourceforge.io/crc-catalogue/16.htm#crc.cat.crc-16-xmodem +// initial: 0xFFFF, xorOut: 0x0000, RefIn: false, RefOut: false, polynomial: 0x1021 +uint16_t calc_crc16_unreflected(const uint8_t *data, uint32_t len, uint16_t remainder, uint16_t final_xor) +{ + uint16_t crc = remainder; + uint16_t temp; + + // unreflected + while (len-- != 0) + { + temp = (*data++ ^ (crc >> 8)) & 0xff; + crc = crc16_0x1021_table[temp] ^ (crc << 8); + } + + crc ^= final_xor; + + return crc; +} + +void calc_crc16_byte_unreflected(uint16_t *crc16, uint8_t bt) +{ + uint16_t temp; + temp = *crc16; + + // unreflected + *crc16 = crc16_0x1021_table[((temp >> 8) ^ bt) & 0xff] ^ (temp << 8); +} + +// initial: 0xFFFF, xorOut: 0x0000, RefIn: false, RefOut: false, polynomial: 0x1021 +uint16_t calc_crc16_buff_unreflected(uint8_t *data, uint16_t len) +{ + uint16_t crc16 = 0xFFFF; + + // unreflected + while (len-- != 0) + { + crc16 = crc16_0x1021_table[((crc16 >> 8) ^ *data++) & 0xff] ^ (crc16 << 8); + } + + return crc16; +} + +// ref.: CRC-16/X25 +// initial: 0xFFFF, xorOut: 0xFFFF, RefIn: true, RefOut: true, polynomial: 0x1021 +uint16_t calc_crc16_reflected(const uint8_t *data, uint32_t len, uint16_t remainder, uint16_t final_xor) +{ + uint16_t crc16 = remainder; + + // reflected + while (len-- != 0) + { + crc16 = crc16_0x1021_table_reverse[(crc16 ^ *data++) & 0xff] ^ (crc16 >> 8); + } + + return (crc16 ^ final_xor); +} + +void calc_crc16_byte_reflected(uint16_t *crc16, uint8_t bt) +{ + uint16_t temp; + temp = *crc16; + + // reflected + *crc16 = crc16_0x1021_table_reverse[(temp ^ bt) & 0xff] ^ (temp >> 8); +} + +// initial: 0xFFFF, xorOut: 0xFFFF, RefIn: true, RefOut: true, polynomial: 0x1021 +uint16_t calc_crc16_buff_reflected(const uint8_t *data, uint16_t len) +{ + uint16_t crc16 = 0xFFFF; + + // reflected + while (len-- != 0) + { + crc16 = crc16_0x1021_table_reverse[(crc16 ^ *data++) & 0xff] ^ (crc16 >> 8); + } + + return (crc16 ^ 0xFFFF); +} diff --git a/thirdparty/tas/hdlc.c b/thirdparty/tas/hdlc.c new file mode 100644 index 0000000..92f9817 --- /dev/null +++ b/thirdparty/tas/hdlc.c @@ -0,0 +1,90 @@ +//************************************************************************************** +/*! \copyright: 2020-2021 Thales Alenia Space Deutschland GmbH +* \project: multiMIND +* \file: (name of source file: hdlc.c) +* \date: (09.02.2022) +* \author: (Stelios Filippopoulos) +* \brief: (hdlc functions) +* \language: (C) +************************************************************************************** +*/ + +#include "tas/hdlc.h" +#include "tas/crc.h" + +#include + +void hdlc_add_byte(uint8_t ch, uint8_t *buff, size_t *pos) +{ + size_t templen = *pos; + + if ((ch == 0x7E) || + (ch == 0x7D) || + (ch == 0x7C)) + { + buff[templen++] = 0x7D; + ch ^= 0x20; + } + buff[templen++] = ch; + + *pos = templen; +} + +void hdlc_add_framing(const uint8_t *src, size_t slen, uint8_t *dst, size_t *dlen) +{ + size_t tlen = 0; + uint16_t ii; + uint16_t crc16; + uint8_t bt; + + // calc crc16 + crc16 = calc_crc16_buff_reflected( src, slen ); + + dst[tlen++] = 0x7E; + for (ii = 0; ii < slen; ii++) + { + bt = *src++; + hdlc_add_byte(bt, dst, &tlen); + } + + // hdlc crc16 is in little endian format + // WARNING: This is not portable code! Bytes need to be swapped on a big + // endian system + // TODO: Fix + hdlc_add_byte((uint8_t) (crc16 & 0xFF), dst, &tlen); + hdlc_add_byte((uint8_t) ((crc16 >> 8) & 0xFF), dst, &tlen); + + dst[tlen++] = 0x7C; + *dlen = tlen; +} + +int hdlc_remove_framing_with_crc_check(const uint8_t *src, size_t slen, uint8_t *dst, size_t *dlen) +{ + uint16_t tlen = 0; + uint16_t ii; + uint8_t bt; + + *dlen = 0; + if (slen < 4) return -1; + if ((src[tlen] != 0x7E) && (src[slen-1] != 0x7C)) return -2; + src++; + for (ii = 1; ii < slen-1; ii++) + { + bt = *src++; + + if (bt == 0x7D) + { + bt = *src++ ^ 0x20; + ii++; + } + dst[tlen++] = bt; + } + // calc crc16 + if(calc_crc16_buff_reflected( dst, tlen ) != 0x0f47) { + return 1; + } + *dlen = tlen - 2; + return 0; +} + + diff --git a/thirdparty/tas/tas/crc.h b/thirdparty/tas/tas/crc.h new file mode 100644 index 0000000..0a45653 --- /dev/null +++ b/thirdparty/tas/tas/crc.h @@ -0,0 +1,116 @@ +/*************************************************************************************** +* \copyright: 2020-2022 Thales Alenia Space Deutschland GmbH +* \project: multiMIND +* \file: crc.c +* \date: 22.02.2022 +* \author: David Woodward +* \brief: CRC algorithms +***************************************************************************************/ + +#ifndef TAS_D_C_CRC_H +#define TAS_D_C_CRC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// NOTE: These defines are in the header as some are needed for (initial) crc function calls +//CRC-32/BZIP2 +#define CRC32_TOPBIT (1UL<<31) +#define CRC32_POLYNOMIAL 0x04C11DB7 +#define CRC32_INITIAL_REMAINDER 0xFFFFFFFF +#define CRC32_FINAL_XOR_VALUE 0xFFFFFFFF + +// CRC-16/CCITT-FALSE +#define CRC16_INITIAL_REMAINDER 0xFFFF +#define CRC16_FINAL_XOR_VALUE 0x0 + +extern const uint16_t crc16_0x1021_table[256]; + +extern const uint16_t crc16_0x1021_table_reverse[256]; + +/** + * \brief CRC-32/BZIP2 algorithm + */ +uint32_t Crc32(const uint8_t *msg, int numBytes, uint32_t remainder); + + +/** + * \brief CRC-16/CCITT-FALSE (alias CRC-16/AUTOSAR) algorithm, +// initial: 0xFFFF, xorOut: 0x0000, RefIn: false, RefOut: false, polynomial: 0x1021 + * using a lookup table + * \param data Data + * \param len Data length + * \param remainder Remainder to be used, + * use initial remainder for non coherent/standalone calculations + * \param final_xor The value that the final result will be xored + * \return CRC result + */ +uint16_t calc_crc16_unreflected(const uint8_t *data, uint32_t len, uint16_t remainder, uint16_t final_xor); + +/** + * generates a 16-bit CRC for the said data + * + * @param data input data for CRC + * @param len length of the data + * @return crc Generated 16-bit CRC + */ +void calc_crc16_byte_unreflected(uint16_t *crc16, uint8_t bt); + +/** + * \brief CRC-16/CCITT-FALSE (alias CRC-16/AUTOSAR) algorithm, + * polynomial: 0x1021, initial: 0xFFFF, final xor: 0x0, + * using a lookup table + * \param data Data + * \param len Data length + * \param remainder Remainder to be used, + * use initial remainder for non coherent/standalone calculations + * \param final_xor The value that the final result will be xored + * \return CRC result + */ +uint16_t calc_crc16_buff_unreflected(uint8_t *data, uint16_t len); + +/** + * \brief CRC-16/X25 algorithm, + * initial: 0xFFFF, xorOut: 0xFFFF, RefIn: true, RefOut: true, polynomial: 0x1021 + * using a lookup table + * \param data Data + * \param len Data length + * \param remainder Remainder to be used, + * use initial remainder for non coherent/standalone calculations + * \param final_xor The value that the final result will be xored + * \return CRC result + */ +uint16_t calc_crc16_reflected(const uint8_t *data, uint32_t len, uint16_t remainder, uint16_t final_xor); + +/** + * \brief CRC-16/X25 algorithm, + * calculates the crc16 for the next byte, given an already calculated crc16 + * + * @param *crc16 : calculated crc16 - the value will be updated + * @param bt : next byte for crc16 calculation + * @return none + */ +void calc_crc16_byte_reflected(uint16_t *crc16, uint8_t bt); + +/** + * \brief CRC-16/X25 algorithm, + * initial: 0xFFFF, xorOut: 0xFFFF, RefIn: true, RefOut: true, polynomial: 0x1021 + * using a lookup table + * \param data Data + * \param len Data length + * \param remainder Remainder to be used, + * use initial remainder for non coherent/standalone calculations + * \param final_xor The value that the final result will be xored + * \return CRC result + */ +uint16_t calc_crc16_buff_reflected(const uint8_t *data, uint16_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/tas/tas/hdlc.h b/thirdparty/tas/tas/hdlc.h new file mode 100644 index 0000000..b137fc4 --- /dev/null +++ b/thirdparty/tas/tas/hdlc.h @@ -0,0 +1,52 @@ +//************************************************************************************** +/*! \copyright: 2020-2021 Thales Alenia Space Deutschland GmbH +* \project: multiMIND +* \file: (name of source file: hdlc.h) +* \date: (09.02.2022) +* \author: (Stelios Filippopoulos) +* \brief: (hdlc header file) +* \language: (C) +************************************************************************************** +*/ + +#ifndef LIB_HDLC_H_ +#define LIB_HDLC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define HDLC_ENABLE + +#define HDLC_START_BYTE (0x7Eu) +#define HDLC_ESC_BYTE (0x7Du) +#define HDLC_END_BYTE (0x7Cu) +#define HDLC_ESCAPE_CHAR (0x20u) + +void hdlc_add_byte(uint8_t ch, uint8_t *buff, size_t *pos); + +void hdlc_add_framing(const uint8_t *src, size_t slen, uint8_t *dst, size_t *dlen); + +/** + * Decode a HDLC frame, including CRC check and CRC removal in addition + * to the removal of the frame markers. + * @param src + * @param slen + * @param dst + * @param dlen + * @return + * -1 Invalid source length + * -2 No start marker at first byte or end marker at slen - 1 + * 1 Invalid CRC + * 0 CRC OK, framing and CRC removed + */ +int hdlc_remove_framing_with_crc_check(const uint8_t *src, size_t slen, uint8_t *dst, size_t *dlen); + +#ifdef __cplusplus +} +#endif + +#endif /* LIB_HDLC_H_ */ diff --git a/thirdparty/tas/tas/uart.h b/thirdparty/tas/tas/uart.h new file mode 100644 index 0000000..233a79d --- /dev/null +++ b/thirdparty/tas/tas/uart.h @@ -0,0 +1,124 @@ +//************************************************************************************** +/*! \copyright: 2020-2021 Thales Alenia Space Deutschland GmbH +* \project: multiMIND +* \file: (name of source file: uart.h) +* \date: (20.05.2021) +* \author: (Sarthak Kelapure) +* \brief: (UART thread to collect data on serial interface) +* \language: (C) +************************************************************************************** +*/ +#ifndef LIB_UART_H +#define LIB_UART_H + +#define BUFF_SIZE 512 +#define POLL_TIMEOUT 2000 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct serial_s serial_t; + +/** + * Destroy the serial structure + */ +void uart_destroy(serial_t* s); + +/** + * Initializes the serial connection + * @param device - serial device name. + * @param baud - baud rate for connection. + * @return serial structure. + */ +serial_t* uart_init(char device[], int baud); + +/** + * Send data. + * @param s - serial structure. + * @param data - character array to transmit. + * @param length - size of the data array. + */ +uint32_t uart_length_send(serial_t* s, uint8_t data[], int length); + +/** + * Send a single character. + * @param s - serial structure. + * @param data - single character to be sent. + */ +void uart_send(serial_t* s, uint8_t data); + +/** + * Determine how much data is available + * in the serial buffer. + * @param s - serial structure. + * @return number of characters available. + */ +int uart_available(serial_t* s); + +/** + * Fetch one char from the serial buffer. + * @param s - serial structure. + * @return character. Null if empty. + */ +char uart_get(serial_t* s); + +/** + * Fetch length of chars from the serial buffer. + * @param s - serial structure. + * @param buff - readback storage + * @param len - length to get + * @return length. zero if empty. + */ +int uart_length_get(serial_t* s, char* buff, int len, bool start_of_packet); + +uint16_t uart_get_hdlc_packet(serial_t* s, uint8_t *buff, uint16_t buff_len); + +/** + * Fetch one char from the serial buffer. + * Blocks until data becomes available. + * @param s - serial structure. + * @return character. + */ +char uart_blocking_get(serial_t* s); + +/** + * Clear the serial buffer. + * @param s - serial structure. + */ +void uart_clear(serial_t* s); + +/** + * Close the serial port. + * @param s - serial structure. + * @return value of close(). + */ +int uart_close(serial_t* s); + +/** + * Deinitializes the UART + * @param s - serial structure. + */ +void uart_deinit(serial_t* s); + +#ifdef __cplusplus +} +#endif + +#endif //LIB_UART_H diff --git a/thirdparty/tas/uart.c b/thirdparty/tas/uart.c new file mode 100644 index 0000000..11e118e --- /dev/null +++ b/thirdparty/tas/uart.c @@ -0,0 +1,603 @@ +//************************************************************************************** +/*! \copyright: 2020-2021 Thales Alenia Space Deutschland GmbH + * \project: multiMIND + * \file: (name of source file: uart.c) + * \date: (20.05.2021) + * \author: (Sarthak Kelapure) + * \brief: (UART thread to collect data on serial interface) + * \language: (C) + ************************************************************************************** + */ + +#include "tas/uart.h" +#include "tas/hdlc.h" + +#ifdef HDLC_ENABLE +#define HDLC_RX_STATE_IDLE (0u) +#define HDLC_RX_STATE_RECEIVING (1u) +#define HDLC_RX_STATE_ESCAPE (2u) +#endif + +/** + * @struct Serial device structure. + * Encapsulates a serial connection. + */ +struct serial_s { + int fd; //>! Connection file descriptor. + int state; //>! Signifies connection state. + int running; //>! Signifies thread state. + + char rxbuff[BUFF_SIZE]; //>! Buffer for RX data. + int start, end; //>! Pointers to start and end of buffer. + + pthread_t rx_thread; //>! Listening thread. +}; + +// --------------- Internal Functions --------------- + +/** + * Connect to a serial device. + * @param s - serial structure. + * @param device - serial device name. + * @param baud - baud rate for connection. + * @return -ve on error, 0 on success. + */ +static int serial_connect(serial_t* s, char device[], int baud); + +/** + * Create the serial structure. + * Convenience method to allocate memory + * and instantiate objects. + * @return serial structure. + */ +static serial_t* serial_create(); + +static int serial_resolve_baud(int baud); + +/** + * Recieve data. + * Retrieves data from the serial device. + * @param s - serial structure. + * @param data - pointer to a buffer to read data into. + * @param maxLength - size of input buffer. + * @return amount of data recieved. + */ +static int serial_recieve(serial_t* obj, uint8_t data[], int maxLength); + +/** + * @brief Serial Listener Thread. + * This blocks waiting for data to be recieved from the serial device, + * and calls the serial_callback method with appropriate context when + * data is recieved. + * Exits when close method is called, or serial error occurs. + * @param param - context passed from thread instantiation. + */ +static void *serial_data_listener(void *param); + +/** + * @brief Start the serial threads. + * This spawns the listening and transmitting threads + * if they are not already running. + * @param s - serial structure. + * @return 0 on success, or -1 on error. + */ +static int serial_start(serial_t* s); + +/** + * Stop serial listener thread. + * @param s - serial structure. + * @return 0; + */ +static int serial_stop(serial_t* s); + +/** + * Callback to handle recieved data. + * Puts recieved data into the rx buffer. + * @param s - serial structure. + * @param data - data to be stored. + * @param length - length of recieved data. + */ +static void serial_rx_callback(serial_t* s, char data[], int length); + +// Put character in rx buffer. +static int buffer_put(serial_t* s, char c) +{ + //if there is space in the buffer + if ( s->end != ((s->start + BUFF_SIZE - 1) % BUFF_SIZE)) { + s->rxbuff[s->end] = c; + s->end ++; + s->end = s->end % BUFF_SIZE; + //printf("Put: %x start: %d, end: %d\r\n", c, s->start, s->end); + return 0; //No error + } else { + //buffer is full, this is a bad state + return -1; //Report error + } +} + +// Get character from rx buffer. +static char buffer_get(serial_t* s) +{ + char c = (char)0; + //if there is data to process + if (s->end != s->start) { + c = (s->rxbuff[s->start]); + s->start ++; + //wrap around + s->start = s->start % BUFF_SIZE; + } else { + } + //printf("Get: %x start: %d, end: %d\r\n", c, s->start, s->end); + return c; +} + +//Get data available in the rx buffer. +static int buffer_available(serial_t* s) +{ + return (s->end - s->start + BUFF_SIZE) % BUFF_SIZE; +} + +// --------------- External Functions --------------- + +//Create serial object. +serial_t* serial_create() +{ + //Allocate serial object. + serial_t* s = malloc(sizeof(serial_t)); + //Reconfigure buffer object. + s->start = 0; + s->end = 0; + //Return pointer. + return s; +} + + +void uart_destroy(serial_t* s) +{ + free(s); +} + + +//Connect to serial device. +int serial_connect(serial_t* s, char device[], int baud) +{ + struct termios oldtio; + + // Resolve baud. + int speed = serial_resolve_baud(baud); + if (speed < 0) { + printf("Error: Baud rate not recognized.\r\n"); + return -1; + } + + //Open device. + s->fd = open(device, O_RDWR | O_NOCTTY); + //Catch file open error. + if (s->fd < 0) { + perror(device); + return -2; + } + //Retrieve settings. + tcgetattr(s->fd, &oldtio); + //Set baud rate. + cfsetspeed(&oldtio, speed); + //Flush cache. + tcflush(s->fd, TCIFLUSH); + + //Set UART settings, standard ones. 8N1 + oldtio.c_cflag = (oldtio.c_cflag & ~CSIZE) | CS8; // 8-bit chars + // disable IGNBRK for mismatched speed tests; otherwise receive break + // as \000 chars + oldtio.c_iflag &= ~IGNBRK; // disable break processing + oldtio.c_lflag = 0; // no signaling chars, no echo, + // no canonical processing + oldtio.c_oflag = 0; // no remapping, no delays + oldtio.c_cc[VMIN] = 0; // read doesn't block + oldtio.c_cc[VTIME] = 5; // 0.5 seconds read timeout + + oldtio.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl + oldtio.c_iflag &= ~(IGNCR | ICRNL | INLCR); // CR and LF characters are not affected + + oldtio.c_cflag |= (CLOCAL | CREAD);// ignore modem controls, + // enable reading + oldtio.c_cflag &= ~(PARENB | PARODD); // shut off parity + oldtio.c_cflag |= 0; + oldtio.c_cflag &= ~CSTOPB; + oldtio.c_cflag &= ~(020000000000); + //Apply settings. + if(tcsetattr(s->fd, TCSANOW, &oldtio) !=0){ + printf("ERROR: serial settings failed\r\n"); + return -1; + } + + //Start listener thread. + int res = serial_start(s); + //Catch error. + if (res < 0) { + printf("Error: serial thread could not be spawned\r\n"); + return -3; + } + + //Indicate connection was successful. + s->state = 1; + return 0; +} + +serial_t* uart_init(char device[], int baud) +{ + serial_t* s = serial_create(); + if(serial_connect(s, device, baud)< 0) + { + return NULL; + } + return s; +} + +//Send data. +uint32_t uart_length_send(serial_t* s, uint8_t data[], int length) +{ +// uint16_t ii; +// int res; +// for (ii = 0; ii < length; ii++) +// { +// res = write(s->fd, &data[ii], 1); +// } + int res = write(s->fd, data, length); + return res; +} + +void uart_send(serial_t* s, uint8_t data) +{ + char arr[1]; + arr[0] = data; + write(s->fd, arr, 1); +} + +//Determine characters available. +int uart_available(serial_t* s) +{ + return buffer_available(s); +} + +//Fetch a character. +char uart_get(serial_t* s) +{ + char c = buffer_get(s); + + return c; +} + +int uart_length_get(serial_t* s, char* buff, int len, bool start_of_packet) +{ + int ret = 0; + if (len > 0 && len < BUFF_SIZE) + { +#ifdef HDLC_ENABLE + uint8_t ch; + uint8_t hdlc_rx_state; + int rxb = 0; + + if (start_of_packet) + hdlc_rx_state = HDLC_RX_STATE_IDLE; + else + hdlc_rx_state = HDLC_RX_STATE_RECEIVING; + + while (rxb < len) + { + ch = uart_blocking_get(s); + + switch (hdlc_rx_state) + { + case HDLC_RX_STATE_IDLE: + if (ch == HDLC_START_BYTE) + { + rxb = 0; + ret = 0; + hdlc_rx_state = HDLC_RX_STATE_RECEIVING; + } + break; + case HDLC_RX_STATE_RECEIVING: + if (ch == HDLC_START_BYTE) + { + rxb = 0; + ret = 0; + break; + } + if (ch == HDLC_ESC_BYTE) + { + hdlc_rx_state = HDLC_RX_STATE_ESCAPE; + break; + } + buff[rxb++] = ch; + ret++; + break; + case HDLC_RX_STATE_ESCAPE: + if (ch == HDLC_START_BYTE) + { + rxb = 0; + ret = 0; + break; + } + buff[rxb++] = ch ^ HDLC_ESCAPE_CHAR; + ret++; + hdlc_rx_state = HDLC_RX_STATE_RECEIVING; + break; + } + } +#else + for (int i=0;i 2u) // do not include HDLC CRC16 + { + return buff_pos; + } + buff_pos = 0u; + hdlc_rx_state = HDLC_RX_STATE_IDLE; + break; + } + if (ch == HDLC_ESC_BYTE) + { + hdlc_rx_state = HDLC_RX_STATE_ESCAPE; + break; + } + if (buff_pos >= buff_len) + { + hdlc_rx_state = HDLC_RX_STATE_RECEIVING; + break; + } + buff[buff_pos++] = ch; + break; + + case HDLC_RX_STATE_ESCAPE: + if ((ch == HDLC_START_BYTE) || (ch == HDLC_END_BYTE)) + { + buff_pos = 0; + hdlc_rx_state = HDLC_RX_STATE_RECEIVING; + break; + } + if (buff_pos >= buff_len) + { + hdlc_rx_state = HDLC_RX_STATE_RECEIVING; + break; + } + buff[buff_pos++] = ch ^ HDLC_ESCAPE_CHAR; + hdlc_rx_state = HDLC_RX_STATE_RECEIVING; + break; + + default: + buff_pos = 0u; + hdlc_rx_state = HDLC_RX_STATE_IDLE; + break; + } + } +} + +char uart_blocking_get(serial_t* s) +{ + while (uart_available(s) == 0); + return uart_get(s); +} + +void uart_clear(serial_t* s) +{ + //Clear the buffer. + while (buffer_available(s)) { + buffer_get(s); + } + tcflush(s->fd, TCIFLUSH); +} + +//Close serial port. +int uart_close(serial_t* s) +{ + //Stop thread. + serial_stop(s); + return 0; +} + +void uart_deinit(serial_t* s){ + uart_clear(s); + uart_close(s); + uart_destroy(s); +} + +// --------------- Internal Functions -------------- + +//Stop serial listener thread. +static int serial_stop(serial_t* s) +{ + s->running = 0; + return close(s->fd); +} + +// Resolves standard baud rates to linux constants. +static int serial_resolve_baud(int baud) +{ + int speed; + // Switch common baud rates to temios constants. + switch (baud) { + case 9600: + speed = B9600; + break; + case 19200: + speed = B19200; + break; + case 38400: + speed = B38400; + break; + case 57600: + speed = B57600; + break; + case 115200: + speed = B115200; + break; + case 230400: + speed = B230400; + break; + case 460800: + speed = B460800; + break; + case 500000: + speed = B500000; + break; + case 576000: + speed = B576000; + break; + case 921600: + speed = B921600; + break; + case 1000000: + speed = B1000000; + break; + case 1152000: + speed = B1152000; + break; + case 1500000: + speed = B1500000; + break; + case 2000000: + speed = B2000000; + break; + case 3000000: + speed = B3000000; + break; + default: + speed = -1; + break; + } + // Return. + return speed; +} + +// Start serial listener. +static int serial_start(serial_t* s) +{ + //Only start if it is not currently running. + if (s->running != 1) { + //Set running. + s->running = 1; + //Spawn thread. + int res; + res = pthread_create(&s->rx_thread, NULL, serial_data_listener, (void*) s); + if (res != 0) { + return -2; + } + //Return result. + return 0; + } else { + return -1; + } +} + + + +//Recieve data. +static int serial_recieve(serial_t* s, uint8_t data[], int maxLength) +{ + return read(s->fd, data, maxLength); +} + +//Callback to store data in buffer. +static void serial_rx_callback(serial_t* s, char data[], int length) +{ + //Put data into buffer. + int i; + //Put data into buffer. + for (i = 0; i < length; i++) { + buffer_put(s, data[i]); + } + +} + +//Serial data listener thread. +static void *serial_data_listener(void *param) +{ + int res = 0; + int err = 0; + struct pollfd ufds; + uint8_t buff[BUFF_SIZE]; + + //Retrieve paramaters and store locally. + serial_t* serial = (serial_t*) param; + int fd = serial->fd; + + //Set up poll file descriptors. + ufds.fd = fd; //Attach socket to watch. + ufds.events = POLLIN; //Set events to notify on. + + //Run until ended. + while (serial->running != 0) { + //Poll socket for data. + res = poll(&ufds, 1, POLL_TIMEOUT); + //If data was recieved. + if (res > 0) { + //Fetch the data. + int count = serial_recieve(serial, buff, BUFF_SIZE - 1); + //If data was recieved. + if (count > 0) { + //Pad end of buffer to ensure there is a termination symbol. + buff[count] = '\0'; + // Call the serial callback. + serial_rx_callback(serial, (char *)buff, count); + //If an error occured. + } else if (count < 0) { + //Inform user and exit thread. + printf("Error: Serial disconnect\r\n"); + err = 1; + break; + } + //If there was an error. + } else if (res < 0) { + //Inform user and exit thread. + printf("Error: Polling error in serial thread"); + err = 1; + break; + } + //Otherwise, keep going around. + } + //If there was an error, close socket. + if (err) { + uart_close(serial); + //raise(SIGLOST); + } + //Close file. + res = close(serial->fd); + + return NULL; +} diff --git a/tmtc b/tmtc new file mode 160000 index 0000000..7721a57 --- /dev/null +++ b/tmtc @@ -0,0 +1 @@ +Subproject commit 7721a57a67df71718b09c73e43cdd7f34a67548b diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt new file mode 100644 index 0000000..808a68e --- /dev/null +++ b/unittest/CMakeLists.txt @@ -0,0 +1,11 @@ +add_subdirectory(controller) +add_subdirectory(mocks) + +target_sources(${UNITTEST_NAME} PRIVATE + main.cpp + testEnvironment.cpp + testGenericFilesystem.cpp + hdlcEncodingRw.cpp + mpsocTests.cpp + printChar.cpp +) diff --git a/unittest/controller/CMakeLists.txt b/unittest/controller/CMakeLists.txt new file mode 100644 index 0000000..7680eef --- /dev/null +++ b/unittest/controller/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(${UNITTEST_NAME} PRIVATE + testThermalController.cpp + testAcsController.cpp + testConfigFileHandler.cpp +) diff --git a/unittest/controller/testAcsController.cpp b/unittest/controller/testAcsController.cpp new file mode 100644 index 0000000..acc4dfe --- /dev/null +++ b/unittest/controller/testAcsController.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include +#include +#include +#include + +#include "../testEnvironment.h" +#include "rapidcsv.h" + +TEST_CASE("ACS Controller", "[AcsController]") { + SECTION("SectionTest") { + // acsCtrl.performOperation(); + CHECK(1 == 1); + + try { + { + std::ifstream file("unittest/meineTestDaten.txt"); + if (file.good()) { + std::string line; + while (std::getline(file, line)) { + std::cout << "ACS CTRL Test test file: " << line << std::endl; + } + } + } + + { + rapidcsv::Document doc("unittest/meineTestDaten.txt"); + std::vector col = doc.GetColumn("test0"); + std::cout << "Read " << col.size() << " values: " << col[0] << ", " << col[1] << std::endl; + } + } catch (const std::exception& e) { + sif::warning << "CSV file test exception occured: " << e.what() << std::endl; + } + + REQUIRE(2 == 2); + } + + SECTION("SectionTest2") {} +} diff --git a/unittest/controller/testConfigFileHandler.cpp b/unittest/controller/testConfigFileHandler.cpp new file mode 100644 index 0000000..b8147e2 --- /dev/null +++ b/unittest/controller/testConfigFileHandler.cpp @@ -0,0 +1,65 @@ + + +#include +#include + +#include +#include +#include +#include + +#include "../testEnvironment.h" + +TEST_CASE("Configfile Handler", "[ConfigHandler]") { + sif::debug << "Testcase config file handler" << std::endl; + // Init handler + GlobalConfigHandler confighandler = GlobalConfigHandler(objects::GLOBAL_JSON_CFG, "JSON.config"); + REQUIRE(confighandler.initialize() == returnvalue::OK); + + // Reset handler + REQUIRE(confighandler.ResetConfigFile() == returnvalue::OK); + + // Get and set double as well as int values + double doubleData = 0.0; + REQUIRE(confighandler.getConfigFileValue(PARAM0, doubleData) == returnvalue::OK); + REQUIRE(doubleData == 5.0); + + doubleData = 55.9; + double doubleDataRead = 0; + REQUIRE(confighandler.setConfigFileValue(PARAM0, doubleData) == returnvalue::OK); + + REQUIRE(confighandler.getConfigFileValue(PARAM0, doubleDataRead) == returnvalue::OK); + REQUIRE(doubleDataRead == doubleData); + + REQUIRE(confighandler.WriteConfigFile() == returnvalue::OK); + + int intData = 0; + + REQUIRE(confighandler.getConfigFileValue(PARAM1, intData) == returnvalue::OK); + REQUIRE(intData == 905); + + intData = 1337; + int intDataRead = 0; + REQUIRE(confighandler.setConfigFileValue(PARAM1, intData) == returnvalue::OK); + + REQUIRE(confighandler.getConfigFileValue(PARAM1, intDataRead) == returnvalue::OK); + REQUIRE(intDataRead == intData); + + REQUIRE(confighandler.WriteConfigFile() == returnvalue::OK); + + // Check file name + REQUIRE(confighandler.getConfigFileName() == "JSON.config"); + + // Reset and check if it worked + REQUIRE(confighandler.ResetConfigFile() == returnvalue::OK); + + doubleData = 0.0; + REQUIRE(confighandler.getConfigFileValue(PARAM0, doubleData) == returnvalue::OK); + REQUIRE(doubleData == 5.0); + + // Test invalid Parameter + REQUIRE(confighandler.getConfigFileValue(PARAM2, doubleData) != + returnvalue::OK); // NVMParameterBase::KEY_NOT_EXISTS is private, why? + REQUIRE(confighandler.setConfigFileValue(PARAM2, doubleData) != returnvalue::OK); + std::filesystem::remove("JSON.config"); +} diff --git a/unittest/controller/testThermalController.cpp b/unittest/controller/testThermalController.cpp new file mode 100644 index 0000000..287943c --- /dev/null +++ b/unittest/controller/testThermalController.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../testEnvironment.h" +#include "dummies/TemperatureSensorInserter.h" +#include "mission/genericFactory.h" +#include "test/gpio/DummyGpioIF.h" + +TEST_CASE("Thermal Controller", "[ThermalController]") { + const object_id_t THERMAL_CONTROLLER_ID = 0x123; + std::atomic_bool tcsBrdShortlyUnavailable = false; + + TemperatureSensorInserter::Max31865DummyMap map0; + TemperatureSensorInserter::Tmp1075DummyMap map1; + new TemperatureSensorInserter(objects::THERMAL_TEMP_INSERTER, map0, map1); + auto dummyGpioIF = new DummyGpioIF(); + auto* dummySwitcher = new DummyPowerSwitcher(objects::PCDU_HANDLER, 18, 0); + new CamSwitcher(objects::CAM_SWITCHER, *dummySwitcher, power::Switches::PDU2_CH8_PAYLOAD_CAMERA); + // TODO: Create dummy heater handler + HeaterHandler* heaterHandler = nullptr; + // new ThermalController(objects::THERMAL_CONTROLLER); + ObjectFactory::createGenericHeaterComponents(*dummyGpioIF, *dummySwitcher, heaterHandler); + + // testEnvironment::initialize(); + + ThermalController controller(THERMAL_CONTROLLER_ID, *heaterHandler, tcsBrdShortlyUnavailable, + true); + ReturnValue_t result = controller.initialize(); + REQUIRE(result == returnvalue::OK); + + PeriodicTaskIF* thermalTask = TaskFactory::instance()->createPeriodicTask( + "THERMAL_CTL_TASK", 40, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + result = thermalTask->addComponent(THERMAL_CONTROLLER_ID); + REQUIRE(result == returnvalue::OK); + + REQUIRE(controller.initializeAfterTaskCreation() == returnvalue::OK); + + testEnvironment::eventManager->clearEventList(); + + MessageQueueId_t controllerQueue = controller.getCommandQueue(); + + CommandMessage modeMessage; + + ModeMessage::setModeMessage(&modeMessage, ModeMessage::CMD_MODE_COMMAND, HasModesIF::MODE_ON, + HasModesIF::SUBMODE_NONE); + + MessageQueueIF* commandQueue = + QueueFactory::instance()->createMessageQueue(5, MessageQueueMessage::MAX_MESSAGE_SIZE); + + commandQueue->sendMessage(controllerQueue, &modeMessage); + + REQUIRE(controller.performOperation(0) == returnvalue::OK); + + REQUIRE(testEnvironment::eventManager->isEventInEventList( + THERMAL_CONTROLLER_ID, HasModesIF::MODE_INFO, HasModesIF::MODE_ON, + HasModesIF::SUBMODE_NONE) == true); + + QueueFactory::instance()->deleteMessageQueue(commandQueue); +} diff --git a/unittest/hdlcEncodingRw.cpp b/unittest/hdlcEncodingRw.cpp new file mode 100644 index 0000000..10c71bc --- /dev/null +++ b/unittest/hdlcEncodingRw.cpp @@ -0,0 +1,38 @@ + + +#include + +#include "mission/acs/rwHelpers.h" + +TEST_CASE("HDLC Encoding RW", "[acs]") { + std::array encodedBuffer{}; + size_t encodedLen = 0; + SECTION("Basic No Escaped Bytes") { + std::array dummyNoEscaped = {1, 2, 3, 4}; + + rws::encodeHdlc(dummyNoEscaped.data(), dummyNoEscaped.size(), encodedBuffer.data(), encodedLen); + REQUIRE(encodedLen == 6); + REQUIRE(encodedBuffer[0] == rws::FRAME_DELIMITER); + REQUIRE(encodedBuffer[1] == 1); + REQUIRE(encodedBuffer[2] == 2); + REQUIRE(encodedBuffer[3] == 3); + REQUIRE(encodedBuffer[4] == 4); + REQUIRE(encodedBuffer[5] == rws::FRAME_DELIMITER); + } + + SECTION("Basic Some Escaped Bytes") { + std::array dummySomeEscaped = {1, 0x7D, 0x7E, 4}; + + rws::encodeHdlc(dummySomeEscaped.data(), dummySomeEscaped.size(), encodedBuffer.data(), + encodedLen); + REQUIRE(encodedLen == 8); + REQUIRE(encodedBuffer[0] == rws::FRAME_DELIMITER); + REQUIRE(encodedBuffer[1] == 1); + REQUIRE(encodedBuffer[2] == 0x7D); + REQUIRE(encodedBuffer[3] == 0x5D); + REQUIRE(encodedBuffer[4] == 0x7D); + REQUIRE(encodedBuffer[5] == 0x5E); + REQUIRE(encodedBuffer[6] == 4); + REQUIRE(encodedBuffer[7] == rws::FRAME_DELIMITER); + } +} diff --git a/unittest/main.cpp b/unittest/main.cpp new file mode 100644 index 0000000..760ff15 --- /dev/null +++ b/unittest/main.cpp @@ -0,0 +1,13 @@ +#include + +#include "testEnvironment.h" + +int main(int argc, char* argv[]) { + testEnvironment::setup(); + testEnvironment::initialize(); + // Catch internal function call + int result = Catch::Session().run(argc, argv); + + // global clean-up + return result; +} diff --git a/unittest/meineTestDaten.txt b/unittest/meineTestDaten.txt new file mode 100644 index 0000000..0fc31c9 --- /dev/null +++ b/unittest/meineTestDaten.txt @@ -0,0 +1,3 @@ +test0,test1,test2 +0,2,3 +4,5,6 diff --git a/unittest/mocks/CMakeLists.txt b/unittest/mocks/CMakeLists.txt new file mode 100644 index 0000000..8f4a09c --- /dev/null +++ b/unittest/mocks/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(${UNITTEST_NAME} PRIVATE + EventManagerMock.cpp + HouseKeepingMock.cpp +) \ No newline at end of file diff --git a/unittest/mocks/EventManagerMock.cpp b/unittest/mocks/EventManagerMock.cpp new file mode 100644 index 0000000..acbf1a0 --- /dev/null +++ b/unittest/mocks/EventManagerMock.cpp @@ -0,0 +1,63 @@ +#include "EventManagerMock.h" + +#include + +EventManagerMock::EventManagerMock() : EventManager(objects::EVENT_MANAGER, 80) {} + +ReturnValue_t EventManagerMock::performOperation(uint8_t opCode) { + ReturnValue_t result = returnvalue::OK; + while (result == returnvalue::OK) { + EventMessage message; + result = eventReportQueue->receiveMessage(&message); + if (result == returnvalue::OK) { + notifyListeners(&message); + eventList.emplace_back(message); + } + } + return returnvalue::OK; +} + +const std::list* EventManagerMock::getEventList() { + // call performOperation() to get all events still in the message Queue into the list + this->performOperation(0); + return &eventList; +} + +void EventManagerMock::clearEventList() { // call performOperation() to get all events still in the + // message Queue into the list + this->performOperation(0); + eventList.clear(); +} + +bool EventManagerMock::isEventInEventList(object_id_t object, Event event) { + return isEventInEventList(object, event::getEventId(event)); +} + +bool EventManagerMock::isEventInEventList(object_id_t object, EventId_t eventId) { + // call performOperation() to get all events still in the message Queue into the list + this->performOperation(0); + for (auto iter = eventList.begin(); iter != eventList.end(); iter++) { + if ((iter->getReporter() == object) && (iter->getEventId() == eventId)) { + return true; + } + } + return false; +} + +bool EventManagerMock::isEventInEventList(object_id_t object, Event event, uint32_t parameter1, + uint32_t parameter2) { + return isEventInEventList(object, event::getEventId(event), parameter1, parameter2); +} + +bool EventManagerMock::isEventInEventList(object_id_t object, EventId_t eventId, + uint32_t parameter1, uint32_t parameter2) { + // call performOperation() to get all events still in the message Queue into the list + this->performOperation(0); + for (auto iter = eventList.begin(); iter != eventList.end(); iter++) { + if ((iter->getReporter() == object) && (iter->getEventId() == eventId) && + (iter->getParameter1() == parameter1) && (iter->getParameter2() == parameter2)) { + return true; + } + } + return false; +} diff --git a/unittest/mocks/EventManagerMock.h b/unittest/mocks/EventManagerMock.h new file mode 100644 index 0000000..8e471b5 --- /dev/null +++ b/unittest/mocks/EventManagerMock.h @@ -0,0 +1,29 @@ +#ifndef EVENTMANAGERMOCK_H_ +#define EVENTMANAGERMOCK_H_ + +#include + +#include + +class EventManagerMock : public EventManager { + public: + EventManagerMock(); + + virtual ReturnValue_t performOperation(uint8_t opCode) override; + + const std::list* getEventList(); + void clearEventList(); + + bool isEventInEventList(object_id_t object, Event event); + bool isEventInEventList(object_id_t object, Event event, uint32_t parameter1, + uint32_t parameter2); + + bool isEventInEventList(object_id_t object, EventId_t eventId); + bool isEventInEventList(object_id_t object, EventId_t eventId, uint32_t parameter1, + uint32_t parameter2); + + private: + std::list eventList; +}; + +#endif /* EVENTMANAGERMOCK_H_ */ \ No newline at end of file diff --git a/unittest/mocks/HouseKeepingMock.cpp b/unittest/mocks/HouseKeepingMock.cpp new file mode 100644 index 0000000..6f1f996 --- /dev/null +++ b/unittest/mocks/HouseKeepingMock.cpp @@ -0,0 +1,12 @@ +#include "HouseKeepingMock.h" + +#include +#include + +HouseKeepingMock::HouseKeepingMock() : SystemObject(objects::PUS_SERVICE_3_HOUSEKEEPING) { + auto mqArgs = MqArgs(objects::PUS_SERVICE_3_HOUSEKEEPING, static_cast(this)); + commandQueue = QueueFactory::instance()->createMessageQueue( + 5, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs); +} + +MessageQueueId_t HouseKeepingMock::getHkQueue() const { return commandQueue->getId(); } diff --git a/unittest/mocks/HouseKeepingMock.h b/unittest/mocks/HouseKeepingMock.h new file mode 100644 index 0000000..a916734 --- /dev/null +++ b/unittest/mocks/HouseKeepingMock.h @@ -0,0 +1,18 @@ +#ifndef HOUSEKEEPINGMOCK_H_ +#define HOUSEKEEPINGMOCK_H_ + +#include +#include +#include + +class HouseKeepingMock : public SystemObject, public AcceptsHkPacketsIF { + public: + HouseKeepingMock(); + + virtual MessageQueueId_t getHkQueue() const; + + private: + MessageQueueIF *commandQueue = nullptr; +}; + +#endif /*HOUSEKEEPINGMOCK_H_*/ diff --git a/unittest/mpsocTests.cpp b/unittest/mpsocTests.cpp new file mode 100644 index 0000000..2ab11b2 --- /dev/null +++ b/unittest/mpsocTests.cpp @@ -0,0 +1,14 @@ + +#include + +TEST_CASE("MPSoC helper tests", "[payload]") { + char divStr[16]{}; + uint32_t divParam = 0; + + SECTION("Simple Test") { + divParam = 3; + CHECK(divParam < 999); + sprintf(divStr, "DIV%03u", divParam); + REQUIRE(strcmp(divStr, "DIV003") == 0); + } +} diff --git a/unittest/printChar.cpp b/unittest/printChar.cpp new file mode 100644 index 0000000..0e29fe2 --- /dev/null +++ b/unittest/printChar.cpp @@ -0,0 +1,11 @@ +#include "printChar.h" + +#include + +void printChar(const char* character, bool errStream) { + if (errStream) { + std::putc(*character, stderr); + return; + } + std::putc(*character, stdout); +} diff --git a/unittest/printChar.h b/unittest/printChar.h new file mode 100644 index 0000000..1991161 --- /dev/null +++ b/unittest/printChar.h @@ -0,0 +1,6 @@ +#ifndef FSFW_UNITTEST_CORE_PRINTCHAR_H_ +#define FSFW_UNITTEST_CORE_PRINTCHAR_H_ + +extern "C" void printChar(const char*, bool errStream); + +#endif /* FSFW_UNITTEST_CORE_PRINTCHAR_H_ */ diff --git a/unittest/rebootLogic/.gitignore b/unittest/rebootLogic/.gitignore new file mode 100644 index 0000000..3774997 --- /dev/null +++ b/unittest/rebootLogic/.gitignore @@ -0,0 +1,2 @@ +/build +c_cpp_properties.json diff --git a/unittest/rebootLogic/.vscode/settings.json b/unittest/rebootLogic/.vscode/settings.json new file mode 100644 index 0000000..688dbbf --- /dev/null +++ b/unittest/rebootLogic/.vscode/settings.json @@ -0,0 +1,51 @@ +{ + "editor.tabSize": 2, + "files.associations": { + "iosfwd": "cpp", + "array": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "type_traits": "cpp", + "tuple": "cpp", + "typeinfo": "cpp", + "utility": "cpp", + "variant": "cpp", + "filesystem": "cpp", + "queue": "cpp" + } +} \ No newline at end of file diff --git a/unittest/rebootLogic/CMakeLists.txt b/unittest/rebootLogic/CMakeLists.txt new file mode 100644 index 0000000..a070f29 --- /dev/null +++ b/unittest/rebootLogic/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.13.0) +project(reboot-logic VERSION 0.1.0) + +include(CTest) +find_package(Catch2 3) +if(NOT Catch2_FOUND) + include(FetchContent) + + FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.0.0-preview4 + ) + + FetchContent_MakeAvailable(Catch2) + #fixes regression -preview4, to be confirmed in later releases + set_target_properties(Catch2 PROPERTIES DEBUG_POSTFIX "") +endif() + +enable_testing() + +add_executable(reboot-logic) + +add_subdirectory(src) +target_link_libraries(reboot-logic PRIVATE Catch2::Catch2WithMain) + +set(CPACK_PROJECT_NAME ${PROJECT_NAME}) +set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) +include(CPack) diff --git a/unittest/rebootLogic/README.md b/unittest/rebootLogic/README.md new file mode 100644 index 0000000..a51f074 --- /dev/null +++ b/unittest/rebootLogic/README.md @@ -0,0 +1,16 @@ +Reboot Logic Unittest +======= + +These tests were written with Catch2 and VS code. +Install VS code with the C++ and CMake plugins and open this folder with VS code. +You should be able to run the tests directly. + +It is also recommended to [install Catch2](https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md): + +```sh +git clone https://github.com/catchorg/Catch2.git +cd Catch2 +git checkout v3.0.0-preview4 +cmake -Bbuild -H. -DBUILD_TESTING=OFF +sudo cmake --build build/ --target install +``` diff --git a/unittest/rebootLogic/src/CMakeLists.txt b/unittest/rebootLogic/src/CMakeLists.txt new file mode 100644 index 0000000..97b7322 --- /dev/null +++ b/unittest/rebootLogic/src/CMakeLists.txt @@ -0,0 +1,14 @@ +target_sources(reboot-logic PRIVATE + main.cpp + CoreController.cpp + SdCardManager.cpp + event.cpp + libxiphos.cpp + print.c +) + +target_include_directories(reboot-logic PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_subdirectory(fsfw) diff --git a/unittest/rebootLogic/src/CoreController.cpp b/unittest/rebootLogic/src/CoreController.cpp new file mode 100644 index 0000000..23461ea --- /dev/null +++ b/unittest/rebootLogic/src/CoreController.cpp @@ -0,0 +1,530 @@ +#include "CoreController.h" + +#include +#include +#include + +#include "HasActionsIF.h" +#include "SdCardManager.h" +#include "conf.h" +#include "event.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "libxiphos.h" + +xsc::Chip CoreController::CURRENT_CHIP = xsc::Chip::NO_CHIP; +xsc::Copy CoreController::CURRENT_COPY = xsc::Copy::NO_COPY; + +CoreController::CoreController() { + sdcMan = new SdCardManager(); + setCurrentBootCopy(xsc::CHIP_0, xsc::COPY_0); +} + +void CoreController::performRebootWatchdogHandling(bool recreateFile) { + using namespace std; + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; + if (not std::filesystem::exists(path) or recreateFile) { +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "CoreController::performRebootFileHandling: Recreating reboot file" << std::endl; +#endif + rebootWatchdogFile.enabled = true; + rebootWatchdogFile.img00Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; + rebootWatchdogFile.lastChip = xsc::Chip::CHIP_0; + rebootWatchdogFile.lastCopy = xsc::Copy::COPY_0; + rebootWatchdogFile.img00Lock = false; + rebootWatchdogFile.img01Lock = false; + rebootWatchdogFile.img10Lock = false; + rebootWatchdogFile.img11Lock = false; + rebootWatchdogFile.mechanismNextChip = xsc::Chip::NO_CHIP; + rebootWatchdogFile.mechanismNextCopy = xsc::Copy::NO_COPY; + rebootWatchdogFile.bootFlag = false; + rewriteRebootWatchdogFile(rebootWatchdogFile); + } else { + if (not parseRebootWatchdogFile(path, rebootWatchdogFile)) { + performRebootWatchdogHandling(true); + } + } + + if (CURRENT_CHIP == xsc::CHIP_0) { + if (CURRENT_COPY == xsc::COPY_0) { + rebootWatchdogFile.img00Cnt++; + } else { + rebootWatchdogFile.img01Cnt++; + } + } else { + if (CURRENT_COPY == xsc::COPY_0) { + rebootWatchdogFile.img10Cnt++; + } else { + rebootWatchdogFile.img11Cnt++; + } + } + + if (rebootWatchdogFile.bootFlag) { + // Trigger event to inform ground that a reboot was triggered + uint32_t p1 = rebootWatchdogFile.lastChip << 16 | rebootWatchdogFile.lastCopy; + uint32_t p2 = rebootWatchdogFile.img00Cnt << 24 | rebootWatchdogFile.img01Cnt << 16 | + rebootWatchdogFile.img10Cnt << 8 | rebootWatchdogFile.img11Cnt; + triggerEvent(REBOOT_MECHANISM_TRIGGERED, p1, p2); + // Clear the boot flag + rebootWatchdogFile.bootFlag = false; + } + + if (rebootWatchdogFile.mechanismNextChip != xsc::NO_CHIP and + rebootWatchdogFile.mechanismNextCopy != xsc::NO_COPY) { + if (CURRENT_CHIP != rebootWatchdogFile.mechanismNextChip or + CURRENT_COPY != rebootWatchdogFile.mechanismNextCopy) { + std::string infoString = std::to_string(rebootWatchdogFile.mechanismNextChip) + " " + + std::to_string(rebootWatchdogFile.mechanismNextCopy); + sif::warning << "CoreController::performRebootFileHandling: Expected to be on image " + << infoString << " but currently on other image. Locking " << infoString + << std::endl; + // Firmware or other component might be corrupt and we are on another image then the target + // image specified by the mechanism. We can't really trust the target image anymore. + // Lock it for now + if (rebootWatchdogFile.mechanismNextChip == xsc::CHIP_0) { + if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) { + rebootWatchdogFile.img00Lock = true; + } else { + rebootWatchdogFile.img01Lock = true; + } + } else { + if (rebootWatchdogFile.mechanismNextCopy == xsc::COPY_0) { + rebootWatchdogFile.img10Lock = true; + } else { + rebootWatchdogFile.img11Lock = true; + } + } + } + } + + rebootWatchdogFile.lastChip = CURRENT_CHIP; + rebootWatchdogFile.lastCopy = CURRENT_COPY; + // Only reboot if the reboot functionality is enabled. + // The handler will still increment the boot counts + if (rebootWatchdogFile.enabled and + (*rebootWatchdogFile.relevantBootCnt >= rebootWatchdogFile.maxCount)) { + // Reboot to other image + bool doReboot = false; + xsc::Chip tgtChip = xsc::NO_CHIP; + xsc::Copy tgtCopy = xsc::NO_COPY; + determineAndExecuteReboot(rebootWatchdogFile, doReboot, tgtChip, tgtCopy); + if (doReboot) { + rebootWatchdogFile.bootFlag = true; +#if OBSW_VERBOSE_LEVEL >= 1 + sif::info << "Boot counter for image " << CURRENT_CHIP << " " << CURRENT_COPY + << " too high. Rebooting to " << tgtChip << " " << tgtCopy << std::endl; +#endif + rebootWatchdogFile.mechanismNextChip = tgtChip; + rebootWatchdogFile.mechanismNextCopy = tgtCopy; + rewriteRebootWatchdogFile(rebootWatchdogFile); + xsc_boot_copy(static_cast(tgtChip), + static_cast(tgtCopy)); + } + } else { + rebootWatchdogFile.mechanismNextChip = xsc::NO_CHIP; + rebootWatchdogFile.mechanismNextCopy = xsc::NO_COPY; + } + rewriteRebootWatchdogFile(rebootWatchdogFile); +} + +void CoreController::determineAndExecuteReboot(RebootWatchdogFile &rf, bool &needsReboot, + xsc::Chip &tgtChip, xsc::Copy &tgtCopy) { + tgtChip = xsc::CHIP_0; + tgtCopy = xsc::COPY_0; + needsReboot = false; + if ((CURRENT_CHIP == xsc::CHIP_0) and (CURRENT_COPY == xsc::COPY_0) and + (rf.img00Cnt >= rf.maxCount)) { + needsReboot = true; + if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) { + tgtCopy = xsc::COPY_1; + return; + } + if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) { + tgtChip = xsc::CHIP_1; + return; + } + if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) { + tgtChip = xsc::CHIP_1; + tgtCopy = xsc::COPY_1; + return; + } + // Can't really do much here. Stay on image + sif::warning + << "All reboot counts too high or all fallback images locked, already on fallback image" + << std::endl; + needsReboot = false; + return; + } + if ((CURRENT_CHIP == xsc::CHIP_0) and (CURRENT_COPY == xsc::COPY_1) and + (rf.img01Cnt >= rf.maxCount)) { + needsReboot = true; + if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) { + // Reboot on fallback image + return; + } + if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) { + tgtChip = xsc::CHIP_1; + return; + } + if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) { + tgtChip = xsc::CHIP_1; + tgtCopy = xsc::COPY_1; + } + if (rf.img00Lock) { + needsReboot = false; + } + // Reboot to fallback image + } + if ((CURRENT_CHIP == xsc::CHIP_1) and (CURRENT_COPY == xsc::COPY_0) and + (rf.img10Cnt >= rf.maxCount)) { + needsReboot = true; + if (rf.img11Cnt < rf.maxCount and not rf.img11Lock) { + tgtChip = xsc::CHIP_1; + tgtCopy = xsc::COPY_1; + return; + } + if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) { + return; + } + if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) { + tgtCopy = xsc::COPY_1; + return; + } + if (rf.img00Lock) { + needsReboot = false; + } + // Reboot to fallback image + } + if ((CURRENT_CHIP == xsc::CHIP_1) and (CURRENT_COPY == xsc::COPY_1) and + (rf.img11Cnt >= rf.maxCount)) { + needsReboot = true; + if (rf.img10Cnt < rf.maxCount and not rf.img10Lock) { + tgtChip = xsc::CHIP_1; + return; + } + if (rf.img00Cnt < rf.maxCount and not rf.img00Lock) { + return; + } + if (rf.img01Cnt < rf.maxCount and not rf.img01Lock) { + tgtCopy = xsc::COPY_1; + return; + } + if (rf.img00Lock) { + needsReboot = false; + } + // Reboot to fallback image + } +} + +bool CoreController::parseRebootWatchdogFile(std::string path, RebootWatchdogFile &rf) { + using namespace std; + std::string selfMatch; + if (CURRENT_CHIP == xsc::CHIP_0) { + if (CURRENT_COPY == xsc::COPY_0) { + selfMatch = "00"; + } else { + selfMatch = "01"; + } + } else { + if (CURRENT_COPY == xsc::COPY_0) { + selfMatch = "10"; + } else { + selfMatch = "11"; + } + } + ifstream file(path); + string word; + string line; + uint8_t lineIdx = 0; + while (std::getline(file, line)) { + istringstream iss(line); + switch (lineIdx) { + case 0: { + iss >> word; + if (word.find("on:") == string::npos) { + // invalid file + return false; + } + iss >> rf.enabled; + break; + } + case 1: { + iss >> word; + if (word.find("maxcnt:") == string::npos) { + return false; + } + iss >> rf.maxCount; + break; + } + case 2: { + iss >> word; + if (word.find("img00:") == string::npos) { + return false; + } + iss >> rf.img00Cnt; + if (word.find(selfMatch) != string::npos) { + rf.relevantBootCnt = &rf.img00Cnt; + } + break; + } + case 3: { + iss >> word; + if (word.find("img01:") == string::npos) { + return false; + } + iss >> rf.img01Cnt; + if (word.find(selfMatch) != string::npos) { + rf.relevantBootCnt = &rf.img01Cnt; + } + break; + } + case 4: { + iss >> word; + if (word.find("img10:") == string::npos) { + return false; + } + iss >> rf.img10Cnt; + if (word.find(selfMatch) != string::npos) { + rf.relevantBootCnt = &rf.img10Cnt; + } + break; + } + case 5: { + iss >> word; + if (word.find("img11:") == string::npos) { + return false; + } + iss >> rf.img11Cnt; + if (word.find(selfMatch) != string::npos) { + rf.relevantBootCnt = &rf.img11Cnt; + } + break; + } + case 6: { + iss >> word; + if (word.find("img00lock:") == string::npos) { + return false; + } + iss >> rf.img00Lock; + break; + } + case 7: { + iss >> word; + if (word.find("img01lock:") == string::npos) { + return false; + } + iss >> rf.img01Lock; + break; + } + case 8: { + iss >> word; + if (word.find("img10lock:") == string::npos) { + return false; + } + iss >> rf.img10Lock; + break; + } + case 9: { + iss >> word; + if (word.find("img11lock:") == string::npos) { + return false; + } + iss >> rf.img11Lock; + break; + } + case 10: { + iss >> word; + if (word.find("bootflag:") == string::npos) { + return false; + } + iss >> rf.bootFlag; + break; + } + case 11: { + iss >> word; + int copyRaw = 0; + int chipRaw = 0; + if (word.find("last:") == string::npos) { + return false; + } + iss >> chipRaw; + if (iss.fail()) { + return false; + } + iss >> copyRaw; + if (iss.fail()) { + return false; + } + + if (chipRaw > 1 or copyRaw > 1) { + return false; + } + rf.lastChip = static_cast(chipRaw); + rf.lastCopy = static_cast(copyRaw); + break; + } + case 12: { + iss >> word; + int copyRaw = 0; + int chipRaw = 0; + if (word.find("next:") == string::npos) { + return false; + } + iss >> chipRaw; + if (iss.fail()) { + return false; + } + iss >> copyRaw; + if (iss.fail()) { + return false; + } + + if (chipRaw > 2 or copyRaw > 2) { + return false; + } + rf.mechanismNextChip = static_cast(chipRaw); + rf.mechanismNextCopy = static_cast(copyRaw); + break; + } + } + if (iss.fail()) { + return false; + } + lineIdx++; + } + if (lineIdx < 12) { + return false; + } + return true; +} + +void CoreController::resetRebootCount(xsc::Chip tgtChip, xsc::Copy tgtCopy) { + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; + // Disable the reboot file mechanism + parseRebootWatchdogFile(path, rebootWatchdogFile); + if (tgtChip == xsc::ALL_CHIP and tgtCopy == xsc::ALL_COPY) { + rebootWatchdogFile.img00Cnt = 0; + rebootWatchdogFile.img01Cnt = 0; + rebootWatchdogFile.img10Cnt = 0; + rebootWatchdogFile.img11Cnt = 0; + } else { + if (tgtChip == xsc::CHIP_0) { + if (tgtCopy == xsc::COPY_0) { + rebootWatchdogFile.img00Cnt = 0; + } else { + rebootWatchdogFile.img01Cnt = 0; + } + } else { + if (tgtCopy == xsc::COPY_0) { + rebootWatchdogFile.img10Cnt = 0; + } else { + rebootWatchdogFile.img11Cnt = 0; + } + } + } + rewriteRebootWatchdogFile(rebootWatchdogFile); +} + +void CoreController::rewriteRebootWatchdogFile(RebootWatchdogFile file) { + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; + std::ofstream rebootFile(path); + if (rebootFile.is_open()) { + // Initiate reboot file first. Reboot handling will be on on initialization + rebootFile << "on: " << file.enabled << "\nmaxcnt: " << file.maxCount + << "\nimg00: " << file.img00Cnt << "\nimg01: " << file.img01Cnt + << "\nimg10: " << file.img10Cnt << "\nimg11: " << file.img11Cnt + << "\nimg00lock: " << file.img00Lock << "\nimg01lock: " << file.img01Lock + << "\nimg10lock: " << file.img10Lock << "\nimg11lock: " << file.img11Lock + << "\nbootflag: " << file.bootFlag << "\nlast: " << static_cast(file.lastChip) + << " " << static_cast(file.lastCopy) + << "\nnext: " << static_cast(file.mechanismNextChip) << " " + << static_cast(file.mechanismNextCopy) << "\n"; + } +} + +ReturnValue_t CoreController::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t *data, size_t size) { + switch (actionId) { + case (SWITCH_REBOOT_FILE_HANDLING): { + if (size < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; + // Disable the reboot file mechanism + parseRebootWatchdogFile(path, rebootWatchdogFile); + if (data[0] == 0) { + rebootWatchdogFile.enabled = false; + rewriteRebootWatchdogFile(rebootWatchdogFile); + } else if (data[0] == 1) { + rebootWatchdogFile.enabled = true; + rewriteRebootWatchdogFile(rebootWatchdogFile); + } else { + return HasActionsIF::INVALID_PARAMETERS; + } + return HasActionsIF::EXECUTION_FINISHED; + } + case (RESET_REBOOT_COUNTERS): { + if (size == 0) { + resetRebootCount(xsc::ALL_CHIP, xsc::ALL_COPY); + } else if (size == 2) { + if (data[0] > 1 or data[1] > 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + resetRebootCount(static_cast(data[0]), static_cast(data[1])); + } + return HasActionsIF::EXECUTION_FINISHED; + } + case (SWITCH_IMG_LOCK): { + if (size != 3) { + return HasActionsIF::INVALID_PARAMETERS; + } + if (data[1] > 1 or data[2] > 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + setRebootMechanismLock(data[0], static_cast(data[1]), + static_cast(data[2])); + return HasActionsIF::EXECUTION_FINISHED; + } + case (SET_MAX_REBOOT_CNT): { + if (size < 1) { + return HasActionsIF::INVALID_PARAMETERS; + } + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; + // Disable the reboot file mechanism + parseRebootWatchdogFile(path, rebootWatchdogFile); + rebootWatchdogFile.maxCount = data[0]; + rewriteRebootWatchdogFile(rebootWatchdogFile); + return HasActionsIF::EXECUTION_FINISHED; + } + default: { + return HasActionsIF::INVALID_ACTION_ID; + } + } +} + +void CoreController::setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy) { + std::string path = sdcMan->getCurrentMountPrefix(sdInfo.active) + REBOOT_WATCHDOG_FILE; + // Disable the reboot file mechanism + parseRebootWatchdogFile(path, rebootWatchdogFile); + if (tgtChip == xsc::CHIP_0) { + if (tgtCopy == xsc::COPY_0) { + rebootWatchdogFile.img00Lock = lock; + } else { + rebootWatchdogFile.img01Lock = lock; + } + } else { + if (tgtCopy == xsc::COPY_0) { + rebootWatchdogFile.img10Lock = lock; + } else { + rebootWatchdogFile.img11Lock = lock; + } + } + rewriteRebootWatchdogFile(rebootWatchdogFile); +} + +void CoreController::setCurrentBootCopy(xsc::Chip chip, xsc::Copy copy) { + CURRENT_CHIP = chip; + CURRENT_COPY = copy; +} diff --git a/unittest/rebootLogic/src/CoreController.h b/unittest/rebootLogic/src/CoreController.h new file mode 100644 index 0000000..34d2a5f --- /dev/null +++ b/unittest/rebootLogic/src/CoreController.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include + +#include "SdCardManager.h" +#include "definitions.h" + +namespace xsc { + +enum Chip : int { CHIP_0, CHIP_1, NO_CHIP, SELF_CHIP, ALL_CHIP }; +enum Copy : int { COPY_0, COPY_1, NO_COPY, SELF_COPY, ALL_COPY }; + +} // namespace xsc + +struct RebootWatchdogFile { + static constexpr uint8_t DEFAULT_MAX_BOOT_CNT = 10; + + bool enabled = true; + size_t maxCount = DEFAULT_MAX_BOOT_CNT; + uint32_t img00Cnt = 0; + uint32_t img01Cnt = 0; + uint32_t img10Cnt = 0; + uint32_t img11Cnt = 0; + bool img00Lock = false; + bool img01Lock = false; + bool img10Lock = false; + bool img11Lock = false; + uint32_t* relevantBootCnt = &img00Cnt; + bool bootFlag = false; + xsc::Chip lastChip = xsc::Chip::CHIP_0; + xsc::Copy lastCopy = xsc::Copy::COPY_0; + xsc::Chip mechanismNextChip = xsc::Chip::NO_CHIP; + xsc::Copy mechanismNextCopy = xsc::Copy::NO_COPY; +}; + +class SdCardManager; + +class CoreController { + public: + static constexpr char REBOOT_WATCHDOG_FILE[] = "/conf/reboot.txt"; + //! [EXPORT] : [COMMENT] The reboot mechanism was triggered. + //! P1: First 16 bits: Last Chip, Last 16 bits: Last Copy, + //! P2: Each byte is the respective reboot count for the slots + static constexpr Event REBOOT_MECHANISM_TRIGGERED = 1; + static xsc::Chip CURRENT_CHIP; + static xsc::Copy CURRENT_COPY; + + static constexpr ActionId_t SWITCH_REBOOT_FILE_HANDLING = 5; + static constexpr ActionId_t RESET_REBOOT_COUNTERS = 6; + static constexpr ActionId_t SWITCH_IMG_LOCK = 7; + static constexpr ActionId_t SET_MAX_REBOOT_CNT = 8; + + CoreController(); + + static void setCurrentBootCopy(xsc::Chip chip, xsc::Copy copy); + ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, + const uint8_t* data, size_t size); + void performRebootWatchdogHandling(bool recreateFile); + void determineAndExecuteReboot(RebootWatchdogFile& rf, bool& needsReboot, xsc::Chip& tgtChip, + xsc::Copy& tgtCopy); + void setRebootMechanismLock(bool lock, xsc::Chip tgtChip, xsc::Copy tgtCopy); + void resetRebootCount(xsc::Chip tgtChip, xsc::Copy tgtCopy); + bool parseRebootWatchdogFile(std::string path, RebootWatchdogFile& file); + void rewriteRebootWatchdogFile(RebootWatchdogFile file); + + private: + struct SdFsmParams { + sd::SdCard active = sd::SdCard::NONE; + sd::SdState activeState = sd::SdState::OFF; + sd::SdCard other = sd::SdCard::NONE; + sd::SdState otherState = sd::SdState::OFF; + } sdInfo; + + SdCardManager* sdcMan = nullptr; + RebootWatchdogFile rebootWatchdogFile = {}; + bool doPerformRebootFileHandling = true; +}; \ No newline at end of file diff --git a/unittest/rebootLogic/src/HasActionsIF.h b/unittest/rebootLogic/src/HasActionsIF.h new file mode 100644 index 0000000..1d56d89 --- /dev/null +++ b/unittest/rebootLogic/src/HasActionsIF.h @@ -0,0 +1,9 @@ +#include "definitions.h" + +class HasActionsIF { + public: + static const ReturnValue_t IS_BUSY = 1; + static const ReturnValue_t INVALID_PARAMETERS = 2; + static const ReturnValue_t EXECUTION_FINISHED = 3; + static const ReturnValue_t INVALID_ACTION_ID = 4; +}; diff --git a/unittest/rebootLogic/src/SdCardManager.cpp b/unittest/rebootLogic/src/SdCardManager.cpp new file mode 100644 index 0000000..bf72595 --- /dev/null +++ b/unittest/rebootLogic/src/SdCardManager.cpp @@ -0,0 +1,5 @@ +#include "SdCardManager.h" + +std::string SdCardManager::getCurrentMountPrefix(sd::SdCard prefSdCard) { return "/tmp"; } + +bool SdCardManager::isSdCardMounted(sd::SdCard sdCard) { return true; } \ No newline at end of file diff --git a/unittest/rebootLogic/src/SdCardManager.h b/unittest/rebootLogic/src/SdCardManager.h new file mode 100644 index 0000000..16aee0f --- /dev/null +++ b/unittest/rebootLogic/src/SdCardManager.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace sd { + +enum SdState : uint8_t { + OFF = 0, + ON = 1, + // A mounted SD card is on as well + MOUNTED = 2 +}; + +enum SdCard : uint8_t { SLOT_0 = 0, SLOT_1 = 1, BOTH, NONE }; + +} // namespace sd + +class SdCardManager { + public: + std::string getCurrentMountPrefix(sd::SdCard prefSdCard); + bool isSdCardMounted(sd::SdCard sdCard); +}; diff --git a/unittest/rebootLogic/src/conf.h b/unittest/rebootLogic/src/conf.h new file mode 100644 index 0000000..e2779c4 --- /dev/null +++ b/unittest/rebootLogic/src/conf.h @@ -0,0 +1 @@ +#define OBSW_VERBOSE_LEVEL 1 \ No newline at end of file diff --git a/unittest/rebootLogic/src/definitions.h b/unittest/rebootLogic/src/definitions.h new file mode 100644 index 0000000..0a52474 --- /dev/null +++ b/unittest/rebootLogic/src/definitions.h @@ -0,0 +1,6 @@ +#include + +using Event = uint32_t; +using ActionId_t = uint32_t; +using MessageQueueId_t = uint32_t; +using ReturnValue_t = uint16_t; diff --git a/unittest/rebootLogic/src/event.cpp b/unittest/rebootLogic/src/event.cpp new file mode 100644 index 0000000..54e5575 --- /dev/null +++ b/unittest/rebootLogic/src/event.cpp @@ -0,0 +1,21 @@ +#include "event.h" + +#include + +std::queue EVENT_QUEUE = {}; + +void triggerEvent(Event event, uint32_t p1, uint32_t p2) { + EventInfo info = {}; + info.event = event; + info.p1 = p1; + info.p2 = p2; + EVENT_QUEUE.push(info); +} + +void eventWasCalled(EventInfo& eventInfo, uint32_t& numEvents) { + numEvents = EVENT_QUEUE.size(); + if (not EVENT_QUEUE.empty()) { + eventInfo = std::move(EVENT_QUEUE.back()); + EVENT_QUEUE.pop(); + } +} \ No newline at end of file diff --git a/unittest/rebootLogic/src/event.h b/unittest/rebootLogic/src/event.h new file mode 100644 index 0000000..fc831fe --- /dev/null +++ b/unittest/rebootLogic/src/event.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "definitions.h" + +struct EventInfo { + // That was just for testing, follow rule of 0 + /* + EventInfo () {} + + EventInfo (const EventInfo& other): event(other.event), p1(other.p1), p2(other.p2) { + std::cout << "Event info copy ctor called" << std::endl; + } + + EventInfo &operator= (const EventInfo& other) { + std::cout << "Event info assignment ctor called" << std::endl; + this->event = other.event; + this->p1 = other.p1; + this->p2 = other.p2; + return *this; + } + + EventInfo &operator= (EventInfo&& other) { + std::cout << "Event info move ctor called" << std::endl; + this->event = other.event; + this->p1 = other.p1; + this->p2 = other.p2; + return *this; + } + */ + + uint32_t event = 0; + uint32_t p1 = 0; + uint32_t p2 = 0; +}; + +void triggerEvent(Event event, uint32_t p1, uint32_t p2); + +void eventWasCalled(EventInfo& eventInfo, uint32_t& numEvents); diff --git a/unittest/rebootLogic/src/fsfw/CMakeLists.txt b/unittest/rebootLogic/src/fsfw/CMakeLists.txt new file mode 100644 index 0000000..13b08d6 --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(serviceinterface) diff --git a/unittest/rebootLogic/src/fsfw/FSFW.h b/unittest/rebootLogic/src/fsfw/FSFW.h new file mode 100644 index 0000000..06d1f9b --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/FSFW.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#define FSFW_CPP_OSTREAM_ENABLED 1 + +namespace fsfwconfig { +static constexpr size_t FSFW_PRINT_BUFFER_SIZE = 256; +} \ No newline at end of file diff --git a/unittest/rebootLogic/src/fsfw/serviceinterface/CMakeLists.txt b/unittest/rebootLogic/src/fsfw/serviceinterface/CMakeLists.txt new file mode 100644 index 0000000..7eded75 --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/serviceinterface/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(reboot-logic PRIVATE + ServiceInterfaceStream.cpp + ServiceInterfaceBuffer.cpp +) \ No newline at end of file diff --git a/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterface.h b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterface.h new file mode 100644 index 0000000..9f05b96 --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterface.h @@ -0,0 +1,13 @@ +#ifndef FSFW_SERVICEINTERFACE_SERVICEINTERFACE_H_ +#define FSFW_SERVICEINTERFACE_SERVICEINTERFACE_H_ + +#include "fsfw/FSFW.h" +#include "serviceInterfaceDefintions.h" + +#if FSFW_CPP_OSTREAM_ENABLED == 1 +#include "ServiceInterfaceStream.h" +#else +#include "ServiceInterfacePrinter.h" +#endif + +#endif /* FSFW_SERVICEINTERFACE_SERVICEINTERFACE_H_ */ diff --git a/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceBuffer.cpp b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceBuffer.cpp new file mode 100644 index 0000000..eb951e5 --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceBuffer.cpp @@ -0,0 +1,252 @@ +#include "ServiceInterfaceBuffer.h" + +#if FSFW_CPP_OSTREAM_ENABLED == 1 + +#include + +#include + +#include "fsfw/serviceinterface/serviceInterfaceDefintions.h" + +#if defined(WIN32) && FSFW_COLORED_OUTPUT == 1 +#include "Windows.h" +#endif + +// to be implemented by bsp +extern "C" void printChar(const char*, bool errStream); + +#ifndef UT699 + +ServiceInterfaceBuffer::ServiceInterfaceBuffer(std::string setMessage, bool addCrToPreamble, + bool buffered, bool errStream, uint16_t port) + : isActive(true), + logMessage(setMessage), + addCrToPreamble(addCrToPreamble), + buffered(buffered), + errStream(errStream) { + if (buffered) { + // Set pointers if the stream is buffered. + setp(buf, buf + BUF_SIZE); + } + +#if FSFW_COLORED_OUTPUT == 1 + if (setMessage.find("DEBUG") != std::string::npos) { + colorPrefix = sif::ANSI_COLOR_CYAN; + } else if (setMessage.find("INFO") != std::string::npos) { + colorPrefix = sif::ANSI_COLOR_GREEN; + } else if (setMessage.find("WARNING") != std::string::npos) { + colorPrefix = sif::ANSI_COLOR_MAGENTA; + } else if (setMessage.find("ERROR") != std::string::npos) { + colorPrefix = sif::ANSI_COLOR_RED; + } else { + colorPrefix = sif::ANSI_COLOR_RESET; + } + +#ifdef WIN32 + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, dwMode); +#endif + +#endif + + preamble.reserve(MAX_PREAMBLE_SIZE); + preamble.resize(MAX_PREAMBLE_SIZE); +} + +void ServiceInterfaceBuffer::putChars(char const* begin, char const* end) { + char array[BUF_SIZE]; + uint32_t length = end - begin; + if (length > sizeof(array)) { + length = sizeof(array); + } + memcpy(array, begin, length); + + for (; begin != end; begin++) { + if (errStream) { + printChar(begin, true); + } else { + printChar(begin, false); + } + } +} + +#endif + +int ServiceInterfaceBuffer::overflow(int c) { + if (not buffered and this->isActive) { + if (c != Traits::eof()) { + if (errStream) { + printChar(reinterpret_cast(&c), true); + } else { + printChar(reinterpret_cast(&c), false); + } + } + return 0; + } + // Handle output + putChars(pbase(), pptr()); + if (c != Traits::eof()) { + char c2 = c; + // Handle the one character that didn't fit to buffer + putChars(&c2, &c2 + 1); + } + // This tells that buffer is empty again + setp(buf, buf + BUF_SIZE - 1); + // I'm not sure about this return value! + return 0; +} + +int ServiceInterfaceBuffer::sync(void) { + if (not this->isActive and not buffered) { + if (not buffered) { + setp(buf, buf + BUF_SIZE - 1); + } + return 0; + } + if (not buffered) { + return 0; + } + + size_t preambleSize = 0; + std::string* preamble = getPreamble(&preambleSize); + // Write logMessage and time + this->putChars(preamble->data(), preamble->data() + preambleSize); + // Handle output + this->putChars(pbase(), pptr()); + // This tells that buffer is empty again + setp(buf, buf + BUF_SIZE - 1); + return 0; +} + +bool ServiceInterfaceBuffer::isBuffered() const { return buffered; } + +std::string* ServiceInterfaceBuffer::getPreamble(size_t* preambleSize) { + size_t currentSize = 0; + char* parsePosition = &preamble[0]; + if (addCrToPreamble) { + preamble[0] = '\r'; + currentSize += 1; + parsePosition += 1; + } + +#if FSFW_COLORED_OUTPUT == 1 + currentSize += sprintf(parsePosition, "%s", colorPrefix.c_str()); + parsePosition += colorPrefix.size(); +#endif + + int32_t charCount = + sprintf(parsePosition, "%s%s | ", this->logMessage.c_str(), sif::ANSI_COLOR_RESET); + if (charCount < 0) { + printf("ServiceInterfaceBuffer: Failure parsing preamble\r\n"); + return &preamble; + } + if (charCount > MAX_PREAMBLE_SIZE) { + printf( + "ServiceInterfaceBuffer: Char count too large for maximum " + "preamble size"); + return &preamble; + } + currentSize += charCount; + if (preambleSize != nullptr) { + *preambleSize = currentSize; + } + return &preamble; +} + +bool ServiceInterfaceBuffer::crAdditionEnabled() const { return addCrToPreamble; } + +#if FSFW_COLORED_OUTPUT == 1 +void ServiceInterfaceBuffer::setAsciiColorPrefix(std::string colorPrefix) { + this->colorPrefix = colorPrefix; +} +#endif + +#ifdef UT699 +#include "../osal/rtems/Interrupt.h" + +ServiceInterfaceBuffer::ServiceInterfaceBuffer(std::string set_message, uint16_t port) { + this->log_message = set_message; + this->isActive = true; + setp(buf, buf + BUF_SIZE); +} + +void ServiceInterfaceBuffer::putChars(char const* begin, char const* end) { + char array[BUF_SIZE]; + uint32_t length = end - begin; + if (length > sizeof(array)) { + length = sizeof(array); + } + memcpy(array, begin, length); + + if (!Interrupt::isInterruptInProgress()) { + std::cout << array; + } else { + // Uncomment the following line if you need ISR debug output. + // printk(array); + } +} +#endif // UT699 + +#ifdef ML505 +#include +ServiceInterfaceBuffer::ServiceInterfaceBuffer(std::string set_message, uint16_t port) + : isActive(true), + log_message(set_message), + udpSocket(0), + remoteAddressLength(sizeof(remoteAddress)) { + setp(buf, buf + BUF_SIZE); + memset((uint8_t*)&remoteAddress, 0, sizeof(remoteAddress)); + remoteAddress.sin_family = AF_INET; + remoteAddress.sin_port = htons(port); + remoteAddress.sin_addr.s_addr = htonl(inet_addr("192.168.250.100")); +} + +void ServiceInterfaceBuffer::putChars(char const* begin, char const* end) { + char array[BUF_SIZE]; + uint32_t length = end - begin; + if (length > sizeof(array)) { + length = sizeof(array); + } + memcpy(array, begin, length); + + if (udpSocket <= 0) { + initSocket(); + } + + if (udpSocket > 0) { + sendto(udpSocket, array, length, 0, (sockaddr*)&remoteAddress, sizeof(remoteAddress)); + } +} + +void ServiceInterfaceBuffer::initSocket() { + sockaddr_in address; + memset((uint8_t*)&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(0); + address.sin_addr.s_addr = htonl(INADDR_ANY); + + udpSocket = socket(PF_INET, SOCK_DGRAM, 0); + if (socket < 0) { + printf("Error opening socket!\n"); + return; + } + timeval timeout = {0, 20}; + if (setsockopt(udpSocket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { + printf("Error setting SO_RCVTIMEO socket options!\n"); + return; + } + if (setsockopt(udpSocket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { + printf("Error setting SO_SNDTIMEO socket options!\n"); + return; + } + if (bind(udpSocket, (sockaddr*)&address, sizeof(address)) < 0) { + printf("Error binding socket!\n"); + } +} + +#endif // ML505 + +#endif /* FSFW_CPP_OSTREAM_ENABLED == 1 */ diff --git a/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceBuffer.h b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceBuffer.h new file mode 100644 index 0000000..5b72093 --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceBuffer.h @@ -0,0 +1,159 @@ +#ifndef FRAMEWORK_SERVICEINTERFACE_SERVICEINTERFACEBUFFER_H_ +#define FRAMEWORK_SERVICEINTERFACE_SERVICEINTERFACEBUFFER_H_ + +#include "fsfw/FSFW.h" + +#if FSFW_CPP_OSTREAM_ENABLED == 1 + +#include +#include +#include + +#ifndef UT699 + +/** + * @brief This is the underlying stream buffer which implements the + * streambuf class and overloads the overflow() and sync() methods + * @details + * This class is used to modify the output of the stream, for example by adding. + * It also calls the char printing function which is implemented in the + * board supply package (BSP). + */ +class ServiceInterfaceBuffer : public std::streambuf { + friend class ServiceInterfaceStream; + + public: + static constexpr uint8_t MAX_PREAMBLE_SIZE = 40; + + ServiceInterfaceBuffer(std::string setMessage, bool addCrToPreamble, bool buffered, + bool errStream, uint16_t port); + + protected: + bool isActive; + //! This is called when buffer becomes full. If + //! buffer is not used, then this is called every + //! time when characters are put to stream. + int overflow(int c = Traits::eof()) override; + + //! This function is called when stream is flushed, + //! for example when std::endl is put to stream. + int sync(void) override; + + bool isBuffered() const; + + private: + //! For additional message information + std::string logMessage; + std::string preamble; + +#if FSFW_COLORED_OUTPUT == 1 + std::string colorPrefix; + void setAsciiColorPrefix(std::string colorPrefix); +#endif + + // For EOF detection + typedef std::char_traits Traits; + + //! This is useful for some terminal programs which do not have + //! implicit carriage return with newline characters. + bool addCrToPreamble; + + //! Specifies whether the stream operates in buffered or unbuffered mode. + bool buffered; + //! This specifies to print to stderr and work in unbuffered mode. + bool errStream; + + //! Needed for buffered mode. + static size_t const BUF_SIZE = fsfwconfig::FSFW_PRINT_BUFFER_SIZE; + char buf[BUF_SIZE]; + + //! In this function, the characters are parsed. + void putChars(char const* begin, char const* end); + + std::string* getPreamble(size_t* preambleSize = nullptr); + + bool crAdditionEnabled() const; +}; + +#endif + +#ifdef UT699 +class ServiceInterfaceBuffer : public std::basic_streambuf > { + friend class ServiceInterfaceStream; + + public: + ServiceInterfaceBuffer(std::string set_message, uint16_t port); + + protected: + bool isActive; + // This is called when buffer becomes full. If + // buffer is not used, then this is called every + // time when characters are put to stream. + virtual int overflow(int c = Traits::eof()); + + // This function is called when stream is flushed, + // for example when std::endl is put to stream. + virtual int sync(void); + + private: + // For additional message information + std::string log_message; + // For EOF detection + typedef std::char_traits Traits; + + // Work in buffer mode. It is also possible to work without buffer. + static size_t const BUF_SIZE = 128; + char buf[BUF_SIZE]; + + // In this function, the characters are parsed. + void putChars(char const* begin, char const* end); +}; +#endif // UT699 + +#ifdef ML505 +#include +#include +#include +#include +#include + +class ServiceInterfaceBuffer : public std::basic_streambuf > { + friend class ServiceInterfaceStream; + + public: + ServiceInterfaceBuffer(std::string set_message, uint16_t port); + + protected: + bool isActive; + // This is called when buffer becomes full. If + // buffer is not used, then this is called every + // time when characters are put to stream. + virtual int overflow(int c = Traits::eof()); + + // This function is called when stream is flushed, + // for example when std::endl is put to stream. + virtual int sync(void); + + private: + // For additional message information + std::string log_message; + // For EOF detection + typedef std::char_traits Traits; + + // Work in buffer mode. It is also possible to work without buffer. + static size_t const BUF_SIZE = 128; + char buf[BUF_SIZE]; + + // In this function, the characters are parsed. + void putChars(char const* begin, char const* end); + + int udpSocket; + sockaddr_in remoteAddress; + socklen_t remoteAddressLength; + void initSocket(); +}; +#endif // ML505 + +#endif /* FSFW_CPP_OSTREAM_ENABLED == 1 */ + +#endif /* FRAMEWORK_SERVICEINTERFACE_SERVICEINTERFACEBUFFER_H_ */ diff --git a/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceStream.cpp b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceStream.cpp new file mode 100644 index 0000000..3ef68c2 --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceStream.cpp @@ -0,0 +1,21 @@ +#include "ServiceInterfaceStream.h" + +#if FSFW_CPP_OSTREAM_ENABLED == 1 + +ServiceInterfaceStream::ServiceInterfaceStream(std::string setMessage, bool addCrToPreamble, + bool buffered, bool errStream, uint16_t port) + : std::ostream(&streambuf), streambuf(setMessage, addCrToPreamble, buffered, errStream, port) {} + +void ServiceInterfaceStream::setActive(bool myActive) { this->streambuf.isActive = myActive; } + +std::string* ServiceInterfaceStream::getPreamble() { return streambuf.getPreamble(); } + +bool ServiceInterfaceStream::crAdditionEnabled() const { return streambuf.crAdditionEnabled(); } + +#if FSFW_COLORED_OUTPUT == 1 +void ServiceInterfaceStream::setAsciiColorPrefix(std::string asciiColorCode) { + streambuf.setAsciiColorPrefix(asciiColorCode); +} +#endif + +#endif diff --git a/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceStream.h b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceStream.h new file mode 100644 index 0000000..ca746e7 --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/serviceinterface/ServiceInterfaceStream.h @@ -0,0 +1,67 @@ +#ifndef FRAMEWORK_SERVICEINTERFACE_SERVICEINTERFACESTREAM_H_ +#define FRAMEWORK_SERVICEINTERFACE_SERVICEINTERFACESTREAM_H_ + +#include "ServiceInterfaceBuffer.h" +#include "fsfw/FSFW.h" + +#if FSFW_CPP_OSTREAM_ENABLED == 1 + +#include +#include + +/** + * Generic service interface stream which can be used like std::cout or + * std::cerr but has additional capability. Add preamble and timestamp + * to output. Can be run in buffered or unbuffered mode. + */ +class ServiceInterfaceStream : public std::ostream { + public: + /** + * This constructor is used by specifying the preamble message. + * Optionally, the output can be directed to stderr and a CR character + * can be prepended to the preamble. + * @param setMessage message of preamble. + * @param addCrToPreamble Useful for applications like Puttty. + * @param buffered specify whether to use buffered mode. + * @param errStream specify which output stream to use (stderr or stdout). + */ + ServiceInterfaceStream(std::string setMessage, bool addCrToPreamble = false, bool buffered = true, + bool errStream = false, uint16_t port = 1234); + + //! An inactive stream will not print anything. + void setActive(bool); + + /** + * This can be used to retrieve the preamble in case it should be printed in + * the unbuffered mode. + * @return Preamle consisting of log message and timestamp. + */ + std::string* getPreamble(); + + /** + * Can be used to determine if the stream was configured to add CR characters in addition + * to newline characters. + * @return + */ + bool crAdditionEnabled() const; + +#if FSFW_COLORED_OUTPUT == 1 + void setAsciiColorPrefix(std::string asciiColorCode); +#endif + + protected: + ServiceInterfaceBuffer streambuf; +}; + +// Forward declaration of interface streams. These should be instantiated in +// main. They can then be used like std::cout or std::cerr. +namespace sif { +extern ServiceInterfaceStream debug; +extern ServiceInterfaceStream info; +extern ServiceInterfaceStream warning; +extern ServiceInterfaceStream error; +} // namespace sif + +#endif /* FSFW_CPP_OSTREAM_ENABLED == 1 */ + +#endif /* FRAMEWORK_SERVICEINTERFACE_SERVICEINTERFACESTREAM_H_ */ diff --git a/unittest/rebootLogic/src/fsfw/serviceinterface/serviceInterfaceDefintions.h b/unittest/rebootLogic/src/fsfw/serviceinterface/serviceInterfaceDefintions.h new file mode 100644 index 0000000..8571057 --- /dev/null +++ b/unittest/rebootLogic/src/fsfw/serviceinterface/serviceInterfaceDefintions.h @@ -0,0 +1,18 @@ +#ifndef FSFW_SERVICEINTERFACE_SERVICEINTERFACEDEFINTIONS_H_ +#define FSFW_SERVICEINTERFACE_SERVICEINTERFACEDEFINTIONS_H_ + +namespace sif { + +enum class OutputTypes { OUT_INFO, OUT_DEBUG, OUT_WARNING, OUT_ERROR }; + +static const char* const ANSI_COLOR_RED = "\x1b[31m"; +static const char* const ANSI_COLOR_GREEN = "\x1b[32m"; +static const char* const ANSI_COLOR_YELLOW = "\x1b[33m"; +static const char* const ANSI_COLOR_BLUE = "\x1b[34m"; +static const char* const ANSI_COLOR_MAGENTA = "\x1b[35m"; +static const char* const ANSI_COLOR_CYAN = "\x1b[36m"; +static const char* const ANSI_COLOR_RESET = "\x1b[0m"; + +} // namespace sif + +#endif /* FSFW_SERVICEINTERFACE_SERVICEINTERFACEDEFINTIONS_H_ */ diff --git a/unittest/rebootLogic/src/libxiphos.cpp b/unittest/rebootLogic/src/libxiphos.cpp new file mode 100644 index 0000000..914b36e --- /dev/null +++ b/unittest/rebootLogic/src/libxiphos.cpp @@ -0,0 +1,25 @@ +#include "libxiphos.h" + +#include "CoreController.h" + +bool rebootWasCalled = false; +xsc_libnor_chip_t lastChip; +xsc_libnor_copy_t lastCopy; + +bool getRebootWasCalled(xsc_libnor_chip_t& tgtChip, xsc_libnor_copy_t& tgtCopy) { + tgtChip = lastChip; + tgtCopy = lastCopy; + bool tmp = rebootWasCalled; + if (rebootWasCalled) { + rebootWasCalled = false; + } + return tmp; +} + +void xsc_boot_copy(xsc_libnor_chip_t boot_chip, xsc_libnor_copy_t boot_copy) { + rebootWasCalled = true; + lastChip = boot_chip; + lastCopy = boot_copy; + CoreController::setCurrentBootCopy(static_cast(boot_chip), + static_cast(boot_copy)); +} \ No newline at end of file diff --git a/unittest/rebootLogic/src/libxiphos.h b/unittest/rebootLogic/src/libxiphos.h new file mode 100644 index 0000000..341007c --- /dev/null +++ b/unittest/rebootLogic/src/libxiphos.h @@ -0,0 +1,25 @@ +#pragma once + +/** Type for identifying the nominal or the gold (redundant) flash copy */ +typedef enum { + XSC_LIBNOR_COPY_NOMINAL, + XSC_LIBNOR_COPY_GOLD, + XSC_LIBNOR_COPY_TOTAL_NUMBER +} xsc_libnor_copy_t; + +/** Type for identifying on of the two NOR flash chips */ +typedef enum { + XSC_LIBNOR_CHIP_0, /* First NOR flash chip */ + XSC_LIBNOR_CHIP_1, /* Second NOR flash chip */ + XSC_LIBNOR_CHIP_TOTAL_NUMBER +} xsc_libnor_chip_t; + +/** + * @brief Used to verify whether reboot function was called + * + * @param tgtChip + * @param tgtCopy + */ +bool getRebootWasCalled(xsc_libnor_chip_t& tgtChip, xsc_libnor_copy_t& tgtCopy); + +void xsc_boot_copy(xsc_libnor_chip_t boot_chip, xsc_libnor_copy_t boot_copy); \ No newline at end of file diff --git a/unittest/rebootLogic/src/main.cpp b/unittest/rebootLogic/src/main.cpp new file mode 100644 index 0000000..0a0febd --- /dev/null +++ b/unittest/rebootLogic/src/main.cpp @@ -0,0 +1,413 @@ +#include +#include +#include +#include + +#include "CoreController.h" +#include "HasActionsIF.h" +#include "event.h" +#include "fsfw/serviceinterface/ServiceInterface.h" +#include "libxiphos.h" + +static constexpr bool CAT_FILE_TO_CONSOLE = false; +const std::string CONF_PATH = "/tmp/conf"; +const std::string REBOOT_FILE = CONF_PATH + "/reboot.txt"; + +void catFileToConsole(); + +ServiceInterfaceStream sif::debug("DEBUG"); +ServiceInterfaceStream sif::info("INFO"); +ServiceInterfaceStream sif::warning("WARNING"); +ServiceInterfaceStream sif::error("ERROR", false, false, true); + +TEST_CASE("Core Controller Reboot File Handling", "[reboot-file]") { + if (not std::filesystem::exists(CONF_PATH)) { + std::filesystem::create_directory(CONF_PATH); + } + CoreController ctrl; + std::array cmdData = {}; + + SECTION("Primary") { + xsc_libnor_chip_t chip; + xsc_libnor_copy_t copy; + RebootFile rf = {}; + ctrl.rewriteRebootFile(rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.maxCount == RebootFile::DEFAULT_MAX_BOOT_CNT); + REQUIRE(rf.img00Cnt == 0); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.bootFlag == 0); + REQUIRE(rf.lastChip == 0); + REQUIRE(rf.lastCopy == 0); + // This recreates the file but should also increment the boot counter of the current image + REQUIRE(ctrl.CURRENT_CHIP == xsc::CHIP_0); + REQUIRE(ctrl.CURRENT_COPY == xsc::COPY_0); + ctrl.performRebootFileHandling(true); + catFileToConsole(); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.maxCount == RebootFile::DEFAULT_MAX_BOOT_CNT); + REQUIRE(rf.img00Cnt == 1); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.bootFlag == 0); + REQUIRE(rf.lastChip == xsc::CHIP_0); + REQUIRE(rf.lastCopy == xsc::COPY_0); + uint8_t newRebootCnt = 3; + CHECK(ctrl.executeAction(CoreController::SET_MAX_REBOOT_CNT, 0, &newRebootCnt, 1) == + HasActionsIF::EXECUTION_FINISHED); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.maxCount == 3); + REQUIRE(not getRebootWasCalled(chip, copy)); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 3); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.bootFlag == true); + REQUIRE(rf.lastChip == xsc::CHIP_0); + REQUIRE(rf.lastCopy == xsc::COPY_0); + REQUIRE(getRebootWasCalled(chip, copy)); + REQUIRE(chip == XSC_LIBNOR_CHIP_0); + REQUIRE(copy == XSC_LIBNOR_COPY_GOLD); + EventInfo info = {}; + uint32_t numEvents = 0; + + // We are now on image 0 1 and an event will be triggered + ctrl.performRebootFileHandling(false); + eventWasCalled(info, numEvents); + CHECK(numEvents == 1); + CHECK(info.event == CoreController::REBOOT_MECHANISM_TRIGGERED); + CHECK(static_cast((info.p1 >> 16) & 0xFFFF) == xsc::CHIP_0); + CHECK(static_cast(info.p1 & 0xFFFF) == xsc::COPY_0); + CHECK(((info.p2 >> 24) & 0xFF) == 3); + CHECK(((info.p2 >> 16) & 0xFF) == 1); + CHECK(((info.p2 >> 8) & 0xFF) == 0); + CHECK((info.p2 & 0xFF) == 0); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 3); + REQUIRE(rf.img01Cnt == 1); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + // Flag was cleared when event was thrown + REQUIRE(rf.bootFlag == false); + REQUIRE(rf.lastChip == xsc::CHIP_0); + REQUIRE(rf.lastCopy == xsc::COPY_1); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 3); + REQUIRE(rf.img01Cnt == 3); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + + // We are now on image 1 0 and an event will be triggered + ctrl.performRebootFileHandling(false); + eventWasCalled(info, numEvents); + CHECK(numEvents == 1); + CHECK(info.event == CoreController::REBOOT_MECHANISM_TRIGGERED); + CHECK(static_cast((info.p1 >> 16) & 0xFFFF) == xsc::CHIP_0); + CHECK(static_cast(info.p1 & 0xFFFF) == xsc::COPY_1); + CHECK(((info.p2 >> 24) & 0xFF) == 3); + CHECK(((info.p2 >> 16) & 0xFF) == 3); + CHECK(((info.p2 >> 8) & 0xFF) == 1); + CHECK((info.p2 & 0xFF) == 0); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 3); + REQUIRE(rf.img01Cnt == 3); + REQUIRE(rf.img10Cnt == 1); + REQUIRE(rf.img11Cnt == 0); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 3); + REQUIRE(rf.img01Cnt == 3); + REQUIRE(rf.img10Cnt == 3); + REQUIRE(rf.img11Cnt == 0); + eventWasCalled(info, numEvents); + + // On image 1 1 now + ctrl.performRebootFileHandling(false); + eventWasCalled(info, numEvents); + CHECK(numEvents == 1); + CHECK(info.event == CoreController::REBOOT_MECHANISM_TRIGGERED); + CHECK(static_cast((info.p1 >> 16) & 0xFFFF) == xsc::CHIP_1); + CHECK(static_cast(info.p1 & 0xFFFF) == xsc::COPY_0); + CHECK(((info.p2 >> 24) & 0xFF) == 3); + CHECK(((info.p2 >> 16) & 0xFF) == 3); + CHECK(((info.p2 >> 8) & 0xFF) == 3); + CHECK((info.p2 & 0xFF) == 1); + + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + // Now it should fall back to 0 0 because all are invalid + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 3); + REQUIRE(rf.img01Cnt == 3); + REQUIRE(rf.img10Cnt == 3); + REQUIRE(rf.img11Cnt == 3); + + // Should remain on image now + ctrl.performRebootFileHandling(false); + eventWasCalled(info, numEvents); + CHECK(numEvents == 1); + CHECK(info.event == CoreController::REBOOT_MECHANISM_TRIGGERED); + CHECK(static_cast((info.p1 >> 16) & 0xFFFF) == xsc::CHIP_1); + CHECK(static_cast(info.p1 & 0xFFFF) == xsc::COPY_1); + CHECK(((info.p2 >> 24) & 0xFF) == 4); + CHECK(((info.p2 >> 16) & 0xFF) == 3); + CHECK(((info.p2 >> 8) & 0xFF) == 3); + CHECK((info.p2 & 0xFF) == 3); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.img00Cnt == 4); + REQUIRE(rf.img01Cnt == 3); + REQUIRE(rf.img10Cnt == 3); + REQUIRE(rf.img11Cnt == 3); + + // Reset a specific reboot counter + cmdData[0] = 0; + cmdData[1] = 1; + ctrl.executeAction(CoreController::RESET_REBOOT_COUNTERS, 0, cmdData.data(), 2); + // Reboot to 0 0 again + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 5); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 3); + REQUIRE(rf.img11Cnt == 3); + CHECK(CoreController::CURRENT_CHIP == xsc::CHIP_0); + CHECK(CoreController::CURRENT_COPY == xsc::COPY_1); + ctrl.executeAction(CoreController::RESET_REBOOT_COUNTERS, 0, nullptr, 0); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 0); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + uint8_t enable = 0; + ctrl.executeAction(CoreController::SWITCH_REBOOT_FILE_HANDLING, 0, &enable, 1); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 0); + REQUIRE(rf.img00Cnt == 0); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + + // Reboot to 1 0 explicitely + CoreController::setCurrentBootCopy(xsc::CHIP_1, xsc::COPY_0); + // Reboot three times and verify that no reboot is performed + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 0); + REQUIRE(rf.img00Cnt == 0); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 4); + REQUIRE(rf.img11Cnt == 0); + // Now enable the functionality again and verify it reboots to 1 1 + enable = 1; + ctrl.executeAction(CoreController::SWITCH_REBOOT_FILE_HANDLING, 0, &enable, 1); + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 0); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 5); + REQUIRE(rf.img11Cnt == 0); + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 0); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 5); + REQUIRE(rf.img11Cnt == 1); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + // Should be on 0 0 now + CHECK(CoreController::CURRENT_CHIP == xsc::CHIP_0); + CHECK(CoreController::CURRENT_COPY == xsc::COPY_0); + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 1); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 5); + REQUIRE(rf.img11Cnt == 3); + + // Now reset all reboot counters manually + cmdData[0] = 0; + cmdData[1] = 0; + ctrl.executeAction(CoreController::RESET_REBOOT_COUNTERS, 0, cmdData.data(), 2); + cmdData[0] = 1; + cmdData[1] = 0; + ctrl.executeAction(CoreController::RESET_REBOOT_COUNTERS, 0, cmdData.data(), 2); + cmdData[0] = 1; + cmdData[1] = 1; + ctrl.executeAction(CoreController::RESET_REBOOT_COUNTERS, 0, cmdData.data(), 2); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 0); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + + // Reset lock on 0 1 + cmdData[0] = false; + cmdData[1] = 0; + cmdData[2] = 1; + ctrl.executeAction(CoreController::SWITCH_IMG_LOCK, 0, cmdData.data(), 3); + CHECK(CoreController::CURRENT_CHIP == xsc::CHIP_0); + CHECK(CoreController::CURRENT_COPY == xsc::COPY_0); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + // Now should be on 0 1 + ctrl.parseRebootFile(REBOOT_FILE, rf); + CHECK(CoreController::CURRENT_CHIP == xsc::CHIP_0); + CHECK(CoreController::CURRENT_COPY == xsc::COPY_1); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 3); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.img00Lock == false); + REQUIRE(rf.img01Lock == false); + REQUIRE(rf.img10Lock == false); + REQUIRE(rf.img11Lock == false); + // Now simulate failure in kernel boot or reboot from ProASIC3 + ctrl.setCurrentBootCopy(xsc::CHIP_0, xsc::COPY_0); + ctrl.performRebootFileHandling(false); + // Image 0 1 should have been marked as invalid now. Reboot will be triggered + // on 1 0 instead of 0 1 because 0 1 is marked as locked + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 4); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.img00Lock == false); + REQUIRE(rf.img01Lock == true); + REQUIRE(rf.img10Lock == false); + REQUIRE(rf.img11Lock == false); + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 4); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 1); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.img00Lock == false); + REQUIRE(rf.img01Lock == true); + REQUIRE(rf.img10Lock == false); + REQUIRE(rf.img11Lock == false); + // Reset lock on 0 1 again + cmdData[0] = false; + cmdData[1] = 0; + cmdData[2] = 1; + ctrl.executeAction(CoreController::SWITCH_IMG_LOCK, 0, cmdData.data(), 3); + // Lock 1 1 manually + // Reset lock on 0 1 again + cmdData[0] = true; + cmdData[1] = 1; + cmdData[2] = 1; + ctrl.executeAction(CoreController::SWITCH_IMG_LOCK, 0, cmdData.data(), 3); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + // Should be on 0 1 now + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 4); + REQUIRE(rf.img01Cnt == 1); + REQUIRE(rf.img10Cnt == 3); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.img00Lock == false); + REQUIRE(rf.img01Lock == false); + REQUIRE(rf.img10Lock == false); + REQUIRE(rf.img11Lock == true); + // Lock everything except 0 0 + cmdData[0] = true; + cmdData[1] = 0; + cmdData[2] = 1; + ctrl.executeAction(CoreController::SWITCH_IMG_LOCK, 0, cmdData.data(), 3); + cmdData[0] = true; + cmdData[1] = 1; + cmdData[2] = 0; + ctrl.executeAction(CoreController::SWITCH_IMG_LOCK, 0, cmdData.data(), 3); + ctrl.executeAction(CoreController::RESET_REBOOT_COUNTERS, 0, nullptr, 0); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 0); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.img00Lock == false); + REQUIRE(rf.img01Lock == true); + REQUIRE(rf.img10Lock == true); + REQUIRE(rf.img11Lock == true); + // Switch to 0 0 + ctrl.setCurrentBootCopy(xsc::CHIP_0, xsc::COPY_0); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + ctrl.performRebootFileHandling(false); + // Should still be on 0 0 + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 4); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.img00Lock == false); + REQUIRE(rf.img01Lock == true); + REQUIRE(rf.img10Lock == true); + REQUIRE(rf.img11Lock == true); + // Unlock 1 0 + cmdData[0] = false; + cmdData[1] = 1; + cmdData[2] = 0; + ctrl.executeAction(CoreController::SWITCH_IMG_LOCK, 0, cmdData.data(), 3); + // Reboots to 1 0 now + ctrl.performRebootFileHandling(false); + ctrl.parseRebootFile(REBOOT_FILE, rf); + REQUIRE(rf.enabled == 1); + REQUIRE(rf.img00Cnt == 5); + REQUIRE(rf.img01Cnt == 0); + REQUIRE(rf.img10Cnt == 0); + REQUIRE(rf.img11Cnt == 0); + REQUIRE(rf.img00Lock == false); + REQUIRE(rf.img01Lock == true); + REQUIRE(rf.img10Lock == false); + REQUIRE(rf.img11Lock == true); + REQUIRE(CoreController::CURRENT_CHIP == xsc::CHIP_1); + REQUIRE(CoreController::CURRENT_COPY == xsc::COPY_0); + } + if (std::filesystem::exists(CONF_PATH)) { + std::uintmax_t n = std::filesystem::remove_all(CONF_PATH); + CHECK(n == 2); + } +} + +void catFileToConsole() { + if (CAT_FILE_TO_CONSOLE) { + std::ifstream file(REBOOT_FILE); + if (file.is_open()) { + std::cout << file.rdbuf(); + } + } +} \ No newline at end of file diff --git a/unittest/rebootLogic/src/print.c b/unittest/rebootLogic/src/print.c new file mode 100644 index 0000000..3d0d9b9 --- /dev/null +++ b/unittest/rebootLogic/src/print.c @@ -0,0 +1,10 @@ +#include +#include + +void printChar(const char* character, bool errStream) { + if (errStream) { + putc(*character, stderr); + return; + } + putc(*character, stdout); +} diff --git a/unittest/testEnvironment.cpp b/unittest/testEnvironment.cpp new file mode 100644 index 0000000..4c19c4c --- /dev/null +++ b/unittest/testEnvironment.cpp @@ -0,0 +1,59 @@ +#include "testEnvironment.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LINUX +ServiceInterfaceStream sif::debug("DEBUG "); +ServiceInterfaceStream sif::info("INFO "); +ServiceInterfaceStream sif::warning("WARNING"); +ServiceInterfaceStream sif::error("ERROR ", false, true, true); +#else +ServiceInterfaceStream sif::debug("DEBUG", true); +ServiceInterfaceStream sif::info("INFO", true); +ServiceInterfaceStream sif::warning("WARNING", true); +ServiceInterfaceStream sif::error("ERROR", true, false, true); +#endif + +namespace testEnvironment { + +void factory(void* args) { + new HouseKeepingMock(); + eventManager = new EventManagerMock(); + new HealthTable(objects::HEALTH_TABLE); + new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER, 5, false, 60.0); + new CdsShortTimeStamper(objects::TIME_STAMPER); + + { + PoolManager::LocalPoolConfig poolCfg = {{300, 16}, {200, 32}, {150, 64}, {150, 128}, + {100, 256}, {50, 512}, {50, 1024}, {10, 2048}}; + new PoolManager(objects::IPC_STORE, poolCfg); + } +} + +void setup() { ObjectManager::instance()->setObjectFactoryFunction(factory, nullptr); } + +void initialize() { ObjectManager::instance()->initialize(); } + +} // namespace testEnvironment + +EventManagerMock* testEnvironment::eventManager = nullptr; + +#include +#include + +namespace messagetypes { +enum MESSAGE_TYPE { + MISSION_MESSAGE_TYPE_START = FW_MESSAGES_COUNT, +}; + +void clearMissionMessage(CommandMessage* message); +} // namespace messagetypes + +void messagetypes::clearMissionMessage(CommandMessage* message) {} diff --git a/unittest/testEnvironment.h b/unittest/testEnvironment.h new file mode 100644 index 0000000..79ff854 --- /dev/null +++ b/unittest/testEnvironment.h @@ -0,0 +1,36 @@ +#ifndef UNITTEST_TESTENVIRONMENT_H_ +#define UNITTEST_TESTENVIRONMENT_H_ + +#include "mocks/EventManagerMock.h" +#include "mocks/HouseKeepingMock.h" + +/* + * This namespace sets up a general environment for unittests + * + * Only objects generally used in all unittest are created here. Objects needed for + * individual tests are to be contstructed in the individual tests. + * + * The object manager can be initialized by the initialize() call + * + * It also caches pointers to generally useful objects so they do + * not need to be gotten from the object manager each time + */ +namespace testEnvironment { + +/* + * Setup code goes here, called by main() befor any tests + */ +void setup(); + +/* + * Initializes the object manager, to be called at the start of each test, after test specific + * objects are constructed + * + * All objects defined in the factory method are created here + */ +void initialize(); + +extern EventManagerMock* eventManager; +} // namespace testEnvironment + +#endif /*UNITTEST_TESTENVIRONMENT_H_*/ \ No newline at end of file diff --git a/unittest/testGenericFilesystem.cpp b/unittest/testGenericFilesystem.cpp new file mode 100644 index 0000000..e5279ba --- /dev/null +++ b/unittest/testGenericFilesystem.cpp @@ -0,0 +1,43 @@ + +#include +#include + +#include "fsfw/timemanager/Clock.h" + +uint8_t extractSuffix(const std::string& pathStr) { + std::string numberStr; + // Find the position of the dot at the end of the file path + size_t dotPos = pathStr.find_last_of('.'); + if (dotPos != std::string::npos && dotPos < pathStr.length() - 1) { + // Extract the substring after the dot + numberStr = pathStr.substr(dotPos + 1); + } + int number = std::stoi(numberStr); + if (number < 0 or number > std::numeric_limits::max()) { + return 0; + } + return static_cast(number); +} + +TEST_CASE("Stamp in Filename", "[Stamp In Filename]") { + Clock::TimeOfDay_t tod; + std::string baseName = "verif"; + std::string pathStr = "verif_2022-05-25T16:55:23Z.bin"; + unsigned int underscorePos = pathStr.find_last_of('_'); + std::string stampStr = pathStr.substr(underscorePos + 1); + float seconds = 0.0; + char* prefix = nullptr; + int count = + sscanf(stampStr.c_str(), + "%4" SCNu32 "-%2" SCNu32 "-%2" SCNu32 "T%2" SCNu32 ":%2" SCNu32 ":%2" SCNu32 "Z", + &tod.year, &tod.month, &tod.day, &tod.hour, &tod.minute, &tod.second); + static_cast(count); + CHECK(count == 6); +} + +TEST_CASE("Suffix Extraction") { + std::string pathStr = "/mnt/sd0/tm/hk/hk-some-stamp.bin.0"; + CHECK(extractSuffix(pathStr) == 0); + pathStr = "/mnt/sd0/tm/hk/hk-some-stamp.bin.2"; + CHECK(extractSuffix(pathStr) == 2); +} diff --git a/watchdog/CMakeLists.txt b/watchdog/CMakeLists.txt new file mode 100644 index 0000000..f7c7330 --- /dev/null +++ b/watchdog/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(${WATCHDOG_NAME} PRIVATE main.cpp Watchdog.cpp) + +target_include_directories(${WATCHDOG_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +install(TARGETS ${WATCHDOG_NAME} RUNTIME DESTINATION bin) diff --git a/watchdog/Watchdog.cpp b/watchdog/Watchdog.cpp new file mode 100644 index 0000000..18d7160 --- /dev/null +++ b/watchdog/Watchdog.cpp @@ -0,0 +1,304 @@ +#include "Watchdog.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "definitions.h" + +WatchdogTask::WatchdogTask() : fd(0) { + int result = 0; + std::error_code e; + // Only create the FIFO if it does not exist yet + if (not std::filesystem::exists(watchdog::FIFO_NAME, e)) { + // Permission 666 or rw-rw-rw- + mode_t mode = DEFFILEMODE; + result = mkfifo(watchdog::FIFO_NAME.c_str(), mode); + if (result != 0) { + std::cerr << "Could not created named pipe at " << watchdog::FIFO_NAME << ", error " << errno + << ": " << strerror(errno) << std::endl; + throw std::runtime_error("eive-watchdog: FIFO creation failed"); + } +#if WATCHDOG_VERBOSE_LEVEL >= 1 + std::cout << "Pipe at " << watchdog::FIFO_NAME << " created successfully" << std::endl; +#endif + } +} + +WatchdogTask::~WatchdogTask() {} + +int WatchdogTask::performOperation() { + // Open FIFO read only and non-blocking + fd = open(watchdog::FIFO_NAME.c_str(), O_RDONLY | O_NONBLOCK); + if (fd < 0) { + std::cerr << "Opening pipe " << watchdog::FIFO_NAME << "read-only failed with " << errno << ": " + << strerror(errno) << std::endl; + return -1; + } + // Clear FIFO by reading until it is empty. + while (true) { + ssize_t readBytes = read(fd, buf.data(), buf.size()); + if (readBytes < 0) { + std::cerr << "Read error of FIFO: " << strerror(errno) << std::endl; + } else if (readBytes == 0) { + break; + } + } + state = States::NOT_STARTED; + + bool breakOuter = false; + while (true) { + watchdogLoop(); + while (not resultQueue.empty()) { + auto nextRequest = resultQueue.front(); + if (not stateMachine(nextRequest)) { + breakOuter = true; + resultQueue.pop(); + break; + } + resultQueue.pop(); + } + if (breakOuter) { + break; + } + } + if (close(fd) < 0) { + std::cerr << "Closing named pipe at " << watchdog::FIFO_NAME << "failed, error " << errno + << ": " << strerror(errno) << std::endl; + } + std::cout << "Closing" << std::endl; + return 0; +} + +void WatchdogTask::watchdogLoop() { + using namespace std::chrono_literals; + struct pollfd waiter = {}; + waiter.fd = fd; + waiter.events = POLLIN; + + // Only poll one file descriptor with timeout + switch (poll(&waiter, 1, watchdog::TIMEOUT_MS)) { + case (0): { + resultQueue.push(LoopResult::TIMEOUT); + return; + } + case (1): { + pollEvent(waiter); + return; + } + default: { + std::cerr << "Unknown poll error at " << watchdog::FIFO_NAME << ", error " << errno << ": " + << strerror(errno) << std::endl; + break; + } + } +} + +void WatchdogTask::pollEvent(struct pollfd& waiter) { + if (waiter.revents & POLLIN) { + ssize_t readLen = read(fd, buf.data(), buf.size()); +#if WATCHDOG_VERBOSE_LEVEL == 2 + std::cout << "Read " << readLen << " byte(s) on the pipe " << watchdog::FIFO_NAME << std::endl; +#endif + if (readLen < 0) { + std::cerr << "Read error on pipe " << watchdog::FIFO_NAME << ", error " << errno << ": " + << strerror(errno) << std::endl; + resultQueue.push(LoopResult::OK); + } else if (readLen >= 1) { + parseCommands(readLen); + } + + } else if (waiter.revents & POLLERR) { + std::cerr << "Poll error error on pipe " << watchdog::FIFO_NAME << std::endl; + resultQueue.push(LoopResult::FAULT); + } else if (waiter.revents & POLLHUP) { + // Writer closed its end + resultQueue.push(LoopResult::HUNG_UP); + } +} + +void WatchdogTask::parseCommands(ssize_t readLen) { + for (ssize_t idx = 0; idx < readLen; idx++) { + char nextChar = buf[idx]; + // Cancel request + if (nextChar == watchdog::first::CANCEL_CHAR) { + resultQueue.push(LoopResult::CANCEL_REQ); + } else if (nextChar == watchdog::first::SUSPEND_CHAR) { + // Suspend request + resultQueue.push(LoopResult::SUSPEND_REQ); + } else if (nextChar == watchdog::first::START_CHAR) { + if (idx < readLen - 1 and static_cast(buf[idx + 1]) == watchdog::second::WATCH_FLAG) { + resultQueue.push(LoopResult::START_WITH_WATCH_REQ); + idx++; + continue; + } + resultQueue.push(LoopResult::START_REQ); + } else if (nextChar == watchdog::first::IDLE_CHAR) { + resultQueue.push(LoopResult::OK); + } + } + // Everything else: All working as expected +} + +int WatchdogTask::performRunningOperation() { + if (state != States::RUNNING) { + state = States::RUNNING; + } + if (notRunningStart.has_value()) { + notRunningStart = std::nullopt; + } + + if (not obswRunning) { + if (printNotRunningLatch) { + // Reset latch so user can see timeouts + printNotRunningLatch = false; + } + + obswRunning = true; + std::cout << "OBSW is running" << std::endl; +#if WATCHDOG_CREATE_FILE_IF_RUNNING == 1 + std::cout << "Creating " << watchdog::RUNNING_FILE_NAME << std::endl; + std::error_code e; + if (not std::filesystem::exists(watchdog::RUNNING_FILE_NAME, e)) { + std::ofstream obswRunningFile(watchdog::RUNNING_FILE_NAME); + if (not obswRunningFile.good()) { + std::cerr << "Creating file " << watchdog::RUNNING_FILE_NAME << " failed" << std::endl; + } + } +#endif + } + return 0; +} + +int WatchdogTask::performNotRunningOperation(LoopResult type) { + // Latch prevents spam on console + if (not printNotRunningLatch) { + if (type == LoopResult::HUNG_UP) { + std::cout << "OBSW hung up" << std::endl; + } else { + std::cout << "The FIFO timed out, OBSW might not be running" << std::endl; + } + printNotRunningLatch = true; + } + + if (not notRunningStart.has_value()) { + notRunningStart = std::chrono::steady_clock::now(); + } + + if (obswRunning) { +#if WATCHDOG_CREATE_FILE_IF_RUNNING == 1 + std::cout << "Removing " << watchdog::RUNNING_FILE_NAME << std::endl; + std::error_code e; + if (std::filesystem::exists(watchdog::RUNNING_FILE_NAME, e)) { + int result = std::remove(watchdog::RUNNING_FILE_NAME.c_str()); + if (result != 0) { + std::cerr << "Removing " << watchdog::RUNNING_FILE_NAME << " failed with code " << errno + << ": " << strerror(errno) << std::endl; + } + } +#endif + obswRunning = false; + } + + if (watchingObsw) { + auto timeNotRunning = std::chrono::steady_clock::now() - notRunningStart.value(); + if (std::chrono::duration_cast(timeNotRunning).count() > + watchdog::MAX_NOT_RUNNING_MS) { + std::cout << "Restarting OBSW with systemctl" << std::endl; + std::system("systemctl restart obsw"); + } + } + if (type == LoopResult::HUNG_UP) { + using namespace std::chrono_literals; + // Prevent spam + std::this_thread::sleep_for(2000ms); + } + return 0; +} + +bool WatchdogTask::stateMachine(LoopResult loopResult) { + using namespace std::chrono_literals; + bool sleep = false; + switch (state) { + case (States::RUNNING): { + switch (loopResult) { + case (LoopResult::TIMEOUT): + case (LoopResult::HUNG_UP): { + performNotRunningOperation(loopResult); + break; + } + case (LoopResult::OK): { + performRunningOperation(); + break; + } + case (LoopResult::SUSPEND_REQ): { + if (state == States::RUNNING or state == States::FAULTY) { + std::cout << "Received suspend request, suspending watchdog operations" << std::endl; + state = States::SUSPENDED; + } + performSuspendOperation(); + sleep = true; + break; + } + case (LoopResult::CANCEL_REQ): { + std::cout << "Received cancel request, closing watchdog.." << std::endl; + return false; + } + } + } + case (States::FAULTY): + case (States::SUSPENDED): + case (States::NOT_STARTED): { + switch (loopResult) { + case (LoopResult::SUSPEND_REQ): { + // Ignore and also delay + sleep = true; + break; + } + case (LoopResult::START_REQ): + case (LoopResult::START_WITH_WATCH_REQ): { + if (state == States::NOT_STARTED or state == States::FAULTY) { + state = States::RUNNING; + } + if (loopResult == LoopResult::START_REQ) { + std::cout << "Start request without watch request received" << std::endl; + watchingObsw = false; + } else if (loopResult == LoopResult::START_WITH_WATCH_REQ) { + std::cout << "Start request with watch request received. Restarting OBSW if not " + "running for " + << watchdog::MAX_NOT_RUNNING_MS / 1000 << " seconds" << std::endl; + watchingObsw = true; + } + performRunningOperation(); + break; + } + default: { + sleep = true; + } + } + break; + } + } + if (loopResult == LoopResult::FAULT) { + // Configuration error + std::cerr << "Fault has occured in watchdog loop" << std::endl; + // Prevent spam + sleep = true; + } + if (sleep) { + std::this_thread::sleep_for(500ms); + } + return true; +} + +int WatchdogTask::performSuspendOperation() { return 0; } diff --git a/watchdog/Watchdog.h b/watchdog/Watchdog.h new file mode 100644 index 0000000..340a9f9 --- /dev/null +++ b/watchdog/Watchdog.h @@ -0,0 +1,56 @@ +#ifndef WATCHDOG_WATCHDOG_H_ +#define WATCHDOG_WATCHDOG_H_ + +#include +#include +#include +#include +#include +#include + +class WatchdogTask { + public: + enum class States { NOT_STARTED, RUNNING, SUSPENDED, FAULTY }; + + enum class LoopResult { + OK, + START_REQ, + START_WITH_WATCH_REQ, + SUSPEND_REQ, + CANCEL_REQ, + TIMEOUT, + HUNG_UP, + FAULT + }; + + WatchdogTask(); + + virtual ~WatchdogTask(); + + int performOperation(); + + private: + int fd = 0; + + bool obswRunning = false; + bool watchingObsw = false; + bool printNotRunningLatch = false; + std::array buf; + std::queue resultQueue; + + std::optional> notRunningStart; + States state = States::NOT_STARTED; + + // Primary loop. Takes care of delaying, and reading from the communication pipe and translating + // messages to loop results. + void watchdogLoop(); + bool stateMachine(LoopResult result); + void pollEvent(struct pollfd& waiter); + void parseCommands(ssize_t readLen); + + int performRunningOperation(); + int performNotRunningOperation(LoopResult type); + int performSuspendOperation(); +}; + +#endif /* WATCHDOG_WATCHDOG_H_ */ diff --git a/watchdog/definitions.h b/watchdog/definitions.h new file mode 100644 index 0000000..5b68023 --- /dev/null +++ b/watchdog/definitions.h @@ -0,0 +1,35 @@ +#ifndef WATCHDOG_DEFINITIONS_H_ +#define WATCHDOG_DEFINITIONS_H_ + +#include + +namespace watchdog { + +namespace first { + +// Start or restart character +static constexpr char START_CHAR = 'b'; +// Suspend watchdog operations temporarily +static constexpr char SUSPEND_CHAR = 's'; +// Causes the watchdog to close down +static constexpr char CANCEL_CHAR = 'c'; +static constexpr char IDLE_CHAR = 'i'; + +} // namespace first + +namespace second { + +// Supplied with the start character. This will instruct the watchdog to actually watch +// the OBSW is runnng all the time. +static constexpr char WATCH_FLAG = 'w'; +} // namespace second + +static constexpr int TIMEOUT_MS = 5 * 1000; +// 2 minutes +static constexpr unsigned MAX_NOT_RUNNING_MS = 2 * 60 * 1000; +const std::string FIFO_NAME = "/tmp/watchdog-pipe"; +const std::string RUNNING_FILE_NAME = "/tmp/obsw-running"; + +} // namespace watchdog + +#endif /* WATCHDOG_DEFINITIONS_H_ */ diff --git a/watchdog/main.cpp b/watchdog/main.cpp new file mode 100644 index 0000000..0173a29 --- /dev/null +++ b/watchdog/main.cpp @@ -0,0 +1,34 @@ + +#include +#include +#include + +#include "Watchdog.h" +#include "definitions.h" + +/** + * @brief This watchdog application uses a FIFO to check whether the OBSW is still running. + * It checks whether the OBSW writes to the the FIFO regularly. + */ +int main() { + std::cout << "Starting OBSW watchdog" << std::endl; + std::error_code e; + if (std::filesystem::exists(watchdog::RUNNING_FILE_NAME, e)) { + std::cout << "Removing " << watchdog::RUNNING_FILE_NAME << std::endl; + int result = std::remove(watchdog::RUNNING_FILE_NAME.c_str()); + if (result != 0) { + std::cerr << "file removal failure" << std::endl; + } + } + try { + WatchdogTask watchdogTask; + int result = watchdogTask.performOperation(); + if (result != 0) { + return result; + } + } catch (const std::runtime_error& e) { + std::cerr << "Run time exception " << e.what() << std::endl; + return -1; + } + return 0; +} diff --git a/watchdog/watchdogConf.h.in b/watchdog/watchdogConf.h.in new file mode 100644 index 0000000..41bebe7 --- /dev/null +++ b/watchdog/watchdogConf.h.in @@ -0,0 +1,9 @@ +#include +#include + +#define WATCHDOG_VERBOSE_LEVEL 1 +/** + * This flag instructs the watchdog to create a special file in /tmp if the OBSW is running + * or to delete it if it is not running + */ +#define WATCHDOG_CREATE_FILE_IF_RUNNING 1