立即注册 找回密码

微雪课堂

搜索
微雪课堂 机器人操作系统(ROS) 查看内容

ROS基础系列教程6:ROS的通讯模式

2020-9-20 10:44| 发布者: waveshare_yf| 查看: 300| 评论: 0|原作者: waveshare|来自: waveshare

摘要: 上一节我们讲述了一些通讯的基础认知,在这一节内容,我们会就ROS的通讯模式,进 行详细的讲解介绍。 在ROS系统里面,通讯主要是在节点于节点之间。节点发布消息数据到达节点管理器 ROS Master,ROS Master回去寻找 ...
  • 上一节我们讲述了一些通讯的基础认知,在这一节内容,我们会就ROS的通讯模式,进 行详细的讲解介绍。
  • 在ROS系统里面,通讯主要是在节点于节点之间。节点发布消息数据到达节点管理器 ROS Master,ROS Master回去寻找相应的节点来处理并执行数据。我们在这里重点围绕 话题通讯和服务通讯进行案例说明。
  • ROS里面的参数编程内容我没有设计内容,我个人觉得这只是一种调用方法,所以我会在后面大致阐述,至于动作action有关内容,目前不做出教程。

    话题通讯

  • 在我的高中时代,我的班主任老师负责九班和十班两个班级,其中我是在九班。在一次活动当中,班主任老师让我去告诉十班的纪律委员,下午的开会取消,可是我并不认识十班的纪律委员。在这种情况下,我先找到了十班的班长,我告诉他,班主任让我告诉你们班的 纪律委员,下午的开会取消,由十班的班长替我进行了转达。我就是这样完成了我的任务。
  • 在这个场景当中,我作为一个发布节点,需要发布一个下午取消开会的消息,发布到十班的纪律委员;而十班的纪律委员就是一个订阅节点,他会知道这个消息;而十班的班长, 则充当了Master的一个效果。图示如下。




  • 是不是觉得这个图很眼熟?没错,这就是我们的rqt节点关系图。在话题通讯当中,我 们所需要的发布一些数据到达ROS Master(班长),这些数据可以被订阅也可以被不订阅。




  • 我们在话题通讯当中,需要有ROS Master节点管理器、发布节点(可选)、订阅节点(可选)组成。
  • 首先是我们的ROS Master节点管理器启动,等待发布节点或者订阅节点前来注 册,当由发布节点或者订阅节点的数据时,ROS Master会进行数据的发送。

    • 假如我们现在有个发布节点Talker来节点管理器注册,它发布了一个名字叫做bar的消息。这个时候Talker需要先在节点管理器做个注册,记录下这个信息,因为Talker只是把bar发不出来,有没有人要Talker并不知道。
    • 接下来是来了一个Listener订阅节点,它在节点管理器注册的时候,告诉节点管理器,我需要一个叫做bar的的消息。
    • ROS Master发现,bar不正是之前那个叫做Talker的家伙发布的吗,在这个时候, ROS Master就把bar的一些信息给了Listener。Listener拿到消息之后,开始连接Talker进行友善的交流。
  • 通俗简单的来说,就是我们节点管理器(班长)启动了,那些个节点们来注册一下,通过节点管理器的调配,进行通信。接下来看一下实例代码。在这一节的内容当中,实例代码是我以Arduino为下位机,使用LM35温度传感器获得温度数字,并在ROS的rqt工具下进行折线图显示。

  • 我们先来构思一下整体设计。我的设计是Arduino读取LM35温度传感器的数据,通过串口发送到具有ROS系统的PC,然后把这个温度数据以话题的形式给发布出来,在ROS里面可以给订阅到。




  • 我们先来处理Arduino单片机部位。LM35温度传感器是一款模拟温度传感器,通过Arduino的A口即可读取数据温度数值。形状酷似三极管,在这里我使用的是自己设计的PCB上面的LM35温度传感器,传感器接在Arduino Nano板的A0口。




  • 在PC和Arduino的之间的通讯,我使用了Firmata协议。Firmata是一个PC与MCU通讯的一个常用协议。其遵旨是能与任何主机PC软件包兼容。到目前为止,已经得到不少语言的支持,这样可方便地将对协议的支持加入软件系统中。Firmata起初是针对于PC与 Arduino通讯的固件(Firmware),其目标是让开发者可以通过PC软件完全地控件 Arduino。我们在Arduino Nano板子下载Arduino IDE的例程—Firmata—StandardFirmata即可使用,默认的波特率是57600,讷伊可以自行修改。




代码附出。

#include <Servo.h>
#include <Wire.h>
#include <Firmata.h>

#define I2C_WRITE                   B00000000
#define I2C_READ                    B00001000
#define I2C_READ_CONTINUOUSLY       B00010000
#define I2C_STOP_READING            B00011000
#define I2C_READ_WRITE_MODE_MASK    B00011000
#define I2C_10BIT_ADDRESS_MODE_MASK B00100000
#define I2C_END_TX_MASK             B01000000
#define I2C_STOP_TX                 1
#define I2C_RESTART_TX              0
#define I2C_MAX_QUERIES             8
#define I2C_REGISTER_NOT_SPECIFIED  -1

// the minimum interval for sampling analog input
#define MINIMUM_SAMPLING_INTERVAL   1

/*==============================================================================
 * GLOBAL VARIABLES
 *============================================================================*/

#ifdef FIRMATA_SERIAL_FEATURE
SerialFirmata serialFeature;
#endif

/* analog inputs */
int analogInputsToReport = 0; // bitwise array to store pin reporting

/* digital input ports */
byte reportPINs[TOTAL_PORTS];       // 1 = report this port, 0 = silence
byte previousPINs[TOTAL_PORTS];     // previous 8 bits sent

/* pins configuration */
byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else

/* timer variables */
unsigned long currentMillis;        // store the current value from millis()
unsigned long previousMillis;       // for comparison with currentMillis
unsigned int samplingInterval = 19; // how often to run the main loop (in ms)

/* i2c data */
struct i2c_device_info {
  byte addr;
  int reg;
  byte bytes;
  byte stopTX;
};

/* for i2c read continuous more */
i2c_device_info query[I2C_MAX_QUERIES];

byte i2cRxData[64];
boolean isI2CEnabled = false;
signed char queryIndex = -1;
// default delay time between i2c read request and Wire.requestFrom()
unsigned int i2cReadDelayTime = 0;

Servo servos[MAX_SERVOS];
byte servoPinMap[TOTAL_PINS];
byte detachedServos[MAX_SERVOS];
byte detachedServoCount = 0;
byte servoCount = 0;

boolean isResetting = false;

// Forward declare a few functions to avoid compiler errors with older versions
// of the Arduino IDE.
void setPinModeCallback(byte, int);
void reportAnalogCallback(byte analogPin, int value);
void sysexCallback(byte, byte, byte*);

/* utility functions */
void wireWrite(byte data)
{
#if ARDUINO >= 100
  Wire.write((byte)data);
#else
  Wire.send(data);
#endif
}

byte wireRead(void)
{
#if ARDUINO >= 100
  return Wire.read();
#else
  return Wire.receive();
#endif
}

/*==============================================================================
 * FUNCTIONS
 *============================================================================*/

void attachServo(byte pin, int minPulse, int maxPulse)
{
  if (servoCount < MAX_SERVOS) {
    // reuse indexes of detached servos until all have been reallocated
    if (detachedServoCount > 0) {
      servoPinMap[pin] = detachedServos[detachedServoCount - 1];
      if (detachedServoCount > 0) detachedServoCount--;
    } else {
      servoPinMap[pin] = servoCount;
      servoCount++;
    }
    if (minPulse > 0 && maxPulse > 0) {
      servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin), minPulse, maxPulse);
    } else {
      servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin));
    }
  } else {
    Firmata.sendString("Max servos attached");
  }
}

void detachServo(byte pin)
{
  servos[servoPinMap[pin]].detach();
  // if we're detaching the last servo, decrement the count
  // otherwise store the index of the detached servo
  if (servoPinMap[pin] == servoCount && servoCount > 0) {
    servoCount--;
  } else if (servoCount > 0) {
    // keep track of detached servos because we want to reuse their indexes
    // before incrementing the count of attached servos
    detachedServoCount++;
    detachedServos[detachedServoCount - 1] = servoPinMap[pin];
  }

  servoPinMap[pin] = 255;
}

void enableI2CPins()
{
  byte i;
  // is there a faster way to do this? would probaby require importing
  // Arduino.h to get SCL and SDA pins
  for (i = 0; i < TOTAL_PINS; i++) {
    if (IS_PIN_I2C(i)) {
      // mark pins as i2c so they are ignore in non i2c data requests
      setPinModeCallback(i, PIN_MODE_I2C);
    }
  }

  isI2CEnabled = true;

  Wire.begin();
}

/* disable the i2c pins so they can be used for other functions */
void disableI2CPins() {
  isI2CEnabled = false;
  // disable read continuous mode for all devices
  queryIndex = -1;
}

void readAndReportData(byte address, int theRegister, byte numBytes, byte stopTX) {
  // allow I2C requests that don't require a register read
  // for example, some devices using an interrupt pin to signify new data available
  // do not always require the register read so upon interrupt you call Wire.requestFrom()
  if (theRegister != I2C_REGISTER_NOT_SPECIFIED) {
    Wire.beginTransmission(address);
    wireWrite((byte)theRegister);
    Wire.endTransmission(stopTX); // default = true
    // do not set a value of 0
    if (i2cReadDelayTime > 0) {
      // delay is necessary for some devices such as WiiNunchuck
      delayMicroseconds(i2cReadDelayTime);
    }
  } else {
    theRegister = 0;  // fill the register with a dummy value
  }

  Wire.requestFrom(address, numBytes);  // all bytes are returned in requestFrom

  // check to be sure correct number of bytes were returned by slave
  if (numBytes < Wire.available()) {
    Firmata.sendString("I2C: Too many bytes received");
  } else if (numBytes > Wire.available()) {
    Firmata.sendString("I2C: Too few bytes received");
  }

  i2cRxData[0] = address;
  i2cRxData[1] = theRegister;

  for (int i = 0; i < numBytes && Wire.available(); i++) {
    i2cRxData[2 + i] = wireRead();
  }

  // send slave address, register and received bytes
  Firmata.sendSysex(SYSEX_I2C_REPLY, numBytes + 2, i2cRxData);
}

void outputPort(byte portNumber, byte portValue, byte forceSend)
{
  // pins not configured as INPUT are cleared to zeros
  portValue = portValue & portConfigInputs[portNumber];
  // only send if the value is different than previously sent
  if (forceSend || previousPINs[portNumber] != portValue) {
    Firmata.sendDigitalPort(portNumber, portValue);
    previousPINs[portNumber] = portValue;
  }
}

/* -----------------------------------------------------------------------------
 * check all the active digital inputs for change of state, then add any events
 * to the Serial output queue using Serial.print() */
void checkDigitalInputs(void)
{
  /* Using non-looping code allows constants to be given to readPort().
   * The compiler will apply substantial optimizations if the inputs
   * to readPort() are compile-time constants. */
  if (TOTAL_PORTS > 0 && reportPINs[0]) outputPort(0, readPort(0, portConfigInputs[0]), false);
  if (TOTAL_PORTS > 1 && reportPINs[1]) outputPort(1, readPort(1, portConfigInputs[1]), false);
  if (TOTAL_PORTS > 2 && reportPINs[2]) outputPort(2, readPort(2, portConfigInputs[2]), false);
  if (TOTAL_PORTS > 3 && reportPINs[3]) outputPort(3, readPort(3, portConfigInputs[3]), false);
  if (TOTAL_PORTS > 4 && reportPINs[4]) outputPort(4, readPort(4, portConfigInputs[4]), false);
  if (TOTAL_PORTS > 5 && reportPINs[5]) outputPort(5, readPort(5, portConfigInputs[5]), false);
  if (TOTAL_PORTS > 6 && reportPINs[6]) outputPort(6, readPort(6, portConfigInputs[6]), false);
  if (TOTAL_PORTS > 7 && reportPINs[7]) outputPort(7, readPort(7, portConfigInputs[7]), false);
  if (TOTAL_PORTS > 8 && reportPINs[8]) outputPort(8, readPort(8, portConfigInputs[8]), false);
  if (TOTAL_PORTS > 9 && reportPINs[9]) outputPort(9, readPort(9, portConfigInputs[9]), false);
  if (TOTAL_PORTS > 10 && reportPINs[10]) outputPort(10, readPort(10, portConfigInputs[10]), false);
  if (TOTAL_PORTS > 11 && reportPINs[11]) outputPort(11, readPort(11, portConfigInputs[11]), false);
  if (TOTAL_PORTS > 12 && reportPINs[12]) outputPort(12, readPort(12, portConfigInputs[12]), false);
  if (TOTAL_PORTS > 13 && reportPINs[13]) outputPort(13, readPort(13, portConfigInputs[13]), false);
  if (TOTAL_PORTS > 14 && reportPINs[14]) outputPort(14, readPort(14, portConfigInputs[14]), false);
  if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false);
}

// -----------------------------------------------------------------------------
/* sets the pin mode to the correct state and sets the relevant bits in the
 * two bit-arrays that track Digital I/O and PWM status
 */
void setPinModeCallback(byte pin, int mode)
{
  if (Firmata.getPinMode(pin) == PIN_MODE_IGNORE)
    return;

  if (Firmata.getPinMode(pin) == PIN_MODE_I2C && isI2CEnabled && mode != PIN_MODE_I2C) {
    // disable i2c so pins can be used for other functions
    // the following if statements should reconfigure the pins properly
    disableI2CPins();
  }
  if (IS_PIN_DIGITAL(pin) && mode != PIN_MODE_SERVO) {
    if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) {
      detachServo(pin);
    }
  }
  if (IS_PIN_ANALOG(pin)) {
    reportAnalogCallback(PIN_TO_ANALOG(pin), mode == PIN_MODE_ANALOG ? 1 : 0); // turn on/off reporting
  }
  if (IS_PIN_DIGITAL(pin)) {
    if (mode == INPUT || mode == PIN_MODE_PULLUP) {
      portConfigInputs[pin / 8] |= (1 << (pin & 7));
    } else {
      portConfigInputs[pin / 8] &= ~(1 << (pin & 7));
    }
  }
  Firmata.setPinState(pin, 0);
  switch (mode) {
    case PIN_MODE_ANALOG:
      if (IS_PIN_ANALOG(pin)) {
        if (IS_PIN_DIGITAL(pin)) {
          pinMode(PIN_TO_DIGITAL(pin), INPUT);    // disable output driver
#if ARDUINO <= 100
          // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
          digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
#endif
        }
        Firmata.setPinMode(pin, PIN_MODE_ANALOG);
      }
      break;
    case INPUT:
      if (IS_PIN_DIGITAL(pin)) {
        pinMode(PIN_TO_DIGITAL(pin), INPUT);    // disable output driver
#if ARDUINO <= 100
        // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
        digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
#endif
        Firmata.setPinMode(pin, INPUT);
      }
      break;
    case PIN_MODE_PULLUP:
      if (IS_PIN_DIGITAL(pin)) {
        pinMode(PIN_TO_DIGITAL(pin), INPUT_PULLUP);
        Firmata.setPinMode(pin, PIN_MODE_PULLUP);
        Firmata.setPinState(pin, 1);
      }
      break;
    case OUTPUT:
      if (IS_PIN_DIGITAL(pin)) {
        if (Firmata.getPinMode(pin) == PIN_MODE_PWM) {
          // Disable PWM if pin mode was previously set to PWM.
          digitalWrite(PIN_TO_DIGITAL(pin), LOW);
        }
        pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
        Firmata.setPinMode(pin, OUTPUT);
      }
      break;
    case PIN_MODE_PWM:
      if (IS_PIN_PWM(pin)) {
        pinMode(PIN_TO_PWM(pin), OUTPUT);
        analogWrite(PIN_TO_PWM(pin), 0);
        Firmata.setPinMode(pin, PIN_MODE_PWM);
      }
      break;
    case PIN_MODE_SERVO:
      if (IS_PIN_DIGITAL(pin)) {
        Firmata.setPinMode(pin, PIN_MODE_SERVO);
        if (servoPinMap[pin] == 255 || !servos[servoPinMap[pin]].attached()) {
          // pass -1 for min and max pulse values to use default values set
          // by Servo library
          attachServo(pin, -1, -1);
        }
      }
      break;
    case PIN_MODE_I2C:
      if (IS_PIN_I2C(pin)) {
        // mark the pin as i2c
        // the user must call I2C_CONFIG to enable I2C for a device
        Firmata.setPinMode(pin, PIN_MODE_I2C);
      }
      break;
    case PIN_MODE_SERIAL:
#ifdef FIRMATA_SERIAL_FEATURE
      serialFeature.handlePinMode(pin, PIN_MODE_SERIAL);
#endif
      break;
    default:
      Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM
  }
  // TODO: save status to EEPROM here, if changed
}

/*
 * Sets the value of an individual pin. Useful if you want to set a pin value but
 * are not tracking the digital port state.
 * Can only be used on pins configured as OUTPUT.
 * Cannot be used to enable pull-ups on Digital INPUT pins.
 */
void setPinValueCallback(byte pin, int value)
{
  if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) {
    if (Firmata.getPinMode(pin) == OUTPUT) {
      Firmata.setPinState(pin, value);
      digitalWrite(PIN_TO_DIGITAL(pin), value);
    }
  }
}

void analogWriteCallback(byte pin, int value)
{
  if (pin < TOTAL_PINS) {
    switch (Firmata.getPinMode(pin)) {
      case PIN_MODE_SERVO:
        if (IS_PIN_DIGITAL(pin))
          servos[servoPinMap[pin]].write(value);
        Firmata.setPinState(pin, value);
        break;
      case PIN_MODE_PWM:
        if (IS_PIN_PWM(pin))
          analogWrite(PIN_TO_PWM(pin), value);
        Firmata.setPinState(pin, value);
        break;
    }
  }
}

void digitalWriteCallback(byte port, int value)
{
  byte pin, lastPin, pinValue, mask = 1, pinWriteMask = 0;

  if (port < TOTAL_PORTS) {
    // create a mask of the pins on this port that are writable.
    lastPin = port * 8 + 8;
    if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS;
    for (pin = port * 8; pin < lastPin; pin++) {
      // do not disturb non-digital pins (eg, Rx & Tx)
      if (IS_PIN_DIGITAL(pin)) {
        // do not touch pins in PWM, ANALOG, SERVO or other modes
        if (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) {
          pinValue = ((byte)value & mask) ? 1 : 0;
          if (Firmata.getPinMode(pin) == OUTPUT) {
            pinWriteMask |= mask;
          } else if (Firmata.getPinMode(pin) == INPUT && pinValue == 1 && Firmata.getPinState(pin) != 1) {
            // only handle INPUT here for backwards compatibility
#if ARDUINO > 100
            pinMode(pin, INPUT_PULLUP);
#else
            // only write to the INPUT pin to enable pullups if Arduino v1.0.0 or earlier
            pinWriteMask |= mask;
#endif
          }
          Firmata.setPinState(pin, pinValue);
        }
      }
      mask = mask << 1;
    }
    writePort(port, (byte)value, pinWriteMask);
  }
}

void reportAnalogCallback(byte analogPin, int value)
{
  if (analogPin < TOTAL_ANALOG_PINS) {
    if (value == 0) {
      analogInputsToReport = analogInputsToReport & ~ (1 << analogPin);
    } else {
      analogInputsToReport = analogInputsToReport | (1 << analogPin);
      // prevent during system reset or all analog pin values will be reported
      // which may report noise for unconnected analog pins
      if (!isResetting) {
        // Send pin value immediately. This is helpful when connected via
        // ethernet, wi-fi or bluetooth so pin states can be known upon
        // reconnecting.
        Firmata.sendAnalog(analogPin, analogRead(analogPin));
      }
    }
  }
  // TODO: save status to EEPROM here, if changed
}

void reportDigitalCallback(byte port, int value)
{
  if (port < TOTAL_PORTS) {
    reportPINs[port] = (byte)value;
    // Send port value immediately. This is helpful when connected via
    // ethernet, wi-fi or bluetooth so pin states can be known upon
    // reconnecting.
    if (value) outputPort(port, readPort(port, portConfigInputs[port]), true);
  }
  // do not disable analog reporting on these 8 pins, to allow some
  // pins used for digital, others analog.  Instead, allow both types
  // of reporting to be enabled, but check if the pin is configured
  // as analog when sampling the analog inputs.  Likewise, while
  // scanning digital pins, portConfigInputs will mask off values from any
  // pins configured as analog
}

/*==============================================================================
 * SYSEX-BASED commands
 *============================================================================*/

void sysexCallback(byte command, byte argc, byte *argv)
{
  byte mode;
  byte stopTX;
  byte slaveAddress;
  byte data;
  int slaveRegister;
  unsigned int delayTime;

  switch (command) {
    case I2C_REQUEST:
      mode = argv[1] & I2C_READ_WRITE_MODE_MASK;
      if (argv[1] & I2C_10BIT_ADDRESS_MODE_MASK) {
        Firmata.sendString("10-bit addressing not supported");
        return;
      }
      else {
        slaveAddress = argv[0];
      }

      // need to invert the logic here since 0 will be default for client
      // libraries that have not updated to add support for restart tx
      if (argv[1] & I2C_END_TX_MASK) {
        stopTX = I2C_RESTART_TX;
      }
      else {
        stopTX = I2C_STOP_TX; // default
      }

      switch (mode) {
        case I2C_WRITE:
          Wire.beginTransmission(slaveAddress);
          for (byte i = 2; i < argc; i += 2) {
            data = argv[i] + (argv[i + 1] << 7);
            wireWrite(data);
          }
          Wire.endTransmission();
          delayMicroseconds(70);
          break;
        case I2C_READ:
          if (argc == 6) {
            // a slave register is specified
            slaveRegister = argv[2] + (argv[3] << 7);
            data = argv[4] + (argv[5] << 7);  // bytes to read
          }
          else {
            // a slave register is NOT specified
            slaveRegister = I2C_REGISTER_NOT_SPECIFIED;
            data = argv[2] + (argv[3] << 7);  // bytes to read
          }
          readAndReportData(slaveAddress, (int)slaveRegister, data, stopTX);
          break;
        case I2C_READ_CONTINUOUSLY:
          if ((queryIndex + 1) >= I2C_MAX_QUERIES) {
            // too many queries, just ignore
            Firmata.sendString("too many queries");
            break;
          }
          if (argc == 6) {
            // a slave register is specified
            slaveRegister = argv[2] + (argv[3] << 7);
            data = argv[4] + (argv[5] << 7);  // bytes to read
          }
          else {
            // a slave register is NOT specified
            slaveRegister = (int)I2C_REGISTER_NOT_SPECIFIED;
            data = argv[2] + (argv[3] << 7);  // bytes to read
          }
          queryIndex++;
          query[queryIndex].addr = slaveAddress;
          query[queryIndex].reg = slaveRegister;
          query[queryIndex].bytes = data;
          query[queryIndex].stopTX = stopTX;
          break;
        case I2C_STOP_READING:
          byte queryIndexToSkip;
          // if read continuous mode is enabled for only 1 i2c device, disable
          // read continuous reporting for that device
          if (queryIndex <= 0) {
            queryIndex = -1;
          } else {
            queryIndexToSkip = 0;
            // if read continuous mode is enabled for multiple devices,
            // determine which device to stop reading and remove it's data from
            // the array, shifiting other array data to fill the space
            for (byte i = 0; i < queryIndex + 1; i++) {
              if (query[i].addr == slaveAddress) {
                queryIndexToSkip = i;
                break;
              }
            }

            for (byte i = queryIndexToSkip; i < queryIndex + 1; i++) {
              if (i < I2C_MAX_QUERIES) {
                query[i].addr = query[i + 1].addr;
                query[i].reg = query[i + 1].reg;
                query[i].bytes = query[i + 1].bytes;
                query[i].stopTX = query[i + 1].stopTX;
              }
            }
            queryIndex--;
          }
          break;
        default:
          break;
      }
      break;
    case I2C_CONFIG:
      delayTime = (argv[0] + (argv[1] << 7));

      if (argc > 1 && delayTime > 0) {
        i2cReadDelayTime = delayTime;
      }

      if (!isI2CEnabled) {
        enableI2CPins();
      }

      break;
    case SERVO_CONFIG:
      if (argc > 4) {
        // these vars are here for clarity, they'll optimized away by the compiler
        byte pin = argv[0];
        int minPulse = argv[1] + (argv[2] << 7);
        int maxPulse = argv[3] + (argv[4] << 7);

        if (IS_PIN_DIGITAL(pin)) {
          if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) {
            detachServo(pin);
          }
          attachServo(pin, minPulse, maxPulse);
          setPinModeCallback(pin, PIN_MODE_SERVO);
        }
      }
      break;
    case SAMPLING_INTERVAL:
      if (argc > 1) {
        samplingInterval = argv[0] + (argv[1] << 7);
        if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) {
          samplingInterval = MINIMUM_SAMPLING_INTERVAL;
        }
      } else {
        //Firmata.sendString("Not enough data");
      }
      break;
    case EXTENDED_ANALOG:
      if (argc > 1) {
        int val = argv[1];
        if (argc > 2) val |= (argv[2] << 7);
        if (argc > 3) val |= (argv[3] << 14);
        analogWriteCallback(argv[0], val);
      }
      break;
    case CAPABILITY_QUERY:
      Firmata.write(START_SYSEX);
      Firmata.write(CAPABILITY_RESPONSE);
      for (byte pin = 0; pin < TOTAL_PINS; pin++) {
        if (IS_PIN_DIGITAL(pin)) {
          Firmata.write((byte)INPUT);
          Firmata.write(1);
          Firmata.write((byte)PIN_MODE_PULLUP);
          Firmata.write(1);
          Firmata.write((byte)OUTPUT);
          Firmata.write(1);
        }
        if (IS_PIN_ANALOG(pin)) {
          Firmata.write(PIN_MODE_ANALOG);
          Firmata.write(10); // 10 = 10-bit resolution
        }
        if (IS_PIN_PWM(pin)) {
          Firmata.write(PIN_MODE_PWM);
          Firmata.write(DEFAULT_PWM_RESOLUTION);
        }
        if (IS_PIN_DIGITAL(pin)) {
          Firmata.write(PIN_MODE_SERVO);
          Firmata.write(14);
        }
        if (IS_PIN_I2C(pin)) {
          Firmata.write(PIN_MODE_I2C);
          Firmata.write(1);  // TODO: could assign a number to map to SCL or SDA
        }
#ifdef FIRMATA_SERIAL_FEATURE
        serialFeature.handleCapability(pin);
#endif
        Firmata.write(127);
      }
      Firmata.write(END_SYSEX);
      break;
    case PIN_STATE_QUERY:
      if (argc > 0) {
        byte pin = argv[0];
        Firmata.write(START_SYSEX);
        Firmata.write(PIN_STATE_RESPONSE);
        Firmata.write(pin);
        if (pin < TOTAL_PINS) {
          Firmata.write(Firmata.getPinMode(pin));
          Firmata.write((byte)Firmata.getPinState(pin) & 0x7F);
          if (Firmata.getPinState(pin) & 0xFF80) Firmata.write((byte)(Firmata.getPinState(pin) >> 7) & 0x7F);
          if (Firmata.getPinState(pin) & 0xC000) Firmata.write((byte)(Firmata.getPinState(pin) >> 14) & 0x7F);
        }
        Firmata.write(END_SYSEX);
      }
      break;
    case ANALOG_MAPPING_QUERY:
      Firmata.write(START_SYSEX);
      Firmata.write(ANALOG_MAPPING_RESPONSE);
      for (byte pin = 0; pin < TOTAL_PINS; pin++) {
        Firmata.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127);
      }
      Firmata.write(END_SYSEX);
      break;

    case SERIAL_MESSAGE:
#ifdef FIRMATA_SERIAL_FEATURE
      serialFeature.handleSysex(command, argc, argv);
#endif
      break;
  }
}

/*==============================================================================
 * SETUP()
 *============================================================================*/

void systemResetCallback()
{
  isResetting = true;

  // initialize a defalt state
  // TODO: option to load config from EEPROM instead of default

#ifdef FIRMATA_SERIAL_FEATURE
  serialFeature.reset();
#endif

  if (isI2CEnabled) {
    disableI2CPins();
  }

  for (byte i = 0; i < TOTAL_PORTS; i++) {
    reportPINs[i] = false;    // by default, reporting off
    portConfigInputs[i] = 0;  // until activated
    previousPINs[i] = 0;
  }

  for (byte i = 0; i < TOTAL_PINS; i++) {
    // pins with analog capability default to analog input
    // otherwise, pins default to digital output
    if (IS_PIN_ANALOG(i)) {
      // turns off pullup, configures everything
      setPinModeCallback(i, PIN_MODE_ANALOG);
    } else if (IS_PIN_DIGITAL(i)) {
      // sets the output to 0, configures portConfigInputs
      setPinModeCallback(i, OUTPUT);
    }

    servoPinMap[i] = 255;
  }
  // by default, do not report any analog inputs
  analogInputsToReport = 0;

  detachedServoCount = 0;
  servoCount = 0;

  /* send digital inputs to set the initial state on the host computer,
   * since once in the loop(), this firmware will only send on change */
  /*
  TODO: this can never execute, since no pins default to digital input
        but it will be needed when/if we support EEPROM stored config
  for (byte i=0; i < TOTAL_PORTS; i++) {
    outputPort(i, readPort(i, portConfigInputs[i]), true);
  }
  */
  isResetting = false;
}

void setup()
{
  Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION);

  Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
  Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback);
  Firmata.attach(REPORT_ANALOG, reportAnalogCallback);
  Firmata.attach(REPORT_DIGITAL, reportDigitalCallback);
  Firmata.attach(SET_PIN_MODE, setPinModeCallback);
  Firmata.attach(SET_DIGITAL_PIN_VALUE, setPinValueCallback);
  Firmata.attach(START_SYSEX, sysexCallback);
  Firmata.attach(SYSTEM_RESET, systemResetCallback);

  // to use a port other than Serial, such as Serial1 on an Arduino Leonardo or Mega,
  // Call begin(baud) on the alternate serial port and pass it to Firmata to begin like this:
  // Serial1.begin(57600);
  // Firmata.begin(Serial1);
  // However do not do this if you are using SERIAL_MESSAGE

  Firmata.begin(57600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for ATmega32u4-based boards and Arduino 101
  }

  systemResetCallback();  // reset to default config
}

/*==============================================================================
 * LOOP()
 *============================================================================*/
void loop()
{
  byte pin, analogPin;

  /* DIGITALREAD - as fast as possible, check for changes and output them to the
   * FTDI buffer using Serial.print()  */
  checkDigitalInputs();

  /* STREAMREAD - processing incoming messagse as soon as possible, while still
   * checking digital inputs.  */
  while (Firmata.available())
    Firmata.processInput();

  // TODO - ensure that Stream buffer doesn't go over 60 bytes

  currentMillis = millis();
  if (currentMillis - previousMillis > samplingInterval) {
    previousMillis += samplingInterval;
    /* ANALOGREAD - do all analogReads() at the configured sampling interval */
    for (pin = 0; pin < TOTAL_PINS; pin++) {
      if (IS_PIN_ANALOG(pin) && Firmata.getPinMode(pin) == PIN_MODE_ANALOG) {
        analogPin = PIN_TO_ANALOG(pin);
        if (analogInputsToReport & (1 << analogPin)) {
          Firmata.sendAnalog(analogPin, analogRead(analogPin));
        }
      }
    }
    // report i2c data for all device with read continuous mode enabled
    if (queryIndex > -1) {
      for (byte i = 0; i < queryIndex + 1; i++) {
        readAndReportData(query[i].addr, query[i].reg, query[i].bytes, query[i].stopTX);
      }
    }
  }

#ifdef FIRMATA_SERIAL_FEATURE
  serialFeature.update();
#endif
}




由于Ubuntu版本支持Python2和Python3,在这里我都给安装。





我们把Arduino Nano板子插入之后,需要个串口一个权限,不然在运行程序的时候会 提示串口错误。





然后是在catkin_ws/src下创建一个temp_value的功能包,并编译和生效环境变量。

catkin_create_pkg temp_value rospy roscpp std_msgs




  • 使用roscd指令进入我们的temp_value功能包下,创建scriptts文件夹以及在scripts下 创建一个temp_read.py的文件。在这里我们先不着急写代码,可以先在Python的交互环境 下试一下使用Firmata协议操作Arduino。




欧克,看来是没有问题的。我们开始写temp_read.py的内容,如下。

#!/usr/bin/env python3 
#coding:utf‐8 

from pyfirmata import Arduino,util #导入pyFirmata模块 
import time #导入时间功能模块

board = Arduino('/dev/ttyUSB0') #实例化板子

it = util.Iterator(board) #线程初始化 
it.start() #开启线程
board.analog[0].enable_reporting() #使能A0引脚 

i = 0 

while i<10: 
    print("temp:" + str(board.analog[0].read())) #读取A0引脚数值并打印 
    i = i + 1 #控制输出显示10次 
    time.sleep(0.5) #每次之间延时0.5s

board.exit() #释放串口资源

运行效果如下。





  • 当然你也可以使用串口直接发送温度数据,不过这是我们在C++部分的内容。
  • 现在我们开始设计ROS的代码,其实我们就是需要做一个发布节点和关于温度的消息。 进入我们的temp_value功能包,创建一个msg文件夹,这里面存放的是我们的消息,我们 在msg文件夹里面创建一个temp.msg的文件,输入以下内容。
float64 temp_value
  • 这个temp_value就是我们的温度消息内容,文件名temp.msg的temp就是我们的话题 名称。我们现在需要来修改我们的package.xml和CMakeList.txt,由于这个功能包是Python 写的,所以不需要配置CMakeList.txt编译输出项,只需要把msg相关内容配置好即可。 我的package.xml内容如下。

 <?xml version="1.0"?>
 <package format="2">
 <name>temp_value</name> 
 <version>0.0.0</version>
 <description>The temp_value package</description>
 <maintainer email="1692697820@qq.com">waveshare</maintainer> 
 <license>TODO</license> 

 <buildtool_depend>catkin</buildtool_depend> 

 <build_depend>roscpp</build_depend> 
 <build_depend>rospy</build_depend> 
 <build_depend>std_msgs</build_depend> 

 <build_depend>message_generation</build_depend> 


<build_export_depend>roscpp</build_export_depend> 
<build_export_depend>rospy</build_export_depend> 
<build_export_depend>std_msgs</build_export_depend>

<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>

<exec_depend>message_runtime</exec_depend> 

<export> 
</export>
</package>
  • 没有太大变化,就是添加了编译依赖项message_generation,这个是输出msg的,我 们需要把自己的temp.msg编译输出;还有一个是运行依赖项message_runtime,这个是 必须的。接下来是我的CMakeList.txt,内容如下。
cmake_minimum_required(VERSION 3.0.2) 
project(temp_value) 

find_package(catkin REQUIRED COMPONENTS 
roscpp 
rospy 
std_msgs 
message_generation
) 

add_message_files( 
FILES 
temp.msg 
) 

generate_messages( 
DEPENDENCIES 
temp_value 
) 

catkin_package(
# INCLUDE_DIRS include
# LIBRARIES temp_value

CATKIN_DEPENDS roscpp rospy std_msgs
DEPENDS system_lib 
) 

include_directories( 
# include 
${catkin_INCLUDE_DIRS}
)
  • 主要是在find_package位置添加了message_generation,这个是catkin_make编译所需要的依赖;然后是add_message_files位置添加我们的消息文件temp.msg;再然后是 generate_messages话题消息编译输出。我们可以来编译并生效一下环境变量,然后通过rosmsg指令查看一下当前功能包的话 题有哪些。
rosmsg show temp_value/temp




接下来我们进入temp_value的scripts文件夹,创建一个发布节点temp_pub.py。代码 内容如下。


#!/usr/bin/env python
import rospy 
from temp_value.msg import temp #导入自定义消息模块

from pyfirmata import Arduino,util 

board = Arduino('/dev/ttyUSB0')

it = util.Iterator(board) 
it.start() 
board.analog[0].enable_reporting()

def talker(): 
    pub = rospy.Publisher('temperature', temp, queue_size=10) #实例化发布节点 
    rospy.init_node('temp_publisher', anonymous=True) 初始化发布节点 
    rate = rospy.Rate(2) #发布频率为2Hz,即一秒发送两次 

    while not rospy.is_shutdown(): 
        pub_temp = board.analog[0].read() 
        if pub_temp == 'None': #可能出现None的情况,数据错误默认为0 
        pub_temp = 0
        rospy.loginfo(pub_temp) #打印输出 
        pub.publish(pub_temp) #发布 
        rate.sleep() 

if __name__ == '__main__': 
    try: 
        talker() 
    except rospy.ROSInterruptException: 
        board.exit() #释放Arduino

我们编译并生效环境变量之后,启动ROS Master节点管理器和pub_temp.py发布节点。

roscore 
rosrun temp_value pub_temp.py

我们看到的是这样的。





我们rostopic list查看下当前的话题。rostopic list





然后启动rqt折线图工具rqt_plot,你看到的会是这样的。

rqt_plot




我们在左上角输入要显示的话题名称,我们目前的是temperature。





选择后点击+号,即可显示。





哈哈,数值比较比较小,Firmata在转换的时候是给转换成电压数值的。趁热打铁,我 们来看下节点关系图。

rqt_graph




  • 这就是我们ROS+Python实现自定义话题的一个步骤,我现在写的是温度数据,那么 是不是也可以写入其他的数据呢?比如MPU6050传感器,有兴趣的小伙伴可以来挑战一下 自我,加油!
  • 我们关于话题通讯的内容到这里就结束了,其实话题通讯并不复杂,主要是你的代码基 础一定要牢固,结合使用时关键。接下来看我们的服务通讯机制。

服务通讯

  • 话题通讯只是单纯的发布一个数据或者订阅一个数据,可能你发布的数据没有订阅节点 来收取,又或者是你订阅到的消息已经是很久很久以前的数据,但是服务通讯绝对不会出现 这样的情况。




  • 服务通讯,顾名思义就是我一定要确保这个数据有人得到了,并且这个人他要告诉我他 收到设个数据。我们前三步还是建立连接,但是在数据交互的第四步、第五步位置,有发必 有回。一个请求,对应一个应答。一个request,对应一个response。
  • 我们在这里一加法为例,实现一个ROS+Python的加法计算器。我们需要在catkin_ws/src路径下建立add_python功能包,并且创建scripts文件夹和srv文件夹。其中scripts文件夹存放我们的Python代码,srv文件夹存放我们的服务文件。
catkin_create_pkg add_python rospy roscpp std_msgs




我们需要在srv文件夹下创建服务文件add.srv,并输入以下内容。

int64 A 
int64 B 
‐‐‐ 
int64 Sum
  • 其中的A和B是我们发起的请求数据,当请求数据被服务节点获取之后,服务节点会执 行一个相加的数据Sum返回给我们。这就是服务,一对一,确保消息一定送到。接下来我们要修改package.xml和CMakeList.txt文件,内容如下。 首先是我们的package.xml文件。
<?xml version="1.0"?>
<package format="2"> 
<name>add_python</name> 
<version>0.0.0</version> 
<description>The add_python package</description> 
<maintainer email="1692697820@qq.com">waveshare</maintainer> 
<license>TODO</license>

<buildtool_depend>catkin</buildtool_depend> 

<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend> 

<build_depend>message_generation</build_depend> 

<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend> 
<build_export_depend>std_msgs</build_export_depend> 

<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend> 
<exec_depend>std_msgs</exec_depend>

<exec_depend>message_runtime</exec_depend>

<export>
</export>
</package>

然后是我们的CMakeList.txt文件,内容如下。

cmake_minimum_required(VERSION 3.0.2)

project(add_python) 

find_package(catkin REQUIRED COMPONENTS 
roscpp
rospy 
std_msgs 
message_generation 
) 

add_service_files( 
FILES 
add.srv 
) 

generate_messages( 
DEPENDENCIES 
std_msgs
) 

catkin_package( 
# INCLUDE_DIRS include 
# LIBRARIES add_python
CATKIN_DEPENDS roscpp rospy std_msgs 
# DEPENDS system_lib 
)

include_directories( 
# include
${catkin_INCLUDE_DIRS} 
)
  • 主要变化是在add_service_files位置添加add.srv文件即可,其余照常。我们编译并生效环境变量,查看一下当前功能包的服务消息。
rossrv show add_python/add




  • 到这个位置,我们的服务文件就做好了,接下来在scripts下写一个服务节点和一个客 户节点。我们先来设计服务节点,接入scripts路径下,创建server.py文件,并赋予可执行权限。




server.py内容如下

#!/usr/bin/env python3

from add_python.srv import * #导入服务功能包
import rospy
def add_int(req): 
#从req获得数据
print("Returning [%s + %s = %s]" % (req.A, req.B, (req.A + req.B)))

Sum = req.A + req.B 
return addResponse(Sum) #返回计算结果

def add_server(): 
    rospy.init_node('add_server') #初始化节点 
    s = rospy.Service('add_server', add, add_int) #实例化服务,服务中断执行函 数为add_int() 
    print("Ready to add Two Ints") 
    rospy.spin() 

if __name__ == "__main__": 
    add_server()

我们可以先编译并生效以下环境变量,然后启动ROS Master节点管理器和server.py 服务节点。

roscore 
rosrun add_python server.py




我们可以通过rosservice工具来查看服务的相关信息,就像rostopic那样。

rosservice list




可以看到add_server服务已经启动。我们使用rosservice args来查看下add_server的 参数。

rosservice args /add_server

最后我们来试一试这个服务通讯好用不好用。

rosservice call /add_server 15 21




可以看到在server.py服务节点得到了计算的过程,而在我们的客户节点则是直接得到 了数值。那么客户节点该怎么写呢?参考代码如下。

#!/usr/bin/env python 

import sys
import os 

import rospy 
from add_python.srv import * #导入服务功能包 
def add_client(x, y):

    rospy.wait_for_service('add_server') #等待接入服务 

    try: 
        add_two_ints = rospy.ServiceProxy('add_server', add) #客户端实例化 
        print "Requesting %s+%s"%(x, y) 
        resp = add_two_ints.call(addRequest(x, y)) #向server传递数据并获得反馈数据
        #验证反馈结果是否正确
        if not resp.Sum == (x + y): 
            raise Exception("test failure, returned sum was %s"%resp.Sum) 
            return resp.Sum

    except rospy.ServiceException, e: 
        print "Service call failed: %s"%e 

    def usage():

        return "%s [x y]"%sys.argv[0]

if __name__ == "__main__": 

    argv = rospy.myargv() 
    if len(argv) == 1: 
        import random 
        x = random.randint(‐50000, 50000)
        y = random.randint(‐50000, 50000) 
    elif len(argv) == 3: 
        try: 
            x = int(argv[1]) 
            y = int(argv[2]) 
        except:
            print usage() 
            sys.exit(1)
    else:
        print usage()
        sys.exit(1) 
    print "%s + %s = %s"%(x, y, add_client(x, y))

运行结果会是这样的。





可以看到和我们上面测试的功能是一样的。这就是服务通讯,有发必有回,有求必有答。

话题通讯和服务通讯对比





  • 这是我对话题通讯和服务通讯的一个对比认知。关于ROS话题通讯和服务通讯的计数到 这里暂时告一段落,打击自可以去对比B/S和C/S架构、Socket、请求应答,尤其是MQTT 通讯模式,它们和ROS之间是存在着共性的,这就是通讯。

24

顶一下

刚表态过的朋友 (24 人)

关键词ROS 机器人

最新评论

微雪官网|产品资料|手机版|小黑屋|微雪课堂. ( 粤ICP备05067009号 )

GMT+8, 2020-12-4 04:12 , Processed in 0.019744 second(s), 13 queries .