蛋皮のhome

从此唯行乐,闲愁奈我何。

ROS系列(一)——初步探索ROS以及turtlebot3_teleop_key源码解析

ROS机器人是上个月就到了的,但之前一直在处于无从下手的边缘,因为里面涉及到众多尚不熟悉的领域。目前已经把turtlebot3_teleop_key的源码初步研究下已了解差不多。希望下周可以把建图的那一块搞出突破口。

ROS主要可供参考的网址:
1. ROBOTIS Turtlebot3
2. ROS Tutorials
还有一个中文网址:
创客智造—Turtlebot3

中文网址仅供英文不好的童鞋参考,但里面可能会有一些问题(至少我发现了一个问题),尽量还是以英文官网为主,否则出了错误又要满世界搜索。

ROS是一个开源的操作系统,从底层的硬件到上层的包管理都提供了支持与服务,这也意味着所有的东西都要做到基本了解,关键部分需要熟悉与掌握才能做到二次开发。

作为突破口,我先从看起来比较简单的turtlebot3_teleop_key部分下手,该代码的路径位于
~/catkin_ws/src/turtlebot3/turtlebot3_teleop/notes/
以下为带上我写注释的源码,如有错误,敬请指出。

#!/usr/bin/env python

import rospy
from geometry_msgs.msg import Twist
import sys, select, os
if os.name == 'nt':
  import msvcrt
else:
  import tty, termios

# burger型小车
BURGER_MAX_LIN_VEL = 0.22 # 直线运动最大速度
BURGER_MAX_ANG_VEL = 2.84 # 旋转运动最大角速度

# waffle和waffle_pi型小车
WAFFLE_MAX_LIN_VEL = 0.26 # 直线运动最大速度
WAFFLE_MAX_ANG_VEL = 1.82 # 旋转运动最大角速度

LIN_VEL_STEP_SIZE = 0.01 # 直线运动的匀速步进范围
ANG_VEL_STEP_SIZE = 0.1  # 旋转运动的角速步进范围

msg = """
Control Your TurtleBot3!
---------------------------
Moving around:
        w
   a    s    d
        x

w/x : increase/decrease linear velocity (Burger : ~ 0.22, Waffle and Waffle Pi : ~ 0.26)
a/d : increase/decrease angular velocity (Burger : ~ 2.84, Waffle and Waffle Pi : ~ 1.82)

space key, s : force stop

CTRL-C to quit
"""

e = """
Communications Failed
"""

#读取键盘数据
def getKey():
    if os.name == 'nt':
      return msvcrt.getch()

    tty.setraw(sys.stdin.fileno())
    rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
    if rlist:
        key = sys.stdin.read(1)# 读取一位键值
    else:
        key = ''

    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
    return key

def vels(target_linear_vel, target_angular_vel): # 显示输出状态信息
    return "currently:\tlinear vel %s\t angular vel %s " % (target_linear_vel,target_angular_vel)

def makeSimpleProfile(output, input, slop):  # 保证每次赋给小车使能的速度值是渐进的,而不是跳跃式的
    if input > output:
        output = min( input, output + slop ) # 渐进的幅值为二分之一步长
    elif input < output:
        output = max( input, output - slop )
    else:
        output = input

    return output

def constrain(input, low, high):   #当前值,最小值,最大值
    if input < low:
      input = low
    elif input > high:
      input = high
    else:
      input = input

    return input

def checkLinearLimitVelocity(vel):
    if turtlebot3_model == "burger":
      vel = constrain(vel, -BURGER_MAX_LIN_VEL, BURGER_MAX_LIN_VEL)   #保证值在设置约束的范围内
    elif turtlebot3_model == "waffle" or turtlebot3_model == "waffle_pi":
      vel = constrain(vel, -WAFFLE_MAX_LIN_VEL, WAFFLE_MAX_LIN_VEL)
    else:
      vel = constrain(vel, -BURGER_MAX_LIN_VEL, BURGER_MAX_LIN_VEL)

    return vel

def checkAngularLimitVelocity(vel):
    if turtlebot3_model == "burger":
      vel = constrain(vel, -BURGER_MAX_ANG_VEL, BURGER_MAX_ANG_VEL)
    elif turtlebot3_model == "waffle" or turtlebot3_model == "waffle_pi":
      vel = constrain(vel, -WAFFLE_MAX_ANG_VEL, WAFFLE_MAX_ANG_VEL)
    else:
      vel = constrain(vel, -BURGER_MAX_ANG_VEL, BURGER_MAX_ANG_VEL)

    return vel

if __name__=="__main__":
    if os.name != 'nt':
        settings = termios.tcgetattr(sys.stdin)

    # ROS初始化
    rospy.init_node('turtlebot3_teleop')
    # 创建一个publisher,并发布一个名为cmd_vel的topic,消息类型为Twist,队列(缓冲区)长度为10
    pub = rospy.Publisher('cmd_vel', Twist, queue_size=10)

    # 选择小车
    turtlebot3_model = rospy.get_param("model", "burger")

    status = 0
    target_linear_vel   = 0.0  # 直线前进速度
    target_angular_vel  = 0.0  # 旋转角速度
    control_linear_vel  = 0.0  # 直线控制速度
    control_angular_vel = 0.0  # 角度控制速度

    try:
        print(msg)
        while(1):
            key = getKey()# 读取键值
            if key == 'w' :
                target_linear_vel = checkLinearLimitVelocity(target_linear_vel + LIN_VEL_STEP_SIZE)
                status = status + 1
                print(vels(target_linear_vel,target_angular_vel))
            elif key == 'x' :
                target_linear_vel = checkLinearLimitVelocity(target_linear_vel - LIN_VEL_STEP_SIZE)
                status = status + 1
                print(vels(target_linear_vel,target_angular_vel))
            elif key == 'a' :
                target_angular_vel = checkAngularLimitVelocity(target_angular_vel + ANG_VEL_STEP_SIZE)
                status = status + 1
                print(vels(target_linear_vel,target_angular_vel))
            elif key == 'd' :
                target_angular_vel = checkAngularLimitVelocity(target_angular_vel - ANG_VEL_STEP_SIZE)
                status = status + 1
                print(vels(target_linear_vel,target_angular_vel))
            elif key == ' ' or key == 's' :
                target_linear_vel   = 0.0
                control_linear_vel  = 0.0
                target_angular_vel  = 0.0
                control_angular_vel = 0.0
                print(vels(target_linear_vel, target_angular_vel))
            else:
                if (key == '\x03'): # Ctrl + C
                    break

            if status == 20 :
                print(msg)
                status = 0

            twist = Twist()# 通过参数控制电机

            # 根据当前的速度值对设置的速度参数进行调整
            control_linear_vel = makeSimpleProfile(control_linear_vel, target_linear_vel, (LIN_VEL_STEP_SIZE/2.0))
            # 直线控制
            twist.linear.x = control_linear_vel; twist.linear.y = 0.0; twist.linear.z = 0.0

            control_angular_vel = makeSimpleProfile(control_angular_vel, target_angular_vel, (ANG_VEL_STEP_SIZE/2.0))
            # 角度控制
            twist.angular.x = 0.0; twist.angular.y = 0.0; twist.angular.z = control_angular_vel
            # 发布消息
            pub.publish(twist)

    except:
        print(e)

    finally:# 停止运转
        twist = Twist()
        twist.linear.x = 0.0; twist.linear.y = 0.0; twist.linear.z = 0.0
        twist.angular.x = 0.0; twist.angular.y = 0.0; twist.angular.z = 0.0
        pub.publish(twist)

    if os.name != 'nt':
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)

通过观看代码可以基本了解到,整个发布的过程为先初始化,然后循环读取键值来修改参数,参数主要分为直线运动速度(target_linear_vel)和角速度(target_angular_vel),这两个参数由终端读取键值并直接设置到该参数上,但作用于小车上的值并非这两个参数,而是另外定义了两个控制的值:control_linear_vel和control_angular_vel。中间额外定义的两个值主要为小车的过渡设置,以防止参数骤增或骤减导致小车出现故障,个人猜测是延长电机使用寿命,且更安全。具体的设置的方式见makeSimpleProfile函数。同时,对控制参数设置了一个最大值。本来我以为是参数设置过大会造成小车电机转速过快而不安全,但后面更改了最大值发现小车电机运转效果与修改前相当,那么默认的值估计便已经是最大的值了。

根据对以上源码的理解,我修改了代码增加了方向键的控制并修改了按键驱动逻辑。因为才刚刚接触python,正好拿来练练手。值得一提的是,因为python是解释器,调试起来感觉比C语言之类的编译器要快得多,也是一种全新的调试感受。

下面附上运行效果和源码,我就不详细写注释了,上面那个代码看懂了这个应该也能看懂。主要需要注意的是方向键的读取逻辑,因为任意方向键按下去一次就会接受三个字节的数据,并且不同的方向键前两个字节都一样,所以读取完第一个字节后需要再读取两次字节,并在最后一个字节确认是方向键中的哪一个键。

#!/usr/bin/env python

import rospy
from geometry_msgs.msg import Twist
import sys, select, os
if os.name == 'nt':
  import msvcrt
else:
  import tty, termios

BURGER_MAX_LIN_VEL = 0.42
BURGER_MAX_ANG_VEL = 4.84

WAFFLE_MAX_LIN_VEL = 0.26
WAFFLE_MAX_ANG_VEL = 1.82

LIN_VEL_STEP_SIZE = 0.01
ANG_VEL_STEP_SIZE = 0.1

msg = """
Control Your TurtleBot3!
---------------------------
Moving around:
        w                        ^
   a    s    d              <    v    >

w/s : increase/decrease linear velocity (Burger : ~ 0.22, Waffle and Waffle Pi : ~ 0.26)
a/d : increase/decrease angular velocity (Burger : ~ 2.84, Waffle and Waffle Pi : ~ 1.82)

space key, Enter(space) : force stop

CTRL-C to quit
"""

e = """
Communications Failed
"""

def getKey():
    if os.name == 'nt':
      return msvcrt.getch()

    tty.setraw(sys.stdin.fileno())
    rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
    if rlist:
        key = sys.stdin.read(1)
    else:
        key = ''

    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
    return key

def vels(target_linear_vel, target_angular_vel):
    return "currently:\tlinear vel %s  \tangular vel %s " % (target_linear_vel,target_angular_vel)

def makeSimpleProfile(output, input, slop):
    if input > output:
        output = min( input, output + slop )
    elif input < output:
        output = max( input, output - slop )
    else:
        output = input

    return output

def constrain(input, low, high):
    if input < low:
      input = low
    elif input > high:
      input = high
    elif input > 0.0001:
      input = input
    elif input < -0.0001:
      input = input
    else :
      input = 0.0

    return input

def checkLinearLimitVelocity(vel):
    if turtlebot3_model == "burger":
      vel = constrain(vel, -BURGER_MAX_LIN_VEL, BURGER_MAX_LIN_VEL)
    elif turtlebot3_model == "waffle" or turtlebot3_model == "waffle_pi":
      vel = constrain(vel, -WAFFLE_MAX_LIN_VEL, WAFFLE_MAX_LIN_VEL)
    else:
      vel = constrain(vel, -BURGER_MAX_LIN_VEL, BURGER_MAX_LIN_VEL)

    return vel

def checkAngularLimitVelocity(vel):
    if turtlebot3_model == "burger":
      vel = constrain(vel, -BURGER_MAX_ANG_VEL, BURGER_MAX_ANG_VEL)
    elif turtlebot3_model == "waffle" or turtlebot3_model == "waffle_pi":
      vel = constrain(vel, -WAFFLE_MAX_ANG_VEL, WAFFLE_MAX_ANG_VEL)
    else:
      vel = constrain(vel, -BURGER_MAX_ANG_VEL, BURGER_MAX_ANG_VEL)

    return vel

if __name__=="__main__":
    if os.name != 'nt':
        settings = termios.tcgetattr(sys.stdin)

    rospy.init_node('turtlebot3_teleop')
    pub = rospy.Publisher('cmd_vel', Twist, queue_size=10)

    turtlebot3_model = rospy.get_param("model", "burger")

    status = 0
    target_linear_vel   = 0.0
    target_angular_vel  = 0.0
    control_linear_vel  = 0.0
    control_angular_vel = 0.0

    first_linear_vel    = 0.1
    first_angular_vel   = 1.0

    run    = False;
    runnum = 0;

    try:
        print(msg)
        while(1):
            key = getKey()
            # if key != '':
            	# print('key = %s', key)
            if key == 'w' :
                first_linear_vel = checkLinearLimitVelocity(first_linear_vel + LIN_VEL_STEP_SIZE)
                status = status + 1
                print(vels(first_linear_vel,first_angular_vel))
            elif key == 's' :
                first_linear_vel = checkLinearLimitVelocity(first_linear_vel - LIN_VEL_STEP_SIZE)
                status = status + 1
                print(vels(first_linear_vel,first_angular_vel))
            elif key == 'a' :
                first_angular_vel = checkAngularLimitVelocity(first_angular_vel + ANG_VEL_STEP_SIZE)
                status = status + 1
                print(vels(first_linear_vel,first_angular_vel))
            elif key == 'd' :
                first_angular_vel = checkAngularLimitVelocity(first_angular_vel - ANG_VEL_STEP_SIZE)
                status = status + 1
                print(vels(first_linear_vel,first_angular_vel))
            elif key == ' ' or key == '\r' :
                target_linear_vel   = 0.0
                control_linear_vel  = 0.0
                target_angular_vel  = 0.0
                control_angular_vel = 0.0
                status = status + 1
                print(vels(target_linear_vel, target_angular_vel))
            elif key != '':
                if (key == '\x03'):   # ctrl+c
                    break
                elif (key == '\x1b'):  # direction
                    key = sys.stdin.read(1)
                    if (key == '[') :
                        key = sys.stdin.read(1)
                        if (key == 'A') :
                            if run == False:
                                run = True
                                target_linear_vel = first_linear_vel
                                status = status + 1
                                runnum = 0;
                                print(vels(target_linear_vel,target_angular_vel) + '\tup')
                                # print('upOK')
                        elif (key == 'B') :
                            if run == False:
                                run = True
                                target_linear_vel = -first_linear_vel
                                status = status + 1
                                runnum = 0;
                                print(vels(target_linear_vel,target_angular_vel) + '\tdown')
                                # print('downOK')
                        elif (key == 'D') :
                            if run == False:
                                run = True
                                target_angular_vel = first_angular_vel
                                status = status + 1
                                runnum = 0;
                                print(vels(target_linear_vel,target_angular_vel) + '\tleft')
                                # print('leftOK')
                        elif (key == 'C') :
                            if run == False:
                                run = True
                                target_angular_vel = -first_angular_vel
                                status = status + 1
                                runnum = 0;
                                print(vels(target_linear_vel,target_angular_vel) + '\tright')
                                # print('rightOK')
            elif run == True:
                if runnum < 4:
                    runnum = runnum + 1
                else :
                    target_linear_vel   = 0.0
                    control_linear_vel  = 0.0
                    target_angular_vel  = 0.0
                    control_angular_vel = 0.0
                    run = False
                    status = status + 1
                    print(vels(target_linear_vel, target_angular_vel))

            if status >= 20 :
                print(msg)
                status = 0

            twist = Twist()

            control_linear_vel = makeSimpleProfile(control_linear_vel, target_linear_vel, (LIN_VEL_STEP_SIZE/2.0))
            twist.linear.x = control_linear_vel; twist.linear.y = 0.0; twist.linear.z = 0.0

            control_angular_vel = makeSimpleProfile(control_angular_vel, target_angular_vel, (ANG_VEL_STEP_SIZE/2.0))
            twist.angular.x = 0.0; twist.angular.y = 0.0; twist.angular.z = control_angular_vel

            pub.publish(twist)

    except:
        print(e)

    finally:
        twist = Twist()
        twist.linear.x = 0.0; twist.linear.y = 0.0; twist.linear.z = 0.0
        twist.angular.x = 0.0; twist.angular.y = 0.0; twist.angular.z = 0.0
        pub.publish(twist)

    if os.name != 'nt':
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)

昨天刚看了古月老师的21讲视频,参考了第10讲的发布者Publisher的编程实现。看完后今天便想尝试一下将原来的python语言写的turtlebot3_teleop_key部分用C/C++语言重写一遍。拿来练手,也加深一下Publisher实现过程的印象。经过反复的调试,下面附上运行图和代码为了区分开来,文件名被我定义为turtlebot3_teleop_ckey.cpp。代码看起来和python差不多,也是有一点点的区别的。同时并不像python解释器那样写好便可解释执行查看运行效果,需要catkin_make编译后才可以执行。同时在首次编译之前还需要添加修改CMakeLists.txt和package.xml两个文件中的内容,具体可以看古月老师讲但的视频:链接,或者看官方教程:链接

值得一提的是,虽然用C/C++语言调试过程麻烦一些,但是应用执行的效率比python快很多,用python写的代码在执行后的初始化需要等待一两秒,而用C/C++语言开发的代码从开始启动不到一秒便已经初始化完成。

#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <stdio.h>
#include <termio.h>
#include <string>

#define BURGER_MAX_LIN_VEL   0.22
#define BURGER_MAX_ANG_VEL   2.84

#define WAFFLE_MAX_LIN_VEL   0.26
#define WAFFLE_MAX_ANG_VEL   1.82

#define LIN_VEL_STEP_SIZE    0.01
#define ANG_VEL_STEP_SIZE    0.1

using namespace std;

int getch();
float constrain(float input, float low, float high);
float checkLinearLimitVelocity(float vel);
float checkAngularLimitVelocity(float vel);
float makeSimpleProfile(float output, float input, float slop);

char msg[] = "\nControl Your TurtleBot3!\n\
---------------------------\n\
Moving around:\n\
        w\n\
   a    s    d\n\
        x\n\
\n\
w/x : increase/decrease linear velocity (Burger : ~ 0.22, Waffle and Waffle Pi : ~ 0.26)\n\
a/d : increase/decrease angular velocity (Burger : ~ 2.84, Waffle and Waffle Pi : ~ 1.82)\n\
\n\
space key, s : force stop\n\
\n\
CTRL-C to quit\n\n";
string turtlebot3_model;

int main(int argc, char **argv)
{
  ros::init(argc, argv, "turtlebot3_teleop");
  ros::NodeHandle n;
  ros::Publisher pub = n.advertise<geometry_msgs::Twist>("cmd_vel", 10);
  turtlebot3_model = "burger";

  int status = 0;
  float target_linear_vel   = 0.0;
  float target_angular_vel  = 0.0;
  float control_linear_vel  = 0.0;
  float control_angular_vel = 0.0;

  printf("%s", msg);
  bool kda = true;

  while(kda)
  {
    int key = getch();
    //printf("key = %d\n", key);
    switch (key)
    {
      case 119:	//w
      {
        target_linear_vel = checkLinearLimitVelocity(target_linear_vel + LIN_VEL_STEP_SIZE);
        status++;
        ROS_INFO("currently:\tlinear vel %.2f\t   angular vel %.2f", target_linear_vel, target_angular_vel);
        break;
      }
      case 120:	//x
      {
        target_linear_vel = checkLinearLimitVelocity(target_linear_vel - LIN_VEL_STEP_SIZE);
        status++;
        ROS_INFO("currently:\tlinear vel %.2f\t   angular vel %.2f", target_linear_vel, target_angular_vel);
        break;
      }
      case  97:	//a
      {
        target_angular_vel = checkAngularLimitVelocity(target_angular_vel + ANG_VEL_STEP_SIZE);
        status++;
        ROS_INFO("currently:\tlinear vel %.2f\t   angular vel %.2f", target_linear_vel, target_angular_vel);
        break;
      }
      case 100:	//d
      {
        target_angular_vel = checkAngularLimitVelocity(target_angular_vel - ANG_VEL_STEP_SIZE);
        status++;
        ROS_INFO("currently:\tlinear vel %.2f\t   angular vel %.2f", target_linear_vel, target_angular_vel);
        break;
      }
      case 115:	//s
      {
        target_linear_vel   = 0.0;
        target_angular_vel  = 0.0;
        control_linear_vel  = 0.0;
        control_angular_vel = 0.0;
        status++;
        ROS_INFO("currently:\tlinear vel %.2f\t   angular vel %.2f", target_linear_vel, target_angular_vel);
        break;
      }
      case 13: break; //Enter
      case 32: 	//space
      {
        target_linear_vel   = 0.0;
        target_angular_vel  = 0.0;
        control_linear_vel  = 0.0;
        control_angular_vel = 0.0;
        status++;
        ROS_INFO("currently:\tlinear vel %.2f\t   angular vel %.2f", target_linear_vel, target_angular_vel);
        break;
      }
      case 27: 		//Esc
        {
          key = getch();
          if (key == 91)
          {
            key = getch();
            if (key == 65) //^
            {
              printf("^\n");
            }
            else if (key == 66) //v
            {
              printf("v\n");
            }
            else if (key == 68) //<
            {
              printf("<\n");
            }
            else if (key == 67) //>
            {
              printf(">\n");
            }
          }
          else
          {
            printf("Esc\n");
          }
          break;
        }
      case 3: 	//Ctrl + c
      {
        target_linear_vel   = 0.0;
        target_angular_vel  = 0.0;
        control_linear_vel  = 0.0;
        control_angular_vel = 0.0;
        kda = false;
        break;
      }
    }
    if (status >= 20)
    {
      printf("%s", msg);
      status = 0;
    }

    geometry_msgs::Twist twist;
    control_linear_vel = makeSimpleProfile(control_linear_vel, target_linear_vel, LIN_VEL_STEP_SIZE/2.0);
    control_angular_vel = makeSimpleProfile(control_angular_vel, target_angular_vel, ANG_VEL_STEP_SIZE/2.0);
    twist.linear.x = control_linear_vel;
    twist.linear.y = 0.0;
    twist.linear.z = 0.0;
    twist.angular.x = 0.0;
    twist.angular.y = 0.0;
    twist.angular.z = control_angular_vel;

    pub.publish(twist);
  }
  
  return 0;
}

float constrain(float input, float low, float high)
{
  if (input < low)
  {
    input = low;
  }
  else if (input > high)
  {
    input = high;
  }
  else
  {
    input = input;
  }
  return input;
}

float makeSimpleProfile(float output, float input, float slop)
{
  if (input > output)
  {
    if (input < output + slop)
    {
      output = input;
    }
    else
    {
      output = output + slop;
    }
  }
  else if (input < output) 
  {
    if (input > output - slop)
    {
      output = input;
    }
    else
    {
      output = output - slop;
    }
  }
  else
  {
    output = input;
  }
  return output;
}

float checkLinearLimitVelocity(float vel)
{
  if (turtlebot3_model == "burger")
  {
    vel = constrain(vel, -BURGER_MAX_LIN_VEL, BURGER_MAX_LIN_VEL);
  }
  else if (turtlebot3_model == "waffle" || turtlebot3_model == "waffle_pi")
  {
    vel = constrain(vel, -WAFFLE_MAX_LIN_VEL, WAFFLE_MAX_LIN_VEL);
  }
  else
  {
    vel = constrain(vel, -BURGER_MAX_LIN_VEL, BURGER_MAX_LIN_VEL);
  }
  return vel;
}

float checkAngularLimitVelocity(float vel)
{
  if (turtlebot3_model == "burger")
  {
    vel = constrain(vel, -BURGER_MAX_ANG_VEL, BURGER_MAX_ANG_VEL);
  }
  else if (turtlebot3_model == "waffle" || turtlebot3_model == "waffle_pi")
  {
    vel = constrain(vel, -WAFFLE_MAX_ANG_VEL, WAFFLE_MAX_ANG_VEL);
  }
  else
  {
    vel = constrain(vel, -BURGER_MAX_ANG_VEL, BURGER_MAX_ANG_VEL);
  }
  return vel;
}

int getch()
{
  struct termios tm, tm_old;
    int fd = 0, ch;
 
    if (tcgetattr(fd, &tm) < 0) {//保存现在的终端设置
        return -1;
    }
 
    tm_old = tm;
    cfmakeraw(&tm);//更改终端设置为原始模式,该模式下所有的输入数据以字节为单位被处理
    if (tcsetattr(fd, TCSANOW, &tm) < 0) {//设置上更改之后的设置
        return -1;
    }
 
    ch = getchar();
    if (tcsetattr(fd, TCSANOW, &tm_old) < 0) {//更改设置为最初的样子
        return -1;
    }
    
    return ch;
}

Next Article

发表评论

Your email address will not be published. Required fields are marked *.

*
*
You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>