Raspberry Pi fixes, GPS Handler update, Stripped Executable #84

Merged
meierj merged 12 commits from mueller/stripped-exec into develop 2021-08-16 13:41:22 +02:00
13 changed files with 122 additions and 62 deletions

View File

@ -153,8 +153,6 @@ if(ADD_JSON_LIB)
add_subdirectory(${LIB_JSON_PATH}) add_subdirectory(${LIB_JSON_PATH})
endif() endif()
if(NOT EIVE_BUILD_WATCHDOG) if(NOT EIVE_BUILD_WATCHDOG)
if(ADD_LINUX_FILES) if(ADD_LINUX_FILES)
add_subdirectory(${LINUX_PATH}) add_subdirectory(${LINUX_PATH})
@ -190,9 +188,14 @@ if((NOT BUILD_Q7S_SIMPLE_MODE) AND (NOT EIVE_BUILD_WATCHDOG))
${LIB_FSFW_NAME} ${LIB_FSFW_NAME}
${LIB_OS_NAME} ${LIB_OS_NAME}
${LIB_LWGPS_NAME} ${LIB_LWGPS_NAME}
)
if(TGT_BSP MATCHES "arm/q7s")
target_link_libraries(${TARGET_NAME} PRIVATE
${LIB_ARCSEC} ${LIB_ARCSEC}
) )
endif() endif()
endif()
if(NOT EIVE_BUILD_WATCHDOG) if(NOT EIVE_BUILD_WATCHDOG)
if(ADD_CSP_LIB) if(ADD_CSP_LIB)
@ -214,8 +217,6 @@ if(ADD_JSON_LIB)
) )
endif() endif()
target_link_libraries(${TARGET_NAME} PRIVATE target_link_libraries(${TARGET_NAME} PRIVATE
${LIB_CXX_FS} ${LIB_CXX_FS}
) )
@ -228,6 +229,11 @@ target_include_directories(${TARGET_NAME} PRIVATE
${ARCSEC_LIB_PATH} ${ARCSEC_LIB_PATH}
) )
if(TGT_BSP MATCHES "arm/q7s")
target_include_directories(${TARGET_NAME} PRIVATE
${ARCSEC_LIB_PATH}
)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(WARNING_FLAGS set(WARNING_FLAGS

View File

@ -134,12 +134,14 @@ void initmission::initTasks() {
objects::INTERNAL_ERROR_REPORTER); objects::INTERNAL_ERROR_REPORTER);
} }
bool startTestPst = true;
#if OBSW_ADD_TEST_PST == 1 #if OBSW_ADD_TEST_PST == 1
FixedTimeslotTaskIF* pstTestTask = factory->createFixedTimeslotTask("ACS_PST", 50, FixedTimeslotTaskIF* pstTestTask = factory->createFixedTimeslotTask("ACS_PST", 50,
PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, 2.0, missedDeadlineFunc); PeriodicTaskIF::MINIMUM_STACK_SIZE * 2, 2.0, missedDeadlineFunc);
result = pst::pstTest(pstTestTask); result = pst::pstTest(pstTestTask);
if(result != HasReturnvaluesIF::RETURN_OK) { if(result != HasReturnvaluesIF::RETURN_OK) {
sif::warning << "initmission::initTasks: ACS PST initialization failed!" << std::endl; sif::info << "initmission::initTasks: ACS PST empty or invalid" << std::endl;
startTestPst = false;
} }
#endif /* RPI_TEST_ACS_BOARD == 1 */ #endif /* RPI_TEST_ACS_BOARD == 1 */
@ -186,7 +188,9 @@ void initmission::initTasks() {
#endif /* OBSW_ADD_TEST_CODE == 1 */ #endif /* OBSW_ADD_TEST_CODE == 1 */
#if OBSW_ADD_TEST_PST == 1 #if OBSW_ADD_TEST_PST == 1
if(startTestPst) {
pstTestTask->startTask(); pstTestTask->startTask();
}
#endif /* RPI_TEST_ACS_BOARD == 1 */ #endif /* RPI_TEST_ACS_BOARD == 1 */
sif::info << "Tasks started.." << std::endl; sif::info << "Tasks started.." << std::endl;
} }

View File

@ -1,6 +1,3 @@
#include <fsfw_hal/linux/uart/UartComIF.h>
#include <fsfw_hal/linux/uart/UartCookie.h>
#include <mission/devices/GPSHyperionHandler.h>
#include "ObjectFactory.h" #include "ObjectFactory.h"
#include "objects/systemObjectList.h" #include "objects/systemObjectList.h"
@ -17,6 +14,7 @@
#include "mission/core/GenericFactory.h" #include "mission/core/GenericFactory.h"
#include "mission/utility/TmFunnel.h" #include "mission/utility/TmFunnel.h"
#include <mission/devices/GPSHyperionHandler.h>
#include "mission/devices/MGMHandlerLIS3MDL.h" #include "mission/devices/MGMHandlerLIS3MDL.h"
#include "mission/devices/MGMHandlerRM3100.h" #include "mission/devices/MGMHandlerRM3100.h"
#include "mission/devices/GyroADIS16507Handler.h" #include "mission/devices/GyroADIS16507Handler.h"
@ -28,8 +26,13 @@
#include "fsfw/tasks/TaskFactory.h" #include "fsfw/tasks/TaskFactory.h"
/* UDP server includes */ /* UDP server includes */
#if OBSW_USE_TMTC_TCP_BRIDGE == 1
#include <fsfw/src/fsfw/osal/common/TcpTmTcBridge.h>
#include <fsfw/src/fsfw/osal/common/TcpTmTcServer.h>
#else
#include "fsfw/osal/common/UdpTmTcBridge.h" #include "fsfw/osal/common/UdpTmTcBridge.h"
#include "fsfw/osal/common/UdpTcPollingTask.h" #include "fsfw/osal/common/UdpTcPollingTask.h"
#endif
#include "fsfw_hal/devicehandlers/GyroL3GD20Handler.h" #include "fsfw_hal/devicehandlers/GyroL3GD20Handler.h"
#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h" #include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h"
@ -37,6 +40,8 @@
#include "fsfw_hal/common/gpio/GpioCookie.h" #include "fsfw_hal/common/gpio/GpioCookie.h"
#include "fsfw_hal/linux/spi/SpiCookie.h" #include "fsfw_hal/linux/spi/SpiCookie.h"
#include "fsfw_hal/linux/spi/SpiComIF.h" #include "fsfw_hal/linux/spi/SpiComIF.h"
#include <fsfw_hal/linux/uart/UartComIF.h>
#include <fsfw_hal/linux/uart/UartCookie.h>
void Factory::setStaticFrameworkObjectIds() { void Factory::setStaticFrameworkObjectIds() {
PusServiceBase::packetSource = objects::PUS_PACKET_DISTRIBUTOR; PusServiceBase::packetSource = objects::PUS_PACKET_DISTRIBUTOR;
@ -59,12 +64,27 @@ void ObjectFactory::produce(void* args){
Factory::setStaticFrameworkObjectIds(); Factory::setStaticFrameworkObjectIds();
ObjectFactory::produceGenericObjects(); ObjectFactory::produceGenericObjects();
#if OBSW_USE_TMTC_TCP_BRIDGE == 1
new TcpTmTcBridge(objects::TMTC_BRIDGE, objects::CCSDS_PACKET_DISTRIBUTOR);
new TcpTmTcServer(objects::TMTC_POLLING_TASK, objects::TMTC_BRIDGE);
#else
new UdpTmTcBridge(objects::TMTC_BRIDGE, objects::CCSDS_PACKET_DISTRIBUTOR); new UdpTmTcBridge(objects::TMTC_BRIDGE, objects::CCSDS_PACKET_DISTRIBUTOR);
new UdpTcPollingTask(objects::TMTC_POLLING_TASK, objects::TMTC_BRIDGE); new UdpTcPollingTask(objects::TMTC_POLLING_TASK, objects::TMTC_BRIDGE);
#endif
GpioIF* gpioIF = new LinuxLibgpioIF(objects::GPIO_IF); GpioIF* gpioIF = new LinuxLibgpioIF(objects::GPIO_IF);
GpioCookie* gpioCookie = nullptr; GpioCookie* gpioCookie = nullptr;
static_cast<void>(gpioCookie); static_cast<void>(gpioCookie);
new SpiComIF(objects::SPI_COM_IF, gpioIF);
std::string spiDev;
SpiCookie* spiCookie = nullptr;
static_cast<void>(spiCookie);
#if OBSW_ADD_TEST_CODE == 1
new TestTask(objects::TEST_TASK);
#if RPI_ADD_SPI_TEST == 1 #if RPI_ADD_SPI_TEST == 1
new SpiTestClass(objects::SPI_TEST, gpioIF); new SpiTestClass(objects::SPI_TEST, gpioIF);
#endif #endif
@ -89,12 +109,6 @@ void ObjectFactory::produce(void* args){
new LibgpiodTest(objects::LIBGPIOD_TEST, objects::GPIO_IF, gpioCookieLoopback); new LibgpiodTest(objects::LIBGPIOD_TEST, objects::GPIO_IF, gpioCookieLoopback);
#endif /* RPI_LOOPBACK_TEST_GPIO == 1 */ #endif /* RPI_LOOPBACK_TEST_GPIO == 1 */
new SpiComIF(objects::SPI_COM_IF, gpioIF);
std::string spiDev;
SpiCookie* spiCookie = nullptr;
static_cast<void>(spiCookie);
#if RPI_TEST_ACS_BOARD == 1 #if RPI_TEST_ACS_BOARD == 1
if(gpioCookie == nullptr) { if(gpioCookie == nullptr) {
gpioCookie = new GpioCookie(); gpioCookie = new GpioCookie();
@ -158,8 +172,9 @@ void ObjectFactory::produce(void* args){
uartCookie->setToFlushInput(true); uartCookie->setToFlushInput(true);
uartCookie->setReadCycles(6); uartCookie->setReadCycles(6);
GPSHyperionHandler* gpsHandler = new GPSHyperionHandler(objects::GPS0_HANDLER, GPSHyperionHandler* gpsHandler = new GPSHyperionHandler(objects::GPS0_HANDLER,
objects::UART_COM_IF, uartCookie); objects::UART_COM_IF, uartCookie, false);
gpsHandler->setStartUpImmediately(); gpsHandler->setStartUpImmediately();
#endif #endif
#endif /* OBSW_ADD_TEST_CODE == 1 */
} }

View File

@ -15,10 +15,7 @@
#define RPI_TEST_ACS_BOARD 0 #define RPI_TEST_ACS_BOARD 0
#endif #endif
#define RPI_ADD_UART_TEST 1 #define RPI_ADD_UART_TEST 0
#if RPI_ADD_UART_TEST == 1
#define RPI_TEST_GPS_DEVICE 0
#endif
/* Adapt these values accordingly */ /* Adapt these values accordingly */
namespace gpio { namespace gpio {

View File

@ -1,4 +1,5 @@
#include "InitMission.h" #include "InitMission.h"
#include "OBSWConfig.h"
#include "OBSWVersion.h" #include "OBSWVersion.h"
#include "fsfw/FSFWVersion.h" #include "fsfw/FSFWVersion.h"

View File

@ -11,17 +11,6 @@ if(LINK_LWIP)
endif() endif()
endif() endif()
if(LINK_HAL)
message(STATUS "Linking against ${LIB_HAL_NAME} HAL library")
if(LIB_HAL_NAME)
target_link_libraries(${TARGET_NAME} PUBLIC
${LIB_HAL_NAME}
)
else()
message(WARNING "HAL library name not set!")
endif()
endif()
if(LINKER_SCRIPT) if(LINKER_SCRIPT)
target_link_options(${TARGET_NAME} PRIVATE target_link_options(${TARGET_NAME} PRIVATE
-T${LINKER_SCRIPT} -T${LINKER_SCRIPT}
@ -52,11 +41,14 @@ target_compile_options(${TARGET_NAME} PRIVATE
$<$<COMPILE_LANGUAGE:ASM>:${ASM_FLAGS}> $<$<COMPILE_LANGUAGE:ASM>:${ASM_FLAGS}>
) )
set(STRIPPED_TARGET_NAME ${TARGET_NAME}-stripped)
add_custom_command( add_custom_command(
TARGET ${TARGET_NAME} TARGET ${TARGET_NAME}
POST_BUILD POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary ${TARGET_NAME} ${TARGET_NAME}.bin COMMAND ${CMAKE_STRIP} --strip-all ${TARGET_NAME} -o ${STRIPPED_TARGET_NAME}
COMMENT "Generating binary file ${CMAKE_PROJECT_NAME}.bin.." BYPRODUCTS ${STRIPPED_TARGET_NAME}
COMMENT "Generating stripped executable ${STRIPPED_TARGET_NAME}.."
) )
endfunction() endfunction()

View File

@ -42,6 +42,7 @@ find_program (CMAKE_CXX_COMPILER ${CROSS_COMPILE_CXX} REQUIRED)
# Useful utilities, not strictly necessary # Useful utilities, not strictly necessary
find_program(CMAKE_SIZE ${CROSS_COMPILE_SIZE}) find_program(CMAKE_SIZE ${CROSS_COMPILE_SIZE})
find_program(CMAKE_OBJCOPY ${CROSS_COMPILE_OBJCOPY}) find_program(CMAKE_OBJCOPY ${CROSS_COMPILE_OBJCOPY})
find_program(CMAKE_STRIP ${CROSS_COMPILE_STRIP})
set(CMAKE_CROSSCOMPILING TRUE) set(CMAKE_CROSSCOMPILING TRUE)
set(CMAKE_SYSROOT "${SYSROOT_PATH}") set(CMAKE_SYSROOT "${SYSROOT_PATH}")

View File

@ -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

View File

@ -6,7 +6,7 @@
#ifndef FSFWCONFIG_OBSWCONFIG_H_ #ifndef FSFWCONFIG_OBSWCONFIG_H_
#define FSFWCONFIG_OBSWCONFIG_H_ #define FSFWCONFIG_OBSWCONFIG_H_
#cmakedefine RASPBERRY_Pi #cmakedefine RASPBERRY_PI
#cmakedefine XIPHOS_Q7S #cmakedefine XIPHOS_Q7S
#cmakedefine BEAGLEBONEBLACK #cmakedefine BEAGLEBONEBLACK

View File

@ -606,7 +606,9 @@ ReturnValue_t pst::pstGompaceCan(FixedTimeslotTaskIF *thisSequence){
ReturnValue_t pst::pstTest(FixedTimeslotTaskIF* thisSequence) { ReturnValue_t pst::pstTest(FixedTimeslotTaskIF* thisSequence) {
/* Length of a communication cycle */ /* Length of a communication cycle */
uint32_t length = thisSequence->getPeriodMs(); uint32_t length = thisSequence->getPeriodMs();
bool notEmpty = false;
#if OBSW_ADD_ACS_BOARD == 1 #if OBSW_ADD_ACS_BOARD == 1
notEmpty = true;
thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0, thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0,
DeviceHandlerIF::PERFORM_OPERATION); DeviceHandlerIF::PERFORM_OPERATION);
thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0.2, thisSequence->addSlot(objects::MGM_0_LIS3_HANDLER, length * 0.2,
@ -678,6 +680,7 @@ ReturnValue_t pst::pstTest(FixedTimeslotTaskIF* thisSequence) {
#endif #endif
#if RPI_TEST_ADIS16507 == 1 #if RPI_TEST_ADIS16507 == 1
notEmpty = true;
thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0, thisSequence->addSlot(objects::GYRO_0_ADIS_HANDLER, length * 0,
DeviceHandlerIF::PERFORM_OPERATION); 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.2, DeviceHandlerIF::SEND_WRITE);
@ -687,6 +690,7 @@ ReturnValue_t pst::pstTest(FixedTimeslotTaskIF* thisSequence) {
#endif #endif
#if RPI_TEST_GPS_HANDLER == 1 #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::PERFORM_OPERATION);
thisSequence->addSlot(objects::GPS0_HANDLER, length * 0, DeviceHandlerIF::SEND_READ); thisSequence->addSlot(objects::GPS0_HANDLER, length * 0, DeviceHandlerIF::SEND_READ);
@ -696,6 +700,9 @@ ReturnValue_t pst::pstTest(FixedTimeslotTaskIF* thisSequence) {
thisSequence->addSlot(objects::GPS0_HANDLER, length * 0.5, DeviceHandlerIF::GET_READ); thisSequence->addSlot(objects::GPS0_HANDLER, length * 0.5, DeviceHandlerIF::GET_READ);
#endif #endif
static_cast<void>(length); static_cast<void>(length);
if(not notEmpty) {
return HasReturnvaluesIF::RETURN_FAILED;
}
if (thisSequence->checkSequence() != HasReturnvaluesIF::RETURN_OK) { if (thisSequence->checkSequence() != HasReturnvaluesIF::RETURN_OK) {
sif::error << "Test PST initialization failed" << std::endl; sif::error << "Test PST initialization failed" << std::endl;
return HasReturnvaluesIF::RETURN_FAILED; return HasReturnvaluesIF::RETURN_FAILED;

View File

@ -7,8 +7,9 @@
#include "lwgps/lwgps.h" #include "lwgps/lwgps.h"
GPSHyperionHandler::GPSHyperionHandler(object_id_t objectId, object_id_t deviceCommunication, GPSHyperionHandler::GPSHyperionHandler(object_id_t objectId, object_id_t deviceCommunication,
CookieIF *comCookie): CookieIF *comCookie, bool debugHyperionGps):
DeviceHandlerBase(objectId, deviceCommunication, comCookie), gpsSet(this) { DeviceHandlerBase(objectId, deviceCommunication, comCookie), gpsSet(this),
debugHyperionGps(debugHyperionGps) {
lwgps_init(&gpsData); lwgps_init(&gpsData);
} }
@ -100,13 +101,13 @@ ReturnValue_t GPSHyperionHandler::scanForReply(const uint8_t *start, size_t len,
gpsSet.hours = gpsData.hours; gpsSet.hours = gpsData.hours;
gpsSet.minutes = gpsData.minutes; gpsSet.minutes = gpsData.minutes;
gpsSet.seconds = gpsData.seconds; gpsSet.seconds = gpsData.seconds;
#if FSFW_HAL_DEBUG_HYPERION_GPS == 1 if(debugHyperionGps) {
sif::info << "GPS Data" << std::endl; sif::info << "GPS Data" << std::endl;
printf("Valid status: %d\n", gpsData.is_valid); printf("Valid status: %d\n", gpsData.is_valid);
printf("Latitude: %f degrees\n", gpsData.latitude); printf("Latitude: %f degrees\n", gpsData.latitude);
printf("Longitude: %f degrees\n", gpsData.longitude); printf("Longitude: %f degrees\n", gpsData.longitude);
printf("Altitude: %f meters\n", gpsData.altitude); printf("Altitude: %f meters\n", gpsData.altitude);
#endif }
} }
*foundLen = len; *foundLen = len;
} }
@ -137,12 +138,13 @@ ReturnValue_t GPSHyperionHandler::initializeLocalDataPool(
localDataPoolMap.emplace(GpsHyperion::UNIX_SECONDS, new PoolEntry<uint32_t>()); localDataPoolMap.emplace(GpsHyperion::UNIX_SECONDS, new PoolEntry<uint32_t>());
localDataPoolMap.emplace(GpsHyperion::SATS_IN_USE, new PoolEntry<uint8_t>()); localDataPoolMap.emplace(GpsHyperion::SATS_IN_USE, new PoolEntry<uint8_t>());
localDataPoolMap.emplace(GpsHyperion::FIX_MODE, new PoolEntry<uint8_t>()); localDataPoolMap.emplace(GpsHyperion::FIX_MODE, new PoolEntry<uint8_t>());
poolManager.subscribeForPeriodicPacket(gpsSet.getSid(), true, 2.0, false);
return HasReturnvaluesIF::RETURN_OK; return HasReturnvaluesIF::RETURN_OK;
} }
void GPSHyperionHandler::fillCommandAndReplyMap() { void GPSHyperionHandler::fillCommandAndReplyMap() {
// Reply length does not matter, packets should always arrive periodically // Reply length does not matter, packets should always arrive periodically
insertInReplyMap(GpsHyperion::GPS_REPLY, 4, nullptr, 0, true); insertInReplyMap(GpsHyperion::GPS_REPLY, 4, &gpsSet, 0, true);
} }
void GPSHyperionHandler::modeChanged() { void GPSHyperionHandler::modeChanged() {

View File

@ -1,13 +1,11 @@
#ifndef MISSION_DEVICES_GPSHYPERIONHANDLER_H_ #ifndef MISSION_DEVICES_GPSHYPERIONHANDLER_H_
#define MISSION_DEVICES_GPSHYPERIONHANDLER_H_ #define MISSION_DEVICES_GPSHYPERIONHANDLER_H_
#include "fsfw/FSFW.h"
#include "fsfw/devicehandlers/DeviceHandlerBase.h" #include "fsfw/devicehandlers/DeviceHandlerBase.h"
#include "devicedefinitions/GPSDefinitions.h" #include "devicedefinitions/GPSDefinitions.h"
#include "lwgps/lwgps.h" #include "lwgps/lwgps.h"
#ifndef FSFW_HAL_DEBUG_HYPERION_GPS
#define FSFW_HAL_DEBUG_HYPERION_GPS 0
#endif
/** /**
* @brief Device handler for the Hyperion HT-GPS200 device * @brief Device handler for the Hyperion HT-GPS200 device
@ -18,10 +16,11 @@
class GPSHyperionHandler: public DeviceHandlerBase { class GPSHyperionHandler: public DeviceHandlerBase {
public: public:
GPSHyperionHandler(object_id_t objectId, object_id_t deviceCommunication, GPSHyperionHandler(object_id_t objectId, object_id_t deviceCommunication,
CookieIF* comCookie); CookieIF* comCookie, bool debugHyperionGps = false);
virtual ~GPSHyperionHandler(); virtual ~GPSHyperionHandler();
protected: protected:
enum class InternalStates { enum class InternalStates {
NONE, NONE,
WAIT_FIRST_MESSAGE, WAIT_FIRST_MESSAGE,
@ -54,6 +53,7 @@ protected:
private: private:
lwgps_t gpsData = {}; lwgps_t gpsData = {};
GpsPrimaryDataset gpsSet; GpsPrimaryDataset gpsSet;
bool debugHyperionGps = false;
}; };
#endif /* MISSION_DEVICES_GPSHYPERIONHANDLER_H_ */ #endif /* MISSION_DEVICES_GPSHYPERIONHANDLER_H_ */

2
tmtc

@ -1 +1 @@
Subproject commit cc6dbd8ef9d5bd028835f37a24a5617224569862 Subproject commit 387a076a2180c14da8de086d690a2ebb5ad63649