diff --git a/CMakeLists.txt b/CMakeLists.txt index 227c830..a1f2449 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,21 +5,29 @@ find_package(catkin REQUIRED COMPONENTS roscpp serial std_msgs + sensor_msgs + message_generation ) catkin_package( CATKIN_DEPENDS serial std_msgs + sensor_msgs + message_runtime ) + include_directories( ${catkin_INCLUDE_DIRS} ) add_executable(serial_example_node src/serial_example_node.cpp) +add_executable(listener src/listener.cpp) target_link_libraries(serial_example_node ${catkin_LIBRARIES} ) - +target_link_libraries(listener + ${catkin_LIBRARIES} +) diff --git a/package.xml b/package.xml index dea94c4..0c14914 100644 --- a/package.xml +++ b/package.xml @@ -11,9 +11,19 @@ BSD catkin + serial std_msgs + sensor_msgs + geometry_msgs + message_generation + serial std_msgs + sensor_msgs + geometry_msgs + message_runtime + + diff --git a/src/algorithm_pkg/CMakeLists.txt b/src/algorithm_pkg/CMakeLists.txt new file mode 100644 index 0000000..c7b0c5a --- /dev/null +++ b/src/algorithm_pkg/CMakeLists.txt @@ -0,0 +1,206 @@ +cmake_minimum_required(VERSION 3.0.2) +project(algorithm_pkg) + +## Compile as C++11, supported in ROS Kinetic and newer +# add_compile_options(-std=c++11) + +## Find catkin macros and libraries +## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) +## is used, also find other catkin packages +find_package(catkin REQUIRED COMPONENTS + roscpp + rospy + std_msgs +) + +## System dependencies are found with CMake's conventions +# find_package(Boost REQUIRED COMPONENTS system) + + +## Uncomment this if the package has a setup.py. This macro ensures +## modules and global scripts declared therein get installed +## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html +# catkin_python_setup() + +################################################ +## Declare ROS messages, services and actions ## +################################################ + +## To declare and build messages, services or actions from within this +## package, follow these steps: +## * Let MSG_DEP_SET be the set of packages whose message types you use in +## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). +## * In the file package.xml: +## * add a build_depend tag for "message_generation" +## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET +## * If MSG_DEP_SET isn't empty the following dependency has been pulled in +## but can be declared for certainty nonetheless: +## * add a exec_depend tag for "message_runtime" +## * In this file (CMakeLists.txt): +## * add "message_generation" and every package in MSG_DEP_SET to +## find_package(catkin REQUIRED COMPONENTS ...) +## * add "message_runtime" and every package in MSG_DEP_SET to +## catkin_package(CATKIN_DEPENDS ...) +## * uncomment the add_*_files sections below as needed +## and list every .msg/.srv/.action file to be processed +## * uncomment the generate_messages entry below +## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) + +## Generate messages in the 'msg' folder +# add_message_files( +# FILES +# Message1.msg +# Message2.msg +# ) + +## Generate services in the 'srv' folder +# add_service_files( +# FILES +# Service1.srv +# Service2.srv +# ) + +## Generate actions in the 'action' folder +# add_action_files( +# FILES +# Action1.action +# Action2.action +# ) + +## Generate added messages and services with any dependencies listed here +# generate_messages( +# DEPENDENCIES +# std_msgs +# ) + +################################################ +## Declare ROS dynamic reconfigure parameters ## +################################################ + +## To declare and build dynamic reconfigure parameters within this +## package, follow these steps: +## * In the file package.xml: +## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" +## * In this file (CMakeLists.txt): +## * add "dynamic_reconfigure" to +## find_package(catkin REQUIRED COMPONENTS ...) +## * uncomment the "generate_dynamic_reconfigure_options" section below +## and list every .cfg file to be processed + +## Generate dynamic reconfigure parameters in the 'cfg' folder +# generate_dynamic_reconfigure_options( +# cfg/DynReconf1.cfg +# cfg/DynReconf2.cfg +# ) + +################################### +## catkin specific configuration ## +################################### +## The catkin_package macro generates cmake config files for your package +## Declare things to be passed to dependent projects +## INCLUDE_DIRS: uncomment this if your package contains header files +## LIBRARIES: libraries you create in this project that dependent projects also need +## CATKIN_DEPENDS: catkin_packages dependent projects also need +## DEPENDS: system dependencies of this project that dependent projects also need +catkin_package( +# INCLUDE_DIRS include +# LIBRARIES algorithm_pkg +# CATKIN_DEPENDS roscpp rospy std_msgs +# DEPENDS system_lib +) + +########### +## Build ## +########### + +## Specify additional locations of header files +## Your package locations should be listed before other locations +include_directories( +# include + ${catkin_INCLUDE_DIRS} +) + +## Declare a C++ library +# add_library(${PROJECT_NAME} +# src/${PROJECT_NAME}/algorithm_pkg.cpp +# ) + +## Add cmake target dependencies of the library +## as an example, code may need to be generated before libraries +## either from message generation or dynamic reconfigure +# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Declare a C++ executable +## With catkin_make all packages are built within a single CMake context +## The recommended prefix ensures that target names across packages don't collide +# add_executable(${PROJECT_NAME}_node src/algorithm_pkg_node.cpp) + +## Rename C++ executable without prefix +## The above recommended prefix causes long target names, the following renames the +## target back to the shorter version for ease of user use +## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" +# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") + +## Add cmake target dependencies of the executable +## same as for the library above +# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Specify libraries to link a library or executable target against +# target_link_libraries(${PROJECT_NAME}_node +# ${catkin_LIBRARIES} +# ) + +############# +## Install ## +############# + +# all install targets should use catkin DESTINATION variables +# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html + +## Mark executable scripts (Python etc.) for installation +## in contrast to setup.py, you can choose the destination +# catkin_install_python(PROGRAMS +# scripts/my_python_script +# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark executables for installation +## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html +# install(TARGETS ${PROJECT_NAME}_node +# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark libraries for installation +## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html +# install(TARGETS ${PROJECT_NAME} +# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} +# ) + +## Mark cpp header files for installation +# install(DIRECTORY include/${PROJECT_NAME}/ +# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} +# FILES_MATCHING PATTERN "*.h" +# PATTERN ".svn" EXCLUDE +# ) + +## Mark other files for installation (e.g. launch and bag files, etc.) +# install(FILES +# # myfile1 +# # myfile2 +# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +# ) + +############# +## Testing ## +############# + +## Add gtest based cpp test target and link libraries +# catkin_add_gtest(${PROJECT_NAME}-test test/test_algorithm_pkg.cpp) +# if(TARGET ${PROJECT_NAME}-test) +# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) +# endif() + +## Add folders to be run by python nosetests +# catkin_add_nosetests(test) diff --git a/src/algorithm_pkg/package.xml b/src/algorithm_pkg/package.xml new file mode 100644 index 0000000..69b4d69 --- /dev/null +++ b/src/algorithm_pkg/package.xml @@ -0,0 +1,68 @@ + + + algorithm_pkg + 0.0.0 + The algorithm_pkg package + + + + + sungjun-kim + + + + + + TODO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + catkin + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + + + + + + + + diff --git a/src/algorithm_pkg/scripts/algorithm_pkg_node.py b/src/algorithm_pkg/scripts/algorithm_pkg_node.py new file mode 100644 index 0000000..c861fa9 --- /dev/null +++ b/src/algorithm_pkg/scripts/algorithm_pkg_node.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import rospy +from sensor_msgs.msg import PointCloud +from std_msgs.msg import String +from scipy.optimize import leastsq +import numpy as np +import pprint +import matplotlib.pyplot as plt +import levenberg_marquardt as LM + +# 전역변수 선언 +zero_setting_flag = 0 # 4~5번 뒤에 offset을 작동시키기 위한 flag변수 선언 +#first_pos = np.array([0, -100, 40]) +first_pos = np.array([118, 118, 65]) +first_h = np.array([0.77, -0.06, 0.45]) +full_packet = "" # 패킷 값을 저장하는 리스트 변수 +sensor_data = [] # 해체작업을 진행할 패킷 값을 저장하는 리스트 변수 +packet_count = 0 # 분할되어 들어오는 패킷을 총 10번만 받게 하기 위해 카운트를 세는 변수 +is_collecting = False + +array_Val = np.array([ [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] ]) # sensor 값들의 numpy조작을 간편하게 하기 위해 + # 옮겨 저장할 배열 변수 선언 +zero_Val = np.array([ [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] ]) # offset을 위해 배경 노이즈값을 저장할 배열 변수 선언 + +P = np.array([ # hall sensor의 각 위치좌표 값 초기화(좌표 단위는 mm) + [-118, 118, 0], + [ 0, 118, 0], + [ 118, 118, 0], + [-118, 0, 0], + [ 0, 0, 0], ## 5번 센서로, 중앙이 되는 센서다 + [ 118, 0, 0], + [-118, -118, 0], + [ 0, -118, 0], + [ 118, -118, 0] ]) + +# 상수 선언 +MU0 = 4*(np.pi)*10**(-7) # 진공투자율[mT/A] +MU = 1.056 # 사용하는 자석의 투자율[mT/A] +MU_R = MU/MU0 # 사용하는 자석의 상대투자율[mT/A] +M0 = 1.012 # 사용하는 자석의 등급에 따른 값[T]: 실험에서 사용하는 자석 등급은 N42 +M_T = (np.pi)*(4.7625**2)*(12.7)*(1.012) # 자석의 자화벡터 값 = pi*(반지름^2)*(높이)*(자석 등급) + + + + + + + + + +# 함수 선언 + +# Serial_example_node.cpp를 통해 받은 패킷 값을 분해하는 함수 +def seperating_Packet(data): + global full_packet, packet_count, is_collecting, sensor_data + + # 'Z'로 시작하는 패킷을 감지하거나 이미 패킷 수집 중인 경우 + if data.data.startswith('Z') or is_collecting: + full_packet += data.data + packet_count += 1 + + # 패킷 수집 시작 + if data.data.startswith('Z'): + is_collecting = True + + # 10개의 패킷을 수집한 경우 + if packet_count == 10: + #print(full_packet) + print(parse_packet(full_packet)) + full_packet = "" # 패킷 초기화 + packet_count = 0 # 분할 패킷 카운트 초기화 + is_collecting = False # 분할 패킷 저장 flag 초기화 + + + + +#패킷 분해 함수에서 부호를 결정해주는 함수 +def parse_value(value_str): + sign = -1 if value_str[0] == '1' else 1 + return sign * int(value_str[1:]) + +# 2차원으로 저장되어 있는 리스트 변수 깔끔하게 출력해주는 함수 +def pretty_print(data): + for row in data: + print(" ".join("{:>10.2f}".format(x) for x in row)) + +# 본격적인 센서 패킷 값 분해 코드 +def parse_packet(packet): + if not packet.startswith("ZZ"): + return None + + raw_sum = 0 # 원본 센서 값들의 합 + sensors_data = [] + for char in range(ord('A'), ord('I') + 1): + start_char = chr(char) + end_char = chr(char + 32) + start_idx = packet.find(start_char) + 1 + end_idx = packet.find(end_char) + sensor_str = packet[start_idx:end_idx] + + sensor_values = [parse_value(value) for value in sensor_str.split(',')] + raw_sum += sum(sensor_values) # 가공 전 원본 데이터 합산 + sensor_values[0] *= -1 + sensor_values = [v / 100.0 for v in sensor_values] + + sensors_data.append(sensor_values) + + checksum_str = packet[packet.find('i') + 1:packet.find('Y')] + checksum = parse_value(checksum_str) + + if raw_sum != checksum: + return None + + pretty_print(sensors_data) + #return sensors_data + + + + + +# 단순히 offset 설정을 위한 flag 변수 값을 조정하는 함수 +def callback_offset(data): + global zero_setting_flag + + zero_setting_flag = 0 + + +# 본격적인 node함수 +def callback1x(data): + global array_Val + global zero_Val + global zero_setting_flag + + # numpy를 사용하기 위해 list 값을 array로 이동 + for i in range(9): + array_Val[i][0] = data.points[i].x + array_Val[i][1] = data.points[i].y + array_Val[i][2] = data.points[i].z + + ''' + for i in range(9): # 9번 반복하면서 i번째 센서 값 출력 + print(round((data.points[i].x), 4), + round((data.points[i].y), 4), + round((data.points[i].z), 4)) + ''' + + #print('-----------------------------------------') + + + if (zero_setting_flag == 0): # flag변수가 0이면 offset 함수 호출 + zero_setting() + + offset_Setting() # offset 설정하는 함수 호출 + + + + +# offset을 위해 배경 노이즈 값을 리스트에 저장하는 함수 +def zero_setting(): + global array_Val + global zero_Val + global zero_setting_flag + + for i in range(9): # 9번 반복문 + zero_Val[i][0] = array_Val[i][0] + zero_Val[i][1] = array_Val[i][1] + zero_Val[i][2] = array_Val[i][2] # i번째 센서에서 측정하는 배경 노이즈 값을 저장 + + zero_setting_flag = 1 # 이후 1로 설정하여 함수가 호출되지 않도록 초기화 + # 이렇게 안 하면 센서 측정 값이 계속 0으로만 출력됨 + + + +# offset 계산하는 함수 +def offset_Setting(): + global array_Val + global zero_Val + global first_pos, first_h, differences + + h_val = np.array([0,0,0,0,0,0,0,0,0]) + + sensor_avg = array_Val.mean(axis=0) # 각 축 성분들의 평균 값 계산 + ''' + # 계산한 평균 값을 각 센서 값에서부터 뺀 후에 출력 + for i in range(9): # 9번 반복하면서 i번째 센서 값 출력 + array_Val[i][0]-sensor_avg[0] + array_Val[i][1]-sensor_avg[1] + array_Val[i][2]-sensor_avg[2] + ''' + + for i in range(9): # i번째 센서 배열에 배경 노이즈를 뺀 값을 저장 + array_Val[i][0] -= zero_Val[i][0]/1000 + array_Val[i][1] -= zero_Val[i][1]/1000 + array_Val[i][2] -= zero_Val[i][2]/1000 + + # 각 센서에서 측정한 자기장 값의 norm을 계산 + for i in range(9): + h_val[i] = np.sqrt( (array_Val[i][0] ** 2) + + (array_Val[i][1] ** 2) + + (array_Val[i][2] ** 2) ) + + #print(first_pos) + initial_guess_A = first_pos # 초기 자석의 위치좌표 값 + initial_guess_H = first_h # 초기 자석의 자계강도 값 + + result_pos = leastsq(residuals, initial_guess_A, args=(initial_guess_H)) + #print(result_pos[0]) + first_pos = np.array(result_pos[0]) + + +''' + for i in range(9): # 9번 반복하면서 i번째 센서 값 출력 + print(i+1, + round((array_Val[i][0]), 4), + round((array_Val[i][1]), 4), + round((array_Val[i][2]), 4), + h_val[i]) +''' + +# 자석의 자기밀도를 계산하는 함수 +# A: 자석의 현재 위치좌표, P: 센서의 위치좌표, H: 자석의 자계강도 +def cal_B(A, H, P): + global MU0, MU_R, M_T + N_t = (MU0 * MU_R * M_T) / (4*(np.pi)) # 상수항 계산 + b1 = (3 * np.dot(H, P.T) * P) / (np.linalg.norm(P-A) ** 5) # 첫째 항 계산 + b2 = H / (np.linalg.norm(P-A) ** 3) # 둘째 항 계산 + + final_B = N_t * (b1 - b2) # 최종 자기밀도 값 계산 + return final_B # 최종 자기밀도 값 반환 + + + +# 측정한 자기장 값과 계산한 자기장 값 사이의 차이를 계산하는 함수 +def residuals(init_pos, init_h): + global array_Val, P + differences = [0,0,0] # 센서 값과 계산 값 사이의 잔차 값을 저장하는 배열변수 선언 + for i in range(9): + buffer_residual = (array_Val[i] - (cal_B(init_pos, init_h, P[i]))) + #print(buffer_residual) + #print("----") + differences[0] += buffer_residual[0] + differences[1] += buffer_residual[1] + differences[2] += buffer_residual[2] + + print(differences) + return differences + + + + + + +# 메인 함수 +def main(): + + global array_Val + + + rospy.init_node('algorithm_pkg_node', anonymous=True) # 해당 노드의 기본 설정 + rospy.Subscriber('read', String, seperating_Packet) # /read를 구독하고 seperating_Packet 함수 호출 + rospy.Subscriber('local_sys', PointCloud, callback1x) # /local_sys를 구독하고 callback1x 함수 호출 + rospy.Subscriber('Is_offset', String, callback_offset) # /Is_offset을 구독하고 callback_offset 함수 호출 + + rospy.spin() # node 무한 반복 + + + + + + +if __name__ == '__main__': + main() # main문 무한 호출 + + +from sensor_msgs.msg import PointCloud \ No newline at end of file diff --git a/src/algorithm_pkg/scripts/levenberg_marquardt.py b/src/algorithm_pkg/scripts/levenberg_marquardt.py new file mode 100644 index 0000000..9fb32d4 --- /dev/null +++ b/src/algorithm_pkg/scripts/levenberg_marquardt.py @@ -0,0 +1,539 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Code adapted from Gavin, H.P. (2020) The Levenberg-Marquardt method for +# nonlinear least squares curve-fitting problems. +# https://people.duke.edu/~hpgavin/ce281/lm.pdf +# github: https://github.com/abnerbog/levenberg-marquardt-method/blob/main/example_LM.py + +''' +해당 .py파일에는 함수가 총 6가지가 정의됨. 한국어로 번역해서 설명을 옮기겠음. + + - lm_func: 비선형 최소제곱 곡선 피팅에 사용되는 모델 함수를 정의 + - lm_FD_J: 유한 차분을 이용하여 자코비안 dy/dp를 계산 + - lm_Broyden_J: Broyden 방정식을 사용하여 자코비안 행렬을 rank-1 업데이트함 + - lm_matx: 선형화된 최적 행렬 JtWJ 및 벡터 JtWdy를 평가하고 Chi-squared 오차 함수 Chi_sq를 계산한다. + 이 함수는 아래 lm 함수에서 사용됨 + - lm: Levenberg-Marquardt 곡선 최적화: 가중된 오차 제곱 값의 합을 최소화함 + - make_lm_plots: 그래프를 띄우는 함수 +''' + + + +import numpy as np +import seaborn as sns +import matplotlib.pylab as pl +import matplotlib.pyplot as plt + +def lm_func(t,p): + """ + + Define model function used for nonlinear least squares curve-fitting. + + Parameters + ---------- + t : independent variable values (assumed to be error-free) (m x 1) + p : parameter values , n = 4 in these examples (n x 1) + + Returns + ------- + y_hat : curve-fit fctn evaluated at points t and with parameters p (m x 1) + + """ + + y_hat = p[0,0]*np.exp(-t/p[1,0]) + p[2,0]*np.sin(t/p[3,0]) + + return y_hat + + +def lm_FD_J(t,p,y,dp): + """ + + Computes partial derivates (Jacobian) dy/dp via finite differences. + + Parameters + ---------- + t : independent variables used as arg to lm_func (m x 1) + p : current parameter values (n x 1) + y : func(t,p,c) initialised by user before each call to lm_FD_J (m x 1) + dp : fractional increment of p for numerical derivatives + - dp(j)>0 central differences calculated + - dp(j)<0 one sided differences calculated + - dp(j)=0 sets corresponding partials to zero; i.e. holds p(j) fixed + + Returns + ------- + J : Jacobian Matrix (n x m) + + """ + + global func_calls + + # number of data points + m = len(y) + # number of parameters + n = len(p) + + # initialize Jacobian to Zero + ps=p + J=np.zeros((m,n)) + del_=np.zeros((n,1)) + + # START --- loop over all parameters + for j in range(n): + # parameter perturbation + del_[j,0] = dp[j,0] * (1+abs(p[j,0])) + # perturb parameter p(j) + p[j,0] = ps[j,0] + del_[j,0] + + if del_[j,0] != 0: + y1 = lm_func(t,p) + func_calls = func_calls + 1 + + if dp[j,0] < 0: + # backwards difference + J[:,j] = (y1-y)/del_[j,0] + else: + # central difference, additional func call + p[j,0] = ps[j,0] - del_[j] + J[:,j] = (y1-lm_func(t,p)) / (2 * del_[j,0]) + func_calls = func_calls + 1 + + # restore p(j) + p[j,0]=ps[j,0] + + return J + + +def lm_Broyden_J(p_old,y_old,J,p,y): + """ + Carry out a rank-1 update to the Jacobian matrix using Broyden's equation. + + Parameters + ---------- + p_old : previous set of parameters (n x 1) + y_old : model evaluation at previous set of parameters, y_hat(t,p_old) (m x 1) + J : current version of the Jacobian matrix (m x n) + p : current set of parameters (n x 1) + y : model evaluation at current set of parameters, y_hat(t,p) (m x 1) + + Returns + ------- + J : rank-1 update to Jacobian Matrix J(i,j)=dy(i)/dp(j) (m x n) + + """ + + h = p - p_old + + a = np.cross( (np.array([y - y_old]).T - np.cross(J,h)) , h.T ) + b = np.cross (h.T , h) + + # Broyden rank-1 update eq'n + J = J + a/b + + return J + +def lm_matx(t,p_old,y_old,dX2,J,p,y_dat,weight,dp): + """ + Evaluate the linearized fitting matrix, JtWJ, and vector JtWdy, and + calculate the Chi-squared error function, Chi_sq used by Levenberg-Marquardt + algorithm (lm). + + Parameters + ---------- + t : independent variables used as arg to lm_func (m x 1) + p_old : previous parameter values (n x 1) + y_old : previous model ... y_old = y_hat(t,p_old) (m x 1) + dX2 : previous change in Chi-squared criteria (1 x 1) + J : Jacobian of model, y_hat, with respect to parameters, p (m x n) + p : current parameter values (n x 1) + y_dat : data to be fit by func(t,p,c) (m x 1) + weight : the weighting vector for least squares fit inverse of + the squared standard measurement errors + dp : fractional increment of 'p' for numerical derivatives + - dp(j)>0 central differences calculated + - dp(j)<0 one sided differences calculated + - dp(j)=0 sets corresponding partials to zero; i.e. holds p(j) fixed + + Returns + ------- + JtWJ : linearized Hessian matrix (inverse of covariance matrix) (n x n) + JtWdy : linearized fitting vector (n x m) + Chi_sq : Chi-squared criteria: weighted sum of the squared residuals WSSR + y_hat : model evaluated with parameters 'p' (m x 1) + J : Jacobian of model, y_hat, with respect to parameters, p (m x n) + + """ + + global iteration,func_calls + + # number of parameters + Npar = len(p) + + # evaluate model using parameters 'p' + y_hat = lm_func(t,p) + + func_calls = func_calls + 1 + + if not np.remainder(iteration,2*Npar) or dX2 > 0: + # finite difference + J = lm_FD_J(t,p,y_hat,dp) + else: + # rank-1 update + J = lm_Broyden_J(p_old,y_old,J,p,y_hat) + + # residual error between model and data + delta_y = np.array([y_dat - y_hat]).T + + # Chi-squared error criteria + Chi_sq = np.cross ( delta_y.T , ( delta_y * weight ) ) + + JtWJ = np.cross ( J.T , ( J * ( weight * np.ones((1,Npar)) ) ) ) + + JtWdy = np.cross ( J.T , ( weight * delta_y ) ) + + + return JtWJ,JtWdy,Chi_sq,y_hat,J + + +def lm(p,t,y_dat): + """ + + Levenberg Marquardt curve-fitting: minimize sum of weighted squared residuals + + Parameters + ---------- + p : initial guess of parameter values (n x 1) + t : independent variables (used as arg to lm_func) (m x 1) + y_dat : data to be fit by func(t,p) (m x 1) + + Returns + ------- + p : least-squares optimal estimate of the parameter values + redX2 : reduced Chi squared error criteria - should be close to 1 + sigma_p : asymptotic standard error of the parameters + sigma_y : asymptotic standard error of the curve-fit + corr_p : correlation matrix of the parameters + R_sq : R-squared cofficient of multiple determination + cvg_hst : convergence history (col 1: function calls, col 2: reduced chi-sq, + col 3 through n: parameter values). Row number corresponds to + iteration number. + + """ + + global iteration, func_calls + + # iteration counter + iteration = 0 + # running count of function evaluations + func_calls = 0 + + # define eps (not available in python) + eps = 2**(-52) + + # number of parameters + Npar = len(p) + # number of data points + Npnt = len(y_dat) + # previous set of parameters + p_old = np.zeros((Npar,1)) + # previous model, y_old = y_hat(t,p_old) + y_old = np.zeros((Npnt,1)) + # a really big initial Chi-sq value + X2 = 1e-3/eps + # a really big initial Chi-sq value + X2_old = 1e-3/eps + # Jacobian matrix + J = np.zeros((Npnt,Npar)) + # statistical degrees of freedom + DoF = np.array([[Npnt - Npar + 1]]) + + + if len(t) != len(y_dat): + print('The length of t must equal the length of y_dat!') + X2 = 0 + corr_p = 0 + sigma_p = 0 + sigma_y = 0 + R_sq = 0 + + # weights or a scalar weight value ( weight >= 0 ) + weight = 1 / ( np.cross(y_dat.T, y_dat) ) + # fractional increment of 'p' for numerical derivatives + dp = [-0.001] + # lower bounds for parameter values + p_min = -100*abs(p) + # upper bounds for parameter values + p_max = 100*abs(p) + + MaxIter = 1000 # maximum number of iterations + epsilon_1 = 1e-3 # convergence tolerance for gradient + epsilon_2 = 1e-3 # convergence tolerance for parameters + epsilon_4 = 1e-1 # determines acceptance of a L-M step + lambda_0 = 1e-2 # initial value of damping paramter, lambda + lambda_UP_fac = 11 # factor for increasing lambda + lambda_DN_fac = 9 # factor for decreasing lambda + Update_Type = 1 # 1: Levenberg-Marquardt lambda update, 2: Quadratic update, 3: Nielsen's lambda update equations + + if len(dp) == 1: + dp = dp*np.ones((Npar,1)) + + idx = np.arange(len(dp)) # indices of the parameters to be fit + stop = 0 # termination flag + + # identical weights vector + if np.var(weight) == 0: + weight = abs(weight)*np.ones((Npnt,1)) + print('Using uniform weights for error analysis') + else: + weight = abs(weight) + + # initialize Jacobian with finite difference calculation + JtWJ,JtWdy,X2,y_hat,J = lm_matx(t,p_old,y_old,1,J,p,y_dat,weight,dp) + if np.abs(JtWdy).max() < epsilon_1: + print('*** Your Initial Guess is Extremely Close to Optimal ***') + + lambda_0 = np.atleast_2d([lambda_0]) + + # Marquardt: init'l lambda + if Update_Type == 1: + lambda_ = lambda_0 + # Quadratic and Nielsen + else: + lambda_ = lambda_0 * max(np.diag(JtWJ)) + nu=2 + + # previous value of X2 + X2_old = X2 + # initialize convergence history + cvg_hst = np.ones((MaxIter,Npar+2)) + + # -------- Start Main Loop ----------- # + while not stop and iteration <= MaxIter: + + iteration = iteration + 1 + + # incremental change in parameters + # Marquardt + if Update_Type == 1: + h = np.linalg.solve((JtWJ + lambda_*np.diag(np.diag(JtWJ)) ), JtWdy) + # Quadratic and Nielsen + else: + h = np.linalg.solve(( JtWJ + lambda_*np.eye(Npar) ), JtWdy) + + # update the [idx] elements + p_try = p + h[idx] + # apply constraints + p_try = np.minimum(np.maximum(p_min,p_try),p_max) + + # residual error using p_try + delta_y = np.array([y_dat - lm_func(t,p_try)]).T + + # floating point error; break + if not all(np.isfinite(delta_y)): + stop = 1 + break + + func_calls = func_calls + 1 + # Chi-squared error criteria + X2_try = np.cross( delta_y.T , ( delta_y * weight ) ) + + # % Quadratic + if Update_Type == 2: + # One step of quadratic line update in the h direction for minimum X2 + alpha = np.divide( np.cross(JtWdy.T , h) , ( (X2_try - X2)/2 + 2*( np.cross(JtWdy.T, h )) )) + h = alpha * h + + # % update only [idx] elements + p_try = p + h[idx] + # % apply constraints + p_try = np.minimum(np.maximum(p_min,p_try),p_max) + + # % residual error using p_try + delta_y = y_dat - lm_func(t,p_try) + func_calls = func_calls + 1 + # % Chi-squared error criteria + X2_try = np.cross( delta_y.T , ( delta_y * weight ) ) + + rho = np.matmul( np.cross( h.T , (lambda_ * h + JtWdy) ) , np.linalg.inv(X2 - X2_try)) + + # it IS significantly better + if ( rho > epsilon_4 ): + + dX2 = X2 - X2_old + X2_old = X2 + p_old = p + y_old = y_hat + # % accept p_try + p = p_try + + JtWJ,JtWdy,X2,y_hat,J = lm_matx(t,p_old,y_old,dX2,J,p,y_dat,weight,dp) + + # % decrease lambda ==> Gauss-Newton method + # % Levenberg + if Update_Type == 1: + lambda_ = max(lambda_/lambda_DN_fac,1.e-7) + # % Quadratic + elif Update_Type == 2: + lambda_ = max( lambda_/(1 + alpha) , 1.e-7 ) + # % Nielsen + else: + lambda_ = lambda_*max( 1/3, 1-(2*rho-1)**3 ) + nu = 2 + + # it IS NOT better + else: + # % do not accept p_try + X2 = X2_old + + if not np.remainder(iteration,2*Npar): + JtWJ,JtWdy,dX2,y_hat,J = lm_matx(t,p_old,y_old,-1,J,p,y_dat,weight,dp) + + # % increase lambda ==> gradient descent method + # % Levenberg + if Update_Type == 1: + lambda_ = min(lambda_*lambda_UP_fac,1.e7) + # % Quadratic + elif Update_Type == 2: + lambda_ = lambda_ + abs((X2_try - X2)/2/alpha) + # % Nielsen + else: + lambda_ = lambda_ * nu + nu = 2*nu + + # update convergence history ... save _reduced_ Chi-square + cvg_hst[iteration-1,0] = func_calls + cvg_hst[iteration-1,1] = X2/DoF + + for i in range(Npar): + cvg_hst[iteration-1,i+2] = p.T[0][i] + + if ( max(abs(JtWdy)) < epsilon_1 and iteration > 2 ): + print('**** Convergence in r.h.s. ("JtWdy") ****') + stop = 1 + + if ( max(abs(h)/(abs(p)+1e-12)) < epsilon_2 and iteration > 2 ): + print('**** Convergence in Parameters ****') + stop = 1 + + if ( iteration == MaxIter ): + print('!! Maximum Number of Iterations Reached Without Convergence !!') + stop = 1 + + # --- End of Main Loop --- # + # --- convergence achieved, find covariance and confidence intervals + + # ---- Error Analysis ---- + # recompute equal weights for paramter error analysis + if np.var(weight) == 0: + weight = DoF/( np.cross(delta_y.T, delta_y) ) * np.ones((Npnt,1)) + + # % reduced Chi-square + redX2 = X2 / DoF + + JtWJ,JtWdy,X2,y_hat,J = lm_matx(t,p_old,y_old,-1,J,p,y_dat,weight,dp) + + # standard error of parameters + covar_p = np.linalg.inv(JtWJ) + sigma_p = np.sqrt(np.diag(covar_p)) + error_p = sigma_p/p + + # standard error of the fit + sigma_y = np.zeros((Npnt,1)) + for i in range(Npnt): + sigma_y[i,0] = np.cross( np.cross(J[i,:] , covar_p) , J[i,:].T ) + + sigma_y = np.sqrt(sigma_y) + + # parameter correlation matrix + corr_p = covar_p / [np.cross(sigma_p, sigma_p.T)] + + # coefficient of multiple determination + R_sq = np.correlate(y_dat, y_hat) + R_sq = 0 + + # convergence history + cvg_hst = cvg_hst[:iteration,:] + + print('\nLM fitting results:') + for i in range(Npar): + print('----------------------------- ') + print('parameter = p%i' %(i+1)) + print('fitted value = %0.4f' % p[i,0]) + print('standard error = %0.2f %%' % error_p[i,0]) + + return p,redX2,sigma_p,sigma_y,corr_p,R_sq,cvg_hst + + + + + + +def make_lm_plots(x,y,cvg_hst): + + + # extract parameters data + p_hst = cvg_hst[:,2:] + p_fit = p_hst[-1,:] + y_fit = lm_func(x,np.array([p_fit]).T) + + # define fonts used for plotting + font_axes = {'family': 'serif', + 'weight': 'normal', + 'size': 12} + font_title = {'family': 'serif', + 'weight': 'normal', + 'size': 14} + + # define colors and markers used for plotting + n = len(p_fit) + colors = pl.cm.ocean(np.linspace(0,.75,n)) + markers = ['o','s','D','v'] + + # create plot of raw data and fitted curve + fig1, ax1 = plt.subplots() + ax1.plot(x,y,'wo',markeredgecolor='black',label='Raw data') + ax1.plot(x,y_fit,'r--',label='Fitted curve',linewidth=2) + ax1.set_xlabel('t',fontdict=font_axes) + ax1.set_ylabel('y(t)',fontdict=font_axes) + ax1.set_title('Data fitting',fontdict=font_title) + ax1.legend() + + # create plot showing convergence of parameters + fig2, ax2 = plt.subplots() + for i in range(n): + ax2.plot(cvg_hst[:,0],p_hst[:,i]/p_hst[0,i],color=colors[i],marker=markers[i], + linestyle='-',markeredgecolor='black',label='p'+'${_%i}$'%(i+1)) + ax2.set_xlabel('Function calls',fontdict=font_axes) + ax2.set_ylabel('Values (norm.)',fontdict=font_axes) + ax2.set_title('Convergence of parameters',fontdict=font_title) + ax2.legend() + + # create plot showing histogram of residuals + fig3, ax3 = plt.subplots() + sns.histplot(ax=ax3,data=y_fit-y,color='deepskyblue') + ax3.set_xlabel('Residual error',fontdict=font_axes) + ax3.set_ylabel('Frequency',fontdict=font_axes) + ax3.set_title('Histogram of residuals',fontdict=font_title) + + # create plot showing objective function surface plot + fig4, ax4 = plt.subplots(subplot_kw={"projection": "3d"}) + # define range of values for gridded parameter search + p2 = np.arange(0.1*p_fit[1], 2.5*p_fit[1], 0.1) + p4 = np.arange(0.1*p_fit[3], 2.5*p_fit[3], 0.1) + X2 = np.zeros((len(p4),len(p2))) + # gridded parameter search + for i in range(len(p2)): + for j in range(len(p4)): + pt = np.array([[p_hst[-1,0],p2[i],p_hst[-1,2],p4[j]]]).T + delta_y = y - lm_func(x,pt) + X2[j,i] = np.log(np.cross(delta_y.T , delta_y)/(len(x)-len(p_fit))) + p2_grid, p4_grid = np.meshgrid(p2, p4) + # make surface plot + ax4.plot_surface(p2_grid, p4_grid, X2, cmap='coolwarm', antialiased=True) + ax4.set_xlabel('P$_2$',fontdict=font_axes) + ax4.set_ylabel('P$_4$',fontdict=font_axes) + ax4.set_zlabel('log$_{10}$($\chi$$^2$)',fontdict=font_axes,rotation=90) + ax4.set_title('Objective Function',fontdict=font_title) + ax4.zaxis.set_rotate_label(False) + ax4.azim = 225 \ No newline at end of file diff --git a/src/algorithm_pkg/scripts/levenberg_marquardt.pyc b/src/algorithm_pkg/scripts/levenberg_marquardt.pyc new file mode 100644 index 0000000..69fca07 Binary files /dev/null and b/src/algorithm_pkg/scripts/levenberg_marquardt.pyc differ diff --git a/src/algorithm_pkg/scripts/offset_order_node.py b/src/algorithm_pkg/scripts/offset_order_node.py new file mode 100644 index 0000000..78af2f9 --- /dev/null +++ b/src/algorithm_pkg/scripts/offset_order_node.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import rospy +from std_msgs.msg import String +import sys +import signal +import numpy as np + + + +def signal_handler(signal, frame): # ctrl + c -> exit program + print('You pressed Ctrl+C!') + sys.exit(0) +signal.signal(signal.SIGINT, signal_handler) + + + +def offset_order_node(): + + pub = rospy.Publisher('Is_offset', String, queue_size=10) + rospy.init_node('offset_order_node', anonymous=True) + rate = rospy.Rate(10) # 10hz + + while not rospy.is_shutdown(): + + x = input("Do you want to set the offset, input 1: ") + + if(x == 1): + pub.publish("1") + rate.sleep() + + else: + print("-- Press the right number --") + + + +if __name__ == '__main__': + try: + offset_order_node() + except rospy.ROSInterruptException: + pass diff --git a/src/algorithm_pkg/scripts/serial_comm.py b/src/algorithm_pkg/scripts/serial_comm.py new file mode 100644 index 0000000..232be20 --- /dev/null +++ b/src/algorithm_pkg/scripts/serial_comm.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import serial + +# 시리얼 포트 설정 +ser = serial.Serial('/dev/ttyUSB0', 115200) + +# 데이터 읽기 +while True: + if ser.in_waiting: + data = ser.readline().decode('utf-8').rstrip() + print(data.decode()[:len(data) - 1]) \ No newline at end of file diff --git a/src/listener.cpp b/src/listener.cpp new file mode 100644 index 0000000..98d0692 --- /dev/null +++ b/src/listener.cpp @@ -0,0 +1,380 @@ +///////////////////// +// 헤더 파일 선언 구간 // +///////////////////// +#include "ros/ros.h" +#include "std_msgs/String.h" +#include "sensor_msgs/PointCloud.h" + + + + + +///////////////// +// 변수 선언 구간 // +///////////////// +char incomingData[320] = ""; // Rosserial을 통해 받는 통신 값을 저장하는 버퍼 +int start_flag = 0; // incomingData의 값들을 받을지 결정하는 플래그 변수 +int i = 0; // incomingData의 인덱스 변수 +int k = 0; // n번째 센서 값 버퍼(구조체)에 대한 인덱스 변수 +int l = 0; // n번째 센서 값의 x, y, z값에 대한 인덱스 변수 +int kk = 0; // 센서 9개에 대한 인덱스 변수 + +char sensor_num_char = 0; // 값을 확인할 센서를 선택하는 변수 +int sensor_num_int = 0; + +int mode = 0; // 센서 값 분리 작업 중 단계를 거치기 위한 플래그 변수 + +char header[3] = { 0, }; // 패킷의 헤더를 분리 후 보관할 배열 변수 +char checkSum_arr[10]; // 패킷에서 넘겨받은 checksum 배열 값 저장 변수 +int checkSum_here = 0; // checkSum_arr에서 정수형으로 변환한 값을 저장하는 변수 +int checkSum = 0; // 패킷 분리 작업 후 실제로 다시 checksum을 계산한 값을 저장하는 변수 +int count_num = 0; + +char large_cap = 'A'; // n번째 센서 값이 시작되는 것을 비교/확인하기 위한 변수 +char small_cap = 'a'; // n번째 센서 값이 끝나는 것을 비교/확인하기 위한 변수 + +// 홀 센서 값을 저장하는 구조체 변수 +struct hallsensor_struct { + char full_val[29] = ""; // 패킷에서 n번째 센서값을 뭉텅이로 먼저 저장하는 임시 버퍼 + char x_val[10] = ""; // n번째 센서 값의 x축 값 + char y_val[10] = ""; // n번째 센서 값의 y축 값 + char z_val[10] = ""; // n번째 센서 값의 z축 값 + // (음수 부호와 null 까지 고려하여 크기를 6으로 설정) + + int x_val_int = 0; + int y_val_int = 0; + int z_val_int = 0; // x, y, z_val에서 정수형으로 변환 후 저장할 변수 선언 + // checksum을 구해야 하므로 먼저 정수형을 구해야 함 + + double x_val_real = 0; + double y_val_real = 0; + double z_val_real = 0; // 정수형으로 변환 후, 손실된 소수점 복구한 최종 센서 값 저장 + +}hall_[9]; // 센서가 9개이므로 구조체 변수 9세트 할당 + +int timerr = 0; +int buffer_size = 0; // 버퍼로 받는 문자열의 길이 값을 저장하는 변수 + +int print_check_mode = 0; // 테스트 출력 설정 모드 + + + + + + + + + + + + +///////////////// +// 함수 선언 구간 // +///////////////// +void working_func(); // 본격적으로 센서 값의 분리 작업을 진행하는 함수 +void chatterCallback(const std_msgs::String::ConstPtr& msg); // Rosserial을 통해 subscribe하는 값을 적절히 받아서 완전한 형태의 패킷으로 정리 & 배열로 다시 저장하는 함수 +void modeCallback(const std_msgs::String::ConstPtr& msg); // 몇 번 센서 값을 출력해서 볼지 결정해주는 변수 값 메세지 입력받는 함수 + +void reset_func(); // 패킷 분리작업 때 사용한 변수들 초기화하는 함수 +void hall_buffer_func(); // n번째 센서 값 별로 분리 후 저장해주는 함수 +void seperate_axis_val(); // n번째 센서 값을 x, y, z축별로 세부 분리 후 실수형으로 따로 저장해주는 함수 +void turn2real(); // 모든 센서 값을 정수형에서 실수형으로 최종 변환해주는 함수 +void allPrint(); // 모든 센서 값을 출력해주는 함수 +int custom_atoi(const char *str); // atoi함수 구현 + + + + + + + + + + +/////////// +// 메인문 // +////////// +int main(int argc, char **argv) +{ + + ros::init(argc, argv, "listener"); + ros::NodeHandle serial; + + // 먼저 여기를 통해 통신 데이터 수신을 받는다. -> chatterCallback 함수 호출 + // ros::Subscriber sensor_mode = serial.subscribe("sensor", 1000, modeCallback); + ros::Subscriber sub = serial.subscribe("read", 1000, chatterCallback); + ros::Publisher pub_all = serial.advertise("local_sys", 100); + + while(ros::ok()){ + + ros::spinOnce(); + + sensor_msgs::PointCloud all_sensor; // 메세지 구조체 선언 + all_sensor.points.resize(9); // Points 개수는 총 9개 + + for(kk=0; kk < 9; kk++){ // 반복문을 통해 센서 9개 값 메세지에 저장 + all_sensor.points[kk].x = hall_[kk].x_val_real; + all_sensor.points[kk].y = hall_[kk].y_val_real; + all_sensor.points[kk].z = hall_[kk].z_val_real; + } + + pub_all.publish(all_sensor); // 최종적으로 publish + + } + + + + return 0; +} + + + + + + + + + + +///////////////// +// 함수 정의 구간 // +///////////////// + +/* +// 몇 번 센서 값을 출력해서 볼지 결정해주는 변수 값 메세지 입력받는 함수 +void modeCallback(const std_msgs::String::ConstPtr& msg) +{ + sensor_num_char = msg->data[0]; // sensor_select node에서 해당 값을 입력받음 + sensor_num_int = sensor_num_char - 48; // 사용하기 편하게 정수형으로 바꿔서 저장 +} +*/ + + +// Rosserial을 통해 subscribe하는 값을 적절히 받아서 완전한 형태의 패킷으로 정리 & 배열로 다시 저장하는 함수 +void chatterCallback(const std_msgs::String::ConstPtr& msg) +{ + // 32개씩 10개의 문자열이 들어오므로, Z가 검출될 때를 기준으로 10번 버퍼에 담으면 된다. + if(start_flag == 0) + if(msg->data[0] == 'Z') start_flag = 1; // 1. 수신받은 문자열 첫 문자가 Z로 시작하면 start_flag 1로 + + + // 2. start_flag 값이 1이면 + if(start_flag == 1) { + for(int k=0; k < 32; k++) + incomingData[i++] = msg->data[k]; // 3. 들어오는 문자열 이어서 버퍼에 저장 + + count_num++; // 분할 패킷을 총 몇 번 받았는지 카운트 + + if(count_num == 10) start_flag = 2; // 분할 패킷을 총 10번 받았으면 완성된 것이므로 다음 작업으로 + } + + + if(start_flag == 2){ + i = 3; // 6. 인덱스 초기화 + start_flag = 0; // 7. 버퍼에 통신 값 저장을 알리는 플래그 변수도 0으로 + count_num = 0; + mode = 1; // 8. 센서 값 분리 모드 On + working_func(); // 9. 센서 값 분리하는 함수 호출 -> 본격적인 작업 시작 + } + +} + + + +// 본격적으로 센서 값의 분리 작업을 진행하는 함수 +void working_func(){ + + if (mode == 1) { + printf("%s\n", incomingData); // 문자열로 출력 + // 헤더 분리 + //for (kk = 0; kk < 3; kk++) header[kk] = incomingData[i++]; + + // 센서마다 값 분리 + for (kk = 0; kk < 9; kk++) { + hall_buffer_func(); + if (print_check_mode == 1) printf("%s\n", hall_[kk].full_val); + } + + // checksum 값 분리 + for (k = 0; k < 9; k++) { // checksum 값 10개만 딱 받기 + checkSum_arr[k] = incomingData[i++]; // 차례로 checksum 변수에 저장 + } + k = 0; // 다른 곳에서 사용될 것을 감안하여 초기화를 해 준다 + + mode++; // 다음 작업으로 + + } + + + // mode = 2일때: 각 센서값을 x, y, z값으로 분해 후 정수형으로 변환 + if (mode == 2) { + + // 축 별로 값 분리 후 정수형으로 변환 + // if (print_check_mode == 1) printf("------------------------------------\n"); + for (kk = 0; kk < 9; kk++) { + seperate_axis_val(); + //if (print_check_mode == 1) printf("%d %d %d\n", hall_[kk].x_val_int, hall_[kk].y_val_int, hall_[kk].z_val_int); + + } + //printf("process complete4\n"); + + // checksum 정수형으로 변환 + int sign = 0; + if (checkSum_arr[0] == '1') { checkSum_arr[0] = '0'; sign = -1; } + else sign = 1; + checkSum = custom_atoi(checkSum_arr) * sign; + + // 지금까지 분리했던 모든 센서 값들 전부 더하기 + for (kk = 0; kk < 9; kk++) + checkSum_here += (hall_[kk].x_val_int) + (hall_[kk].y_val_int) + (hall_[kk].z_val_int); + + + //if (print_check_mode == 1) printf("%d, %d\n", checkSum, checkSum_here); + + // checksum 비교 + if (checkSum == checkSum_here) { + for (kk = 0; kk < 9; kk++) turn2real(); + //printf("Packet Comm Success!!\n"); + //printf("x: %f | y: %f | z: %f | count: %d %d\n", hall_[sensor_num_int].x_val_real, hall_[sensor_num_int].y_val_real, hall_[sensor_num_int].z_val_real, timerr++, sensor_num_int); + allPrint(); // 센서 값 출력 + } + else { + //printf("Try again...\n"); + //mode = 2; + } // 그러나 일치하지 않으면 다시 처음부터 + + reset_func(); // 한 번 통신이 끝나면 지금까지 사용했던 변수들 전부 초기화 + } + + +} + + + + +// n번째 센서 값 별로 분리 후 저장해주는 함수 +void hall_buffer_func() { + + for (kk = 0; kk < 9; kk++) { + for (int iii=0; iii < 29; iii++) // 딱 29개의 문자를 저장 + hall_[kk].full_val[iii] = incomingData[i++]; // 센서값 통째로 버퍼에 저장 + + i = i+2; // 중간중간 껴 있는 알파벳 무시하고 바로 다음 센서 값으로 + } + i--; // 센서 값 이후 index를 Checksum에 위치시키기 위한 조절 +} + + + +// n번째 센서 값을 x, y, z축별로 세부 분리 후 정수형으로 따로 저장해주는 함수 +void seperate_axis_val(){ + + int sign = 0; // 센서 값의 부호를 결정하기 위한 부호 변수(정수) + k = 0; + + for(kk = 0; kk <9; kk++){ // n번째 센서 선택 + + for(l = 0; l < 9; l++) hall_[kk].x_val[l] = hall_[kk].full_val[k++]; // n번째 센서 값의 x값 추출 + if (print_check_mode == 1) std::cout << hall_[kk].x_val; + if (print_check_mode == 1) std::cout << " "; + if (hall_[kk].x_val[0] == '1') { hall_[kk].x_val[0] = '0'; sign = -1; } // 패킷 맨 앞자리가 1이면 음수이므로, 0으로 바꾼 후 음수 연산자 설정 + else sign = 1; // 그런데 0으로 시작하면 양수이므로, 양수 연산자로 설정 + hall_[kk].x_val_int = custom_atoi(hall_[kk].x_val) * sign; // 추출한 값을 정수형으로 변환 + 부호 복원 + + k++; // 콤마 패스 + + for(l = 0; l < 9; l++) hall_[kk].y_val[l] = hall_[kk].full_val[k++]; // n번째 센서 값의 y값 추출 + if (print_check_mode == 1) std::cout << hall_[kk].y_val; + if (print_check_mode == 1) std::cout << " "; + if (hall_[kk].y_val[0] == '1') { hall_[kk].y_val[0] = '0'; sign = -1; } + else sign = 1; + hall_[kk].y_val_int = custom_atoi(hall_[kk].y_val) * sign; + + k++; // 콤마 패스 + + for(l = 0; l < 9; l++) hall_[kk].z_val[l] = hall_[kk].full_val[k++]; // n번째 센서 값의 z값 추출 + if (print_check_mode == 1) std::cout << hall_[kk].z_val ; + if (print_check_mode == 1) std::cout << "\n\n"; + if (hall_[kk].z_val[0] == '1') { hall_[kk].z_val[0] = '0'; sign = -1; } + else sign = 1; + hall_[kk].z_val_int = custom_atoi(hall_[kk].z_val) * sign; + + k = 0; + l = 0; // n+1번째 센서 값의 세부 값 추출을 위해 인덱스 변수 초기화 + + } + +} + + + + +// 모든 센서 값을 정수형에서 실수형으로 최종 변환해주는 함수 +void turn2real() { + for (kk = 0; kk < 9; kk++){ + hall_[kk].x_val_real = (double)hall_[kk].x_val_int / 100 * (-1); // 왼손법칙 좌표계에서 오른손법칙 좌표계로 변환하기 위해 부호 변경 + hall_[kk].y_val_real = (double)hall_[kk].y_val_int / 100; // 정수 변환 때는 checksum 계산 때문에 나중에 부호 변경을 해야 함 + hall_[kk].z_val_real = (double)hall_[kk].z_val_int / 100; + } +} + + + +// 모든 센서 값을 출력해주는 함수 +void allPrint() { + // printf("%d\n\n", timerr++); + for (kk = 0; kk < 9; kk++) + printf("%dth sensor x: %f | y: %f | z: %f | \n", kk+1, hall_[kk].x_val_real, hall_[kk].y_val_real, hall_[kk].z_val_real); + printf("\n\n"); + +} + + + +// 패킷 분리작업 때 사용한 변수들 초기화하는 함수 +void reset_func() { + mode = 0; + //for (kk = 0; kk < 9; kk++) hall_[kk] = { 0, }; + //for (kk = 0; kk < 4096; kk++) incomingData[kk] = { 0 }; + //for (kk = 0; kk < 10; kk++) checkSum_arr[kk] = 0; + for (kk = 0; kk < 3; kk++) header[kk] = 0; + i = 0; + k = 0; + l = 0; + kk = 0; + checkSum = 0; + checkSum_here = 0; + + + //large_cap = 'A'; + //small_cap = 'a'; + +} + + + +// atoi 함수 구현 +int custom_atoi(const char *str) +{ + long long int sign; + long long int value; + + sign = 1; + value = 0; + while (*str == '\t' || *str == '\n' || *str == '\v' \ + || *str == '\f' || *str == '\r' || *str == ' ') + str++; + if (*str == '+' || *str == '-') + { + if (*str == '-') + sign *= -1; + str++; + } + while (*str >= 48 && *str <= 57) + { + value *= 10; + value += *str - 48; + str++; + } + return (value * sign); +} \ No newline at end of file diff --git a/src/serial-example/CMakeLists.txt b/src/serial-example/CMakeLists.txt new file mode 100644 index 0000000..a1f2449 --- /dev/null +++ b/src/serial-example/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 2.8.3) +project(serial_example) + +find_package(catkin REQUIRED COMPONENTS + roscpp + serial + std_msgs + sensor_msgs + message_generation +) + +catkin_package( + CATKIN_DEPENDS + serial + std_msgs + sensor_msgs + message_runtime +) + + +include_directories( + ${catkin_INCLUDE_DIRS} +) + +add_executable(serial_example_node src/serial_example_node.cpp) +add_executable(listener src/listener.cpp) + +target_link_libraries(serial_example_node + ${catkin_LIBRARIES} +) +target_link_libraries(listener + ${catkin_LIBRARIES} +) diff --git a/src/serial-example/launch/serial_example.launch b/src/serial-example/launch/serial_example.launch new file mode 100644 index 0000000..ec4f9a5 --- /dev/null +++ b/src/serial-example/launch/serial_example.launch @@ -0,0 +1,24 @@ + + + + + + + diff --git a/src/serial-example/package.xml b/src/serial-example/package.xml new file mode 100644 index 0000000..0c14914 --- /dev/null +++ b/src/serial-example/package.xml @@ -0,0 +1,29 @@ + + + serial_example + 0.0.0 + Example package for using http://wjwwood.io/serial/ library + + Gary Servin + + Gary Servin + + BSD + + catkin + + serial + std_msgs + sensor_msgs + geometry_msgs + message_generation + + serial + std_msgs + sensor_msgs + geometry_msgs + message_runtime + + + + diff --git a/src/serial-example/src/listener.cpp b/src/serial-example/src/listener.cpp new file mode 100644 index 0000000..432cc69 --- /dev/null +++ b/src/serial-example/src/listener.cpp @@ -0,0 +1,380 @@ +///////////////////// +// 헤더 파일 선언 구간 // +///////////////////// +#include "ros/ros.h" +#include "std_msgs/String.h" +#include "sensor_msgs/PointCloud.h" + + + + + +///////////////// +// 변수 선언 구간 // +///////////////// +char incomingData[320] = ""; // Rosserial을 통해 받는 통신 값을 저장하는 버퍼 +int start_flag = 0; // incomingData의 값들을 받을지 결정하는 플래그 변수 +int i = 0; // incomingData의 인덱스 변수 +int k = 0; // n번째 센서 값 버퍼(구조체)에 대한 인덱스 변수 +int l = 0; // n번째 센서 값의 x, y, z값에 대한 인덱스 변수 +int kk = 0; // 센서 9개에 대한 인덱스 변수 + +char sensor_num_char = 0; // 값을 확인할 센서를 선택하는 변수 +int sensor_num_int = 0; + +int mode = 0; // 센서 값 분리 작업 중 단계를 거치기 위한 플래그 변수 + +char header[3] = { 0, }; // 패킷의 헤더를 분리 후 보관할 배열 변수 +char checkSum_arr[10]; // 패킷에서 넘겨받은 checksum 배열 값 저장 변수 +int checkSum_here = 0; // checkSum_arr에서 정수형으로 변환한 값을 저장하는 변수 +int checkSum = 0; // 패킷 분리 작업 후 실제로 다시 checksum을 계산한 값을 저장하는 변수 +int count_num = 0; + +char large_cap = 'A'; // n번째 센서 값이 시작되는 것을 비교/확인하기 위한 변수 +char small_cap = 'a'; // n번째 센서 값이 끝나는 것을 비교/확인하기 위한 변수 + +// 홀 센서 값을 저장하는 구조체 변수 +struct hallsensor_struct { + char full_val[29] = ""; // 패킷에서 n번째 센서값을 뭉텅이로 먼저 저장하는 임시 버퍼 + char x_val[10] = ""; // n번째 센서 값의 x축 값 + char y_val[10] = ""; // n번째 센서 값의 y축 값 + char z_val[10] = ""; // n번째 센서 값의 z축 값 + // (음수 부호와 null 까지 고려하여 크기를 6으로 설정) + + int x_val_int = 0; + int y_val_int = 0; + int z_val_int = 0; // x, y, z_val에서 정수형으로 변환 후 저장할 변수 선언 + // checksum을 구해야 하므로 먼저 정수형을 구해야 함 + + double x_val_real = 0; + double y_val_real = 0; + double z_val_real = 0; // 정수형으로 변환 후, 손실된 소수점 복구한 최종 센서 값 저장 + +}hall_[9]; // 센서가 9개이므로 구조체 변수 9세트 할당 + +int timerr = 0; +int buffer_size = 0; // 버퍼로 받는 문자열의 길이 값을 저장하는 변수 + +int print_check_mode = 0; // 테스트 출력 설정 모드 + + + + + + + + + + + + +///////////////// +// 함수 선언 구간 // +///////////////// +void working_func(); // 본격적으로 센서 값의 분리 작업을 진행하는 함수 +void chatterCallback(const std_msgs::String::ConstPtr& msg); // Rosserial을 통해 subscribe하는 값을 적절히 받아서 완전한 형태의 패킷으로 정리 & 배열로 다시 저장하는 함수 +void modeCallback(const std_msgs::String::ConstPtr& msg); // 몇 번 센서 값을 출력해서 볼지 결정해주는 변수 값 메세지 입력받는 함수 + +void reset_func(); // 패킷 분리작업 때 사용한 변수들 초기화하는 함수 +void hall_buffer_func(); // n번째 센서 값 별로 분리 후 저장해주는 함수 +void seperate_axis_val(); // n번째 센서 값을 x, y, z축별로 세부 분리 후 실수형으로 따로 저장해주는 함수 +void turn2real(); // 모든 센서 값을 정수형에서 실수형으로 최종 변환해주는 함수 +void allPrint(); // 모든 센서 값을 출력해주는 함수 +int custom_atoi(const char *str); // atoi함수 구현 + + + + + + + + + + +/////////// +// 메인문 // +////////// +int main(int argc, char **argv) +{ + + ros::init(argc, argv, "listener"); + ros::NodeHandle serial; + + // 먼저 여기를 통해 통신 데이터 수신을 받는다. -> chatterCallback 함수 호출 + // ros::Subscriber sensor_mode = serial.subscribe("sensor", 1000, modeCallback); + ros::Subscriber sub = serial.subscribe("read", 1000, chatterCallback); + ros::Publisher pub_all = serial.advertise("local_sys", 100); + + while(ros::ok()){ + + ros::spinOnce(); + + sensor_msgs::PointCloud all_sensor; // 메세지 구조체 선언 + all_sensor.points.resize(9); // Points 개수는 총 9개 + + for(kk=0; kk < 9; kk++){ // 반복문을 통해 센서 9개 값 메세지에 저장 + all_sensor.points[kk].x = hall_[kk].x_val_real; + all_sensor.points[kk].y = hall_[kk].y_val_real; + all_sensor.points[kk].z = hall_[kk].z_val_real; + } + + pub_all.publish(all_sensor); // 최종적으로 publish + + } + + + + return 0; +} + + + + + + + + + + +///////////////// +// 함수 정의 구간 // +///////////////// + +/* +// 몇 번 센서 값을 출력해서 볼지 결정해주는 변수 값 메세지 입력받는 함수 +void modeCallback(const std_msgs::String::ConstPtr& msg) +{ + sensor_num_char = msg->data[0]; // sensor_select node에서 해당 값을 입력받음 + sensor_num_int = sensor_num_char - 48; // 사용하기 편하게 정수형으로 바꿔서 저장 +} +*/ + + +// Rosserial을 통해 subscribe하는 값을 적절히 받아서 완전한 형태의 패킷으로 정리 & 배열로 다시 저장하는 함수 +void chatterCallback(const std_msgs::String::ConstPtr& msg) +{ + // 32개씩 10개의 문자열이 들어오므로, Z가 검출될 때를 기준으로 10번 버퍼에 담으면 된다. + if(start_flag == 0) + if(msg->data[0] == 'Z') start_flag = 1; // 1. 수신받은 문자열 첫 문자가 Z로 시작하면 start_flag 1로 + + + // 2. start_flag 값이 1이면 + if(start_flag == 1) { + for(int k=0; k < 32; k++) + incomingData[i++] = msg->data[k]; // 3. 들어오는 문자열 이어서 버퍼에 저장 + + count_num++; // 분할 패킷을 총 몇 번 받았는지 카운트 + + if(count_num == 10) start_flag = 2; // 분할 패킷을 총 10번 받았으면 완성된 것이므로 다음 작업으로 + } + + + if(start_flag == 2){ + i = 3; // 6. 인덱스 초기화 + start_flag = 0; // 7. 버퍼에 통신 값 저장을 알리는 플래그 변수도 0으로 + count_num = 0; + mode = 1; // 8. 센서 값 분리 모드 On + working_func(); // 9. 센서 값 분리하는 함수 호출 -> 본격적인 작업 시작 + } + +} + + + +// 본격적으로 센서 값의 분리 작업을 진행하는 함수 +void working_func(){ + + if (mode == 1) { + printf("%s\n", incomingData); // 문자열로 출력 + // 헤더 분리 + //for (kk = 0; kk < 3; kk++) header[kk] = incomingData[i++]; + + // 센서마다 값 분리 + for (kk = 0; kk < 9; kk++) { + hall_buffer_func(); + if (print_check_mode == 1) printf("%s\n", hall_[kk].full_val); + } + + // checksum 값 분리 + for (k = 0; k < 9; k++) { // checksum 값 10개만 딱 받기 + checkSum_arr[k] = incomingData[i++]; // 차례로 checksum 변수에 저장 + } + k = 0; // 다른 곳에서 사용될 것을 감안하여 초기화를 해 준다 + + mode++; // 다음 작업으로 + + } + + + // mode = 2일때: 각 센서값을 x, y, z값으로 분해 후 정수형으로 변환 + if (mode == 2) { + + // 축 별로 값 분리 후 정수형으로 변환 + // if (print_check_mode == 1) printf("------------------------------------\n"); + for (kk = 0; kk < 9; kk++) { + seperate_axis_val(); + //if (print_check_mode == 1) printf("%d %d %d\n", hall_[kk].x_val_int, hall_[kk].y_val_int, hall_[kk].z_val_int); + + } + //printf("process complete4\n"); + + // checksum 정수형으로 변환 + int sign = 0; + if (checkSum_arr[0] == '1') { checkSum_arr[0] = '0'; sign = -1; } + else sign = 1; + checkSum = custom_atoi(checkSum_arr) * sign; + + // 지금까지 분리했던 모든 센서 값들 전부 더하기 + for (kk = 0; kk < 9; kk++) + checkSum_here += (hall_[kk].x_val_int) + (hall_[kk].y_val_int) + (hall_[kk].z_val_int); + + + //if (print_check_mode == 1) printf("%d, %d\n", checkSum, checkSum_here); + + // checksum 비교 + if (checkSum == checkSum_here) { + for (kk = 0; kk < 9; kk++) turn2real(); + //printf("Packet Comm Success!!\n"); + //printf("x: %f | y: %f | z: %f | count: %d %d\n", hall_[sensor_num_int].x_val_real, hall_[sensor_num_int].y_val_real, hall_[sensor_num_int].z_val_real, timerr++, sensor_num_int); + allPrint(); // 센서 값 출력 + } + else { + //printf("Try again...\n"); + //mode = 2; + } // 그러나 일치하지 않으면 다시 처음부터 + + reset_func(); // 한 번 통신이 끝나면 지금까지 사용했던 변수들 전부 초기화 + } + + +} + + + + +// n번째 센서 값 별로 분리 후 저장해주는 함수 +void hall_buffer_func() { + + for (kk = 0; kk < 9; kk++) { + for (int iii=0; iii < 29; iii++) // 딱 29개의 문자를 저장 + hall_[kk].full_val[iii] = incomingData[i++]; // 센서값 통째로 버퍼에 저장 + + i = i+2; // 중간중간 껴 있는 알파벳 무시하고 바로 다음 센서 값으로 + } + i--; // 센서 값 이후 index를 Checksum에 위치시키기 위한 조절 +} + + + +// n번째 센서 값을 x, y, z축별로 세부 분리 후 정수형으로 따로 저장해주는 함수 +void seperate_axis_val(){ + + int sign = 0; // 센서 값의 부호를 결정하기 위한 부호 변수(정수) + k = 0; + + for(kk = 0; kk <9; kk++){ // n번째 센서 선택 + + for(l = 0; l < 9; l++) hall_[kk].x_val[l] = hall_[kk].full_val[k++]; // n번째 센서 값의 x값 추출 + if (print_check_mode == 1) std::cout << hall_[kk].x_val; + if (print_check_mode == 1) std::cout << " "; + if (hall_[kk].x_val[0] == '1') { hall_[kk].x_val[0] = '0'; sign = -1; } // 패킷 맨 앞자리가 1이면 음수이므로, 0으로 바꾼 후 음수 연산자 설정 + else sign = 1; // 그런데 0으로 시작하면 양수이므로, 양수 연산자로 설정 + hall_[kk].x_val_int = custom_atoi(hall_[kk].x_val) * sign; // 추출한 값을 정수형으로 변환 + 부호 복원 + + k++; // 콤마 패스 + + for(l = 0; l < 9; l++) hall_[kk].y_val[l] = hall_[kk].full_val[k++]; // n번째 센서 값의 y값 추출 + if (print_check_mode == 1) std::cout << hall_[kk].y_val; + if (print_check_mode == 1) std::cout << " "; + if (hall_[kk].y_val[0] == '1') { hall_[kk].y_val[0] = '0'; sign = -1; } + else sign = 1; + hall_[kk].y_val_int = custom_atoi(hall_[kk].y_val) * sign; + + k++; // 콤마 패스 + + for(l = 0; l < 9; l++) hall_[kk].z_val[l] = hall_[kk].full_val[k++]; // n번째 센서 값의 z값 추출 + if (print_check_mode == 1) std::cout << hall_[kk].z_val ; + if (print_check_mode == 1) std::cout << "\n\n"; + if (hall_[kk].z_val[0] == '1') { hall_[kk].z_val[0] = '0'; sign = -1; } + else sign = 1; + hall_[kk].z_val_int = custom_atoi(hall_[kk].z_val) * sign; + + k = 0; + l = 0; // n+1번째 센서 값의 세부 값 추출을 위해 인덱스 변수 초기화 + + } + +} + + + + +// 모든 센서 값을 정수형에서 실수형으로 최종 변환해주는 함수 +void turn2real() { + for (kk = 0; kk < 9; kk++){ + hall_[kk].x_val_real = (double)hall_[kk].x_val_int / 100 * (-1); // 왼손법칙 좌표계에서 오른손법칙 좌표계로 변환하기 위해 부호 변경 + hall_[kk].y_val_real = (double)hall_[kk].y_val_int / 100; // 정수 변환 때는 checksum 계산 때문에 나중에 부호 변경을 해야 함 + hall_[kk].z_val_real = (double)hall_[kk].z_val_int / 100; + } +} + + + +// 모든 센서 값을 출력해주는 함수 +void allPrint() { + // printf("%d\n\n", timerr++); + for (kk = 0; kk < 9; kk++) + printf("%dth sensor x: %f | y: %f | z: %f | \n", kk+1, hall_[kk].x_val_real, hall_[kk].y_val_real, hall_[kk].z_val_real); + printf("\n\n"); + +} + + + +// 패킷 분리작업 때 사용한 변수들 초기화하는 함수 +void reset_func() { + mode = 0; + //for (kk = 0; kk < 9; kk++) hall_[kk] = { 0, }; + //for (kk = 0; kk < 4096; kk++) incomingData[kk] = { 0 }; + //for (kk = 0; kk < 10; kk++) checkSum_arr[kk] = 0; + for (kk = 0; kk < 3; kk++) header[kk] = 0; + i = 0; + k = 0; + l = 0; + kk = 0; + checkSum = 0; + checkSum_here = 0; + + + //large_cap = 'A'; + //small_cap = 'a'; + +} + + + +// atoi 함수 구현 +int custom_atoi(const char *str) +{ + long long int sign; + long long int value; + + sign = 1; + value = 0; + while (*str == '\t' || *str == '\n' || *str == '\v' \ + || *str == '\f' || *str == '\r' || *str == ' ') + str++; + if (*str == '+' || *str == '-') + { + if (*str == '-') + sign *= -1; + str++; + } + while (*str >= 48 && *str <= 57) + { + value *= 10; + value += *str - 48; + str++; + } + return (value * sign); +} \ No newline at end of file diff --git a/src/serial-example/src/serial_example_node.cpp b/src/serial-example/src/serial_example_node.cpp new file mode 100644 index 0000000..4911183 --- /dev/null +++ b/src/serial-example/src/serial_example_node.cpp @@ -0,0 +1,74 @@ +/*** + * This example expects the serial port has a loopback on it. + * + * Alternatively, you could use an Arduino: + * + *
+ *  void setup() {
+ *    Serial.begin();
+ *  }
+ *
+ *  void loop() {
+ *    if (Serial.available()) {
+ *      Serial.write(Serial.read());
+ *    }
+ *  }
+ * 
+ */ + +#include +#include +#include +#include + +serial::Serial ser; + +void write_callback(const std_msgs::String::ConstPtr& msg){ + ROS_INFO_STREAM("Writing to serial port" << msg->data); + ser.write(msg->data); +} + +int main (int argc, char** argv){ + ros::init(argc, argv, "serial_example_node"); + ros::NodeHandle nh; + + ros::Subscriber write_sub = nh.subscribe("write", 1000, write_callback); + ros::Publisher read_pub = nh.advertise("read", 1000); + + try + { + ser.setPort("/dev/ttyUSB0"); + serial::Timeout to = serial::Timeout::simpleTimeout(1000); + ser.setBaudrate(115200); + ser.setTimeout(to); + ser.open(); + } + catch (serial::IOException& e) + { + ROS_ERROR_STREAM("Unable to open port "); + return -1; + } + + if(ser.isOpen()){ + ROS_INFO_STREAM("Serial Port initialized"); + }else{ + return -1; + } + + ros::Rate loop_rate(1000); // comm velocity + while(ros::ok()){ + + ros::spinOnce(); + + if(ser.available()){ + //ROS_INFO_STREAM("Reading from serial port"); + std_msgs::String result; + result.data = ser.read(ser.available()); + //ROS_INFO_STREAM("Read: " << result.data); + ROS_INFO_STREAM(result.data); // print comm results + read_pub.publish(result); + } + loop_rate.sleep(); + + } +} diff --git a/src/serial_example_node.cpp b/src/serial_example_node.cpp index fdd289c..4911183 100644 --- a/src/serial_example_node.cpp +++ b/src/serial_example_node.cpp @@ -24,7 +24,7 @@ serial::Serial ser; void write_callback(const std_msgs::String::ConstPtr& msg){ - ROS_INFO_STREAM("Writing to serial port" << msg->data); + ROS_INFO_STREAM("Writing to serial port" << msg->data); ser.write(msg->data); } @@ -37,9 +37,9 @@ int main (int argc, char** argv){ try { - ser.setPort("/dev/ttyACM0"); - ser.setBaudrate(9600); + ser.setPort("/dev/ttyUSB0"); serial::Timeout to = serial::Timeout::simpleTimeout(1000); + ser.setBaudrate(115200); ser.setTimeout(to); ser.open(); } @@ -55,20 +55,20 @@ int main (int argc, char** argv){ return -1; } - ros::Rate loop_rate(5); + ros::Rate loop_rate(1000); // comm velocity while(ros::ok()){ ros::spinOnce(); if(ser.available()){ - ROS_INFO_STREAM("Reading from serial port"); + //ROS_INFO_STREAM("Reading from serial port"); std_msgs::String result; result.data = ser.read(ser.available()); - ROS_INFO_STREAM("Read: " << result.data); + //ROS_INFO_STREAM("Read: " << result.data); + ROS_INFO_STREAM(result.data); // print comm results read_pub.publish(result); } loop_rate.sleep(); } } -