Files
BionxControl/bcdrivertinycan.cpp
Christoph Holzheuer c0ce6a81e3 Removed debug messages.
2026-01-12 09:01:06 +01:00

317 lines
8.8 KiB
C++

/***************************************************************************
BionxControl
© 2025 -2026 christoph holzheuer
christoph.holzheuer@gmail.com
Using:
mhs_can_drv.c
© 2011 - 2023 by MHS-Elektronik GmbH & Co. KG, Germany
Klaus Demlehner, klaus@mhs-elektronik.de
@see www.mhs-elektronik.de
Based on Bionx data type descriptions from:
BigXionFlasher USB V 0.2.4 rev. 97
© 2011-2013 by Thomas Koenig <info@bigxionflasher.org>
@see www.bigxionflasher.org
Bionx Bike Info
© 2018 Thorsten Schmidt (tschmidt@ts-soft.de)
@see www.ts-soft.de
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
@see https://github.com/bikemike/bionx-bikeinfo
***************************************************************************/
#include <QDebug>
#include <bcdrivertinycan.h>
#include <can_drv.h>
#if defined(Q_OS_WIN)
// Unter Windows steht der Treibername in der registry
static const char* cMHS_DRIVERNAME = NULL;
#elif defined(Q_OS_LINUX)
// Unter linux(artigen) muss der Treibername explizit mit übergeben werden
static const char* cMHS_DRIVERNAME = "libmhstcan.so";
#endif
/*
// TinyCan C-Api
// -------------
int32_t CanInitDriver(char *options);
void CanDownDriver(void);
int32_t CanSetOptions(char *options);
int32_t CanDeviceOpen(uint32_t index, char *parameter);
int32_t CanDeviceClose(uint32_t index);
int32_t CanSetMode(uint32_t index, unsigned char can_op_mode, uint16_t can_command);
int32_t CanTransmit(uint32_t index, struct TCanMsg *msg, int32_t count);
void CanTransmitClear(uint32_t index);
uint32_t CanTransmitGetCount(uint32_t index);
int32_t CanTransmitSet(uint32_t index, uint16_t cmd, uint32_t time);
int32_t CanReceive(uint32_t index, struct TCanMsg *msg, int32_t count);
void CanReceiveClear(uint32_t index);
uint32_t CanReceiveGetCount(uint32_t index);
int32_t CanSetSpeed(uint32_t index, uint16_t speed);
int32_t CanSetSpeedUser(uint32_t index, uint32_t value);
char* CanDrvInfo(void);
char* CanDrvHwInfo(uint32_t index);
int32_t CanSetFilter(uint32_t index, struct TMsgFilter *msg_filter);
int32_t CanGetDeviceStatus(uint32_t index, struct TDeviceStatus *status);
void CanSetPnPEventCallback(void (DRV_CALLBACK_TYPE *event)(uint32_t index, int32_t status));
void CanSetStatusEventCallback(void (DRV_CALLBACK_TYPE *event) (uint32_t index, struct TDeviceStatus *device_status) );
void CanSetRxEventCallback(void (DRV_CALLBACK_TYPE *event)(uint32_t index, struct TCanMsg *msg, int32_t count) );
void CanSetEvents( uint16_t events );
uint32_t CanEventStatus(void);
*/
/**
* @brief Destruktor. Entlädt den CAN-Bus Treiber wieder.
*/
BCDriverTinyCan::~BCDriverTinyCan()
{
resetDriver();
}
/**
* @brief BCDriverTinyCan::loadAndStartDriver
* @return
*/
BCDriver::DriverStateResult BCDriverTinyCan::loadAndStartDriver()
{
DriverStateResult result;
if( _driverState < DriverState::Opened)
result = loadDriver();
if( _driverState == DriverState::Opened)
result = setConsoleSlaveMode();
return result;
}
/**
* @brief BCDriverTinyCan::loadDriver
* @return
*/
BCDriver::DriverStateResult BCDriverTinyCan::loadDriver()
{
auto callLoadDriver = [&]() -> DriverStateResult
{
if( ::LoadDriver( cMHS_DRIVERNAME ) < 0 )
return std::unexpected(QString("Driver Error: 'LoadDriver'"));
_driverState = DriverState::Loaded;
return _driverState;
};
auto callInitDriver = [&](DriverState state) -> DriverStateResult
{
Q_UNUSED(state)
if( ::CanInitDriver( NULL ) < 0 )
return std::unexpected(QString("Driver Error: 'InitDriver'"));
_driverState = DriverState::Initialized;
return _driverState;
};
auto callOpenDevice = [&](DriverState state) -> DriverStateResult
{
Q_UNUSED(state)
if( ::CanDeviceOpen( 0, NULL ) < 0 )
return std::unexpected(QString("Driver Error: 'DeviceOpen'"));
::TDeviceStatus deviceStatus;
::CanSetSpeed( 0, CAN_125K_BIT );
::CanSetMode( 0, OP_CAN_START, CAN_CMD_ALL_CLEAR );
::CanGetDeviceStatus( 0, &deviceStatus );
if( deviceStatus.DrvStatus < DRV_STATUS_CAN_OPEN )
return std::unexpected(QString("Driver Error: could not open device." ));
if( deviceStatus.CanStatus == CAN_STATUS_BUS_OFF )
{
::CanSetMode( 0, OP_CAN_RESET, CAN_CMD_NONE );
return std::unexpected(QString("Driver Error: CAN Status 'BusOff'" ));
}
_driverState = DriverState::Opened;
return _driverState;
};
// #1. erstmal komplett zurücksetzen
resetDriver();
// #2. Treiber laden, initialisieren und
// mit dem tinyCan Interface verbinden.
auto newDriverState = callLoadDriver()
.and_then( callInitDriver )
.and_then( callOpenDevice );
// in Fehlerfall ist der Errorstring gesetzt,
// der interne _driverstate ist
// irgendwo unter DriverState::Opened
return newDriverState;
}
/**
* @brief Um mit dem Bionx eBike reden zu können, müssen wir
* die Console in den Slave-Mode setzen.
* @return Fehlerstring oder DriverState::DeviceReady
*/
BCDriver::DriverStateResult BCDriverTinyCan::setConsoleSlaveMode()
{
// Wir versuchen ein Test-Byte zu lesen, hier: einfach die Hardware
// Revision der Console.
uint32_t console = static_cast<uint32_t>(BCDevice::ID::Console);
uint8_t slaveFlag = static_cast<uint8_t> (BC::ID::Cons_Status_Slave);
unsigned int retry = cTimeOuts;
TransmitResult isSlave = 0;
// Already slave?
isSlave = readRawByte( console, slaveFlag );
if( isSlave.has_value() && isSlave.value() == 1 )
goto happyEnd;
do
{
writeRawByte( console, slaveFlag, 1 );
isSlave = readRawByte( console, slaveFlag );
bc::delay_millis( 200 );
} while( retry-- && !(*isSlave) );
bc::delay_millis( 500 ); // give the Console some time to settle
if( isSlave.has_value() && isSlave.value() == 1 )
goto happyEnd;
// ist das jetzt irgendwie schlimm, wenn wir keine slave Console haben
return DriverState::Opened;
happyEnd:
_driverState = DriverState::DeviceReady;
return DriverState::DeviceReady;
}
void BCDriverTinyCan::resetDriver()
{
if( _driverState > DriverState::NotPresent )
{
::CanDownDriver();
::UnloadDriver();
_driverState = DriverState::NotPresent;
}
}
/**
* @brief BCDriverTinyCan::readRawByte
* Kapselt den Treiberzugiff über die legacy C-Api. Liest ein byte, gibt dieses im std::expected aber
* als uint32_t zurück, um mit der ReadFunctions der ValueTypes kompatible zu sein.
* Im Fehlerfall wird ein Fehlerstring zurückgegeben.
*/
TransmitResult BCDriverTinyCan::readRawByte( uint32_t deviceID, uint8_t registerID ) const
{
if( _driverState <DriverState::Opened )
return std::unexpected(QString("readRawValue error: Treiber nicht geladen." ) );
::TCanMsg msg;
// msg verpacken
msg.MsgFlags = 0L;
msg.Id = deviceID;
msg.MsgLen = 2;
msg.MsgData[0] = 0x00;
msg.MsgData[1] = registerID;
// msg verschicken
::CanTransmit( 0, &msg, 1 );
int retries = cRetries; // 5
int timeOuts = cTimeOuts; // 20
// ... warten bis der Sendepuffer leer ist
while( timeOuts-- && ::CanTransmitGetCount( 0 ) )
bc::delay_millis( cTIMEOUT_MS );
if( timeOuts == -1 )
return std::unexpected(QString("readRawValue error: Sendefehler" ));
retry:
// cTimeOuts (== 20) mal cTIMEOUT_MS (== 10 ms ) Versuche ...
timeOuts = cTimeOuts;
// ... warten, bis der Empfangspuffer nicht mehr leer ist
while( timeOuts-- && !::CanReceiveGetCount( 0 ) )
bc::delay_millis( cTIMEOUT_MS );
if( timeOuts == -1 )
return std::unexpected(QString("readRawValue error: (Node)Timeout" ));
// message empfangen
int err = ::CanReceive( 0, &msg, 1 );
if( err < 0 )
return std::unexpected(QString("readRawValue error: Lesefehler" ));
if( --retries && ( msg.Id != (uint32_t)BC::ID::ID_Bib || msg.MsgLen != 4 || msg.MsgData[1] != registerID ) )
goto retry;
if( !timeOuts )
return std::unexpected(QString("CAN response errror: Timeout" ));
return (uint32_t) msg.MsgData[3];
}
TransmitResult BCDriverTinyCan::writeRawByte( uint32_t deviceID, uint8_t registerID, uint8_t value ) const
{
if( _driverState <DriverState::Opened )
return std::unexpected(QString("readRawValue error: driver not loaded." ) );
::TCanMsg msg;
int timeout_count = cTIMEOUT_COUNT;
msg.MsgFlags = 0L;
msg.Id = deviceID;
msg.MsgLen = 4;
msg.MsgData[0] = 0x00;
msg.MsgData[1] = registerID;
msg.MsgData[2] = 0x00;
msg.MsgData[3] = value;
::CanTransmit( 0, &msg, 1 );
while( timeout_count-- && ::CanTransmitGetCount( 0 ) )
bc::delay_millis( cTIMEOUT_MS );
if( timeout_count == -1 )
return std::unexpected(QString("error: could not send value to %1" ).arg( deviceID ) );
return uint32_t(0); // kein Fehler
}