Program Listing for File whill.cpp

Return to documentation for file (src/model_cr2/whill.cpp)

// Copyright (c) 2024 WHILL, Inc.
// Released under the MIT license
// https://opensource.org/licenses/mit-license.php

#include "whill_driver/model_cr2/whill.hpp"

#include <math.h>

namespace whill_driver
{
namespace model_cr2
{

enum CommandId : uint8_t
{
  kStartSendingData = 0x00,
  kStopSendingData,
  kSetPower,
  kSetJoystick,
  kSetSpeedProfile,
  kSetBatteryVoltageOut,
  kReserved1,
  kReserved2,
  kSetVelocity,
  kMax
};

enum CommandSize : uint8_t
{
  kStartSendingDataCommandSize = 6,
  kStopSendingDataCommandSize = 2,
  kSetPowerCommandSize = 3,
  kSetJoystickCommandSize = 5,
  kSetSpeedProfileCommandSize = 12,
  kSetBatteryVoltageOutCommandSize = 3,
  kReserved1CommandSize,
  kReserved2CommandSize,
  kSetVelocityCommandSize = 7,
  kMaxCommandSize = 16
};

enum SetJoyStickCommand : uint8_t
{
  kUserControlDisable = 0x00,
  kUserControlEnable = 0x01,
};

constexpr float kMotorAngleFactor = 0.001;

constexpr float kMotorSpeedFactor = 0.004;

constexpr uint8_t kHeaderSize = 2;

Whill::Whill(const std::string & port)
{
  port_ = std::make_shared<hardware::SerialPort>();
  if (port_->Open(port) < 0) {
    fprintf(stderr, "Can't initizalize UART to communicate with WHILL\n");
  }

  parser_ = std::make_shared<Parser>();
}

Whill::~Whill()
{
  port_->Close();
}

int Whill::SendStartSendingDataCommand(
  uint16_t interval_ms, DatasetNumber dataset_number,
  SpeedMode speed_mode)
{
  int idx = 0;
  uint8_t packet[kHeaderSize + CommandSize::kStartSendingDataCommandSize] = {0};

  packet[idx++] = kProtocolSign;
  packet[idx++] = CommandSize::kStartSendingDataCommandSize;
  packet[idx++] = CommandId::kStartSendingData;
  packet[idx++] = dataset_number;
  packet[idx++] = (uint8_t)(interval_ms >> 8);
  packet[idx++] = (uint8_t)interval_ms;
  packet[idx++] = speed_mode;
  packet[idx] = parser_->Checksum(packet, idx);
  return port_->Send(packet, sizeof(packet));
}

int Whill::SendStopSendingDataCommand()
{
  int idx = 0;
  uint8_t packet[kHeaderSize + CommandSize::kStopSendingDataCommandSize] = {0};

  packet[idx++] = kProtocolSign;
  packet[idx++] = CommandSize::kStopSendingDataCommandSize;
  packet[idx++] = CommandId::kStopSendingData;
  packet[idx] = parser_->Checksum(packet, idx);
  return port_->Send(packet, sizeof(packet));
}

int Whill::SendSetPower(bool turn_on)
{
  int idx = 0;
  uint8_t packet[kHeaderSize + CommandSize::kSetPowerCommandSize] = {0};

  packet[idx++] = kProtocolSign;
  packet[idx++] = CommandSize::kSetPowerCommandSize;
  packet[idx++] = CommandId::kSetPower;
  packet[idx++] = turn_on ? 0x01 : 0x00;
  packet[idx] = parser_->Checksum(packet, idx);
  return port_->Send(packet, sizeof(packet));
}

int Whill::SetPowerOn()
{
  return SendSetPower(true);
}

int Whill::SetPowerOff()
{
  return SendSetPower(false);
}

int Whill::SendSetJoystickCommand(uint8_t fb, uint8_t lr)
{
  int idx = 0;
  uint8_t packet[kHeaderSize + CommandSize::kSetJoystickCommandSize] = {0};

  packet[idx++] = kProtocolSign;
  packet[idx++] = CommandSize::kSetJoystickCommandSize;
  packet[idx++] = CommandId::kSetJoystick;
  packet[idx++] = kUserControlDisable;
  packet[idx++] = fb;
  packet[idx++] = lr;
  packet[idx] = parser_->Checksum(packet, idx);
  return port_->Send(packet, sizeof(packet));
}

int Whill::SendSetJoystickCommandWithLocal()
{
  int idx = 0;
  uint8_t packet[kHeaderSize + CommandSize::kSetJoystickCommandSize] = {0};

  packet[idx++] = kProtocolSign;
  packet[idx++] = CommandSize::kSetJoystickCommandSize;
  packet[idx++] = CommandId::kSetJoystick;
  packet[idx++] = kUserControlEnable;
  packet[idx++] = 0;
  packet[idx++] = 0;
  packet[idx] = parser_->Checksum(packet, idx);
  return port_->Send(packet, sizeof(packet));
}

int Whill::SendSetSpeedProfileCommand(
  uint8_t s1, uint8_t fm1, uint8_t fa1, uint8_t fd1, uint8_t rm1, uint8_t ra1,
  uint8_t rd1, uint8_t tm1, uint8_t ta1, uint8_t td1)
{
  int idx = 0;
  uint8_t packet[kHeaderSize + CommandSize::kSetSpeedProfileCommandSize] = {0};

  packet[idx++] = kProtocolSign;
  packet[idx++] = CommandSize::kSetSpeedProfileCommandSize;
  packet[idx++] = CommandId::kSetSpeedProfile;
  packet[idx++] = s1;
  packet[idx++] = fm1;
  packet[idx++] = fa1;
  packet[idx++] = fd1;
  packet[idx++] = rm1;
  packet[idx++] = ra1;
  packet[idx++] = rd1;
  packet[idx++] = tm1;
  packet[idx++] = ta1;
  packet[idx++] = td1;
  packet[idx] = parser_->Checksum(packet, idx);
  return port_->Send(packet, sizeof(packet));
}

int Whill::SendSetBatteryVoltageOutCommand(uint8_t battery_out)
{
  int idx = 0;
  uint8_t packet[kHeaderSize + CommandSize::kSetBatteryVoltageOutCommandSize] = {0};

  packet[idx++] = kProtocolSign;
  packet[idx++] = CommandSize::kSetBatteryVoltageOutCommandSize;
  packet[idx++] = CommandId::kSetBatteryVoltageOut;
  packet[idx++] = battery_out;
  packet[idx] = parser_->Checksum(packet, idx);
  return port_->Send(packet, sizeof(packet));
}

int Whill::SendSetVelocityCommand(int linear, int angular)
{
  int idx = 0;
  uint8_t packet[kHeaderSize + CommandSize::kSetVelocityCommandSize] = {0};

  packet[idx++] = kProtocolSign;
  packet[idx++] = CommandSize::kSetVelocityCommandSize;
  packet[idx++] = CommandId::kSetVelocity;
  packet[idx++] = kUserControlDisable;
  packet[idx++] = (uint8_t)((linear >> 8) & 0x000000FF);
  packet[idx++] = (uint8_t)(linear & 0x000000FF);
  packet[idx++] = (uint8_t)((angular >> 8) & 0x000000FF);
  packet[idx++] = (uint8_t)(angular & 0x000000FF);
  packet[idx] = parser_->Checksum(packet, idx);
  return port_->Send(packet, sizeof(packet));
}

int Whill::ReceiveDataset0(std::shared_ptr<whill_msgs::msg::SpeedProfile> & msg)
{
  // Read data
  uint8_t buf[kDatasetMaxSize] = {0};
  int received_len = port_->Receive(buf, (int)kDatasetMaxSize);
  if (received_len < 1) {return 0;}

  // Parse data
  uint8_t payload[kDatasetMaxSize] = {0};
  int payload_len = parser_->Parse(buf, received_len, payload);
  if (payload_len < 1) {return 0;}
  if (payload_len >= 32) {return 0;}

  if (payload[0] == 0) {
    // Dataset0
    msg->s1 = payload[1];
    msg->fm1 = payload[2];
    msg->fa1 = payload[3];
    msg->fd1 = payload[4];
    msg->rm1 = payload[5];
    msg->ra1 = payload[6];
    msg->rd1 = payload[7];
    msg->tm1 = payload[8];
    msg->ta1 = payload[9];
    msg->td1 = payload[10];
  }
  return 1;
}

int Whill::ReceiveDataset1(std::shared_ptr<whill_msgs::msg::ModelCr2State> & msg)
{
  // Read data
  uint8_t buf[kDatasetMaxSize] = {0};
  int received_len = port_->Receive(buf, (int)kDatasetMaxSize);
  if (received_len < 1) {return 0;}

  // Parse data
  uint8_t payload[kDatasetMaxSize] = {0};
  int payload_len = parser_->Parse(buf, received_len, payload);
  if (payload_len < 1) {return 0;}
  if (payload_len >= 32) {return 0;}

  if (payload[0] == 1) {
    // Dataset1
    msg->battery_power = int(payload[15] & 0xff);
    msg->battery_current = Calc16BitSignedData(payload[16], payload[17]);
    msg->right_motor_angle =
      Calc16BitSignedData(payload[18], payload[19]) * kMotorAngleFactor;
    msg->left_motor_angle = Calc16BitSignedData(payload[20], payload[21]) * kMotorAngleFactor;
    msg->right_motor_speed =
      Calc16BitSignedData(payload[22], payload[23]) * kMotorSpeedFactor;
    msg->left_motor_speed = Calc16BitSignedData(payload[24], payload[25]) * kMotorSpeedFactor;
    msg->power_on = int(payload[26] & 0xff);
    msg->speed_mode_indicator = int(payload[27] & 0xff);
    msg->error = int(payload[28] & 0xff);
    if (msg->error != 0) {
      fprintf(stderr, "WHILL sends error message. error id: %d", msg->error);
    }
  }
  return 1;
}

float Whill::Calc16BitSignedData(uint8_t d1, uint8_t d2)
{
  float d = float(((d1 & 0xff) << 8) | (d2 & 0xff));
  if (d > pow(2, 15)) {
    d -= pow(2, 16);
  }
  return d;
}

}  // namespace model_cr2
}  // namespace whill_driver