initial commit
This commit is contained in:
commit
afbad8227c
22 changed files with 1612 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
build/
|
||||||
|
.cache/
|
||||||
22
CMakeLists.txt
Normal file
22
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(ant)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
set(CMAKE_BUILD_TYPE DEBUG)
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Wswitch-enum")
|
||||||
|
|
||||||
|
get_cmake_property(_variableNames VARIABLES)
|
||||||
|
list (SORT _variableNames)
|
||||||
|
foreach (_variableName ${_variableNames})
|
||||||
|
message(STATUS "${_variableName}=${${_variableName}}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
add_executable(test src/main.cpp
|
||||||
|
src/ant/ant_device.cpp
|
||||||
|
src/ant/channel/channel.cpp
|
||||||
|
src/ant/channel/heart_rate_channel.cpp
|
||||||
|
src/ant/channel/power_channel.cpp
|
||||||
|
src/ant/channel/fitness_equipment_channel.cpp
|
||||||
|
src/ant/message.cpp
|
||||||
|
src/ant/message_processor.cpp
|
||||||
|
)
|
||||||
116
src/ant/ant.hpp
Normal file
116
src/ant/ant.hpp
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
typedef uint8_t Page;
|
||||||
|
|
||||||
|
struct CommonPages {
|
||||||
|
enum : Page {
|
||||||
|
kRequestDataPage = 0x46,
|
||||||
|
kCommandStatus = 0x47,
|
||||||
|
kSubfieldData = 0x54,
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HeartRateProfilePages {
|
||||||
|
enum : Page {
|
||||||
|
kDefaultPage = 0x00,
|
||||||
|
kCumulativeOperatingTime,
|
||||||
|
kManufacturerInformation,
|
||||||
|
kProductInformation,
|
||||||
|
kPreviousHeartbeatEventTime,
|
||||||
|
kSwimIntervalSummary,
|
||||||
|
kCapabilities,
|
||||||
|
kBatteryStatus,
|
||||||
|
kDeviceInformation,
|
||||||
|
kHeartRateFeature = 32,
|
||||||
|
kRequestDataPage = 70,
|
||||||
|
kModeSettings = 76,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FitnessEquipmentPages {
|
||||||
|
enum : Page {
|
||||||
|
kBikeSepcificData = 0x19,
|
||||||
|
kBasicResistance = 0x30,
|
||||||
|
kTargetPower,
|
||||||
|
kWindResistance,
|
||||||
|
kTrackResistance,
|
||||||
|
kCapabilities = 0x36,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PowerProfilePages {
|
||||||
|
enum : Page {
|
||||||
|
kCalibrationMessage = 0x01,
|
||||||
|
kGetSetPage = 0x02,
|
||||||
|
kMeasurement = 0x03,
|
||||||
|
kStandardPower = 0x10,
|
||||||
|
kStandardTorqueAtWheel = 0x11,
|
||||||
|
kStandardTorqueAtCrnak = 0x12,
|
||||||
|
kStandardTorqueEffectiveness = 0x13,
|
||||||
|
kCrankTorqueFrequency = 0x20,
|
||||||
|
kRightForceAngle = 0xe0,
|
||||||
|
kLeftForceAngle = 0xe1,
|
||||||
|
kPedalPosition = 0xe2,
|
||||||
|
kBattery = 0x52,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
struct PowerProfileSubpages {
|
||||||
|
enum : Page {
|
||||||
|
kCrankParameters = 0x01,
|
||||||
|
kAdvancedCapabilities = 0xfd,
|
||||||
|
kAdvancedCapabilities2 = 0xfe,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint8_t key[8] = {0xB9, 0xA5, 0x21, 0xFB,
|
||||||
|
0xBD, 0x72, 0xC3, 0x45};
|
||||||
|
|
||||||
|
static constexpr uint8_t kSyncByte = 0xA4;
|
||||||
|
static constexpr uint8_t kReservedByte = 0xff;
|
||||||
|
|
||||||
|
typedef uint8_t ChannelID;
|
||||||
|
typedef uint8_t ChannelFrequency;
|
||||||
|
typedef uint8_t TransmissionType;
|
||||||
|
typedef uint16_t DeviceNumber;
|
||||||
|
struct DeviceNumbers {
|
||||||
|
enum : DeviceNumber {
|
||||||
|
Wildcard = 0x0000,
|
||||||
|
LennartsGarminVector3 = 0x5ad0,
|
||||||
|
LennartsPolar = 0x27f6
|
||||||
|
};
|
||||||
|
};
|
||||||
|
typedef uint16_t ChannelPeriod;
|
||||||
|
|
||||||
|
static constexpr DeviceNumber DeviceNumberWildcard = 0x00;
|
||||||
|
|
||||||
|
enum class DeviceType : uint8_t {
|
||||||
|
kAny = 0x00,
|
||||||
|
kHeartRate = 0x78,
|
||||||
|
kPower = 0x0B,
|
||||||
|
kFitnessEquipment = 0x11,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ChannelType : uint8_t {
|
||||||
|
kQuickSearch = 0x10,
|
||||||
|
kWaiting = 0x20,
|
||||||
|
kRX = 0x0,
|
||||||
|
kTX = 0x10,
|
||||||
|
kPair = 0x40,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ChannelConfig {
|
||||||
|
ChannelType type;
|
||||||
|
uint8_t *network_key;
|
||||||
|
ChannelFrequency frequency;
|
||||||
|
TransmissionType transmission_type;
|
||||||
|
DeviceType device_type;
|
||||||
|
DeviceNumber device_number;
|
||||||
|
ChannelPeriod channel_period;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ant
|
||||||
119
src/ant/ant_device.cpp
Normal file
119
src/ant/ant_device.cpp
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#include "ant_device.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <sys/select.h>
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
ANTDevice::ANTDevice() {}
|
||||||
|
|
||||||
|
void ANTDevice::Init() {
|
||||||
|
if (!Open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t buf[300];
|
||||||
|
printf("Sending a reset command!\n");
|
||||||
|
sleep(1);
|
||||||
|
SendMessage(Message::SystemReset());
|
||||||
|
sleep(1);
|
||||||
|
uint8_t network = 1;
|
||||||
|
SendMessage(Message::SetNetworkKey(network, key));
|
||||||
|
while (ReceiveMessage())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ANTDevice::ReceiveMessage() {
|
||||||
|
int n;
|
||||||
|
uint8_t buf[1];
|
||||||
|
do {
|
||||||
|
n = Read(buf, 1, 500);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
if (processor.FeedData(buf[i])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (n > 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ANTDevice::AddDevice(int device_number, int device_type,
|
||||||
|
int channel_number) {
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ANTDevice::Open() {
|
||||||
|
serial_port_ = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||||
|
if (serial_port_ < 0) {
|
||||||
|
perror("Failed to open port");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all lingering data from the buffers
|
||||||
|
tcflush(serial_port_, TCIOFLUSH);
|
||||||
|
|
||||||
|
int line_discipline = N_TTY;
|
||||||
|
if (ioctl(serial_port_, TIOCSETD, &line_discipline) < 0) {
|
||||||
|
perror("Failed to set line discipline.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct termios settings;
|
||||||
|
tcgetattr(serial_port_, &settings);
|
||||||
|
cfmakeraw(&settings);
|
||||||
|
cfsetspeed(&settings, B115200);
|
||||||
|
settings.c_iflag = IGNPAR;
|
||||||
|
settings.c_oflag = 0;
|
||||||
|
// clear size and stop bit settings so we can configure them
|
||||||
|
settings.c_cflag &= (~CSIZE & CSTOPB);
|
||||||
|
settings.c_cflag |= (CS8 | CREAD | HUPCL | CRTSCTS);
|
||||||
|
settings.c_lflag = 0;
|
||||||
|
|
||||||
|
// return reads immediately
|
||||||
|
settings.c_cc[VMIN] = 0;
|
||||||
|
settings.c_cc[VTIME] = 0;
|
||||||
|
|
||||||
|
if (tcsetattr(serial_port_, TCSANOW, &settings) < 0) {
|
||||||
|
perror("Failed to set serial settings.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ANTDevice::Close() {
|
||||||
|
tcflush(serial_port_, TCIOFLUSH);
|
||||||
|
if (close(serial_port_) == -1) {
|
||||||
|
perror("Failed to close serial port");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ANTDevice::SendMessage(Message msg) { Write(msg.data, msg.data_length); }
|
||||||
|
|
||||||
|
int ANTDevice::Write(uint8_t *buf, int size, int timeout) {
|
||||||
|
int n_written = write(serial_port_, buf, size);
|
||||||
|
if (n_written != size) {
|
||||||
|
fprintf(stderr, "Wanted to write %i bytes but have written %i bytes.\n",
|
||||||
|
size, n_written);
|
||||||
|
}
|
||||||
|
return n_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ANTDevice::Read(uint8_t *buf, int bytes, int timeout_ms) {
|
||||||
|
fd_set readfs;
|
||||||
|
timeval tv;
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = 1000 * timeout_ms;
|
||||||
|
FD_ZERO(&readfs);
|
||||||
|
FD_SET(serial_port_, &readfs);
|
||||||
|
select(serial_port_ + 1, &readfs, NULL, NULL, &tv);
|
||||||
|
int n_bytes = 0;
|
||||||
|
if (FD_ISSET(serial_port_, &readfs)) {
|
||||||
|
n_bytes = read(serial_port_, buf, bytes);
|
||||||
|
if (n_bytes < 0) {
|
||||||
|
perror("Failed to read from serial device.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n_bytes;
|
||||||
|
}
|
||||||
|
} // namespace ant
|
||||||
40
src/ant/ant_device.hpp
Normal file
40
src/ant/ant_device.hpp
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
#include "message.hpp"
|
||||||
|
#include "message_processor.hpp"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "ant.hpp"
|
||||||
|
|
||||||
|
#define GARMIN_USB2_VENDOR_ID 0x0fcf
|
||||||
|
#define GARMIN_USB2_PRODUCT_ID 0x1008
|
||||||
|
namespace ant {
|
||||||
|
class ANTDevice {
|
||||||
|
public:
|
||||||
|
ANTDevice();
|
||||||
|
int Read(uint8_t *buf, int bytes, int timeout_ms = 125);
|
||||||
|
int Write(uint8_t *bytes, int size, int timeout = 125);
|
||||||
|
void SendMessage(Message);
|
||||||
|
int AddDevice(int device_number, int device_type, int channel_number);
|
||||||
|
bool ReceiveMessage();
|
||||||
|
bool Open();
|
||||||
|
bool Close();
|
||||||
|
void Init();
|
||||||
|
MessageProcessor processor;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Buffer {
|
||||||
|
static constexpr int kBufferSize = 128;
|
||||||
|
uint8_t data[kBufferSize];
|
||||||
|
uint8_t index{0};
|
||||||
|
};
|
||||||
|
Buffer read_buffer_;
|
||||||
|
int serial_port_;
|
||||||
|
};
|
||||||
|
} // namespace ant
|
||||||
95
src/ant/bicycle_power.hpp
Normal file
95
src/ant/bicycle_power.hpp
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
#pragma once
|
||||||
|
#include "message.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
namespace bicycle_power {
|
||||||
|
class AdvancedCapabilities {
|
||||||
|
public:
|
||||||
|
AdvancedCapabilities(uint8_t _mask = 0xff, uint8_t _value = 0xff,
|
||||||
|
uint8_t _properties = 0xff)
|
||||||
|
: mask(_mask), value(_value), properties(_properties) {}
|
||||||
|
static AdvancedCapabilities FromPayload(BroadcastPayload payload) {
|
||||||
|
return AdvancedCapabilities(payload.raw_data.at(4), payload.raw_data.at(6),
|
||||||
|
payload.raw_data.at(2));
|
||||||
|
}
|
||||||
|
bool Supports4Hz() { return SupportsProperty(0); }
|
||||||
|
bool Supports8Hz() { return SupportsProperty(1); }
|
||||||
|
bool SupportsAutoZero() { return SupportsProperty(4); }
|
||||||
|
bool SupportsAutoCrankLength() { return SupportsProperty(5); }
|
||||||
|
bool SupportsTorqueEfficiencyAndSmoothness() { return SupportsProperty(6); }
|
||||||
|
|
||||||
|
bool Is4HzEnabled() { return IsPropertyEnabled(0); }
|
||||||
|
bool Is8HzEnabled() { return IsPropertyEnabled(1); }
|
||||||
|
bool IsAutoZeroEnabled() { return IsPropertyEnabled(4); }
|
||||||
|
bool IsAutoCrankLengthEnabled() { return IsPropertyEnabled(5); }
|
||||||
|
bool IsTorqueEfficiencyAndSmoothnessEnabled() { return IsPropertyEnabled(6); }
|
||||||
|
|
||||||
|
void EnableTorqueEfficiencyAndSmoothness() { value &= 0b10111111; }
|
||||||
|
void UnmaskTorqueEfficiencyAndSmoothness() { mask &= 0b10111111; }
|
||||||
|
|
||||||
|
void SetMask(uint8_t _mask) { mask = _mask; }
|
||||||
|
uint8_t Mask() { return mask; }
|
||||||
|
void SetValue(uint8_t _value) { value = _value; }
|
||||||
|
uint8_t Value() { return value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool SupportsProperty(uint8_t bit_position) {
|
||||||
|
return !(mask & (1 << bit_position));
|
||||||
|
}
|
||||||
|
bool IsPropertyEnabled(uint8_t bit_position) {
|
||||||
|
return !(value & (1 << bit_position));
|
||||||
|
}
|
||||||
|
uint8_t mask;
|
||||||
|
uint8_t value;
|
||||||
|
uint8_t properties;
|
||||||
|
};
|
||||||
|
class AdvancedCapabilities2 {
|
||||||
|
public:
|
||||||
|
AdvancedCapabilities2(uint8_t _mask = 0xff, uint8_t _value = 0xff)
|
||||||
|
: mask(_mask), value(_value) {}
|
||||||
|
|
||||||
|
static AdvancedCapabilities2 FromPayload(BroadcastPayload payload) {
|
||||||
|
return AdvancedCapabilities2(payload.raw_data.at(4),
|
||||||
|
payload.raw_data.at(6));
|
||||||
|
}
|
||||||
|
bool Supports4Hz() { return SupportsProperty(0); }
|
||||||
|
bool Supports8Hz() { return SupportsProperty(1); }
|
||||||
|
bool SupportsPowerPhase() { return SupportsProperty(3); }
|
||||||
|
bool SupportsPCO() { return SupportsProperty(4); }
|
||||||
|
bool SupportsRiderPosition() { return SupportsProperty(5); }
|
||||||
|
bool SupportsTorqueBarycenter() { return SupportsProperty(6); }
|
||||||
|
|
||||||
|
bool Is4HzEnabled() { return IsPropertyEnabled(0); }
|
||||||
|
bool Is8HzEnabled() { return IsPropertyEnabled(1); }
|
||||||
|
bool IsPowerPhaseEnabled() { return IsPropertyEnabled(3); }
|
||||||
|
bool IsPCOEnabled() { return IsPropertyEnabled(4); }
|
||||||
|
bool IsRiderPositionEnabled() { return IsPropertyEnabled(5); }
|
||||||
|
bool IsTorqueBarycenterEnabled() { return IsPropertyEnabled(6); }
|
||||||
|
|
||||||
|
void EnableAllDynamics() { value &= 0b10000111; }
|
||||||
|
void UnmaskAllDynamics() { mask &= 0b10000111; }
|
||||||
|
void Enable8Hz() { value &= 0b11111101; }
|
||||||
|
void Unmask8Hz() { mask &= 0b11111101; }
|
||||||
|
void Enable4Hz() { value &= 0b11111110; }
|
||||||
|
void Unmask4Hz() { mask &= 0b11111110; }
|
||||||
|
|
||||||
|
void SetMask(uint8_t _mask) { mask = _mask; }
|
||||||
|
uint8_t Mask() { return mask; }
|
||||||
|
void SetValue(uint8_t _value) { value = _value; }
|
||||||
|
uint8_t Value() { return value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool SupportsProperty(uint8_t bit_position) {
|
||||||
|
// 0: does support
|
||||||
|
// 1: does NOT support
|
||||||
|
return !(mask & (1 << bit_position));
|
||||||
|
}
|
||||||
|
bool IsPropertyEnabled(uint8_t bit_position) {
|
||||||
|
return !(value & (1 << bit_position));
|
||||||
|
}
|
||||||
|
uint8_t mask;
|
||||||
|
uint8_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bicycle_power
|
||||||
|
} // namespace ant
|
||||||
124
src/ant/channel/channel.cpp
Normal file
124
src/ant/channel/channel.cpp
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
#include "channel.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
Channel::Channel(ANTDevice &_ant_device, ChannelID _channel_id,
|
||||||
|
DeviceNumber _device_number)
|
||||||
|
: ant_device_(_ant_device) {
|
||||||
|
channel_config_.frequency = 57;
|
||||||
|
channel_config_.transmission_type = 0;
|
||||||
|
channel_config_.device_number = _device_number;
|
||||||
|
id_ = _channel_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::StartSearch() {
|
||||||
|
SetChannelState(ChannelState::kSearching);
|
||||||
|
ant_device_.SendMessage(Message::AssignChannel(id_, channel_config_.type, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::OnSearchTimeout() {
|
||||||
|
printf("[channel %hu] search timed out.\n", id_);
|
||||||
|
printf("[channel %hu] restarting search.\n", id_);
|
||||||
|
StartSearch();
|
||||||
|
}
|
||||||
|
void Channel::OnChannelResponse(ChannelResponseMessage msg) {
|
||||||
|
if (msg.channel_id_ != id_) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"Channel %hu: Received msg for %hu. This should not be possible...\n",
|
||||||
|
id_, msg.channel_id_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state_ == ChannelState::kConnected) {
|
||||||
|
printf(
|
||||||
|
"[channel %hu] Connected and received channel response: 0x%02x %hu\n",
|
||||||
|
id_, msg.msg_id_, msg.msg_code_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state_ == ChannelState::kSearching) {
|
||||||
|
// printf("Channel Response: 0x%02x %hu\n", msg.msg_id_, msg.msg_code_);
|
||||||
|
switch (msg.msg_id_) {
|
||||||
|
// RF Event
|
||||||
|
case 1:
|
||||||
|
switch (msg.msg_code_) {
|
||||||
|
case MessageCodes::kEventRXSearchTimeout:
|
||||||
|
// nothing to do state wise. we will receive a channel closed event
|
||||||
|
fprintf(stderr, "[channel %hu] Search timed out.\n", id_);
|
||||||
|
break;
|
||||||
|
case MessageCodes::kEventChannelClosed:
|
||||||
|
printf("[channel %hu] closed.\n", id_);
|
||||||
|
SetChannelState(ChannelState::kClosed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageIDs::kAssignChannel:
|
||||||
|
if (msg.msg_code_ != MessageCodes::kResponseNoError) {
|
||||||
|
fprintf(stderr, "[channel %hu] failed to assign channel.\n", id_);
|
||||||
|
SetChannelState(ChannelState::kUndefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ant_device_.SendMessage(Message::SetChannelID(
|
||||||
|
id_, channel_config_.device_number, channel_config_.device_type,
|
||||||
|
channel_config_.transmission_type));
|
||||||
|
break;
|
||||||
|
case MessageIDs::kChannelID:
|
||||||
|
if (msg.msg_code_ != MessageCodes::kResponseNoError) {
|
||||||
|
fprintf(stderr, "[channel %hu] failed to set channel id!\n", id_);
|
||||||
|
SetChannelState(ChannelState::kUndefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ant_device_.SendMessage(
|
||||||
|
Message::SetChannelFrequency(id_, channel_config_.frequency));
|
||||||
|
break;
|
||||||
|
case MessageIDs::kChannelFrequency:
|
||||||
|
if (msg.msg_code_ != MessageCodes::kResponseNoError) {
|
||||||
|
fprintf(stderr, "[channel %hu] failed to set channel frequency!\n",
|
||||||
|
id_);
|
||||||
|
SetChannelState(ChannelState::kUndefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ant_device_.SendMessage(
|
||||||
|
Message::SetChannelPeriod(id_, channel_config_.channel_period));
|
||||||
|
break;
|
||||||
|
case MessageIDs::kChannelPeriod:
|
||||||
|
if (msg.msg_code_ != MessageCodes::kResponseNoError) {
|
||||||
|
fprintf(stderr, "[channel %hu] failed to set channel period!\n", id_);
|
||||||
|
SetChannelState(ChannelState::kUndefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ant_device_.SendMessage(Message::SetLPSearchTimeout(id_, 4));
|
||||||
|
break;
|
||||||
|
case MessageIDs::kLPSearchTimeout:
|
||||||
|
if (msg.msg_code_ != MessageCodes::kResponseNoError) {
|
||||||
|
fprintf(stderr, "[channel %hu] failed to set channel period!\n", id_);
|
||||||
|
SetChannelState(ChannelState::kUndefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ant_device_.SendMessage(Message::SetHPSearchTimeout(id_, 0));
|
||||||
|
break;
|
||||||
|
case MessageIDs::kSearchTimeout:
|
||||||
|
if (msg.msg_code_ != MessageCodes::kResponseNoError) {
|
||||||
|
fprintf(stderr, "[channel %hu] failed to set channel timeout\n", id_);
|
||||||
|
SetChannelState(ChannelState::kUndefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ant_device_.SendMessage(Message::OpenChannel(id_));
|
||||||
|
break;
|
||||||
|
case MessageIDs::kOpenChannel:
|
||||||
|
if (msg.msg_code_ != MessageCodes::kResponseNoError) {
|
||||||
|
fprintf(stderr, "[channel %hu] failed to open channel\n", id_);
|
||||||
|
SetChannelState(ChannelState::kUndefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SetChannelState(ChannelState::kSearching);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("[channel %hu] Unhandled Channel Response with id: 0x%02x code: "
|
||||||
|
"%hu\n",
|
||||||
|
id_, msg.msg_id_, msg.msg_code_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ant
|
||||||
35
src/ant/channel/channel.hpp
Normal file
35
src/ant/channel/channel.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ant.hpp"
|
||||||
|
#include "../ant_device.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
enum class ChannelState {
|
||||||
|
kClosed,
|
||||||
|
kOpening,
|
||||||
|
kSearching,
|
||||||
|
kConnected,
|
||||||
|
kUndefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Channel {
|
||||||
|
public:
|
||||||
|
Channel(ANTDevice &, ChannelID, DeviceNumber);
|
||||||
|
void StartSearch();
|
||||||
|
void OnSearchTimeout();
|
||||||
|
virtual void OnBroadcastData(BroadcastPayload) = 0;
|
||||||
|
void OnChannelResponse(ChannelResponseMessage);
|
||||||
|
ChannelID channel_id() const { return id_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void SetChannelState(ChannelState _state) { state_ = _state; }
|
||||||
|
|
||||||
|
ChannelConfig channel_config_;
|
||||||
|
ANTDevice ant_device_;
|
||||||
|
::ant::ChannelID id_;
|
||||||
|
ChannelState state_{ChannelState::kUndefined};
|
||||||
|
bool connected_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ant
|
||||||
124
src/ant/channel/fitness_equipment_channel.cpp
Normal file
124
src/ant/channel/fitness_equipment_channel.cpp
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
#include "fitness_equipment_channel.hpp"
|
||||||
|
#include "../common.hpp"
|
||||||
|
#include "../fitness_equipment.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
FitnessEquipmentChannel::FitnessEquipmentChannel(ANTDevice &_ant_device,
|
||||||
|
ChannelID _channel_id,
|
||||||
|
DeviceNumber _device_number)
|
||||||
|
: Channel(_ant_device, _channel_id, _device_number) {
|
||||||
|
channel_config_.type = ChannelType::kRX;
|
||||||
|
channel_config_.transmission_type = 0;
|
||||||
|
channel_config_.device_type = DeviceType::kFitnessEquipment;
|
||||||
|
channel_config_.channel_period = 8192;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FitnessEquipmentChannel::OnBroadcastData(BroadcastPayload payload) {
|
||||||
|
static uint8_t counter = 0;
|
||||||
|
if (connected_) {
|
||||||
|
if (!counter) {
|
||||||
|
SetTargetPower(100);
|
||||||
|
}
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
if (!connected_) {
|
||||||
|
printf("Received first broadcast data!\n");
|
||||||
|
connected_ = true;
|
||||||
|
SetChannelState(ChannelState::kConnected);
|
||||||
|
ant_device_.SendMessage(Message::RequestChannelID(id_));
|
||||||
|
SetTargetPower(100);
|
||||||
|
}
|
||||||
|
uint8_t page_number = payload.raw_data[0];
|
||||||
|
switch (page_number) {
|
||||||
|
case FitnessEquipmentPages::kBikeSepcificData: {
|
||||||
|
fitness_equipment::BikeSpecificDataPage bike_data(
|
||||||
|
fitness_equipment::BikeSpecificDataPage::FromPayload(payload));
|
||||||
|
uint16_t avg_power =
|
||||||
|
(bike_data.AccumulatedPower() - bike_data_accumulated_power_) /
|
||||||
|
(bike_data.UpdateEventCount() - bike_data_event_count_);
|
||||||
|
bike_data_event_count_ = bike_data.UpdateEventCount();
|
||||||
|
bike_data_accumulated_power_ = bike_data.AccumulatedPower();
|
||||||
|
printf("Cadence: %hu, InstPower: %hu, EventCount: %hu, AvgPower: %hu\n",
|
||||||
|
bike_data.InstantaneousCadence(), bike_data.InstantaneousPower(),
|
||||||
|
bike_data.UpdateEventCount(), avg_power);
|
||||||
|
RequestCapabilies(1);
|
||||||
|
RequestCommandStatus(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FitnessEquipmentPages::kTargetPower: {
|
||||||
|
uint16_t power = (payload.raw_data.at(7) << 8 | payload.raw_data.at(6)) / 4;
|
||||||
|
printf("Currently set power target: %hu\n", power);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CommonPages::kCommandStatus: {
|
||||||
|
common::CommandStatusPage page(
|
||||||
|
common::CommandStatusPage::FromPayload(payload));
|
||||||
|
PrintCommandStatus(page);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FitnessEquipmentPages::kCapabilities: {
|
||||||
|
fitness_equipment::Capabilities caps(
|
||||||
|
fitness_equipment::Capabilities::FromPayload(payload));
|
||||||
|
PrintCapabilities(caps);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FitnessEquipmentChannel::PrintCommandStatus(
|
||||||
|
const common::CommandStatusPage &page) {
|
||||||
|
std::string data_str;
|
||||||
|
std::string command_name;
|
||||||
|
|
||||||
|
switch (page.PreviousCommand()) {
|
||||||
|
case FitnessEquipmentPages::kBasicResistance: {
|
||||||
|
uint8_t resistance = page.Data3();
|
||||||
|
command_name = "Basic Resistance";
|
||||||
|
data_str = "Resistance: " + std::to_string(resistance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FitnessEquipmentPages::kTargetPower: {
|
||||||
|
uint16_t power = page.Data2() | (page.Data3() << 8);
|
||||||
|
command_name = "Target Power";
|
||||||
|
data_str = "Power: " + std::to_string(power);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FitnessEquipmentPages::kWindResistance: {
|
||||||
|
uint8_t wind_resistance_coeff = page.Data1();
|
||||||
|
uint8_t wind_speed = page.Data2();
|
||||||
|
uint8_t drafting_factor = page.Data3();
|
||||||
|
command_name = "Wind Resistance";
|
||||||
|
data_str =
|
||||||
|
"wind_resistance_coeff: " + std::to_string(wind_resistance_coeff) +
|
||||||
|
" wind_speed: " + std::to_string(wind_speed) +
|
||||||
|
" drafting_factor: " + std::to_string(drafting_factor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FitnessEquipmentPages::kTrackResistance: {
|
||||||
|
uint16_t slope = page.Data1() | (page.Data2() << 8);
|
||||||
|
uint8_t coeff = page.Data3();
|
||||||
|
command_name = "Track Resistance";
|
||||||
|
data_str =
|
||||||
|
"Slope: " + std::to_string(slope) + " Coeff: " + std::to_string(coeff);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
command_name =
|
||||||
|
"Unhandled Command Page: " + std::to_string(page.PreviousCommand());
|
||||||
|
data_str = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("%s (%s)\n%s\n", command_name.c_str(), page.StatusString().c_str(),
|
||||||
|
data_str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FitnessEquipmentChannel::PrintCapabilities(
|
||||||
|
const fitness_equipment::Capabilities &caps) {
|
||||||
|
printf("Capabilities:\n\tMax Resistance: %hu\n\tSupported Caps:\n\t\tBasic "
|
||||||
|
"Resistance: %hu\n\t\tTarget Power: %hu\n\t\tSimulation: %hu\n",
|
||||||
|
caps.MaxResistance(), caps.IsBasicResistanceSupported(),
|
||||||
|
caps.IsTargetPowerSupported(), caps.IsSimulationSupported());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ant
|
||||||
36
src/ant/channel/fitness_equipment_channel.hpp
Normal file
36
src/ant/channel/fitness_equipment_channel.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.hpp"
|
||||||
|
#include "../fitness_equipment.hpp"
|
||||||
|
#include "channel.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
class FitnessEquipmentChannel : public Channel {
|
||||||
|
public:
|
||||||
|
FitnessEquipmentChannel(ANTDevice &, ChannelID, DeviceNumber);
|
||||||
|
void OnBroadcastData(BroadcastPayload) final;
|
||||||
|
|
||||||
|
void SetTargetPower(uint16_t power) {
|
||||||
|
ant_device_.SendMessage(
|
||||||
|
Message::FitnessEquipmentTargetPower(id_, power * 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestCapabilies(uint8_t requested_transmissions) {
|
||||||
|
ant_device_.SendMessage(Message::FitnessEquipmentRequestCapabilities(
|
||||||
|
id_, requested_transmissions));
|
||||||
|
}
|
||||||
|
void RequestCommandStatus(uint8_t requested_transmissions) {
|
||||||
|
ant_device_.SendMessage(
|
||||||
|
Message::CommonRequestCommandStatus(id_, requested_transmissions));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintCommandStatus(const common::CommandStatusPage &);
|
||||||
|
void PrintCapabilities(const fitness_equipment::Capabilities &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t bike_data_event_count_{0};
|
||||||
|
uint16_t bike_data_accumulated_power_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ant
|
||||||
28
src/ant/channel/heart_rate_channel.cpp
Normal file
28
src/ant/channel/heart_rate_channel.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#include "heart_rate_channel.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
HeartRateChannel::HeartRateChannel(ANTDevice &_ant_device,
|
||||||
|
ChannelID _channel_id,
|
||||||
|
DeviceNumber _device_number)
|
||||||
|
: Channel(_ant_device, _channel_id, _device_number) {
|
||||||
|
channel_config_.type = ChannelType::kRX;
|
||||||
|
channel_config_.transmission_type = 0;
|
||||||
|
channel_config_.device_type = DeviceType::kHeartRate;
|
||||||
|
channel_config_.channel_period = 8070;
|
||||||
|
}
|
||||||
|
void HeartRateChannel::OnBroadcastData(BroadcastPayload data) {
|
||||||
|
if (!connected_) {
|
||||||
|
printf("Received first broadcast data!\n");
|
||||||
|
connected_ = true;
|
||||||
|
SetChannelState(ChannelState::kConnected);
|
||||||
|
ant_device_.SendMessage(Message::RequestChannelID(id_));
|
||||||
|
}
|
||||||
|
// mask out the toggle bit. only the first 7 bits declare the page number
|
||||||
|
uint8_t page_number = data.raw_data[0] & (~0x80);
|
||||||
|
bool page_toggled = data.raw_data[0] & 0x80;
|
||||||
|
uint8_t heart_rate = data.raw_data[7];
|
||||||
|
printf("Heart Rate: %hu\n", heart_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ant
|
||||||
13
src/ant/channel/heart_rate_channel.hpp
Normal file
13
src/ant/channel/heart_rate_channel.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "channel.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
class HeartRateChannel : public Channel {
|
||||||
|
public:
|
||||||
|
HeartRateChannel(ANTDevice &, ChannelID, DeviceNumber);
|
||||||
|
void OnBroadcastData(BroadcastPayload) final;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ant
|
||||||
80
src/ant/channel/power_channel.cpp
Normal file
80
src/ant/channel/power_channel.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
#include "power_channel.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
PowerChannel::PowerChannel(ANTDevice &_ant_device, ChannelID channel_id,
|
||||||
|
DeviceNumber device_number)
|
||||||
|
: Channel(_ant_device, channel_id, device_number) {
|
||||||
|
channel_config_.type = ChannelType::kRX;
|
||||||
|
channel_config_.frequency = 57;
|
||||||
|
channel_config_.transmission_type = 0;
|
||||||
|
channel_config_.device_type = DeviceType::kPower;
|
||||||
|
channel_config_.channel_period = 8192;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerChannel::OnBroadcastData(BroadcastPayload data) {
|
||||||
|
if (!connected_) {
|
||||||
|
// First data received from master
|
||||||
|
printf("Received first broadcast data!\n");
|
||||||
|
connected_ = true;
|
||||||
|
SetChannelState(ChannelState::kConnected);
|
||||||
|
ant_device_.SendMessage(Message::RequestChannelID(id_));
|
||||||
|
bicycle_power::AdvancedCapabilities2 caps2;
|
||||||
|
// caps2.EnableAllDynamics();
|
||||||
|
caps2.Enable8Hz();
|
||||||
|
// caps2.UnmaskAllDynamics();
|
||||||
|
caps2.Unmask8Hz();
|
||||||
|
SetAdvancedCapabilities2(caps2);
|
||||||
|
bicycle_power::AdvancedCapabilities caps;
|
||||||
|
RequestAdvancedCapabilities2();
|
||||||
|
RequestAdvancedCapabilities1();
|
||||||
|
// caps.EnableTorqueEfficiencyAndSmoothness();
|
||||||
|
// caps.UnmaskTorqueEfficiencyAndSmoothness();
|
||||||
|
// SetAdvancedCapabilities(caps);
|
||||||
|
}
|
||||||
|
uint8_t page_number = data.raw_data[0];
|
||||||
|
switch (page_number) {
|
||||||
|
case PowerProfilePages::kBattery:
|
||||||
|
// BatteryStatusMessage msg(BatteryStatusMessage::FromPayload(data));
|
||||||
|
// printf("BatteryState: %s\n", msg.Status().c_str());
|
||||||
|
// printf("BatteryVoltage: %f\n", msg.battery_voltage);
|
||||||
|
// printf("BatteryTime: %02u:%02u:%02u\n", msg.operating_time / 3600,
|
||||||
|
// (msg.operating_time % 3600) / 60, (msg.operating_time % 60));
|
||||||
|
break;
|
||||||
|
case PowerProfilePages::kGetSetPage:
|
||||||
|
switch (data.raw_data[1]) {
|
||||||
|
case PowerProfileSubpages::kAdvancedCapabilities2: {
|
||||||
|
std::string fmt_string{"%s\n\tsupported: %d\n\tenabled: %d\n"};
|
||||||
|
auto fmt = fmt_string.c_str();
|
||||||
|
bicycle_power::AdvancedCapabilities2 caps =
|
||||||
|
bicycle_power::AdvancedCapabilities2::FromPayload(data);
|
||||||
|
printf("----------\nCaps2\n----------\n");
|
||||||
|
printf(fmt, "4Hz", caps.Supports4Hz(), caps.Is4HzEnabled());
|
||||||
|
printf(fmt, "8Hz", caps.Supports8Hz(), caps.Is8HzEnabled());
|
||||||
|
printf(fmt, "PowerPhase", caps.SupportsPowerPhase(),
|
||||||
|
caps.IsPowerPhaseEnabled());
|
||||||
|
printf(fmt, "PCO", caps.SupportsPCO(), caps.IsPCOEnabled());
|
||||||
|
printf(fmt, "RiderPosition", caps.SupportsRiderPosition(),
|
||||||
|
caps.IsRiderPositionEnabled());
|
||||||
|
printf(fmt, "TorqueBarycenter", caps.SupportsTorqueBarycenter(),
|
||||||
|
caps.IsTorqueBarycenterEnabled());
|
||||||
|
} break;
|
||||||
|
case PowerProfileSubpages::kAdvancedCapabilities: {
|
||||||
|
std::string fmt_string{"%s\n\tsupported: %d\n\tenabled: %d\n"};
|
||||||
|
auto fmt = fmt_string.c_str();
|
||||||
|
auto caps = bicycle_power::AdvancedCapabilities::FromPayload(data);
|
||||||
|
printf("----------\nCaps1\n----------\n");
|
||||||
|
printf(fmt, "4Hz", caps.Supports4Hz(), caps.Is4HzEnabled());
|
||||||
|
printf(fmt, "8Hz", caps.Supports8Hz(), caps.Is8HzEnabled());
|
||||||
|
printf(fmt, "AutoZero", caps.SupportsAutoZero(),
|
||||||
|
caps.IsAutoZeroEnabled());
|
||||||
|
printf(fmt, "AutoCrankLength", caps.SupportsAutoCrankLength(),
|
||||||
|
caps.IsAutoCrankLengthEnabled());
|
||||||
|
printf(fmt, "TE/PS", caps.SupportsTorqueEfficiencyAndSmoothness(),
|
||||||
|
caps.IsTorqueEfficiencyAndSmoothnessEnabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace ant
|
||||||
45
src/ant/channel/power_channel.hpp
Normal file
45
src/ant/channel/power_channel.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "channel.hpp"
|
||||||
|
#include "../bicycle_power.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
class PowerChannel : public Channel {
|
||||||
|
public:
|
||||||
|
PowerChannel(ANTDevice &, ChannelID, DeviceNumber);
|
||||||
|
void OnBroadcastData(BroadcastPayload) final;
|
||||||
|
// type specific
|
||||||
|
void RequestCrankParameters(uint8_t n_transmissions = 10) {
|
||||||
|
ant_device_.SendMessage(Message::BicyclePowerRequestPage(
|
||||||
|
id_, PowerProfileSubpages::kCrankParameters, n_transmissions));
|
||||||
|
}
|
||||||
|
void RequestAdvancedCapabilities1(uint8_t n_transmissions = 10) {
|
||||||
|
ant_device_.SendMessage(Message::BicyclePowerRequestPage(
|
||||||
|
id_, PowerProfileSubpages::kAdvancedCapabilities, n_transmissions));
|
||||||
|
}
|
||||||
|
void RequestAdvancedCapabilities2(uint8_t n_tramsissions = 10) {
|
||||||
|
ant_device_.SendMessage(Message::BicyclePowerRequestPage(
|
||||||
|
id_, PowerProfileSubpages::kAdvancedCapabilities2, n_tramsissions));
|
||||||
|
}
|
||||||
|
void Set8Hz(bool enabled) {
|
||||||
|
ant_device_.SendMessage(Message::SetParameter(id_, 0x02, 0xfe, 0xff, 0xff,
|
||||||
|
~(1 << 1), 0xff,
|
||||||
|
0xff * (1 - enabled), 0xff));
|
||||||
|
}
|
||||||
|
void SetAdvancedCapabilities(bicycle_power::AdvancedCapabilities caps) {
|
||||||
|
ant_device_.SendMessage(
|
||||||
|
Message::SetParameter(id_, PowerProfilePages::kGetSetPage,
|
||||||
|
PowerProfileSubpages::kAdvancedCapabilities,
|
||||||
|
kReservedByte, kReservedByte, caps.Mask(),
|
||||||
|
kReservedByte, caps.Value(), kReservedByte));
|
||||||
|
}
|
||||||
|
void SetAdvancedCapabilities2(bicycle_power::AdvancedCapabilities2 caps) {
|
||||||
|
ant_device_.SendMessage(
|
||||||
|
Message::SetParameter(id_, PowerProfilePages::kGetSetPage,
|
||||||
|
PowerProfileSubpages::kAdvancedCapabilities2,
|
||||||
|
kReservedByte, kReservedByte, caps.Mask(),
|
||||||
|
kReservedByte, caps.Value(), kReservedByte));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace ant
|
||||||
6
src/ant/channels.hpp
Normal file
6
src/ant/channels.hpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "channel/heart_rate_channel.hpp"
|
||||||
|
#include "channel/power_channel.hpp"
|
||||||
|
#include "channel/fitness_equipment_channel.hpp"
|
||||||
|
|
||||||
84
src/ant/common.hpp
Normal file
84
src/ant/common.hpp
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
#pragma once
|
||||||
|
#include "message.hpp"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef uint8_t CommandStatus;
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
namespace common {
|
||||||
|
|
||||||
|
struct CommandStatuses {
|
||||||
|
enum : CommandStatus {
|
||||||
|
kPass = 0x00,
|
||||||
|
kFail,
|
||||||
|
kNotSupported,
|
||||||
|
kRejected,
|
||||||
|
kPending,
|
||||||
|
kNotInitialized = 0xFF,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class CommandStatusPage {
|
||||||
|
public:
|
||||||
|
static constexpr Page id = CommonPages::kCommandStatus;
|
||||||
|
CommandStatusPage(Page previous_command_page, uint8_t sequence,
|
||||||
|
CommandStatus status, uint8_t data0, uint8_t data1,
|
||||||
|
uint8_t data2, uint8_t data3)
|
||||||
|
: previous_command_(previous_command_page), sequence_(sequence),
|
||||||
|
status_(status), data0_(data0), data1_(data1), data2_(data2),
|
||||||
|
data3_(data3) {}
|
||||||
|
static CommandStatusPage FromPayload(BroadcastPayload payload) {
|
||||||
|
return CommandStatusPage(payload.raw_data[1], payload.raw_data[2],
|
||||||
|
payload.raw_data[3], payload.raw_data[4],
|
||||||
|
payload.raw_data[5], payload.raw_data[6],
|
||||||
|
payload.raw_data[7]);
|
||||||
|
}
|
||||||
|
Page PreviousCommand() const { return previous_command_; }
|
||||||
|
std::string StatusString() const {
|
||||||
|
switch (status_) {
|
||||||
|
case CommandStatuses::kPass:
|
||||||
|
return "Pass";
|
||||||
|
case CommandStatuses::kFail:
|
||||||
|
return "Fail";
|
||||||
|
case CommandStatuses::kPending:
|
||||||
|
return "Pending";
|
||||||
|
case CommandStatuses::kNotSupported:
|
||||||
|
return "Not Supported";
|
||||||
|
case CommandStatuses::kRejected:
|
||||||
|
return "Rejected";
|
||||||
|
case CommandStatuses::kNotInitialized:
|
||||||
|
return "Not Initialized";
|
||||||
|
default:
|
||||||
|
return "Reserved Value";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool HasCommandPassed() const { return status_ == CommandStatuses::kPass; }
|
||||||
|
bool HasCommandFailed() const { return status_ == CommandStatuses::kFail; }
|
||||||
|
bool IsCommandPending() const { return status_ == CommandStatuses::kPending; }
|
||||||
|
bool IsCommandUnsupported() const {
|
||||||
|
return status_ == CommandStatuses::kNotSupported;
|
||||||
|
}
|
||||||
|
bool IsCommandRejected() const {
|
||||||
|
return status_ == CommandStatuses::kRejected;
|
||||||
|
}
|
||||||
|
bool IsCommandNotInitialized() const {
|
||||||
|
return status_ == CommandStatuses::kNotInitialized;
|
||||||
|
}
|
||||||
|
uint8_t Data0() const { return data0_; }
|
||||||
|
uint8_t Data1() const { return data1_; }
|
||||||
|
uint8_t Data2() const { return data2_; }
|
||||||
|
uint8_t Data3() const { return data3_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Page previous_command_;
|
||||||
|
uint8_t sequence_;
|
||||||
|
CommandStatus status_;
|
||||||
|
uint8_t data0_;
|
||||||
|
uint8_t data1_;
|
||||||
|
uint8_t data2_;
|
||||||
|
uint8_t data3_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace common
|
||||||
|
} // namespace ant
|
||||||
63
src/ant/fitness_equipment.hpp
Normal file
63
src/ant/fitness_equipment.hpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#pragma once
|
||||||
|
#include "message.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
namespace fitness_equipment {
|
||||||
|
class BikeSpecificDataPage {
|
||||||
|
public:
|
||||||
|
static constexpr Page id = FitnessEquipmentPages::kBikeSepcificData;
|
||||||
|
BikeSpecificDataPage(uint8_t update_event_count, uint8_t inst_cadence,
|
||||||
|
uint16_t accumulated_power, uint16_t inst_power)
|
||||||
|
: update_event_count_(update_event_count), inst_cadence_(inst_cadence),
|
||||||
|
accumulated_power_(accumulated_power), inst_power_(inst_power) {}
|
||||||
|
|
||||||
|
static BikeSpecificDataPage FromPayload(BroadcastPayload payload) {
|
||||||
|
uint8_t update_event_count = payload.raw_data.at(1);
|
||||||
|
uint8_t inst_cadence = payload.raw_data.at(2);
|
||||||
|
uint16_t acc_power =
|
||||||
|
payload.raw_data.at(3) | ((payload.raw_data.at(4) & 0x0F) << 8);
|
||||||
|
uint16_t inst_power =
|
||||||
|
payload.raw_data.at(5) | ((payload.raw_data.at(6) & 0x0F) << 8);
|
||||||
|
return BikeSpecificDataPage(update_event_count, inst_cadence, acc_power,
|
||||||
|
inst_power);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPowerInvalid() { return inst_power_ == 0xFFF; }
|
||||||
|
uint8_t InstantaneousCadence() const { return inst_cadence_; }
|
||||||
|
uint16_t InstantaneousPower() const { return inst_power_; }
|
||||||
|
uint16_t AccumulatedPower() const { return accumulated_power_; }
|
||||||
|
uint8_t UpdateEventCount() const { return update_event_count_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t update_event_count_;
|
||||||
|
uint8_t inst_cadence_;
|
||||||
|
uint16_t accumulated_power_;
|
||||||
|
uint16_t inst_power_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Capabilities {
|
||||||
|
public:
|
||||||
|
Capabilities(uint16_t max_resistance, uint8_t capabilities_bitfield)
|
||||||
|
: max_resistance_(max_resistance), capabilities_(capabilities_bitfield) {}
|
||||||
|
static Capabilities FromPayload(BroadcastPayload payload) {
|
||||||
|
uint16_t max_resistance =
|
||||||
|
payload.raw_data.at(5) | (payload.raw_data.at(6) << 8);
|
||||||
|
uint8_t capabilites = payload.raw_data.at(7);
|
||||||
|
return Capabilities(max_resistance, capabilites);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t MaxResistance() const { return max_resistance_; }
|
||||||
|
|
||||||
|
bool IsBasicResistanceSupported() const {
|
||||||
|
return capabilities_ & (0x01 << 0);
|
||||||
|
}
|
||||||
|
bool IsTargetPowerSupported() const { return capabilities_ & (0x01 << 1); }
|
||||||
|
bool IsSimulationSupported() const { return capabilities_ & (0x01 << 2); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t max_resistance_;
|
||||||
|
uint8_t capabilities_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fitness_equipment
|
||||||
|
} // namespace ant
|
||||||
33
src/ant/message.cpp
Normal file
33
src/ant/message.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#include "message.hpp"
|
||||||
|
#include "ant.hpp"
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
Message::Message() { Init(); }
|
||||||
|
|
||||||
|
Message::Message(uint8_t length, uint8_t type, uint8_t b3, uint8_t b4,
|
||||||
|
uint8_t b5, uint8_t b6, uint8_t b7, uint8_t b8, uint8_t b9,
|
||||||
|
uint8_t b10, uint8_t b11, uint8_t b12) {
|
||||||
|
data[0] = kSyncByte;
|
||||||
|
data[1] = length;
|
||||||
|
data[2] = type;
|
||||||
|
data[3] = b3;
|
||||||
|
data[4] = b4;
|
||||||
|
data[5] = b5;
|
||||||
|
data[6] = b6;
|
||||||
|
data[7] = b7;
|
||||||
|
data[8] = b8;
|
||||||
|
data[9] = b9;
|
||||||
|
data[10] = b10;
|
||||||
|
data[11] = b11;
|
||||||
|
uint8_t crc = 0;
|
||||||
|
// iterate over header + actual payload length
|
||||||
|
for (int i = 0; i < (length + kHeaderSize); ++i) {
|
||||||
|
crc ^= data[i];
|
||||||
|
}
|
||||||
|
// crc goes after header + payload
|
||||||
|
data[length + kHeaderSize] = crc;
|
||||||
|
data_length = length + kHeaderSize + kCrcSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Message::Init() {}
|
||||||
|
} // namespace ant
|
||||||
321
src/ant/message.hpp
Normal file
321
src/ant/message.hpp
Normal file
|
|
@ -0,0 +1,321 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ant.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#define ANT_UNASSIGN_CHANNEL 0x41
|
||||||
|
#define ANT_ASSIGN_CHANNEL 0x42
|
||||||
|
#define ANT_CHANNEL_ID 0x51
|
||||||
|
#define ANT_CHANNEL_PERIOD 0x43
|
||||||
|
#define ANT_SEARCH_TIMEOUT 0x44
|
||||||
|
#define ANT_CHANNEL_FREQUENCY 0x45
|
||||||
|
#define ANT_SET_NETWORK 0x46
|
||||||
|
#define ANT_TX_POWER 0x47
|
||||||
|
#define ANT_ID_LIST_ADD 0x59
|
||||||
|
#define ANT_ID_LIST_CONFIG 0x5A
|
||||||
|
#define ANT_CHANNEL_TX_POWER 0x60
|
||||||
|
#define ANT_LP_SEARCH_TIMEOUT 0x63
|
||||||
|
#define ANT_SET_SERIAL_NUMBER 0x65
|
||||||
|
#define ANT_ENABLE_EXT_MSGS 0x66
|
||||||
|
#define ANT_ENABLE_LED 0x68
|
||||||
|
#define ANT_SYSTEM_RESET 0x4A
|
||||||
|
#define ANT_OPEN_CHANNEL 0x4B
|
||||||
|
#define ANT_CLOSE_CHANNEL 0x4C
|
||||||
|
#define ANT_OPEN_RX_SCAN_CH 0x5B
|
||||||
|
#define ANT_REQ_MESSAGE 0x4D
|
||||||
|
#define ANT_BROADCAST_DATA 0x4E
|
||||||
|
#define ANT_ACK_DATA 0x4F
|
||||||
|
#define ANT_BURST_DATA 0x50
|
||||||
|
#define ANT_CHANNEL_EVENT 0x40
|
||||||
|
#define ANT_CHANNEL_STATUS 0x52
|
||||||
|
#define ANT_CHANNEL_ID 0x51
|
||||||
|
#define ANT_VERSION 0x3E
|
||||||
|
#define ANT_CAPABILITIES 0x54
|
||||||
|
#define ANT_SERIAL_NUMBER 0x61
|
||||||
|
#define ANT_NOTIF_STARTUP 0x6F
|
||||||
|
#define ANT_CW_INIT 0x53
|
||||||
|
#define ANT_CW_TEST 0x48
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
typedef uint8_t MessageID;
|
||||||
|
struct MessageIDs {
|
||||||
|
enum : MessageID {
|
||||||
|
kStartupMessage = 0x6f,
|
||||||
|
kSerialErrorMessage = 0xae,
|
||||||
|
kChannelResponse = 0x40,
|
||||||
|
kUnassignChannel = 0x41,
|
||||||
|
kAssignChannel = 0x42,
|
||||||
|
kChannelID = 0x51,
|
||||||
|
kResetSystem = 0x4a,
|
||||||
|
kOpenChannel = 0x4b,
|
||||||
|
kCloseChannel = 0x4c,
|
||||||
|
kChannelFrequency = 0x45,
|
||||||
|
kChannelPeriod = 0x43,
|
||||||
|
kBroadcastData = 0x4e,
|
||||||
|
kAckData = 0x4f,
|
||||||
|
kBurstTransferData = 0x50,
|
||||||
|
kAdvancedBurstData = 0x72,
|
||||||
|
kChannelStatus = 0x52,
|
||||||
|
kANTVersion = 0x3e,
|
||||||
|
kCapabilities = 0x54,
|
||||||
|
kSerialNumber = 0x61,
|
||||||
|
kEventBufferConfiguration = 0x74,
|
||||||
|
kSearchTimeout = 0x44,
|
||||||
|
kLPSearchTimeout = 0x63,
|
||||||
|
kRequestMessage = 0x4d,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static constexpr int kMaxPayloadSize = 8;
|
||||||
|
static constexpr int kHeaderSize = 3;
|
||||||
|
static constexpr int kCrcSize = 1;
|
||||||
|
|
||||||
|
static constexpr int kOffsetSyncByte = 0;
|
||||||
|
static constexpr int kOffsetLengthByte = 1;
|
||||||
|
static constexpr int kOffsetMessageIDByte = 2;
|
||||||
|
static constexpr int kOffsetPayloadStart = 3;
|
||||||
|
|
||||||
|
static constexpr int kMaxMessageSize = 12 + 1;
|
||||||
|
|
||||||
|
typedef uint8_t MessageCode;
|
||||||
|
|
||||||
|
struct MessageCodes {
|
||||||
|
enum : MessageCode {
|
||||||
|
kResponseNoError = 0,
|
||||||
|
kEventRXSearchTimeout,
|
||||||
|
kEventRXFail,
|
||||||
|
kEventTX,
|
||||||
|
kEventTransferRXFailed,
|
||||||
|
kEventTransferTXCompleted,
|
||||||
|
kEventTransferTXFailed,
|
||||||
|
kEventChannelClosed,
|
||||||
|
kEventRXFailGoToSearch,
|
||||||
|
kEventChannelCollision,
|
||||||
|
kEventTransferTXStart,
|
||||||
|
kEventTransferNextDataBlock,
|
||||||
|
kChannelInWrongState,
|
||||||
|
kChannelNotOpened,
|
||||||
|
kChannelIDNotSet,
|
||||||
|
kCloseAllChannels,
|
||||||
|
kTransferInProgress,
|
||||||
|
kTransferSequenceNumberError,
|
||||||
|
kTransferInError,
|
||||||
|
kMesageSizeExceedsLimit,
|
||||||
|
kInvalidMessage,
|
||||||
|
kInvalidNetworkNumber,
|
||||||
|
kInvalidListID,
|
||||||
|
kInvalidScanTXChannel,
|
||||||
|
kInvalidParameterProvided,
|
||||||
|
kEventSerialQueOverflow,
|
||||||
|
kEventQueOverflow,
|
||||||
|
kEncryptNegotiationSuccess,
|
||||||
|
kEncryptNegotiationFail,
|
||||||
|
kNVMFullError,
|
||||||
|
kNVMWriteError,
|
||||||
|
kUSBStringWriteFail,
|
||||||
|
kMesgSerialSerrorID,
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef uint8_t CommandType;
|
||||||
|
struct CommandTypes {
|
||||||
|
enum : CommandType {
|
||||||
|
RequestDataPage = 0x01,
|
||||||
|
RequestANTFSSession = 0x02,
|
||||||
|
RequestDataPageFromSlave = 0x03,
|
||||||
|
RequestDataPageSet = 0x04,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class BroadcastPayload {
|
||||||
|
public:
|
||||||
|
BroadcastPayload(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4,
|
||||||
|
uint8_t b5, uint8_t b6, uint8_t b7) {
|
||||||
|
raw_data[0] = b0;
|
||||||
|
raw_data[1] = b1;
|
||||||
|
raw_data[2] = b2;
|
||||||
|
raw_data[3] = b3;
|
||||||
|
raw_data[4] = b4;
|
||||||
|
raw_data[5] = b5;
|
||||||
|
raw_data[6] = b6;
|
||||||
|
raw_data[7] = b7;
|
||||||
|
}
|
||||||
|
static BroadcastPayload FromSequentialBuffer(uint8_t *buffer) {
|
||||||
|
return BroadcastPayload(buffer[0], buffer[1], buffer[2], buffer[3],
|
||||||
|
buffer[4], buffer[5], buffer[6], buffer[7]);
|
||||||
|
}
|
||||||
|
std::array<uint8_t, 8> raw_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChannelResponseMessage {
|
||||||
|
public:
|
||||||
|
ChannelResponseMessage(ChannelID channel_id, MessageID msg_id,
|
||||||
|
MessageCode msg_code)
|
||||||
|
: channel_id_(channel_id), msg_id_(msg_id), msg_code_(msg_code) {}
|
||||||
|
static ChannelResponseMessage FromPayload(std::array<uint8_t, 3> _data) {
|
||||||
|
return ChannelResponseMessage(_data[0], _data[1], _data[2]);
|
||||||
|
}
|
||||||
|
ChannelID channel_id_;
|
||||||
|
MessageID msg_id_;
|
||||||
|
MessageCode msg_code_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BatteryStatusMessage {
|
||||||
|
public:
|
||||||
|
BatteryStatusMessage(uint8_t _n_batteries, uint8_t _identifier,
|
||||||
|
uint32_t _operating_time, float _battery_voltage,
|
||||||
|
uint8_t _status, bool _high_resolution)
|
||||||
|
: n_batteries(_n_batteries), identifier(_identifier),
|
||||||
|
operating_time(_operating_time), battery_voltage(_battery_voltage),
|
||||||
|
status(_status), high_resolution(_high_resolution) {
|
||||||
|
operating_time = high_resolution ? operating_time * 2 : operating_time * 16;
|
||||||
|
}
|
||||||
|
static BatteryStatusMessage FromPayload(std::array<uint8_t, 8> _data) {
|
||||||
|
return BatteryStatusMessage(_data[2] & 0x0f, (_data[2] & 0xf0) >> 4,
|
||||||
|
_data[3] | (_data[4] << 8) | (_data[5] << 16),
|
||||||
|
static_cast<float>(_data[7] & 0x0f) +
|
||||||
|
_data[6] / 256.0,
|
||||||
|
(_data[7] & 0b1110000) >> 4, _data[7] & 0x80);
|
||||||
|
}
|
||||||
|
std::string Status() {
|
||||||
|
switch (status) {
|
||||||
|
case 1:
|
||||||
|
return "new";
|
||||||
|
case 2:
|
||||||
|
return "good";
|
||||||
|
case 3:
|
||||||
|
return "ok";
|
||||||
|
case 4:
|
||||||
|
return "low";
|
||||||
|
case 5:
|
||||||
|
return "critical";
|
||||||
|
case 7:
|
||||||
|
return "invalid";
|
||||||
|
default:
|
||||||
|
return "undefined";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint8_t n_batteries;
|
||||||
|
uint8_t identifier;
|
||||||
|
uint32_t operating_time;
|
||||||
|
float battery_voltage;
|
||||||
|
uint8_t status;
|
||||||
|
bool high_resolution;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
uint8_t n_batteries;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Message();
|
||||||
|
Message(uint8_t b1, uint8_t b2 = '\0', uint8_t b3 = '\0', uint8_t b4 = '\0',
|
||||||
|
uint8_t b5 = '\0', uint8_t b6 = '\0', uint8_t b7 = '\0',
|
||||||
|
uint8_t b8 = '\0', uint8_t b9 = '\0', uint8_t b10 = '\0',
|
||||||
|
uint8_t b11 = '\0', uint8_t b12 = '\0');
|
||||||
|
static Message SystemReset() { return Message(1, ANT_SYSTEM_RESET); }
|
||||||
|
static Message UnassignChannel(ChannelID channel) {
|
||||||
|
return Message(1, ANT_UNASSIGN_CHANNEL, channel);
|
||||||
|
}
|
||||||
|
static Message AssignChannel(ChannelID channel, ChannelType type,
|
||||||
|
uint8_t network) {
|
||||||
|
return Message(3, ANT_ASSIGN_CHANNEL, channel, static_cast<uint8_t>(type),
|
||||||
|
network);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Message SetChannelPeriod(ChannelID channel, ChannelPeriod period) {
|
||||||
|
return Message(3, ANT_CHANNEL_PERIOD, channel, period & 0xFF,
|
||||||
|
(period >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
static Message SetChannelFrequency(uint8_t channel, uint8_t frequency) {
|
||||||
|
return Message(2, ANT_CHANNEL_FREQUENCY, channel, frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Message SetHPSearchTimeout(uint8_t channel, uint8_t timeout) {
|
||||||
|
return Message(2, ANT_SEARCH_TIMEOUT, channel, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Message RequestChannelID(ChannelID channel) {
|
||||||
|
return Message(2, MessageIDs::kRequestMessage, channel,
|
||||||
|
MessageIDs::kChannelID);
|
||||||
|
}
|
||||||
|
static Message CommonRequestPage(ChannelID channel, uint8_t descriptor1,
|
||||||
|
uint8_t descriptor2,
|
||||||
|
uint8_t requested_transmissions,
|
||||||
|
uint8_t page_number,
|
||||||
|
CommandType command_type) {
|
||||||
|
return Message(9, MessageIDs::kAckData, channel,
|
||||||
|
CommonPages::kRequestDataPage, kReservedByte, kReservedByte,
|
||||||
|
descriptor1, descriptor2, requested_transmissions,
|
||||||
|
page_number, command_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Message CommonRequestCommandStatus(ChannelID channel,
|
||||||
|
uint8_t requested_transmissions) {
|
||||||
|
return CommonRequestPage(
|
||||||
|
channel, kReservedByte, kReservedByte, requested_transmissions,
|
||||||
|
CommonPages::kCommandStatus, CommandTypes::RequestDataPage);
|
||||||
|
}
|
||||||
|
static Message BicyclePowerRequestPage(ChannelID channel, Page subpage,
|
||||||
|
uint8_t requested_transmissions) {
|
||||||
|
return CommonRequestPage(
|
||||||
|
channel, subpage, kReservedByte, requested_transmissions,
|
||||||
|
PowerProfilePages::kGetSetPage, CommandTypes::RequestDataPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Message FitnessEquipmentTargetPower(ChannelID channel,
|
||||||
|
uint16_t power) {
|
||||||
|
return Message(9, MessageIDs::kAckData, channel,
|
||||||
|
FitnessEquipmentPages::kTargetPower, kReservedByte,
|
||||||
|
kReservedByte, kReservedByte, kReservedByte, kReservedByte,
|
||||||
|
(uint8_t)power & 0xFF, (uint8_t)(power >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Message
|
||||||
|
FitnessEquipmentRequestCapabilities(ChannelID channel,
|
||||||
|
uint8_t requested_transmissions) {
|
||||||
|
return CommonRequestPage(
|
||||||
|
channel, kReservedByte, kReservedByte, requested_transmissions,
|
||||||
|
FitnessEquipmentPages::kCapabilities, CommandTypes::RequestDataPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Message SetParameter(ChannelID channel, uint8_t page_number,
|
||||||
|
uint8_t subpage_number, uint8_t b2, uint8_t b3,
|
||||||
|
uint8_t b4, uint8_t b5, uint8_t b6, uint8_t b7) {
|
||||||
|
return Message(9, MessageIDs::kAckData, channel, page_number,
|
||||||
|
subpage_number, b2, b3, b4, b5, b6, b7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the channel ID
|
||||||
|
*
|
||||||
|
* @param channel Number of the channel
|
||||||
|
* @param device Device number. Use 0 as wildcard to match any.
|
||||||
|
* @param device_type Device type. use 0 as wildcard to match any.
|
||||||
|
* @param transmission_type Transmission type. use 0 as wildcard to match any.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static Message SetChannelID(ChannelID channel, DeviceNumber device,
|
||||||
|
DeviceType device_type,
|
||||||
|
TransmissionType transmission_type) {
|
||||||
|
return Message(5, ANT_CHANNEL_ID, channel, device & 0xFF,
|
||||||
|
(device >> 8) & 0xFF, static_cast<uint8_t>(device_type),
|
||||||
|
static_cast<uint8_t>(transmission_type));
|
||||||
|
}
|
||||||
|
static Message SetNetworkKey(uint8_t network, const uint8_t *key) {
|
||||||
|
return Message(9, ANT_SET_NETWORK, network, key[0], key[1], key[2], key[3],
|
||||||
|
key[4], key[5], key[6], key[7]);
|
||||||
|
}
|
||||||
|
static Message SetLPSearchTimeout(ChannelID channel, uint8_t timeout) {
|
||||||
|
return Message(2, ANT_LP_SEARCH_TIMEOUT, channel, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Message OpenChannel(ChannelID channel) {
|
||||||
|
return Message(1, ANT_OPEN_CHANNEL, channel);
|
||||||
|
}
|
||||||
|
uint8_t data[kMaxMessageSize];
|
||||||
|
uint8_t data_length;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Init();
|
||||||
|
};
|
||||||
|
} // namespace ant
|
||||||
138
src/ant/message_processor.cpp
Normal file
138
src/ant/message_processor.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
#include "message_processor.hpp"
|
||||||
|
#include "ant.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
MessageProcessor::MessageProcessor() {}
|
||||||
|
void MessageProcessor::SetOnChannelResponseCallback(
|
||||||
|
ChannelID _channel_id,
|
||||||
|
std::function<void(ChannelResponseMessage)> _callback) {
|
||||||
|
channel_response_cbs_[_channel_id] = _callback;
|
||||||
|
}
|
||||||
|
void MessageProcessor::SetOnBroadcastDataCallback(
|
||||||
|
ChannelID _channel_id, std::function<void(BroadcastPayload)> _callback) {
|
||||||
|
broadcast_data_cbs_[_channel_id] = _callback;
|
||||||
|
}
|
||||||
|
bool MessageProcessor::FeedData(uint8_t _data) {
|
||||||
|
switch (state_) {
|
||||||
|
case State::kWaitForSync:
|
||||||
|
if (_data == kSyncByte) {
|
||||||
|
// next byte is the message length byte
|
||||||
|
state_ = State::kGetLength;
|
||||||
|
// the XOR checksum is initialized with the first byte, i.e. sync byte
|
||||||
|
checksum_ = kSyncByte;
|
||||||
|
msg_buffer_.clear();
|
||||||
|
msg_buffer_.reserve(kMaxMessageSize);
|
||||||
|
msg_buffer_.push_back(_data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::kGetLength:
|
||||||
|
if (_data == 0 || _data > 128) {
|
||||||
|
fprintf(stderr, "Ignoring invalid message length in header: %hu\n",
|
||||||
|
_data);
|
||||||
|
// lets start to process data as new message
|
||||||
|
state_ = State::kWaitForSync;
|
||||||
|
} else {
|
||||||
|
msg_buffer_.push_back(_data);
|
||||||
|
checksum_ ^= _data;
|
||||||
|
state_ = State::kGetMessageID;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::kGetMessageID:
|
||||||
|
msg_buffer_.push_back(_data);
|
||||||
|
checksum_ ^= _data;
|
||||||
|
state_ = State::kGetData;
|
||||||
|
break;
|
||||||
|
case State::kGetData:
|
||||||
|
msg_buffer_.push_back(_data);
|
||||||
|
checksum_ ^= _data;
|
||||||
|
if (msg_buffer_.size() >= msg_buffer_.at(kOffsetLengthByte) + kHeaderSize) {
|
||||||
|
// we are done with the payload!
|
||||||
|
state_ = State::kValidatePacket;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::kValidatePacket:
|
||||||
|
// now let's start all over again
|
||||||
|
state_ = State::kWaitForSync;
|
||||||
|
// last data byte we receive is the checksum.
|
||||||
|
// let's check that it is the same as our self computed one.
|
||||||
|
if (checksum_ == _data) {
|
||||||
|
ProcessMessage();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageProcessor::ProcessMessage() {
|
||||||
|
// printf("Got a message! ID=0x%02x\n", msg_buffer_.at(kOffsetMessageIDByte));
|
||||||
|
uint8_t byte = msg_buffer_.at(kOffsetPayloadStart);
|
||||||
|
MessageID id = msg_buffer_.at(kOffsetMessageIDByte);
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
case MessageIDs::kStartupMessage:
|
||||||
|
if (byte == 0) {
|
||||||
|
printf("POWER_ON_RESET\n");
|
||||||
|
} else if (byte == (1 << 0)) {
|
||||||
|
printf("HARDWARE_RESET_LINE\n");
|
||||||
|
} else if (byte == (1 << 1)) {
|
||||||
|
printf("WATCHDOG_RESET\n");
|
||||||
|
} else if (byte == (1 << 5)) {
|
||||||
|
printf("COMMAND_RESET\n");
|
||||||
|
} else if (byte == (1 << 6)) {
|
||||||
|
printf("SYNCHRONOUS_RESET\n");
|
||||||
|
} else if (byte == (1 << 7)) {
|
||||||
|
printf("SUSPEND_RESET\n");
|
||||||
|
} else {
|
||||||
|
printf("??\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageIDs::kSerialErrorMessage:
|
||||||
|
printf("Errno from ANT: %hu\n", byte);
|
||||||
|
printf("Wrong message: ");
|
||||||
|
for (int i = 0; i < msg_buffer_.at(kOffsetLengthByte); ++i) {
|
||||||
|
printf("%02x, ", msg_buffer_.at(i + kOffsetPayloadStart));
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
break;
|
||||||
|
case MessageIDs::kChannelResponse: {
|
||||||
|
std::array<uint8_t, 3> payload;
|
||||||
|
typedef decltype(msg_buffer_)::const_iterator iterator;
|
||||||
|
iterator start = msg_buffer_.begin() + kOffsetPayloadStart;
|
||||||
|
iterator end = msg_buffer_.begin() + kOffsetPayloadStart + 3;
|
||||||
|
assert(msg_buffer_.size() >= kOffsetPayloadStart + 3);
|
||||||
|
std::copy(start, end, payload.begin());
|
||||||
|
ChannelResponseMessage msg = ChannelResponseMessage::FromPayload(payload);
|
||||||
|
if (channel_response_cbs_.count(msg.channel_id_)) {
|
||||||
|
channel_response_cbs_[msg.channel_id_](msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageIDs::kChannelID: {
|
||||||
|
uint16_t device_number = (msg_buffer_.at(kOffsetPayloadStart + 1)) |
|
||||||
|
(msg_buffer_.at(kOffsetPayloadStart + 2) << 8);
|
||||||
|
printf("----------\nDevice Number: 0x%04x\n----------\n", device_number);
|
||||||
|
printf("%hu, %hu\n", msg_buffer_.at(kOffsetPayloadStart + 1),
|
||||||
|
msg_buffer_.at(kOffsetPayloadStart + 2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageIDs::kBroadcastData: {
|
||||||
|
//printf("Broadcast page: 0x%02x\n", msg_buffer_.at(kOffsetPayloadStart + 1));
|
||||||
|
ChannelID channel_id = msg_buffer_.at(kOffsetPayloadStart);
|
||||||
|
BroadcastPayload msg(BroadcastPayload::FromSequentialBuffer(
|
||||||
|
&msg_buffer_.at(kOffsetPayloadStart + 1)));
|
||||||
|
if (broadcast_data_cbs_.count(channel_id)) {
|
||||||
|
broadcast_data_cbs_[channel_id](msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
printf("RECEIVED MESSAGE with id: 0x%02x\n",
|
||||||
|
msg_buffer_.at(kOffsetMessageIDByte));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace ant
|
||||||
37
src/ant/message_processor.hpp
Normal file
37
src/ant/message_processor.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
#include "message.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace ant {
|
||||||
|
|
||||||
|
class MessageProcessor {
|
||||||
|
public:
|
||||||
|
MessageProcessor();
|
||||||
|
enum class State {
|
||||||
|
kWaitForSync = 0,
|
||||||
|
kGetLength,
|
||||||
|
kGetMessageID,
|
||||||
|
kGetData,
|
||||||
|
kValidatePacket,
|
||||||
|
};
|
||||||
|
bool FeedData(uint8_t data);
|
||||||
|
void ProcessMessage();
|
||||||
|
void SetOnChannelResponseCallback(
|
||||||
|
ChannelID channel_id,
|
||||||
|
std::function<void(ChannelResponseMessage)> callback);
|
||||||
|
void SetOnBroadcastDataCallback(
|
||||||
|
ChannelID channel_id,
|
||||||
|
std::function<void(BroadcastPayload)> callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
State state_{State::kWaitForSync};
|
||||||
|
uint8_t checksum_;
|
||||||
|
std::vector<uint8_t> msg_buffer_;
|
||||||
|
std::map<ChannelID, std::function<void(ChannelResponseMessage)>> channel_response_cbs_;
|
||||||
|
std::map<ChannelID, std::function<void(BroadcastPayload)>> broadcast_data_cbs_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ant
|
||||||
51
src/main.cpp
Normal file
51
src/main.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
#include "ant/ant_device.hpp"
|
||||||
|
#include "ant/channels.hpp"
|
||||||
|
#include "ant/message.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
ant::ANTDevice ant_device;
|
||||||
|
ant_device.Init();
|
||||||
|
|
||||||
|
// ant::PowerChannel power_channel(ant_device, 2,
|
||||||
|
// ant::DeviceNumbers::Wildcard); ant::HeartRateChannel hr_channel(ant_device,
|
||||||
|
// 3, ant::DeviceNumbers::Wildcard);
|
||||||
|
ant::FitnessEquipmentChannel fe_channel(ant_device, 4,
|
||||||
|
ant::DeviceNumbers::Wildcard);
|
||||||
|
|
||||||
|
// ant_device.processor.SetOnChannelResponseCallback(
|
||||||
|
// power_channel.channel_id(),
|
||||||
|
// [&power_channel](ant::ChannelResponseMessage msg) {
|
||||||
|
// power_channel.OnChannelResponse(msg);
|
||||||
|
// });
|
||||||
|
// ant_device.processor.SetOnBroadcastDataCallback(
|
||||||
|
// power_channel.channel_id(), [&power_channel](ant::BroadcastPayload
|
||||||
|
// data) {
|
||||||
|
// power_channel.OnBroadcastData(data);
|
||||||
|
// });
|
||||||
|
// power_channel.StartSearch();
|
||||||
|
// ant_device.processor.SetOnChannelResponseCallback(
|
||||||
|
// hr_channel.channel_id(), [&hr_channel](ant::ChannelResponseMessage msg)
|
||||||
|
// {
|
||||||
|
// hr_channel.OnChannelResponse(msg);
|
||||||
|
// });
|
||||||
|
// ant_device.processor.SetOnBroadcastDataCallback(
|
||||||
|
// hr_channel.channel_id(), [&hr_channel](ant::BroadcastPayload data) {
|
||||||
|
// hr_channel.OnBroadcastData(data);
|
||||||
|
// });
|
||||||
|
// hr_channel.StartSearch();
|
||||||
|
ant_device.processor.SetOnBroadcastDataCallback(
|
||||||
|
fe_channel.channel_id(), [&fe_channel](ant::BroadcastPayload data) {
|
||||||
|
fe_channel.OnBroadcastData(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
ant_device.processor.SetOnChannelResponseCallback(
|
||||||
|
fe_channel.channel_id(), [&fe_channel](ant::ChannelResponseMessage msg) {
|
||||||
|
fe_channel.OnChannelResponse(msg);
|
||||||
|
});
|
||||||
|
fe_channel.StartSearch();
|
||||||
|
while (true) {
|
||||||
|
ant_device.ReceiveMessage();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue