diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..212386fe9c39a5fa8804e8ba83d7b6af144d7f80 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,84 @@ +# Pre-requisites about cmake itself +CMAKE_MINIMUM_REQUIRED(VERSION 2.4) + +if(COMMAND cmake_policy) + cmake_policy(SET CMP0005 NEW) + cmake_policy(SET CMP0003 NEW) +endif(COMMAND cmake_policy) + +# The project name and the type of project +PROJECT(comm) + +SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin) +SET(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib) +SET(CMAKE_INSTALL_PREFIX /usr/local) + +IF (NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE "DEBUG") +ENDIF (NOT CMAKE_BUILD_TYPE) + +SET(CMAKE_C_FLAGS_DEBUG "-g -Wall") +SET(CMAKE_C_FLAGS_RELEASE "-O3") + +ADD_SUBDIRECTORY(src) + +FIND_PACKAGE(Doxygen) + +FIND_PATH(IRI_DOC_DIR doxygen.conf ${CMAKE_SOURCE_DIR}/doc/iri_doc/) +IF (IRI_DOC_DIR) + ADD_CUSTOM_TARGET (doc ${DOXYGEN_EXECUTABLE} ${CMAKE_SOURCE_DIR}/doc/iri_doc/doxygen.conf) +ELSE (IRI_DOC_DIR) + ADD_CUSTOM_TARGET (doc ${DOXYGEN_EXECUTABLE} ${CMAKE_SOURCE_DIR}/doc/doxygen.conf) +ENDIF (IRI_DOC_DIR) + +ADD_CUSTOM_TARGET (distclean @echo cleaning cmake files) + +IF (UNIX) + ADD_CUSTOM_COMMAND( + COMMENT "distribution clean" + COMMAND make ARGS clean + COMMAND rm ARGS -rf ${CMAKE_SOURCE_DIR}/build/* + + TARGET distclean + ) +ELSE(UNIX) + ADD_CUSTOM_COMMAND( + COMMENT "distclean only implemented in unix" + TARGET distclean + ) +ENDIF(UNIX) + +ADD_CUSTOM_TARGET (uninstall @echo uninstall package) + +IF (UNIX) + ADD_CUSTOM_COMMAND( + COMMENT "uninstall package" + COMMAND xargs ARGS rm < install_manifest.txt + + TARGET uninstall + ) +ELSE(UNIX) + ADD_CUSTOM_COMMAND( + COMMENT "uninstall only implemented in unix" + TARGET uninstall + ) +ENDIF(UNIX) + + +IF (UNIX) + SET(CPACK_PACKAGE_FILE_NAME "iri-${PROJECT_NAME}-dev-${CPACK_PACKAGE_VERSION}${DISTRIB}${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") + SET(CPACK_PACKAGE_NAME "iri-${PROJECT_NAME}-dev") + SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Part of IRI-laboratory libraries. More information at http://wikiri.upc.es/index.php/Robotics_Lab") + SET(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) + SET(CPACK_GENERATOR "DEB") + SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "labrobotica@iri.upc.edu") + SET(CPACK_SET_DESTDIR "ON") # Necessary because of the absolute install paths + + INCLUDE(CPack) +ELSE(UNIX) + ADD_CUSTOM_COMMAND( + COMMENT "packaging only implemented in unix" + TARGET uninstall + ) +ENDIF(UNIX) + diff --git a/Findcomm.cmake b/Findcomm.cmake new file mode 100644 index 0000000000000000000000000000000000000000..8408e22ccf83143cb335c3760d48cbd32f8376c9 --- /dev/null +++ b/Findcomm.cmake @@ -0,0 +1,21 @@ +FIND_PATH(comm_INCLUDE_DIR comm.h commexceptions.h rs232.h rs232exceptions.h ftdiserver.h ftdimodule.h ftdiexcetpions.h socket.h socketclient.h socketserver.h socketexceptions.h /usr/include/iridrivers /usr/local/include/iridrivers) + + +FIND_LIBRARY(comm_LIBRARY + NAMES comm + PATHS /usr/lib /usr/local/lib /usr/local/lib/iridrivers) + +IF (comm_INCLUDE_DIR AND comm_LIBRARY) + SET(comm_FOUND TRUE) +ENDIF (comm_INCLUDE_DIR AND comm_LIBRARY) + +IF (comm_FOUND) + IF (NOT comm_FIND_QUIETLY) + MESSAGE(STATUS "Found comm library: ${comm_LIBRARY}") + ENDIF (NOT comm_FIND_QUIETLY) +ELSE (comm_FOUND) + IF (comm_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find comm library") + ENDIF (comm_FIND_REQUIRED) +ENDIF (comm_FOUND) + diff --git a/ReadMe.txt b/ReadMe.txt new file mode 100644 index 0000000000000000000000000000000000000000..6095168a12c39397be843096b9cd6f6d7800c403 --- /dev/null +++ b/ReadMe.txt @@ -0,0 +1,17 @@ +Copyright (C) 2009-2010 Institut de Robòtica i Informà tica Industrial, CSIC-UPC. +Author shernand (shernand@iri.upc.edu) +All rights reserved. + +This file is part of IRI communications library +IRI communications library is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see <http://www.gnu.org/licenses/> diff --git a/doc/doxygen.conf b/doc/doxygen.conf new file mode 100644 index 0000000000000000000000000000000000000000..347af2ef86bf4786d49b13d48c12146b0d001b01 --- /dev/null +++ b/doc/doxygen.conf @@ -0,0 +1,251 @@ +# Doxyfile 1.5.5 + +@INCLUDE_PATH = ../doc/ +@INCLUDE = doxygen_project_name.conf + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NUMBER = +OUTPUT_DIRECTORY = ../doc +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = NO +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +TYPEDEF_HIDES_STRUCT = NO +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = YES +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = ../src \ + ../doc/main.dox +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c \ + *.h \ + *.cpp +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = *.tab.c \ + *.tab.h \ + lex* \ + *glr.h \ + *llr.h \ + *glr.c \ + *llr.c \ + *general.h +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = ../src/examples +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = ../doc/images +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +HTML_DYNAMIC_SECTIONS = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = _USE_MPI=1 +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = NO +CALL_GRAPH = YES +CALLER_GRAPH = YES +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = NO +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 2 +DOT_TRANSPARENT = YES +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/doc/doxygen_project_name.conf b/doc/doxygen_project_name.conf new file mode 100644 index 0000000000000000000000000000000000000000..b8e4e368fcdb5e11cdbd8f9a97430c5f97ef6190 --- /dev/null +++ b/doc/doxygen_project_name.conf @@ -0,0 +1 @@ +PROJECT_NAME = "IRI communications" diff --git a/doc/images/comm_states.eps b/doc/images/comm_states.eps new file mode 100644 index 0000000000000000000000000000000000000000..0eb51e74a71d7b8a53d29b9699379e4582201846 --- /dev/null +++ b/doc/images/comm_states.eps @@ -0,0 +1,506 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%BoundingBox: 0 0 794 595 +%%Pages: 0 +%%Creator: Sun Microsystems, Inc. +%%Title: none +%%CreationDate: none +%%LanguageLevel: 2 +%%EndComments +%%BeginProlog +%%BeginResource: procset SDRes-Prolog 1.0 0 +/b4_inc_state save def +/dict_count countdictstack def +/op_count count 1 sub def +userdict begin +0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit[] 0 setdash newpath +/languagelevel where {pop languagelevel 1 ne {false setstrokeadjust false setoverprint} if} if +/bdef {bind def} bind def +/c {setgray} bdef +/l {neg lineto} bdef +/rl {neg rlineto} bdef +/lc {setlinecap} bdef +/lj {setlinejoin} bdef +/lw {setlinewidth} bdef +/ml {setmiterlimit} bdef +/ld {setdash} bdef +/m {neg moveto} bdef +/ct {6 2 roll neg 6 2 roll neg 6 2 roll neg curveto} bdef +/r {rotate} bdef +/t {neg translate} bdef +/s {scale} bdef +/sw {show} bdef +/gs {gsave} bdef +/gr {grestore} bdef +/f {findfont dup length dict begin +{1 index /FID ne {def} {pop pop} ifelse} forall /Encoding ISOLatin1Encoding def +currentdict end /NFont exch definefont pop /NFont findfont} bdef +/p {closepath} bdef +/sf {scalefont setfont} bdef +/ef {eofill}bdef +/pc {closepath stroke}bdef +/ps {stroke}bdef +/pum {matrix currentmatrix}bdef +/pom {setmatrix}bdef +/bs {/aString exch def /nXOfs exch def /nWidth exch def currentpoint nXOfs 0 rmoveto pum nWidth aString stringwidth pop div 1 scale aString show pom moveto} bdef +%%EndResource +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +%%EndPageSetup +pum +0.02835 0.02833 s +0 -21000 t +/tm matrix currentmatrix def +gs +0 0 m 27999 0 l 27999 20999 l 0 20999 l 0 0 l eoclip newpath +gs +0 0 m 27999 0 l 27999 20999 l 0 20999 l 0 0 l eoclip newpath +0 0 m 28000 0 l 28000 21000 l 0 21000 l 0 0 l eoclip newpath +gs +gs +pum +1402 19590 t +pom +gr +gr +gs +gs +pum +13937 19590 t +pom +gr +gr +0 lw 1 lj 0.000 c 10160 7773 m 9459 7773 8890 7204 8890 6503 ct 8890 5802 9459 5233 10160 5233 ct +10861 5233 11430 5802 11430 6503 ct 11430 7204 10861 7773 10160 7773 ct pc +gs +gs +pum +9340 6638 t +206 -223 m 206 -187 l 196 -193 185 -197 174 -200 ct 163 -203 152 -205 141 -205 ct +117 -205 97 -197 84 -181 ct 70 -166 63 -144 63 -115 ct 63 -87 70 -65 84 -50 ct +97 -34 117 -26 141 -26 ct 152 -26 163 -28 174 -31 ct 185 -34 196 -38 206 -44 ct +206 -9 l 196 -4 185 0 173 2 ct 162 5 150 6 137 6 ct 102 6 75 -5 54 -27 ct 34 -49 23 -78 23 -115 ct +23 -153 34 -183 54 -205 ct 75 -226 104 -237 140 -237 ct 151 -237 163 -236 174 -233 ct +185 -231 196 -227 206 -223 ct p ef +407 -196 m 403 -198 398 -200 393 -201 ct 388 -203 382 -203 376 -203 ct 355 -203 338 -196 327 -182 ct +315 -168 310 -148 310 -122 ct 310 0 l 271 0 l 271 -231 l 310 -231 l 310 -195 l +318 -210 328 -220 341 -227 ct 354 -234 369 -237 388 -237 ct 390 -237 393 -237 396 -236 ct +399 -236 403 -236 407 -235 ct 407 -196 l p ef +644 -125 m 644 -106 l 469 -106 l 471 -80 479 -60 493 -47 ct 507 -33 526 -26 552 -26 ct +566 -26 580 -28 594 -32 ct 608 -35 621 -41 635 -48 ct 635 -12 l 621 -6 607 -2 593 1 ct +579 4 564 6 549 6 ct 512 6 483 -5 462 -26 ct 440 -48 429 -77 429 -113 ct 429 -151 440 -181 460 -204 ct +480 -226 508 -237 543 -237 ct 574 -237 598 -227 617 -207 ct 635 -187 644 -160 644 -125 ct +p +606 -136 m 605 -157 600 -174 588 -186 ct 577 -199 562 -205 543 -205 ct 522 -205 505 -199 492 -187 ct +480 -175 472 -158 470 -136 ct 606 -136 l p ef +814 -116 m 783 -116 762 -113 750 -106 ct 738 -99 732 -87 732 -70 ct 732 -56 737 -46 746 -38 ct +755 -30 767 -26 782 -26 ct 803 -26 820 -33 833 -48 ct 845 -63 852 -83 852 -108 ct +852 -116 l 814 -116 l p +890 -132 m 890 0 l 852 0 l 852 -35 l 843 -21 832 -11 819 -4 ct 806 3 791 6 772 6 ct +748 6 729 -1 715 -14 ct 701 -27 694 -45 694 -67 ct 694 -93 703 -113 721 -126 ct +738 -139 764 -146 799 -146 ct 852 -146 l 852 -150 l 852 -167 846 -181 835 -190 ct +823 -200 807 -205 786 -205 ct 773 -205 760 -203 747 -200 ct 735 -197 723 -192 711 -186 ct +711 -221 l 725 -226 739 -230 752 -233 ct 765 -236 778 -237 790 -237 ct 823 -237 848 -228 865 -211 ct +882 -194 890 -167 890 -132 ct p ef +1004 -297 m 1004 -232 l 1083 -232 l 1083 -202 l 1004 -202 l 1004 -76 l +1004 -57 1007 -45 1012 -40 ct 1017 -35 1028 -32 1044 -32 ct 1083 -32 l 1083 0 l +1044 0 l 1014 0 994 -5 983 -16 ct 972 -27 966 -47 966 -76 ct 966 -202 l 938 -202 l +938 -232 l 966 -232 l 966 -297 l 1004 -297 l p ef +1330 -125 m 1330 -106 l 1155 -106 l 1157 -80 1165 -60 1179 -47 ct 1193 -33 1212 -26 1238 -26 ct +1252 -26 1266 -28 1280 -32 ct 1294 -35 1307 -41 1321 -48 ct 1321 -12 l 1307 -6 1293 -2 1279 1 ct +1265 4 1250 6 1235 6 ct 1198 6 1169 -5 1148 -26 ct 1126 -48 1115 -77 1115 -113 ct +1115 -151 1126 -181 1146 -204 ct 1166 -226 1194 -237 1229 -237 ct 1260 -237 1284 -227 1303 -207 ct +1321 -187 1330 -160 1330 -125 ct p +1292 -136 m 1291 -157 1286 -174 1274 -186 ct 1263 -199 1248 -205 1229 -205 ct +1208 -205 1191 -199 1178 -187 ct 1166 -175 1158 -158 1156 -136 ct 1292 -136 l +p ef +1547 -197 m 1547 -322 l 1585 -322 l 1585 0 l 1547 0 l 1547 -35 l +1539 -21 1529 -11 1517 -4 ct 1505 2 1490 6 1473 6 ct 1445 6 1422 -5 1405 -28 ct +1387 -50 1378 -80 1378 -116 ct 1378 -152 1387 -182 1405 -204 ct 1422 -227 1445 -238 1473 -238 ct +1490 -238 1505 -234 1517 -228 ct 1529 -221 1539 -211 1547 -197 ct p +1418 -116 m 1418 -88 1423 -66 1435 -50 ct 1446 -34 1462 -26 1482 -26 ct 1502 -26 1518 -34 1530 -50 ct +1541 -66 1547 -88 1547 -116 ct 1547 -144 1541 -166 1530 -182 ct 1518 -198 1502 -206 1482 -206 ct +1462 -206 1446 -198 1435 -182 ct 1423 -166 1418 -144 1418 -116 ct p ef +pom +gr +gr +19685 7773 m 18984 7773 18415 7204 18415 6503 ct 18415 5802 18984 5233 19685 5233 ct +20386 5233 20955 5802 20955 6503 ct 20955 7204 20386 7773 19685 7773 ct pc +gs +gs +pum +18891 6638 t +130 -205 m 109 -205 93 -197 81 -181 ct 69 -165 63 -143 63 -115 ct 63 -88 69 -66 81 -50 ct +93 -34 109 -26 130 -26 ct 150 -26 166 -34 178 -50 ct 189 -66 195 -88 195 -116 ct +195 -143 189 -165 178 -181 ct 166 -197 150 -205 130 -205 ct p +130 -237 m 163 -237 189 -226 207 -205 ct 226 -183 236 -154 236 -115 ct 236 -78 226 -48 207 -26 ct +189 -5 163 6 130 6 ct 96 6 70 -5 52 -26 ct 33 -48 23 -78 23 -115 ct 23 -154 33 -183 52 -205 ct +70 -226 96 -237 130 -237 ct p ef +335 -35 m 335 88 l 296 88 l 296 -231 l 335 -231 l 335 -196 l 343 -210 353 -220 365 -227 ct +377 -234 392 -237 409 -237 ct 437 -237 459 -226 477 -204 ct 495 -181 503 -152 503 -115 ct +503 -79 495 -50 477 -27 ct 459 -5 437 6 409 6 ct 392 6 377 3 365 -4 ct 353 -11 343 -21 335 -35 ct +p +464 -116 m 464 -143 458 -165 447 -181 ct 435 -197 419 -205 399 -205 ct 379 -205 363 -197 352 -181 ct +340 -165 335 -143 335 -115 ct 335 -88 340 -66 352 -50 ct 363 -34 379 -26 399 -26 ct +419 -26 435 -34 447 -50 ct 458 -66 464 -88 464 -116 ct p ef +763 -125 m 763 -106 l 588 -106 l 590 -80 598 -60 612 -47 ct 626 -33 645 -26 671 -26 ct +685 -26 699 -28 713 -32 ct 727 -35 740 -41 754 -48 ct 754 -12 l 740 -6 726 -2 712 1 ct +698 4 683 6 668 6 ct 631 6 602 -5 581 -26 ct 559 -48 548 -77 548 -113 ct 548 -151 559 -181 579 -204 ct +599 -226 627 -237 662 -237 ct 693 -237 717 -227 736 -207 ct 754 -187 763 -160 763 -125 ct +p +725 -136 m 724 -157 719 -174 707 -186 ct 696 -199 681 -205 662 -205 ct 641 -205 624 -199 611 -187 ct +599 -175 591 -158 589 -136 ct 725 -136 l p ef +1019 -140 m 1019 0 l 981 0 l 981 -138 l 981 -160 977 -177 968 -188 ct +960 -199 947 -204 930 -204 ct 909 -204 893 -197 881 -184 ct 870 -171 864 -153 864 -131 ct +864 0 l 825 0 l 825 -231 l 864 -231 l 864 -195 l 873 -209 883 -220 896 -227 ct +908 -234 922 -237 938 -237 ct 965 -237 985 -229 999 -212 ct 1012 -196 1019 -172 1019 -140 ct +p ef +1292 -125 m 1292 -106 l 1117 -106 l 1119 -80 1127 -60 1141 -47 ct 1155 -33 1174 -26 1200 -26 ct +1214 -26 1228 -28 1242 -32 ct 1256 -35 1269 -41 1283 -48 ct 1283 -12 l 1269 -6 1255 -2 1241 1 ct +1227 4 1212 6 1197 6 ct 1160 6 1131 -5 1110 -26 ct 1088 -48 1077 -77 1077 -113 ct +1077 -151 1088 -181 1108 -204 ct 1128 -226 1156 -237 1191 -237 ct 1222 -237 1246 -227 1265 -207 ct +1283 -187 1292 -160 1292 -125 ct p +1254 -136 m 1253 -157 1248 -174 1236 -186 ct 1225 -199 1210 -205 1191 -205 ct +1170 -205 1153 -199 1140 -187 ct 1128 -175 1120 -158 1118 -136 ct 1254 -136 l +p ef +1509 -197 m 1509 -322 l 1547 -322 l 1547 0 l 1509 0 l 1509 -35 l +1501 -21 1491 -11 1479 -4 ct 1467 2 1452 6 1435 6 ct 1407 6 1384 -5 1367 -28 ct +1349 -50 1340 -80 1340 -116 ct 1340 -152 1349 -182 1367 -204 ct 1384 -227 1407 -238 1435 -238 ct +1452 -238 1467 -234 1479 -228 ct 1491 -221 1501 -211 1509 -197 ct p +1380 -116 m 1380 -88 1385 -66 1397 -50 ct 1408 -34 1424 -26 1444 -26 ct 1464 -26 1480 -34 1492 -50 ct +1503 -66 1509 -88 1509 -116 ct 1509 -144 1503 -166 1492 -182 ct 1480 -198 1464 -206 1444 -206 ct +1424 -206 1408 -198 1397 -182 ct 1385 -166 1380 -144 1380 -116 ct p ef +pom +gr +gr +19685 15393 m 18984 15393 18415 14824 18415 14123 ct 18415 13422 18984 12853 19685 12853 ct +20386 12853 20955 13422 20955 14123 ct 20955 14824 20386 15393 19685 15393 ct +pc +gs +gs +pum +18547 14258 t +206 -223 m 206 -187 l 196 -193 185 -197 174 -200 ct 163 -203 152 -205 141 -205 ct +117 -205 97 -197 84 -181 ct 70 -166 63 -144 63 -115 ct 63 -87 70 -65 84 -50 ct +97 -34 117 -26 141 -26 ct 152 -26 163 -28 174 -31 ct 185 -34 196 -38 206 -44 ct +206 -9 l 196 -4 185 0 173 2 ct 162 5 150 6 137 6 ct 102 6 75 -5 54 -27 ct 34 -49 23 -78 23 -115 ct +23 -153 34 -183 54 -205 ct 75 -226 104 -237 140 -237 ct 151 -237 163 -236 174 -233 ct +185 -231 196 -227 206 -223 ct p ef +363 -205 m 342 -205 326 -197 314 -181 ct 302 -165 296 -143 296 -115 ct 296 -88 302 -66 314 -50 ct +326 -34 342 -26 363 -26 ct 383 -26 399 -34 411 -50 ct 422 -66 428 -88 428 -116 ct +428 -143 422 -165 411 -181 ct 399 -197 383 -205 363 -205 ct p +363 -237 m 396 -237 422 -226 440 -205 ct 459 -183 469 -154 469 -115 ct 469 -78 459 -48 440 -26 ct +422 -5 396 6 363 6 ct 329 6 303 -5 285 -26 ct 266 -48 256 -78 256 -115 ct 256 -154 266 -183 285 -205 ct +303 -226 329 -237 363 -237 ct p ef +723 -140 m 723 0 l 685 0 l 685 -138 l 685 -160 681 -177 672 -188 ct 664 -199 651 -204 634 -204 ct +613 -204 597 -197 585 -184 ct 574 -171 568 -153 568 -131 ct 568 0 l 529 0 l +529 -231 l 568 -231 l 568 -195 l 577 -209 587 -220 600 -227 ct 612 -234 626 -237 642 -237 ct +669 -237 689 -229 703 -212 ct 716 -196 723 -172 723 -140 ct p ef +915 -322 m 915 -290 l 879 -290 l 865 -290 856 -288 850 -282 ct 845 -277 842 -267 842 -252 ct +842 -232 l 905 -232 l 905 -202 l 842 -202 l 842 0 l 804 0 l 804 -202 l +768 -202 l 768 -232 l 804 -232 l 804 -248 l 804 -274 810 -293 822 -304 ct +834 -316 853 -322 879 -322 ct 915 -322 l p ef +946 -232 m 984 -232 l 984 -1 l 946 -1 l 946 -232 l p +946 -322 m 984 -322 l 984 -274 l 946 -274 l 946 -322 l p ef +1216 -118 m 1216 -146 1210 -167 1199 -182 ct 1188 -198 1172 -205 1151 -205 ct +1131 -205 1115 -198 1104 -182 ct 1092 -167 1087 -146 1087 -118 ct 1087 -91 1092 -70 1104 -55 ct +1115 -39 1131 -32 1151 -32 ct 1172 -32 1188 -39 1199 -55 ct 1210 -70 1216 -91 1216 -118 ct +p +1254 -29 m 1254 11 1245 40 1228 59 ct 1210 78 1184 88 1148 88 ct 1134 88 1122 87 1110 85 ct +1098 83 1086 80 1075 76 ct 1075 39 l 1086 45 1097 49 1108 52 ct 1119 55 1130 57 1142 57 ct +1166 57 1185 50 1197 37 ct 1210 24 1216 4 1216 -22 ct 1216 -41 l 1208 -27 1198 -17 1186 -10 ct +1174 -3 1159 0 1142 0 ct 1114 0 1091 -11 1073 -32 ct 1056 -54 1047 -83 1047 -118 ct +1047 -154 1056 -183 1073 -205 ct 1091 -226 1114 -237 1142 -237 ct 1159 -237 1174 -234 1186 -227 ct +1198 -220 1208 -210 1216 -196 ct 1216 -231 l 1254 -231 l 1254 -29 l p ef +1327 -91 m 1327 -231 l 1365 -231 l 1365 -93 l 1365 -71 1369 -54 1378 -43 ct +1386 -33 1399 -27 1416 -27 ct 1437 -27 1453 -34 1465 -47 ct 1477 -60 1483 -78 1483 -100 ct +1483 -231 l 1521 -231 l 1521 0 l 1483 0 l 1483 -36 l 1473 -21 1463 -11 1451 -4 ct +1438 3 1424 6 1408 6 ct 1382 6 1361 -2 1348 -19 ct 1334 -35 1327 -59 1327 -91 ct +p +p ef +1732 -196 m 1728 -198 1723 -200 1718 -201 ct 1713 -203 1707 -203 1701 -203 ct +1680 -203 1663 -196 1652 -182 ct 1640 -168 1635 -148 1635 -122 ct 1635 0 l 1596 0 l +1596 -231 l 1635 -231 l 1635 -195 l 1643 -210 1653 -220 1666 -227 ct 1679 -234 1694 -237 1713 -237 ct +1715 -237 1718 -237 1721 -236 ct 1724 -236 1728 -236 1732 -235 ct 1732 -196 l +p ef +1969 -125 m 1969 -106 l 1794 -106 l 1796 -80 1804 -60 1818 -47 ct 1832 -33 1851 -26 1877 -26 ct +1891 -26 1905 -28 1919 -32 ct 1933 -35 1946 -41 1960 -48 ct 1960 -12 l 1946 -6 1932 -2 1918 1 ct +1904 4 1889 6 1874 6 ct 1837 6 1808 -5 1787 -26 ct 1765 -48 1754 -77 1754 -113 ct +1754 -151 1765 -181 1785 -204 ct 1805 -226 1833 -237 1868 -237 ct 1899 -237 1923 -227 1942 -207 ct +1960 -187 1969 -160 1969 -125 ct p +1931 -136 m 1930 -157 1925 -174 1913 -186 ct 1902 -199 1887 -205 1868 -205 ct +1847 -205 1830 -199 1817 -187 ct 1805 -175 1797 -158 1795 -136 ct 1931 -136 l +p ef +2186 -197 m 2186 -322 l 2224 -322 l 2224 0 l 2186 0 l 2186 -35 l +2178 -21 2168 -11 2156 -4 ct 2144 2 2129 6 2112 6 ct 2084 6 2061 -5 2044 -28 ct +2026 -50 2017 -80 2017 -116 ct 2017 -152 2026 -182 2044 -204 ct 2061 -227 2084 -238 2112 -238 ct +2129 -238 2144 -234 2156 -228 ct 2168 -221 2178 -211 2186 -197 ct p +2057 -116 m 2057 -88 2062 -66 2074 -50 ct 2085 -34 2101 -26 2121 -26 ct 2141 -26 2157 -34 2169 -50 ct +2180 -66 2186 -88 2186 -116 ct 2186 -144 2180 -166 2169 -182 ct 2157 -198 2141 -206 2121 -206 ct +2101 -206 2085 -198 2074 -182 ct 2062 -166 2057 -144 2057 -116 ct p ef +pom +gr +gr +18785 5603 m 18414 5307 l 18666 5144 l 18785 5603 l p ef +1 lw 0 lj 11058 5603 m 11064 5543 l 11080 5484 l 11107 5428 l 11145 5374 l +11249 5270 l 11390 5175 l 11565 5087 l 11771 5007 l 12265 4869 l 12850 4762 l +13503 4685 l 14922 4624 l 16340 4685 l 16993 4762 l 17578 4869 l 18072 5007 l +18278 5087 l 18453 5175 l 18594 5270 l 18620 5296 l ps +gs +gs +pum +14076 4336 t +130 -205 m 109 -205 93 -197 81 -181 ct 69 -165 63 -143 63 -115 ct 63 -88 69 -66 81 -50 ct +93 -34 109 -26 130 -26 ct 150 -26 166 -34 178 -50 ct 189 -66 195 -88 195 -116 ct +195 -143 189 -165 178 -181 ct 166 -197 150 -205 130 -205 ct p +130 -237 m 163 -237 189 -226 207 -205 ct 226 -183 236 -154 236 -115 ct 236 -78 226 -48 207 -26 ct +189 -5 163 6 130 6 ct 96 6 70 -5 52 -26 ct 33 -48 23 -78 23 -115 ct 23 -154 33 -183 52 -205 ct +70 -226 96 -237 130 -237 ct p ef +335 -35 m 335 88 l 296 88 l 296 -231 l 335 -231 l 335 -196 l 343 -210 353 -220 365 -227 ct +377 -234 392 -237 409 -237 ct 437 -237 459 -226 477 -204 ct 495 -181 503 -152 503 -115 ct +503 -79 495 -50 477 -27 ct 459 -5 437 6 409 6 ct 392 6 377 3 365 -4 ct 353 -11 343 -21 335 -35 ct +p +464 -116 m 464 -143 458 -165 447 -181 ct 435 -197 419 -205 399 -205 ct 379 -205 363 -197 352 -181 ct +340 -165 335 -143 335 -115 ct 335 -88 340 -66 352 -50 ct 363 -34 379 -26 399 -26 ct +419 -26 435 -34 447 -50 ct 458 -66 464 -88 464 -116 ct p ef +763 -125 m 763 -106 l 588 -106 l 590 -80 598 -60 612 -47 ct 626 -33 645 -26 671 -26 ct +685 -26 699 -28 713 -32 ct 727 -35 740 -41 754 -48 ct 754 -12 l 740 -6 726 -2 712 1 ct +698 4 683 6 668 6 ct 631 6 602 -5 581 -26 ct 559 -48 548 -77 548 -113 ct 548 -151 559 -181 579 -204 ct +599 -226 627 -237 662 -237 ct 693 -237 717 -227 736 -207 ct 754 -187 763 -160 763 -125 ct +p +725 -136 m 724 -157 719 -174 707 -186 ct 696 -199 681 -205 662 -205 ct 641 -205 624 -199 611 -187 ct +599 -175 591 -158 589 -136 ct 725 -136 l p ef +1019 -140 m 1019 0 l 981 0 l 981 -138 l 981 -160 977 -177 968 -188 ct +960 -199 947 -204 930 -204 ct 909 -204 893 -197 881 -184 ct 870 -171 864 -153 864 -131 ct +864 0 l 825 0 l 825 -231 l 864 -231 l 864 -195 l 873 -209 883 -220 896 -227 ct +908 -234 922 -237 938 -237 ct 965 -237 985 -229 999 -212 ct 1012 -196 1019 -172 1019 -140 ct +p ef +1185 -322 m 1167 -290 1153 -259 1144 -228 ct 1135 -197 1131 -166 1131 -134 ct +1131 -102 1135 -70 1144 -39 ct 1153 -8 1167 23 1185 55 ct 1152 55 l 1131 23 1116 -9 1106 -41 ct +1095 -72 1090 -103 1090 -134 ct 1090 -164 1095 -195 1106 -227 ct 1116 -258 1131 -289 1152 -322 ct +1185 -322 l p ef +1253 -322 m 1286 -322 l 1307 -289 1322 -258 1332 -227 ct 1343 -195 1348 -164 1348 -134 ct +1348 -103 1343 -72 1332 -41 ct 1322 -9 1307 23 1286 55 ct 1253 55 l 1271 23 1285 -8 1294 -39 ct +1303 -70 1307 -102 1307 -134 ct 1307 -166 1303 -197 1294 -228 ct 1285 -259 1271 -290 1253 -322 ct +p ef +pom +gr +gr +11058 7401 m 11508 7251 l 11508 7551 l 11058 7401 l p ef +18785 7401 m 11418 7401 l ps +gs +gs +pum +14208 7247 t +206 -223 m 206 -187 l 196 -193 185 -197 174 -200 ct 163 -203 152 -205 141 -205 ct +117 -205 97 -197 84 -181 ct 70 -166 63 -144 63 -115 ct 63 -87 70 -65 84 -50 ct +97 -34 117 -26 141 -26 ct 152 -26 163 -28 174 -31 ct 185 -34 196 -38 206 -44 ct +206 -9 l 196 -4 185 0 173 2 ct 162 5 150 6 137 6 ct 102 6 75 -5 54 -27 ct 34 -49 23 -78 23 -115 ct +23 -153 34 -183 54 -205 ct 75 -226 104 -237 140 -237 ct 151 -237 163 -236 174 -233 ct +185 -231 196 -227 206 -223 ct p ef +273 -322 m 311 -322 l 311 0 l 273 0 l 273 -322 l p ef +481 -205 m 460 -205 444 -197 432 -181 ct 420 -165 414 -143 414 -115 ct 414 -88 420 -66 432 -50 ct +444 -34 460 -26 481 -26 ct 501 -26 517 -34 529 -50 ct 540 -66 546 -88 546 -116 ct +546 -143 540 -165 529 -181 ct 517 -197 501 -205 481 -205 ct p +481 -237 m 514 -237 540 -226 558 -205 ct 577 -183 587 -154 587 -115 ct 587 -78 577 -48 558 -26 ct +540 -5 514 6 481 6 ct 447 6 421 -5 403 -26 ct 384 -48 374 -78 374 -115 ct 374 -154 384 -183 403 -205 ct +421 -226 447 -237 481 -237 ct p ef +797 -225 m 797 -189 l 787 -194 775 -198 764 -201 ct 752 -204 740 -205 728 -205 ct +709 -205 695 -202 685 -197 ct 676 -191 671 -182 671 -170 ct 671 -162 675 -155 681 -150 ct +688 -145 702 -140 722 -135 ct 735 -132 l 762 -127 781 -119 793 -108 ct 804 -97 810 -83 810 -64 ct +810 -42 801 -25 784 -13 ct 767 0 744 6 714 6 ct 702 6 689 5 675 2 ct 662 0 648 -4 633 -8 ct +633 -48 l 647 -40 661 -35 674 -31 ct 688 -28 702 -26 715 -26 ct 733 -26 747 -29 756 -35 ct +766 -41 771 -50 771 -61 ct 771 -71 767 -79 760 -85 ct 753 -90 738 -96 715 -101 ct +701 -104 l 678 -109 661 -116 650 -127 ct 640 -137 635 -151 635 -169 ct 635 -191 642 -207 658 -219 ct +673 -231 695 -237 723 -237 ct 737 -237 751 -236 763 -234 ct 775 -232 787 -229 797 -225 ct +p ef +1068 -125 m 1068 -106 l 893 -106 l 895 -80 903 -60 917 -47 ct 931 -33 950 -26 976 -26 ct +990 -26 1004 -28 1018 -32 ct 1032 -35 1045 -41 1059 -48 ct 1059 -12 l 1045 -6 1031 -2 1017 1 ct +1003 4 988 6 973 6 ct 936 6 907 -5 886 -26 ct 864 -48 853 -77 853 -113 ct 853 -151 864 -181 884 -204 ct +904 -226 932 -237 967 -237 ct 998 -237 1022 -227 1041 -207 ct 1059 -187 1068 -160 1068 -125 ct +p +1030 -136 m 1029 -157 1024 -174 1012 -186 ct 1001 -199 986 -205 967 -205 ct +946 -205 929 -199 916 -187 ct 904 -175 896 -158 894 -136 ct 1030 -136 l p ef +1223 -322 m 1205 -290 1191 -259 1182 -228 ct 1173 -197 1169 -166 1169 -134 ct +1169 -102 1173 -70 1182 -39 ct 1191 -8 1205 23 1223 55 ct 1190 55 l 1169 23 1154 -9 1144 -41 ct +1133 -72 1128 -103 1128 -134 ct 1128 -164 1133 -195 1144 -227 ct 1154 -258 1169 -289 1190 -322 ct +1223 -322 l p ef +1291 -322 m 1324 -322 l 1345 -289 1360 -258 1370 -227 ct 1381 -195 1386 -164 1386 -134 ct +1386 -103 1381 -72 1370 -41 ct 1360 -9 1345 23 1324 55 ct 1291 55 l 1309 23 1323 -8 1332 -39 ct +1341 -70 1345 -102 1345 -134 ct 1345 -166 1341 -197 1332 -228 ct 1323 -259 1309 -290 1291 -322 ct +p ef +pom +gr +gr +10159 5233 m 9910 4829 l 10202 4761 l 10159 5233 l p ef +4475 2927 m 4479 2989 l 4491 3047 l 4511 3101 l 4539 3153 l 4616 3246 l +4719 3329 l 4848 3400 l 5000 3462 l 5363 3563 l 5793 3638 l 6273 3697 l +7317 3799 l 8361 3936 l 8841 4039 l 9271 4175 l 9462 4259 l 9634 4355 l +9786 4463 l 9915 4585 l 10018 4722 l 10095 4875 l 10097 4880 l ps +gs +gs +pum +5953 3331 t +206 -223 m 206 -187 l 196 -193 185 -197 174 -200 ct 163 -203 152 -205 141 -205 ct +117 -205 97 -197 84 -181 ct 70 -166 63 -144 63 -115 ct 63 -87 70 -65 84 -50 ct +97 -34 117 -26 141 -26 ct 152 -26 163 -28 174 -31 ct 185 -34 196 -38 206 -44 ct +206 -9 l 196 -4 185 0 173 2 ct 162 5 150 6 137 6 ct 102 6 75 -5 54 -27 ct 34 -49 23 -78 23 -115 ct +23 -153 34 -183 54 -205 ct 75 -226 104 -237 140 -237 ct 151 -237 163 -236 174 -233 ct +185 -231 196 -227 206 -223 ct p ef +407 -196 m 403 -198 398 -200 393 -201 ct 388 -203 382 -203 376 -203 ct 355 -203 338 -196 327 -182 ct +315 -168 310 -148 310 -122 ct 310 0 l 271 0 l 271 -231 l 310 -231 l 310 -195 l +318 -210 328 -220 341 -227 ct 354 -234 369 -237 388 -237 ct 390 -237 393 -237 396 -236 ct +399 -236 403 -236 407 -235 ct 407 -196 l p ef +644 -125 m 644 -106 l 469 -106 l 471 -80 479 -60 493 -47 ct 507 -33 526 -26 552 -26 ct +566 -26 580 -28 594 -32 ct 608 -35 621 -41 635 -48 ct 635 -12 l 621 -6 607 -2 593 1 ct +579 4 564 6 549 6 ct 512 6 483 -5 462 -26 ct 440 -48 429 -77 429 -113 ct 429 -151 440 -181 460 -204 ct +480 -226 508 -237 543 -237 ct 574 -237 598 -227 617 -207 ct 635 -187 644 -160 644 -125 ct +p +606 -136 m 605 -157 600 -174 588 -186 ct 577 -199 562 -205 543 -205 ct 522 -205 505 -199 492 -187 ct +480 -175 472 -158 470 -136 ct 606 -136 l p ef +814 -116 m 783 -116 762 -113 750 -106 ct 738 -99 732 -87 732 -70 ct 732 -56 737 -46 746 -38 ct +755 -30 767 -26 782 -26 ct 803 -26 820 -33 833 -48 ct 845 -63 852 -83 852 -108 ct +852 -116 l 814 -116 l p +890 -132 m 890 0 l 852 0 l 852 -35 l 843 -21 832 -11 819 -4 ct 806 3 791 6 772 6 ct +748 6 729 -1 715 -14 ct 701 -27 694 -45 694 -67 ct 694 -93 703 -113 721 -126 ct +738 -139 764 -146 799 -146 ct 852 -146 l 852 -150 l 852 -167 846 -181 835 -190 ct +823 -200 807 -205 786 -205 ct 773 -205 760 -203 747 -200 ct 735 -197 723 -192 711 -186 ct +711 -221 l 725 -226 739 -230 752 -233 ct 765 -236 778 -237 790 -237 ct 823 -237 848 -228 865 -211 ct +882 -194 890 -167 890 -132 ct p ef +1004 -297 m 1004 -232 l 1083 -232 l 1083 -202 l 1004 -202 l 1004 -76 l +1004 -57 1007 -45 1012 -40 ct 1017 -35 1028 -32 1044 -32 ct 1083 -32 l 1083 0 l +1044 0 l 1014 0 994 -5 983 -16 ct 972 -27 966 -47 966 -76 ct 966 -202 l 938 -202 l +938 -232 l 966 -232 l 966 -297 l 1004 -297 l p ef +1330 -125 m 1330 -106 l 1155 -106 l 1157 -80 1165 -60 1179 -47 ct 1193 -33 1212 -26 1238 -26 ct +1252 -26 1266 -28 1280 -32 ct 1294 -35 1307 -41 1321 -48 ct 1321 -12 l 1307 -6 1293 -2 1279 1 ct +1265 4 1250 6 1235 6 ct 1198 6 1169 -5 1148 -26 ct 1126 -48 1115 -77 1115 -113 ct +1115 -151 1126 -181 1146 -204 ct 1166 -226 1194 -237 1229 -237 ct 1260 -237 1284 -227 1303 -207 ct +1321 -187 1330 -160 1330 -125 ct p +1292 -136 m 1291 -157 1286 -174 1274 -186 ct 1263 -199 1248 -205 1229 -205 ct +1208 -205 1191 -199 1178 -187 ct 1166 -175 1158 -158 1156 -136 ct 1292 -136 l +p ef +1486 -322 m 1468 -290 1454 -259 1445 -228 ct 1436 -197 1432 -166 1432 -134 ct +1432 -102 1436 -70 1445 -39 ct 1454 -8 1468 23 1486 55 ct 1453 55 l 1432 23 1417 -9 1407 -41 ct +1396 -72 1391 -103 1391 -134 ct 1391 -164 1396 -195 1407 -227 ct 1417 -258 1432 -289 1453 -322 ct +1486 -322 l p ef +1554 -322 m 1587 -322 l 1608 -289 1623 -258 1633 -227 ct 1644 -195 1649 -164 1649 -134 ct +1649 -103 1644 -72 1633 -41 ct 1623 -9 1608 23 1587 55 ct 1554 55 l 1572 23 1586 -8 1595 -39 ct +1604 -70 1608 -102 1608 -134 ct 1608 -166 1604 -197 1595 -228 ct 1586 -259 1572 -290 1554 -322 ct +p ef +pom +gr +gr +19684 12853 m 19549 12398 l 19849 12408 l 19684 12853 l p ef +20583 7401 m 20544 8733 l 20500 9200 l 20443 9560 l 20374 9834 l 20299 10041 l +20217 10202 l 20134 10336 l 20050 10464 l 19968 10605 l 19893 10780 l +19824 11008 l 19767 11309 l 19723 11704 l 19696 12493 l ps +gs +gs +pum +20558 10554 t +206 -223 m 206 -187 l 196 -193 185 -197 174 -200 ct 163 -203 152 -205 141 -205 ct +117 -205 97 -197 84 -181 ct 70 -166 63 -144 63 -115 ct 63 -87 70 -65 84 -50 ct +97 -34 117 -26 141 -26 ct 152 -26 163 -28 174 -31 ct 185 -34 196 -38 206 -44 ct +206 -9 l 196 -4 185 0 173 2 ct 162 5 150 6 137 6 ct 102 6 75 -5 54 -27 ct 34 -49 23 -78 23 -115 ct +23 -153 34 -183 54 -205 ct 75 -226 104 -237 140 -237 ct 151 -237 163 -236 174 -233 ct +185 -231 196 -227 206 -223 ct p ef +363 -205 m 342 -205 326 -197 314 -181 ct 302 -165 296 -143 296 -115 ct 296 -88 302 -66 314 -50 ct +326 -34 342 -26 363 -26 ct 383 -26 399 -34 411 -50 ct 422 -66 428 -88 428 -116 ct +428 -143 422 -165 411 -181 ct 399 -197 383 -205 363 -205 ct p +363 -237 m 396 -237 422 -226 440 -205 ct 459 -183 469 -154 469 -115 ct 469 -78 459 -48 440 -26 ct +422 -5 396 6 363 6 ct 329 6 303 -5 285 -26 ct 266 -48 256 -78 256 -115 ct 256 -154 266 -183 285 -205 ct +303 -226 329 -237 363 -237 ct p ef +723 -140 m 723 0 l 685 0 l 685 -138 l 685 -160 681 -177 672 -188 ct 664 -199 651 -204 634 -204 ct +613 -204 597 -197 585 -184 ct 574 -171 568 -153 568 -131 ct 568 0 l 529 0 l +529 -231 l 568 -231 l 568 -195 l 577 -209 587 -220 600 -227 ct 612 -234 626 -237 642 -237 ct +669 -237 689 -229 703 -212 ct 716 -196 723 -172 723 -140 ct p ef +915 -322 m 915 -290 l 879 -290 l 865 -290 856 -288 850 -282 ct 845 -277 842 -267 842 -252 ct +842 -232 l 905 -232 l 905 -202 l 842 -202 l 842 0 l 804 0 l 804 -202 l +768 -202 l 768 -232 l 804 -232 l 804 -248 l 804 -274 810 -293 822 -304 ct +834 -316 853 -322 879 -322 ct 915 -322 l p ef +946 -232 m 984 -232 l 984 -1 l 946 -1 l 946 -232 l p +946 -322 m 984 -322 l 984 -274 l 946 -274 l 946 -322 l p ef +1216 -118 m 1216 -146 1210 -167 1199 -182 ct 1188 -198 1172 -205 1151 -205 ct +1131 -205 1115 -198 1104 -182 ct 1092 -167 1087 -146 1087 -118 ct 1087 -91 1092 -70 1104 -55 ct +1115 -39 1131 -32 1151 -32 ct 1172 -32 1188 -39 1199 -55 ct 1210 -70 1216 -91 1216 -118 ct +p +1254 -29 m 1254 11 1245 40 1228 59 ct 1210 78 1184 88 1148 88 ct 1134 88 1122 87 1110 85 ct +1098 83 1086 80 1075 76 ct 1075 39 l 1086 45 1097 49 1108 52 ct 1119 55 1130 57 1142 57 ct +1166 57 1185 50 1197 37 ct 1210 24 1216 4 1216 -22 ct 1216 -41 l 1208 -27 1198 -17 1186 -10 ct +1174 -3 1159 0 1142 0 ct 1114 0 1091 -11 1073 -32 ct 1056 -54 1047 -83 1047 -118 ct +1047 -154 1056 -183 1073 -205 ct 1091 -226 1114 -237 1142 -237 ct 1159 -237 1174 -234 1186 -227 ct +1198 -220 1208 -210 1216 -196 ct 1216 -231 l 1254 -231 l 1254 -29 l p ef +1422 -322 m 1404 -290 1390 -259 1381 -228 ct 1372 -197 1368 -166 1368 -134 ct +1368 -102 1372 -70 1381 -39 ct 1390 -8 1404 23 1422 55 ct 1389 55 l 1368 23 1353 -9 1343 -41 ct +1332 -72 1327 -103 1327 -134 ct 1327 -164 1332 -195 1343 -227 ct 1353 -258 1368 -289 1389 -322 ct +1422 -322 l p ef +1490 -322 m 1523 -322 l 1544 -289 1559 -258 1569 -227 ct 1580 -195 1585 -164 1585 -134 ct +1585 -103 1580 -72 1569 -41 ct 1559 -9 1544 23 1523 55 ct 1490 55 l 1508 23 1522 -8 1531 -39 ct +1540 -70 1544 -102 1544 -134 ct 1544 -166 1540 -197 1531 -228 ct 1522 -259 1508 -290 1490 -322 ct +p ef +pom +gr +gr +10159 7773 m 10359 8203 l 10060 8237 l 10159 7773 l p ef +18785 13223 m 18760 12834 l 18688 12484 l 18572 12171 l 18414 11892 l +18219 11644 l 17989 11426 l 17727 11234 l 17437 11066 l 16784 10792 l +16056 10585 l 14472 10290 l 12888 10021 l 12160 9846 l 11507 9618 l +11217 9478 l 10955 9317 l 10725 9132 l 10530 8922 l 10372 8683 l 10256 8414 l +10189 8132 l ps +gs +gs +pum +14843 10131 t +206 -223 m 206 -187 l 196 -193 185 -197 174 -200 ct 163 -203 152 -205 141 -205 ct +117 -205 97 -197 84 -181 ct 70 -166 63 -144 63 -115 ct 63 -87 70 -65 84 -50 ct +97 -34 117 -26 141 -26 ct 152 -26 163 -28 174 -31 ct 185 -34 196 -38 206 -44 ct +206 -9 l 196 -4 185 0 173 2 ct 162 5 150 6 137 6 ct 102 6 75 -5 54 -27 ct 34 -49 23 -78 23 -115 ct +23 -153 34 -183 54 -205 ct 75 -226 104 -237 140 -237 ct 151 -237 163 -236 174 -233 ct +185 -231 196 -227 206 -223 ct p ef +273 -322 m 311 -322 l 311 0 l 273 0 l 273 -322 l p ef +481 -205 m 460 -205 444 -197 432 -181 ct 420 -165 414 -143 414 -115 ct 414 -88 420 -66 432 -50 ct +444 -34 460 -26 481 -26 ct 501 -26 517 -34 529 -50 ct 540 -66 546 -88 546 -116 ct +546 -143 540 -165 529 -181 ct 517 -197 501 -205 481 -205 ct p +481 -237 m 514 -237 540 -226 558 -205 ct 577 -183 587 -154 587 -115 ct 587 -78 577 -48 558 -26 ct +540 -5 514 6 481 6 ct 447 6 421 -5 403 -26 ct 384 -48 374 -78 374 -115 ct 374 -154 384 -183 403 -205 ct +421 -226 447 -237 481 -237 ct p ef +797 -225 m 797 -189 l 787 -194 775 -198 764 -201 ct 752 -204 740 -205 728 -205 ct +709 -205 695 -202 685 -197 ct 676 -191 671 -182 671 -170 ct 671 -162 675 -155 681 -150 ct +688 -145 702 -140 722 -135 ct 735 -132 l 762 -127 781 -119 793 -108 ct 804 -97 810 -83 810 -64 ct +810 -42 801 -25 784 -13 ct 767 0 744 6 714 6 ct 702 6 689 5 675 2 ct 662 0 648 -4 633 -8 ct +633 -48 l 647 -40 661 -35 674 -31 ct 688 -28 702 -26 715 -26 ct 733 -26 747 -29 756 -35 ct +766 -41 771 -50 771 -61 ct 771 -71 767 -79 760 -85 ct 753 -90 738 -96 715 -101 ct +701 -104 l 678 -109 661 -116 650 -127 ct 640 -137 635 -151 635 -169 ct 635 -191 642 -207 658 -219 ct +673 -231 695 -237 723 -237 ct 737 -237 751 -236 763 -234 ct 775 -232 787 -229 797 -225 ct +p ef +1068 -125 m 1068 -106 l 893 -106 l 895 -80 903 -60 917 -47 ct 931 -33 950 -26 976 -26 ct +990 -26 1004 -28 1018 -32 ct 1032 -35 1045 -41 1059 -48 ct 1059 -12 l 1045 -6 1031 -2 1017 1 ct +1003 4 988 6 973 6 ct 936 6 907 -5 886 -26 ct 864 -48 853 -77 853 -113 ct 853 -151 864 -181 884 -204 ct +904 -226 932 -237 967 -237 ct 998 -237 1022 -227 1041 -207 ct 1059 -187 1068 -160 1068 -125 ct +p +1030 -136 m 1029 -157 1024 -174 1012 -186 ct 1001 -199 986 -205 967 -205 ct +946 -205 929 -199 916 -187 ct 904 -175 896 -158 894 -136 ct 1030 -136 l p ef +1223 -322 m 1205 -290 1191 -259 1182 -228 ct 1173 -197 1169 -166 1169 -134 ct +1169 -102 1173 -70 1182 -39 ct 1191 -8 1205 23 1223 55 ct 1190 55 l 1169 23 1154 -9 1144 -41 ct +1133 -72 1128 -103 1128 -134 ct 1128 -164 1133 -195 1144 -227 ct 1154 -258 1169 -289 1190 -322 ct +1223 -322 l p ef +1291 -322 m 1324 -322 l 1345 -289 1360 -258 1370 -227 ct 1381 -195 1386 -164 1386 -134 ct +1386 -103 1381 -72 1370 -41 ct 1360 -9 1345 23 1324 55 ct 1291 55 l 1309 23 1323 -8 1332 -39 ct +1341 -70 1345 -102 1345 -134 ct 1345 -166 1341 -197 1332 -228 ct 1323 -259 1309 -290 1291 -322 ct +p ef +pom +gr +gr +gr +gs +0 0 m 27999 0 l 27999 20999 l 0 20999 l 0 0 l eoclip newpath +gr +gr +0 21000 t +pom +count op_count sub {pop} repeat countdictstack dict_count sub {end} repeat b4_inc_state restore +%%PageTrailer +%%Trailer +%%EOF diff --git a/doc/images/comm_states.odp b/doc/images/comm_states.odp new file mode 100644 index 0000000000000000000000000000000000000000..3cd96c6f84857f1dd2ad958453ade4ecf691d861 Binary files /dev/null and b/doc/images/comm_states.odp differ diff --git a/doc/images/comm_states.png b/doc/images/comm_states.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a452f08de48c6415ee98a66a1c8bc346d6ab27 Binary files /dev/null and b/doc/images/comm_states.png differ diff --git a/doc/main.dox b/doc/main.dox new file mode 100644 index 0000000000000000000000000000000000000000..73bc774760e8e2ea0f44d11c80f6151860fc69a7 --- /dev/null +++ b/doc/main.dox @@ -0,0 +1,143 @@ +/*! \mainpage IRI Communications + + \section Introduction + + This set of drivers for standard communication devices. This library offers a + generic communication device which can be used to create drivers for new + communication devices easily: it is only necessary to implement the low level, + operating system dependent functions, all the other features are already + implemented. + + The main features of all communication devices are: + + - Data is automatically received and stored into a data FIFO by an internal thread. + - An event is generated whenever there is new data into this FIFO. Read operations are non-blocking. + - An event is generated when an unexpected error happen. + - Only a few functions have to be implemented to generate a new driver. + + The communication devices currently supported by this library include: + + - serial port: This drivers provides a simple and easy to use yet + complete interface to a standard computer serial port. This driver allows + the user to open any available serial port, configure it as needed and send + and receive data to and from it. Some USB devices can also use this driver + (those that appear as a virtual COM port). + + - ftdi driver: This driver is intended to be used to interface with + devices with any of the following FTDI chips: + - FT2232H + - FT4232H + - FT232R + - FT245R + - FT2232 + - FT232B + - FT245B + - FT8U232AM + - FT8U245AM + This driver can also be used with USB devices that appear as virtual serial + ports in the host computer. In this case it is also possible to use the serial + port driver + + - sockets: This driver provide an implementation of server and client + TCP sockets in Linux. UDP connections are not yet supported. The server + side can handle as many connections as needed, providing notifications for + the following event for each of the connections: + - new connection + - disconnection + - reception of new data + + Both the client and server can be used with other implementations of the POSIX sockets. + + \section Installation + + \subsection Pre-Requisites + + This package requires of the following libraries and packages + - <A href="http://www.cmake.org">cmake</A>, a cross-platform build system. + - <A href="http://www.doxygen.org">doxygen</a> and + <A href="http://www.graphviz.org">graphviz</a> to generate the documentation. + - <A href="http://wikiri.upc.es/index.php/Utilities_library">utilities library</a>, a set of basic tools to develop software. + . + + All the IRI-related dependencies can be automatically installed by calling + - sudo make dep + . + from the <em>build</em> folder and after calling the <em>cmake ..</em> command. + + Under MacOS most of the packages are available via <a href="http://www.finkproject.org/">fink</a>. <br> + + \subsection Compilation + + Just download this package, uncompress it, and execute + - cd build + - cmake .. + . + to generate the makefile and then + - make + . + to obtain the executable (in this example called <em>main_example</em>). + + The <em>cmake</em> only need to be executed once (make will automatically call + <em>cmake</em> if you modify one of the <em>CMakeList.txt</em> files). + + To generate this documentation type + - make doc + . + + The files in the <em>build</em> directory are genetated by <em>cmake</em> + and <em>make</em> and can be safely removed. + After doing so you will need to call cmake manually again. + + \subsection Configuration + + The default build mode is DEBUG. That is, objects and executables + include debug information. + + The RELEASE build mode optimizes for speed. To build in this mode + execute + - cmake .. -DCMAKE_BUILD_TYPE=RELEASE + . + The release mode will be kept until next time cmake is executed. + + \section Customization + + To build a new application using these library, first it is necessary to locate if the library + has been installed or not using the following command + + - FIND_PACKAGE(comm) + + In the case that the package is present, it is necessary to add the header files directory to + the include directory path by using + + - INCLUDE_DIRECTORIES(${comm_INCLUDE_DIR}) + + Finally, it is also nevessary to link with the desired libraries by using the following command + + - TARGET_LINK_LIBRARIES(<executable name> ${comm_LIBRARY}) + . + + All these steps are automatically done when the new project is created following the instructions + in <A href="http://wikiri.upc.es/index.php/Create_a_new_development_project">here</a> + + \section License + + This package is licensed under a + <a href="http://creativecommons.org/licenses/by/3.0/"> + Creative Commons Attribution 3.0 Unported License</a>. + + \section Disclaimer + + This package is distributed in the hope that it will be useful, but without any warranty. + it is provided "as is" without warranty of any kind, either expressed or implied, including, + but not limited to, the implied warranties of merchantability and fitness for a particular + purpose. The entire risk as to the quality and performance of the program is with you. + should the program prove defective, the GMR group does not assume the cost of any necessary + servicing, repair or correction. + + In no event unless required by applicable law the author will be liable to you for damages, + including any general, special, incidental or consequential damages arising out of the use or + inability to use the program (including but not limited to loss of data or data being rendered + inaccurate or losses sustained by you or third parties or a failure of the program to operate + with any other programs), even if the author has been advised of the possibility of such damages. + + */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..6fc247ffff1ca50868db59fb35c64addc004f27e --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,57 @@ +FIND_PATH(FTDI_INCLUDE_DIR ftd2xx.h WinTypes.h /usr/include/ /usr/local/include/) + +FIND_LIBRARY(FTDI_LIBRARY + NAMES ftd2xx + PATHS /usr/lib /usr/local/lib) + +IF(FTDI_INCLUDE_DIR AND FTDI_LIBRARY) + SET(BUILD_FTDI TRUE) +ELSE(FTDI_INCLUDE_DIR AND FTDI_LIBRARY) + MESSAGE(STATUS "FTDI library won't be build. Impossible to locate the necessary files") +ENDIF(FTDI_INCLUDE_DIR AND FTDI_LIBRARY) + +# edit the following line to add all the source code files of the library +SET(sources ./comm.cpp ./commexceptions.cpp ./serial/rs232.cpp ./serial/rs232exceptions.cpp ./sockets/socket.cpp ./sockets/socketclient.cpp ./sockets/socketserver.cpp ./sockets/socketexceptions.cpp) +# edit the following line to add all the header files of the library +SET(headers ./comm.h ./commexceptions.h ./serial/rs232.h ./serial/rs232exceptions.h ./sockets/socket.h ./sockets/socketclient.h ./sockets/socketserver.h ./sockets/socketexceptions.h) + +IF(BUILD_FTDI) + SET(sources ${sources} ./usb_ftdi/ftdiserver.cpp ./usb_ftdi/ftdimodule.cpp ./usb_ftdi/ftdiexceptions.cpp) + SET(headers ${headers} ./usb_ftdi/ftdiserver.h ./usb_ftdi/ftdimodule.h ./usb_ftdi/ftdiexceptions.h) +ENDIF(BUILD_FTDI) + +INCLUDE_DIRECTORIES(.) + +# edit the following line to find the necessary packages +FIND_PACKAGE(iriutils REQUIRED) + +INCLUDE_DIRECTORIES(${iriutils_INCLUDE_DIR}) + +# edit the following line to add the necessary include directories +INCLUDE_DIRECTORIES(./serial ./usb_ftdi ./sockets) + +IF(BUILD_FTDI) + INCLUDE_DIRECTORIES(${FTDI_INCLUDE_DIR}) +ENDIF(BUILD_FTDI) + +ADD_LIBRARY(comm SHARED ${sources}) + +#edit the following line to add the necessary system libraries (if any) + +TARGET_LINK_LIBRARIES(comm ${iriutils_LIBRARY}) + +IF(BUILD_FTDI) + TARGET_LINK_LIBRARIES(comm ${FTDI_LIBRARY}) +ENDIF(BUILD_FTDI) + +INSTALL(TARGETS comm + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib/iridrivers + ARCHIVE DESTINATION lib/iridrivers +) + +INSTALL(FILES ${headers} DESTINATION include/iridrivers) + +INSTALL(FILES ../Findcomm.cmake DESTINATION ${CMAKE_ROOT}/Modules/) + +ADD_SUBDIRECTORY(examples) diff --git a/src/comm.cpp b/src/comm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..643753534fdb643b299f1acff51f2f0aecf3282e --- /dev/null +++ b/src/comm.cpp @@ -0,0 +1,328 @@ +#include <string.h> +#include <unistd.h> +#include <sys/select.h> +#include <sys/ioctl.h> +#include "comm.h" +#include "commexceptions.h" +#include "ctime.h" + +CComm::CComm(const std::string& comm_id) +{ + this->rx_event_id=""; + this->error_event_id=""; + this->comm_thread_id=""; + this->error_msg=""; + this->state=created; + try{ + this->set_id(comm_id); + /* get a reference to the event handler */ + this->event_server=CEventServer::instance(); + /* create the events */ + this->rx_event_id+=comm_id; + this->rx_event_id+="_rx_event_id"; + this->event_server->create_event(this->rx_event_id); + this->error_event_id+=comm_id; + this->error_event_id+="_error_event"; + this->event_server->create_event(this->error_event_id); + /* get the reference to the thread handler */ + this->thread_server=CThreadServer::instance(); + /* create the thread */ + this->comm_thread_id+=comm_id; + this->comm_thread_id+="_comm_thread"; + this->thread_server->create_thread(this->comm_thread_id); + /* attach the thread function */ + this->thread_server->attach_thread(this->comm_thread_id,comm_thread,this); + }catch(CException &e){ + this->close(); + /* delete the events */ + throw; + } +} + +void CComm::set_id(const std::string& comm_id) +{ + if(comm_id.size()==0) + { + /* handle exceptions */ + throw CCommException(_HERE_,"Invalid communication id.\n","empty name"); + } + else + this->comm_id=comm_id; +} + +std::string& CComm::get_id(void) +{ + return this->comm_id; +} + +void CComm::open(void *comm_dev) +{ + if(this->state==created) + { + try{ + this->access_comm.enter(); + this->hard_open(comm_dev); + this->thread_server->start_thread(this->comm_thread_id); + this->state=opened; + this->access_comm.exit(); + }catch(CException &e){ + this->access_comm.exit(); + /* handle exception */ + throw; + } + } + else + { + /* handle exceptions */ + throw CCommException(_HERE_,"The communication device is already opened\n.",this->comm_id); + } +} + +void CComm::config(void *config) +{ + this->access_comm.enter(); + if(this->state==created) + { + this->access_comm.exit(); + /* handle exceptions */ + throw CCommException(_HERE_,"The communication device has not been opened yet.\n",this->comm_id); + } + else + { + try{ + if(this->state==opened) + this->state=configured; + /* otherwise, keep the current state */ + this->hard_config(config); + }catch(CException &e){ + this->access_comm.exit(); + /* handle exception */ + throw; + } + } + this->access_comm.exit(); +} + +int CComm::read(unsigned char *data,int len) +{ + int num_available=0; + int i=0; + + if(data==NULL) + { + /* handle exceptions */ + throw CCommException(_HERE_,"Invalid data buffer to save the data received (NULL pointer).\n",this->comm_id); + } + else + { + if(this->state==configured || this->state==sending) + { + this->access_comm.enter(); + num_available=this->receive_queue.size(); + if(len>num_available) + { + len=num_available; + this->event_server->reset_event(this->rx_event_id); + } + for(i=0;i<len;i++) + { + data[i]=*this->receive_queue.begin(); + this->receive_queue.pop_front(); + } + this->access_comm.exit(); + return len; + } + else + { + throw CCommException(_HERE_,"The communication device is not configured yet.\n",this->comm_id); + } + } + return len; +} + +int CComm::write(unsigned char *data,int len) +{ + int written_len=0; + + if(data==NULL) + { + /* handle exceptions */ + throw CCommException(_HERE_,"Invalid data buffer to be sent (NULL pointer).\n",this->comm_id); + } + else + { + this->access_comm.enter(); + if(this->state==configured) + { + try{ + written_len=this->hard_write(data,len); + }catch(CException &e){ + this->access_comm.exit(); + /* handle exceptions */ + throw; + } + if(written_len!=len) + { + /* handle exceptions */ + this->access_comm.exit(); + throw CCommException(_HERE_,"Unexpected error while writing to the communication device.\n",this->comm_id); + } + } + else + { + this->access_comm.exit(); + throw CCommException(_HERE_,"The communication device is not configured yet.\n",this->comm_id); + } + this->access_comm.exit(); + return len; + } + return len; +} + +unsigned int CComm::get_num_data(void) +{ + unsigned int num=0; + + if(this->state==configured || this->state==sending) + { + this->access_comm.enter(); + num=this->receive_queue.size(); + this->access_comm.exit(); + } + + return num; +} + +std::string& CComm::get_rx_event_id(void) +{ + return this->rx_event_id; +} + +std::string& CComm::get_error_event_id(void) +{ + return this->error_event_id; +} + +std::string& CComm::get_error(void) +{ + return this->error_msg; +} + +int CComm::get_state(void) +{ + return this->state; +} + +void CComm::close(void) +{ + if(this->state==configured || this->state==sending || this->state==opened) + { + /* finish the thread */ + if(this->state==configured) + this->thread_server->kill_thread(this->comm_thread_id); + /* close the communication device */ + this->access_comm.try_enter(); + try{ + this->hard_close(); + }catch(CException &e){ + this->access_comm.exit(); + /* handle exceptions */ + throw; + } + /* flush the data queues */ + this->receive_queue.erase(this->receive_queue.begin(),this->receive_queue.end()); + /* change the current state */ + this->state=created; + this->access_comm.exit(); + } +} + +void *CComm::comm_thread(void *param) +{ + CComm *comm_dev=(CComm *)param; + int wait_result; + bool end=false; + + while(!end) + { + wait_result=comm_dev->hard_wait_comm_event(); + comm_dev->access_comm.enter(); + if(wait_result==-1) + end=true; + else + { + if(wait_result==1)/* data has been received */ + { + comm_dev->on_receive(); + } + if(wait_result==2) + { + comm_dev->on_error(); + } + } + comm_dev->access_comm.exit(); + } + /* handle exceptions */ +// throw CCommException(_HERE_,"Unexpected error while waiting for new data.\n",comm_dev->comm_id); + + return NULL; +} + +void CComm::on_receive(void) +{ + unsigned char *data=NULL; + int num=0,num_read=0,i=0; + + if((num=this->hard_get_num_data())==-1) + { + /* handle exceptions */ + throw CCommException(_HERE_,"Impossible to get the number of new data available on the communication device.\n",this->comm_id); + } + data=new unsigned char[num]; + if((num_read=this->hard_read(data,num))==-1) + { + delete[] data; + /* handle exceptions */ + throw CCommException(_HERE_,"Impossible to read from the communication device.\n",this->comm_id); + } + else + { + if(num_read!=num) + { + delete[] data; + /* handle exceptions */ + throw CCommException(_HERE_,"Not all desired data has been read from the communicationd device.\n",this->comm_id); + } + else + { + for(i=0;i<num;i++) + this->receive_queue.push_back(data[i]); + if(!this->event_server->event_is_set(this->rx_event_id)) + { + this->event_server->set_event(this->rx_event_id); + } + delete[] data; + } + } +} + +void CComm::on_error(void) +{ + /* handle exceptions */ +} + +CComm::~CComm() +{ + /* delete the events */ + if(this->rx_event_id.size()!=0) + this->event_server->delete_event(this->rx_event_id); + if(this->error_event_id.size()!=0) + { + this->event_server->delete_event(this->error_event_id); + } + /* delete the thread */ + if(this->comm_thread_id.size()!=0) + { + this->thread_server->delete_thread(this->comm_thread_id); + } +} diff --git a/src/comm.h b/src/comm.h new file mode 100644 index 0000000000000000000000000000000000000000..27919ea21ab0c9262bb31f44d478aff242c2d047 --- /dev/null +++ b/src/comm.h @@ -0,0 +1,649 @@ +#ifndef _COMM_H +#define _COMM_H + +#include <list> +#include "eventserver.h" +#include "threadserver.h" +#include "mutex.h" + +typedef enum {created,configured,opened,sending} comm_states; + +/** + * \brief Implementation of a generic communication devidei + * + * This class implements the basic features of a communication device. All + * standard communication devices (pipes, serial ports, sockets, etc...) will + * inherit from this base class and only need to define the specific functions + * to open (hard_open()), configure (hard_config()), read (hard_read(), write + * (hard_write()), get the number of available data (hard_get_num_data()), + * close (hard_close()) and wait for communication events (hard_wait_comm_event()) + * on the device. + * + * The interface provided by this class includes two events (data reception, + * and error) and one data queue where the received data is temporarly stored. + * The events can be accessed through the event handler (CEventServer) by any + * other object, and two functions are provided to retrieve their + * identifiers (get_rx_event_id(), and error_event_id()). + * + * The recive event indicates if the device has received data and it remains + * active while there is data in the internal queue to be read. When the receive + * queue is empty, the event is reset. Finally the error event indicates that an + * error ocurred. Use the get_error() function to retrieve the error code. + * + * When created the communication device starts a thread that is actually the + * one responsible of handling the communication events (received data, + * and errors). This thread is used exlcusively inside the class and can not be + * accessed outside it. This thread uses the wait function provided by the + * inherited class to wait for any communication events. + * + * Most devices in Linux are referenced by a file descriptor for both read and + * write operations, or also, two different file descriptors, one for read + * operations, and the other one for write operations. However, other devices + * may provide different methods of interfacing them, so no specific device + * handler is provided by this class. It is the inherited class that must provide + * it, together with the functions to open it, configure it, read from and write + * to it and close it. + * + * To open the device, the user must call the base class open() function and also + * provide a function (hard_open()) to actually open the communication device. + * The user provided function is automatically called by the base class open() + * function and it is responsible of initializing the device handler. + * + * After that, the device must be configured. Similarly to what happen with the + * open() function, the inherited class must provide the hard_config() function + * to perform all the required configuration steps. Both the open() and config() + * function have a void * parameter to allow the inherited class to pass through + * any data structure. This parameter is passed to the corresponding inherited + * class function (hard_open() or hard_config() respectively). + * + * Only after configuration, it is possible to read from and write to the device. + * Any previous attempt to do so will result in an error. The inherited class + * must also provide functions to actually read from (hard_read()) and write to + * (hard_write()) the device. To close the communication device, it is necessary + * to call the close() function which ends the thread and flushes any data still + * inside the transmission and reception queues. Also this function calls the + * hard_close() function to actually close the device handle. + * + * The events and the thread itself are not destroyed until the communciation + * device object is destroyed. It is possible to close the device at any time. + * + * For a propper operation, any inherited class must provide an implementation + * for the hard_open(), hard_config(), hard_read(), hard_write(), hard_close(), + * hard_get_num_data() and hard_wait_comm_event() functions. This functions are + * automatically called by the base class and must not be called directly. + * + * Any communication device is driven by an internal state machine, whose state is + * automatically changed when the different functions are called. The next figure + * shows the different states and also the state transition conditions. + * + * \image html comm_states.png + * \image latex comm_states.eps "Communication device states" width=10cm + * + */ +class CComm +{ + protected: + /** + * \brief receive event identifier + * + * This string has a unique identifier of the reception event that is used + * through out the code to take action on the desired event. This string is + * initialized at construction time using the identifier of the communication + * device provided to the constructor and can not be modified afterwards. The + * function get_rx_event_id() can be used to retrieve this identifier. + */ + std::string rx_event_id; + /** + * \brief error event identifier + * + * This string has a unique identifier of the error event that is used + * through out the code to take action on the desired event. This string is + * initialized at construction time using the identifier of the communication + * device provided to the constructor and can not be modified afterwards. The + * function get_error_event_id() can be used to retrieve this identifier. + */ + std::string error_event_id; + /** + * \brief communication thread identifier + * + * This string has a unique identifier of the communication thread that is used + * through out the code to take action on the desired thread. This string is + * initialized at construction time using the identifier of the communication + * device provided to the constructor and can not be modified afterwards. This + * thread is only accessible from inside the class so it is not possible to + * retrieve its identifier. + */ + std::string comm_thread_id; + /** + * \brief communication error message + * + * This string has the information message of any error that could happen on + * the communication device. By default it is initialized to NULL, and it is + * only created when there is an error. The error message is automatically + * created when an error is detected by the communication thread, and it can + * be retrieved by calling the get_error() function. + */ + std::string error_msg; + /** + * \brief received data queue + * + * This dynamic queue stores all the data that is recieved by the communication + * device until it is read by the user. There is no limit in the number of bytes + * stored in this list except for the physical memory available. The data + * received through the serial port is automatically put into the queue by the + * communication thread, and the function read() is used to retrieve it. The + * function get_num_data() returns the number of bytes in this queue. By default, + * this queue is empty. + */ + std::list<unsigned char> receive_queue; + /** + * \brief current state of the communication device + * + * This variable keeps the current state of the communication device. The state + * of the device is changed automatically by the class functions and can not be + * modified outside it. The possible states of the communication device are: + * + * - created: when the object has been just created. + * - opened: after opening the device with the open() function. + * - configured: after calling the config() function. + * - sending: when the device is busy sending data. + * + * It is possible to retrieve the current state of the communication device at + * any time using the get_state() function. + */ + comm_states state; + /** + * \brief Reference to the unique event handler + * + * This reference to the unique event handler is initialized when an object of + * this class is first created. It is used to create and handle all the + * communication events. The object pointed by this reference is shared by all + * objects in any application. + */ + CEventServer *event_server; + /** + * \brief Reference to the unique thread handler + * + * This reference to the unique thread handler is initialized when an object of + * this class is first created. It is used to create and handle all the + * communication threads. The object pointed by this reference is shared by all + * objects in any application. + */ + CThreadServer *thread_server; + /** + * \brief A unique identifier for the obejct + * + * This string has a unique identifier of the object that is used throug + * out the code to take action on the desired object. This string is + * initialized at contruction time and can not be modified afterwards. + */ + std::string comm_id; + /** + * \brief Communication mutual exclusion object + * + * This object is intended to be used by to handle the access to the shared + * communication resource defined by the inherited classes. Using this mutex + * multiple simultaneous read or write operations on the communication device + * are avoided which would result in data corruption or unexpected errors. + * This object is initialized at contruction time. + */ + CMutex access_comm; + /** + * \brief Thread function + * + * This is the main function executed by the communication device to handle + * the communication events. This function waits for either a receive or error + * events permanently, and the calls the appropriate function to handle the + * events (on_receive() and on_error() respectively). + * + * Except for the time where this function is actually waiting for an event + * to get active, it locks the user mutex of the base class in order to avoid + * that other threads interfere with the correct execution of the function. + * The mutex is freed when the function is waiting to allow other threads to + * access the class shared resources. + * + * When data is received, this function automatically reads it from the physical + * communication device and stores it into the internal receive queue. Then the + * receive event is activated to awake any waiting thread. + * + * In case of an error, the error event is activated. To retrieve the error + * message, it is necessary to use the get_error() function. In case of an error, + * the thread is terminated, but no exception is thrown because there is no way + * to catch it. + * + * \param param a reference to the communication device object that is associated + * to the thread. This reference is necessary to access the internal attributes + * of the class. + */ + static void *comm_thread(void *param); + /** + * \brief function to handle the reception events + * + * This function handles the receive events. When called, it first gets how many + * bytes have been received. Then all the bytes are read from the physical + * communication device and stored into the internal receive queue. If the event + * is not already set, it is activated to indicate to any waiting thread that + * data is available. + * + * This function is executed only by the communication thread when new data is + * received, so its execution can not be interrupted by other threads trying to + * access the class shared resources because the mutex is already locked by the + * communications thread function + * + * This function throws a CCommException in case of any error. + */ + void on_receive(void); + /** + * \brief function to handle the errors + * + * This function handles any error in the communication device. This function + * creates an error message with the information of the error, and the activates + * the error event and throws an exception in order to finish the thread. It is + * important to note than the thread is terminated if there is any error. + * + * This function is executed only by the communication thread when new data is + * received, so its execution can not be interrupted by other threads trying to + * access the class shared resources because the mutex is already locked by the + * communications thread function + * + * This function throws a CCommException in case of any error. + */ + void on_error(void); + /** + * \brief Function to set the object identifier + * + * This function sets the unique identifier of each object. This function + * is protected, and therefore can only be executed inside the class, because + * the identifier can not be modified after construction of the object. + * + * This function throws a CCommException if there is any error. + * + * \param comm_id A null terminated string which identified the + * communication device. This string is used to create a + * unique identifier for all the threads and events of the + * class. + */ + void set_id(const std::string& comm_id); + /** + * \brief Function to actually open the device + * + * This function is called automatically when the base class open() function + * is called. It must create a handle to the communication device in order to + * be used in the future. The device handle must be provided by any inherited + * class since it is device dependant. + * + * This class accepts a generic parameter (void *) to allow the user to pass + * to the function any data structure. This parameter comes from the base + * class open() function, but no action is performed on it. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param comm_dev a generic pointer (void *) to the data structure needed to + * identify the communication device to open and any other + * required information. This parameter may be NULL if not + * needed. + */ + virtual void hard_open(void *comm_dev=NULL)=0; + /** + * \brief Function to actually configure the device + * + * This function is called automatically when the base class config() function + * is called. It must configure the communication device as required by the + * application. + * + * This class accepts a generic parameter (void *) to allow the user to pass + * to the function any data structure. This parameter comes from the base + * class open() function, but no action is performed on it. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param config a generic pointer (void *) to the data structure needed to + * configure the communicationd device. This parameter may be + * NULL if not needed. + */ + virtual void hard_config(void *config=NULL)=0; + /** + * \brief Function to actually read from the device + * + * This function is automatically called when the new data received is activated. + * The read() function from the base class gets data from the internal queue, so + * this function is not used. It must try to read the ammount of data specified + * and store it in the data buffer provided without blocking. Also, it must + * return the number of bytes actually read from the devicve, since they may be + * different than the desired value. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param data a reference to the buffer where the received data must be + * copied. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * read from the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually read. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + virtual int hard_read(unsigned char *data, int len)=0; + /** + * \brief Function to actually write to the device + * + * This function is automatically called when the base class write() function + * is called. It must try to write the desired ammount of data to the communication + * device without blocking. Also it must return the number of bytes actually + * written to the communication device since they may be different from the + * desired value. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param data a reference to the buffer with the data must be send to the + * device. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * written to the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually written. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + virtual int hard_write(unsigned char *data, int len)=0; + /** + * \brief Function to actually get the number of bytes availables + * + * This function is called when the new data received event is activated. + * It must get the number of data bytes available from the communication + * device and return its value without blocking. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \return an integer with the number of bytes available in the receive queue. + * This value can be 0 if there is no data available, but there is ni upper + * limit in its value. + */ + virtual int hard_get_num_data(void)=0; + /** + * \brief Function to actually wait for a given communication event + * + * This function is called in the internal communciation thread to wait for + * any event on the communuication device. It must check for any event on the + * device (reception or error) and return the corresponding identifier. When + * an event is activated, this function must return to allow the base class + * to handle it, and the it is called again to wait for the next event. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \return -1 if there has been any error or else the identifier of the + * communciation event: + * + * - 1 for the new data received event + * - 2 for the error event + */ + virtual int hard_wait_comm_event(void)=0; + /** + * \brief Function to actually close the device + * + * This function is called when the base class close() funciton is called. It + * must free the device handle initialized by the open() function and also free + * any other resource allocated by the inherited class. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + */ + virtual void hard_close(void)=0; + public: + /** + * \brief Constructor + * + * This constructor creates the receive, send and error events and also the + * thread to handle these events. By default all the events are reset. After + * creating the object is already possible to get the event identifiers for + * the receive and error events. + * + * The thread is attached to the function to be executed, but it is not + * started yet. It is only started when the open() function is called. + * + * Also the error message is not initialized (set to NULL) at construction + * time. This message is only initialized when there is an error on the + * communication device. + * + * By default, the intial state of the communication device is created. + * + * \param comm_id A null terminated string which identified the + * communication device. This string is used to create a + * unique identifier for all the threads and events of the + * class. + */ + CComm(const std::string& comm_id); + /** + * \brief Function to retrieve the object identifier + * + * This function returns the object identifier of the object as a null + * terminated string. + * + * This function thows a CCommException if there is any error. + * + * \return A refernce to a non allocated memory space where the + * identifier of the object is to be copied. The necessary memory is allocated + * internally to match the size of the identifier. The calling process is + * responsible of freeing the allocated memory. + */ + std::string& get_id(void); + /** + * \brief function to open the communication device + * + * This function starts the communication thread that has been created at + * construction time if the device is in the created state. Otherwise, an + * exception is thrown because the device should be alredy opened. If it is + * necessary to open a new communication device using a previously + * initialized CCom object, it is necessary to first close the communication + * device with the close() function and the call this function again. If + * successfull, this function changes the current state of the device to + * opened. + * + * This function does not actually open any communication device, since this + * class is a generic placeholder for communication devices. It is the + * inherited class which has to provide a hard_open() function that actually + * opens the desired communication device. The inherited class hard_open() + * function must initialize the device handler. + * + * This function throws a CCommException if there is any error. + */ + void open(void *comm_dev=NULL); + /** + * \brief function to configure the communication device + * + * This function only changes the current state of the communication device + * if it is not already configured, but does not configure any of the + * device parameters since they greatly vary from one communication device to + * another. If the device has not been previously opened, this function + * throws an exception. + * + * It is the inherited class that has to provide a hard_config() function that + * actually configures the particular parameters of the communication device. + * + * This function throws a CCommException if there is any error. + */ + void config(void *config=NULL); + /** + * \brief function to read from the communication device + * + * This function tries to read a given number of bytes from the communication + * device if the device has been opened and configured. Otherwise it throws + * an exception. If the number of bytes currently in the receive queue is less + * than the desired number, the function returns all the available data and + * also the number of bytes actually read. + * + * If the number of bytes in the queue is greater or equal than the desired + * number, the function returns all the required data. In both cases, the + * function will never block. When read, the bytes are removed from the + * internal queue, so they can not be read again. Use the get_num_data() + * function to get how many bytes are available in the receive queue. + * + * If the internal receive queue gets empty after a read operation, the + * receive event is cleared to indicate that there is no more data available. + * This event will be set again by the communication thread when new data + * is received. + * + * This function throws a CCommException if there is any error. + * + * \param data a reference to the buffer where the received data must be + * copied. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * read from the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually read. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + int read(unsigned char *data,int len); + /** + * \brief function to write to the communication device + * + * This function tries to write a given number of bytes to the communication + * device if the device has been opened and configured. Otherwise it throws + * an exception. If the device is currently sending data, the new data provided + * to this function is temporary stored into the internal send queue to be + * trasmited when the device is ready. Otherwise, the information is send + * immediately. + * + * If the new data is send immediatelly, the send event is reset to indicate + * that the device is not ready to send data, and any further write operation + * will temporary store the data into the internal queue. This event is set + * again by the communication device when a transmission is ended and there + * is no data in the send queue. + * + * This function throws a CCommException if there is any error. + * + * \param data a reference to the buffer with the data must be send to the + * device. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * written to the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually written. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + int write(unsigned char *data, int len); + /** + * \brief function to get the number of bytes in the reception queue + * + * This function returns the number of byte currently available in the internal + * receive queue. + * + * \return an integer with the number of bytes available in the receive queue. + * This value can be 0 if there is no data available, but there is ni upper + * limit in its value. + */ + unsigned int get_num_data(void); + /** + * \brief function to get the receive event identifier + * + * This fucntion returns the receive event identifier as a null terminated + * string that can be used to access the event from any thread. + * + * This function throws a CCommException in case of any error. + * + * \return A reference to a non-allocated memory space where the + * identifier of the event is to be copied. The necessary memory is allocated + * internally to match the size of the identifier. The calling process is + * responsible of freeing the allocated memory. + * + */ + std::string& get_rx_event_id(void); + /** + * \brief function to get the error event identifier + * + * This fucntion returns the error event identifier as a null terminated + * string that can be used to access the event from any thread. + * + * This function throws a CCommException in case of any error. + * + * \return A reference to a non-allocated memory space where the + * identifier of the event is to be copied. The necessary memory is allocated + * internally to match the size of the identifier. The calling process is + * responsible of freeing the allocated memory. + */ + std::string& get_error_event_id(void); + /** + * \brief function to get the error message + * + * This function returns the error message of the error that caused the + * communication thread to end. This function will return always a NULL + * pointer except when an error has ocurred. + * + * This function throws a CCommException in case of any error. + * + * \return A reference to a non-allocated memory space where the + * error message is to be copied. The necessary memory is allocated + * internally to match the size of the identifier. The calling process is + * responsible of freeing the allocated memory. + */ + std::string& get_error(void); + /** + * \brief function to get the current state of the comunication device + * + * This function returns the current state of the communication device. The + * state is automatically updated by the class itself and can not be modified + * outside the class. + * + * This function throws a CCommException in case of any error. + * + * \return the current state of the communication device. The possible state + * are: + * - created: when the object has been just created. + * - opened: after opening the device with the open() function. + * - configured: after calling the config() function. + */ + int get_state(void); + /** + * \brief function to close the communication device + * + * This function closes the communication device. This operation consists on + * terminating the communication thread to avoid the event from being set or + * reset by it. Even if the particular communication device is opened by an + * inherited class, this function closes it. So the inherited class does not + * have to do it. Finally, any data in both the receive and the send queues + * are flushed. + * + * This function does not destroy the events or the thread objects because + * the device can still be opened again. These objects are only destroyed + * when the object itself is destroyed. After calling this function, the + * current state of the communication device is set to created whatever + * the old state was. + * + * This function throws a CCommException in case of any error. + */ + void close(void); + /** + * \brief destructor + * + * This function destroys the communication object. First, it calls the + * close() function to finish the thread and flush any remaining data. It + * then destroys all the events and the communication thread. + * + * This function throws a CCommException in case of any error. + */ + virtual ~CComm(); +}; + +#endif diff --git a/src/commexceptions.cpp b/src/commexceptions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1c17119cf9968dc31fa7d372f3ff52daf1537d25 --- /dev/null +++ b/src/commexceptions.cpp @@ -0,0 +1,12 @@ +#include "commexceptions.h" +#include <string.h> +#include <stdio.h> + +const std::string comm_exception_msg="[CComm class] - "; + +CCommException::CCommException(const std::string& where,const std::string& error_msg,const std::string& comm_id):CException(where,comm_exception_msg) +{ + this->error_msg+=error_msg; + this->error_msg+=" - "; + this->error_msg+=comm_id; +} diff --git a/src/commexceptions.h b/src/commexceptions.h new file mode 100644 index 0000000000000000000000000000000000000000..30b535b8aa5f061c52044ce42547995528330dde --- /dev/null +++ b/src/commexceptions.h @@ -0,0 +1,58 @@ +#ifndef COMM_EXCEPTIONS +#define COMM_EXCEPTIONS + +#include "exceptions.h" + +/** + * \brief Generic communication exception class + * + * This class implements the exceptions for the CComm class. In addition + * to the basic error message provided by the base class CException, this + * exception class provides also the unique identifier of the communication + * device that generated the exception. + * + * Also, similarly to other exception classes, it appends a class identifer + * string ("[CComm class] - ") to the error message in order to identify the + * class that generated the exception. + * + * The base class can be used to catch any exception thrown by the application + * or also, this class can be used in order to catch only exceptions generated + * by CComm objects. + */ +class CCommException : public CException +{ + public: + /** + * \brief Constructor + * + * The constructor calls the base class constructor to add the general + * exception identifier and then adds the class identifier string + * "[CComm class]" and the supplied error message. + * + * It also appends the unique identifier of the communication device + * that generated the exception. So, the total exception message will + * look like this: + * + * \verbatim + * [Exception caught] - <where> + * [CComm class] - <error message> - <comm id> + * \endverbatim + * + * \param where a null terminated string with the information about the name + * of the function, the source code filename and the line where + * the exception was generated. This string must be generated + * by the _HERE_ macro. + * + * \param error_msg a null terminated string that contains the error message. + * This string may have any valid character and there is no + * limit on its length. + * + * \param comm_id a null terminated string that contains the communication + * device unique identifier. This string must be the one used + * to create the communication device. + * + */ + CCommException(const std::string& where, const std::string& error_msg,const std::string& comm_id); +}; + +#endif diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e90ba1216e40736893f854cbed0c5e47597b159f --- /dev/null +++ b/src/examples/CMakeLists.txt @@ -0,0 +1,35 @@ +# edit the following line to add the source code for the example and the name of the executable +ADD_EXECUTABLE(test_rs232 test_rs232.cpp) + +# edit the following line to add the necessary libraries +TARGET_LINK_LIBRARIES(test_rs232 ${IRIUTILS_LIBRARY} comm pthread) + +IF(BUILD_FTDI) + ADD_EXECUTABLE(test_ftdi test_ftdi.cpp) + + TARGET_LINK_LIBRARIES(test_ftdi ${IRIUTILS_LIBRARY} ${FTDI_LIBRARY} comm pthread) +ENDIF(BUILD_FTDI) + +# edit the following line to add the source code for the example and the name of the executable +ADD_EXECUTABLE(test_client test_simple_client.cpp) + +# edit the following line to add the necessary libraries +TARGET_LINK_LIBRARIES(test_client ${IRIUTILS_LIBRARY} comm pthread) + +# edit the following line to add the source code for the example and the name of the executable +ADD_EXECUTABLE(test_server test_simple_server.cpp) + +# edit the following line to add the necessary libraries +TARGET_LINK_LIBRARIES(test_server ${IRIUTILS_LIBRARY} comm pthread) + +# edit the following line to add the source code for the example and the name of the executable +ADD_EXECUTABLE(test_multiple_server test_multiple_server.cpp) + +# edit the following line to add the necessary libraries +TARGET_LINK_LIBRARIES(test_multiple_server ${IRIUTILS_LIBRARY} comm pthread) + +# edit the following line to add the source code for the example and the name of the executable +ADD_EXECUTABLE(test_multiple_client test_multiple_client.cpp) + +# edit the following line to add the necessary libraries +TARGET_LINK_LIBRARIES(test_multiple_client ${IRIUTILS_LIBRARY} comm pthread) diff --git a/src/examples/test_ftdi.cpp b/src/examples/test_ftdi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad031e3fa1cdee98a8c28ecea2ed7b00ea851154 --- /dev/null +++ b/src/examples/test_ftdi.cpp @@ -0,0 +1,73 @@ +#include "ftdimodule.h" +#include "ftdiserver.h" +#include "ftdiexceptions.h" +#include <stdio.h> + +/** \example test_ftdi.cpp + * + * This example shows how to use the D2XX FTDI driver. + * + * This examples first creates an FTDI server which scans for all FTDI devices + * connected to the computer with the default PID and VID combinations provided + * by the manufacturer. If devices with different PID and VID combinations are + * used, it is necessary to use the add_custom_PID() function. + * + * When done, the information of all devices detected is displayed on screen. + * Then the first FTDI device, if any, is opened and configured. If this + * operations are successfull, the specific information of the device is also + * displyed on screen. + * + * The output of this example when a single FTDI device is connected is as + * follows: + * + * \verbatim + * Number of FTDI devices detected: 1 + * Device 0 + * The device is closed + * The device is full speed + * Type: 5 + * Device id: 67330049 + * Location: 0 + * Serial number: A2001mHr + * Description: FT232R USB UART + * + * ****************** FTDI Devices Info *********************** + * Device name: FTDI_A2001mHr + * Type: 5 + * Device id: 67330049 + * Serial Number: A2001mHr + * Description: FT232R USB UART + * \endverbatim + * + * This example program does not try to read and write from and to the device + * because it highly depends on the particular device connected. This example + * only scans for available devices and displays its information. For a full + * example of this driver, see the segwayRMP200 driver. + */ +int main(int argc, char *argv[]) +{ + CFTDIServer *ftdi_server=CFTDIServer::instance(); + CFTDI *ftdi_device=NULL; + TFTDIconfig ftdi_config; + + ftdi_config.baud_rate = 115200; + ftdi_config.word_length = 8; + ftdi_config.stop_bits = 2; + ftdi_config.parity = 0; + ftdi_config.read_timeout = 1000; + ftdi_config.write_timeout = 1000; + ftdi_config.latency_timer = 16; + + std::cout << (*ftdi_server) << std::endl; + if(ftdi_server->get_num_devices()>0) + { + ftdi_device=ftdi_server->get_device(ftdi_server->get_serial_number(0)); + ftdi_device->config(&ftdi_config); + std::cout << (*ftdi_device) << std::endl; + ftdi_device->close(); + } + if(ftdi_device!=NULL) + delete ftdi_device; +} + + diff --git a/src/examples/test_multiple_client.cpp b/src/examples/test_multiple_client.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfc5a003f33c2efc10866f37ca5f102919b9162d --- /dev/null +++ b/src/examples/test_multiple_client.cpp @@ -0,0 +1,51 @@ +#include "socketclient.h" +#include "socketexceptions.h" +#include "eventexceptions.h" + +std::string server_ip="147.83.76.185"; +int server_port=2012; + +int main(int argc,char *argv[]) +{ + CEventServer *event_server=CEventServer::instance(); + CSocketClient client("client"); + std::list<std::string> events; + unsigned char msg[5]="hola"; + int i=0,event_id=0; + TSocket_info info; + + try{ + info.address=server_ip; + info.port=server_port; + std::cout << "connecting ... " << std::endl; + while(!client.is_connected()) + { + try{ + client.open(&info); + }catch(CSocketNoConnectionException &e){ + std::cout << " nobody is listening" << std::endl; + sleep(1); + } + } + client.config(); + std::cout << "connected." << std::endl; + events.push_back(client.get_rx_event_id()); + events.push_back(client.get_connection_closed_event()); + for(i=0;i<10;i++) + { + try{ + event_id=event_server->wait_first(events,1000); + if(event_id==1) + break; + }catch(CEventTimeoutException &e){ + std::cout << "sending data ..." << std::endl; + client.write(msg,5); + } + } + sleep(1); + std::cout << "closing connection ..." << std::endl; + client.close(); + }catch(CException &e){ + std::cout << e.what() << std::endl; + } +} diff --git a/src/examples/test_multiple_server.cpp b/src/examples/test_multiple_server.cpp new file mode 100644 index 0000000000000000000000000000000000000000..376491d6ee68036608f3fea373023de62457929a --- /dev/null +++ b/src/examples/test_multiple_server.cpp @@ -0,0 +1,84 @@ +#include "eventexceptions.h" +#include "socketserver.h" +#include "eventserver.h" + +std::string server_IP_address="147.83.76.185"; +int server_port=2012; +int num_listen=1; + +int main(int argc,char *argv[]) +{ + CEventServer *event_server=CEventServer::instance(); + std::list<TClient_info *>::iterator it; + int event_id=0,num=0,client_index=0; + std::list<TClient_info *> info; + CSocketServer server("server"); + std::list<std::string> events; + TClient_info *client_info; + TSocket_info sock_info; + unsigned char *data; + + try{ + sock_info.port=server_port; + sock_info.address=server_IP_address; + server.open(&sock_info); + server.config(&num_listen); + server.set_max_clients(5); + server.start_server(); + while(1) + { + try{ + events.clear(); + events.push_back(server.get_new_connection_event_id()); + for(it=info.begin();it!=info.end();it++) + { + events.push_back((*it)->rx_event_id); + events.push_back((*it)->disconnect_event_id); + } + event_id=event_server->wait_first(events,1000); + if(event_id==0) + { + std::cout << "new connection ..." << std::endl; + client_info=server.get_new_client(); + info.push_back(client_info); + std::cout << " client id: " << client_info->client_id << std::endl; + std::cout << " IP address: " << client_info->IP << " at port " << client_info->port << std::endl; + } + else + { + event_id--; + for(it=info.begin();it!=info.end();it++) + { + if(event_id>=2) + event_id-=2; + else + { + if((event_id%2)==1)// disconnect event + { + std::cout << "client " << (*it)->client_id << " disconnected." << std::endl; + server.free_client((*it)); + it=info.erase(it); + } + else// rx event + { + std::cout << " data received from " << (*it)->client_id << " "; + num=server.get_num_data_from((*it)->client_id); + data=new unsigned char[num]; + server.read_from((*it)->client_id,data,num); + std::cout << data << std::endl; + delete[] data; + } + break; + } + } + } + }catch(CEventTimeoutException &e){ + std::cout << "waiting ..." << std::endl; + }catch(CException &e){ + std::cout << e.what() << std::endl; + } + } + }catch(CException &e){ + std::cout << e.what() << std::endl; + } +} diff --git a/src/examples/test_rs232.cpp b/src/examples/test_rs232.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4598df360b6299d8b1f5c7dbb6f0ec548cbe8c14 --- /dev/null +++ b/src/examples/test_rs232.cpp @@ -0,0 +1,119 @@ +#include "eventserver.h" +#include "threadserver.h" +#include "commexceptions.h" +#include "rs232.h" +#include <stdio.h> +#include <unistd.h> +#include <string> +#include <iostream> + +const std::string serial_dev="/dev/ttyS0"; +const std::string empty_string=""; + +/** + * \example test_rs232.cpp + * + * This example shows how to use the serial port driver + * + * This example tries to use the first serial port available to send and + * receive data. This example is intended to be used with the RX and TX signals + * of the serial cable connected, so that all the data sent by the driver is + * recevived by itself. + * + * \verbatim + * RX --\ + * | + * TX --/ + * \endverbatim + * + * The CComm base class provide events to signal the data reception and also + * some unexpected error on the port. These events are used in this example to + * wait for all the required data, instead of peridically polling it for new + * data (the event server is used). + * + * By default the serial port used is the /dev/ttyS0. If more serial ports are + * available or USB to serial adapters are present, the device name can be + * changed to the desired one: /dev/ttySn or /dev/ttyUSBn. + * + * The output of this example should be something like this: + * + * \verbatim + * + * \endverbatim + * + * Several error may be thrown either by the CComm class or the CRS232 class: + * + * * Invalid device name to open. + * + * * Trying to configure the serial port before being opened. + * + * * Trying to open the port once it is already opened. + * + * * Trying to send or receive data before configuring the serial port. + * + * * Trying to write invalid data- + * + * * Providing an invalid buffer to save all the received data. + */ +int main(int argc,char *argv[]) +{ + CEventServer *event_server=CEventServer::instance(); + unsigned char msg[5]="hola"; + std::string rx_event; + CRS232 serial_port("serial_port"); + std::list<std::string> events; + int num=0,total_length=0,i=0; + TRS232_config serial_config; + + serial_config.baud=9600; + serial_config.num_bits=8; + serial_config.parity=none; + serial_config.stop_bits=1; + try{ + serial_port.open((void *)&empty_string); + }catch(CCommException &e){ + std::cout << e.what() << std::endl; + } + try{ + serial_port.config(&serial_config); + }catch(CCommException &e){ + std::cout << e.what() << std::endl; + } + serial_port.open((void *)&serial_dev); + rx_event=serial_port.get_rx_event_id(); + std::cout << "Data reception event id: " << rx_event << std::endl; + events.push_back(rx_event); + try{ + serial_port.open((void *)"/dev/ttyS0"); + }catch(CCommException &e){ + std::cout << e.what() << std::endl; + } + try{ + serial_port.write(msg,5); + }catch(CCommException &e){ + std::cout << e.what() << std::endl; + } + serial_port.config(&serial_config); + try{ + serial_port.write(NULL,5); + }catch(CCommException &e){ + std::cout << e.what() << std::endl; + } + try{ + for(i=0;i<10;i++) + { + serial_port.write(msg,5); + do{ + event_server->wait_all(events,1000); + num=serial_port.get_num_data(); + serial_port.read(&msg[total_length],num); + total_length+=num; + }while(total_length<5); + total_length=0; + printf("Message received: %s\n",msg); + } + }catch(CException &e){ + std::cout << e.what() << std::endl; + } + serial_port.close(); +} diff --git a/src/examples/test_simple_client.cpp b/src/examples/test_simple_client.cpp new file mode 100644 index 0000000000000000000000000000000000000000..488d1815826140682f5819b9a26d8e6f389ab0f9 --- /dev/null +++ b/src/examples/test_simple_client.cpp @@ -0,0 +1,44 @@ +#include "socketclient.h" +#include "socketexceptions.h" + +std::string server_ip="192.168.0.13"; +int server_port=6653; + +int main(int argc,char *argv[]) +{ + TSocket_info info; + CSocketClient client("client"); + unsigned char msg[5]="hola",answer[5]; + + try{ + while(1) + { + info.address=server_ip; + info.port=server_port; + std::cout << "connecting ... " << std::endl; + while(!client.is_connected()) + { + try{ + client.open(&info); + }catch(CSocketNoConnectionException &e){ + std::cout << " nobody is listening" << std::endl; + sleep(1); + } + } + client.config(NULL); + std::cout << "connected." << std::endl; + sleep(1); + std::cout << "sending data ..." << std::endl; + client.write(msg,5); + sleep(1); + client.read(answer,5); + std::cout << "data received ... " << answer << std::endl; + sleep(1); + std::cout << "closing connection ..." << std::endl; + client.close(); + sleep(1); + } + }catch(CException &e){ + std::cout << e.what() << std::endl; + } +} diff --git a/src/examples/test_simple_server.cpp b/src/examples/test_simple_server.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4c6a7a2fe2c09d3ccc23e9c295e66dfd246b752f --- /dev/null +++ b/src/examples/test_simple_server.cpp @@ -0,0 +1,65 @@ +#include "eventexceptions.h" +#include "socketserver.h" +#include "eventserver.h" + +std::string server_IP_address="127.0.0.1"; +int server_port=2012; +int num_listen=1; + +int main(int argc,char *argv[]) +{ + CEventServer *event_server=CEventServer::instance(); + CSocketServer server("server"); + std::list<std::string> con_events,client_events; + TSocket_info sock_info; + unsigned char *data; + TClient_info *info; + int event_id,num; + + try{ + sock_info.port=server_port; + sock_info.address=server_IP_address; + server.open(&sock_info); + server.config(&num_listen); + server.set_max_clients(5); + con_events.push_back(server.get_new_connection_event_id()); + server.start_server(); + while(1) + { + try{ + event_server->wait_first(con_events,1000); + std::cout << "new connection ..." << std::endl; + info=server.get_new_client(); + std::cout << " client id: " << info->client_id << std::endl; + std::cout << " IP address: " << info->IP << " at port " << info->port << std::endl; + client_events.clear(); + client_events.push_back(info->rx_event_id); + client_events.push_back(info->disconnect_event_id); + do{ + event_id=event_server->wait_first(client_events); + if(event_id==0) + { + std::cout << " data received ... "; + num=server.get_num_data_from(info->client_id); + data=new unsigned char[num]; + server.read_from(info->client_id,data,num); + std::cout << data << std::endl; + delete[] data; + } + if(event_id==1) + { + server.free_client(info); + std::cout << "client disconnected ... " << std::endl; + } + }while(event_id!=1); + }catch(CEventTimeoutException &e){ + std::cout << "waiting ..." << std::endl; + }catch(CException &e){ + std::cout << e.what() << std::endl; + } + } + server.stop_server(); + }catch(CException &e){ + std::cout << e.what() << std::endl; + } +} diff --git a/src/serial/rs232.cpp b/src/serial/rs232.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ceffa0a668f2dd734fae15d19ef017bf945f7560 --- /dev/null +++ b/src/serial/rs232.cpp @@ -0,0 +1,357 @@ +#include "rs232.h" +#include "rs232exceptions.h" +#include "ctime.h" + +CRS232::CRS232(const std::string& comm_id) : CComm(comm_id) +{ + this->serial_fd=-1; +} + +void CRS232::set_baudrate(int baud) +{ + struct termios config; + int baudrate; + + if(tcgetattr(this->serial_fd,&config)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to get the attribute structure.\n",this->comm_id); + } + else + { + switch(baud) + { + case 50: baudrate=B50; + break; + case 75: baudrate=B75; + break; + case 110: baudrate=B110; + break; + case 134: baudrate=B134; + break; + case 150: baudrate=B150; + break; + case 200: baudrate=B200; + break; + case 300: baudrate=B300; + break; + case 600: baudrate=B600; + break; + case 1200: baudrate=B1200; + break; + case 1800: baudrate=B1800; + break; + case 2400: baudrate=B2400; + break; + case 4800: baudrate=B4800; + break; + case 9600: baudrate=B9600; + break; + case 19200: baudrate=B19200; + break; + case 38400: baudrate=B38400; + break; + case 57600: baudrate=B57600; + break; + case 115200: baudrate=B115200; + break; + default: /* handle exception */ + throw CRS232Exception(_HERE_,"Invalid baudrate. See the documentation for the possible values.\n",this->comm_id); + break; + } + cfsetispeed(&config,baudrate); + cfsetospeed(&config,baudrate); + if(tcsetattr(this->serial_fd,TCSANOW,&config)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to set up the new attribute structure.\n",this->comm_id); + } + } +} + +void CRS232::set_num_bits(char num_bits) +{ + struct termios config; + int bits; + + if(tcgetattr(this->serial_fd,&config)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to get the attribute structure.\n",this->comm_id); + } + else + { + switch(num_bits) + { + case 5: bits=CS5; + break; + case 6: bits=CS6; + break; + case 7: bits=CS7; + break; + case 8: bits=CS8; + break; + default: /* handle exceptions */ + throw CRS232Exception(_HERE_,"Invalid number of bits per packet. See the documentation for the possible values.\n",this->comm_id); + break; + } + config.c_cflag&=~CSIZE; + config.c_cflag|=bits; + if(tcsetattr(this->serial_fd,TCSANOW,&config)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to set up the new attribute structure.\n",this->comm_id); + } + } +} + +void CRS232::set_parity(parity_type parity) +{ + struct termios config; + + if(tcgetattr(this->serial_fd,&config)==-1) + { + /* handle exception */ + throw CRS232Exception(_HERE_,"Impossible to get the attribute structure.\n",this->comm_id); + } + else + { + if(parity!=none) + { + config.c_cflag|=PARENB; + if(parity==odd) config.c_cflag|=PARODD; + else if(parity==even) config.c_cflag&=~PARODD; + else + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Invalid parity type. See the documentation for the possible values.\n",this->comm_id); + } + } + else config.c_cflag&=~PARENB; + if(tcsetattr(this->serial_fd,TCSANOW,&config)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to set up the new attribute structure.\n",this->comm_id); + } + } +} + +void CRS232::set_stop_bits(char stop_bits) +{ + struct termios config; + + if(tcgetattr(this->serial_fd,&config)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to get the attribute structure.\n",this->comm_id); + } + else + { + if(stop_bits==2) config.c_cflag|=CSTOPB; + else if(stop_bits==1)config.c_cflag&=~CSTOPB; + else + { + /* handle execptions */ + throw CRS232Exception(_HERE_,"Invalid number of stop bits. See the documentation for the possible values.\n",this->comm_id); + } + if(tcsetattr(this->serial_fd,TCSANOW,&config)==-1) + { + /* handle exception */ + throw CRS232Exception(_HERE_,"Impossible to set up the new attribute structure.\n",this->comm_id); + } + } +} + +void CRS232::hard_open(void *comm_dev) +{ + std::string *serial_dev=(std::string *)comm_dev; + struct termios config; + + if(serial_dev->size()==0) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Invalid serial port device name (empty string).\n",this->comm_id); + } + else + { + this->serial_fd=::open(serial_dev->c_str(),O_RDWR | O_NOCTTY | O_NONBLOCK | O_ASYNC); + if(this->serial_fd==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to open the serial port.\n",this->comm_id); + } + else + { + if(tcgetattr(this->serial_fd,&config)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to get the attribute structure.\n",this->comm_id); + } + else + { + config.c_cflag|=(CLOCAL | CREAD); + config.c_cflag&=~CRTSCTS; + config.c_lflag&=~(ICANON | ECHO | ECHOE | ISIG); + config.c_iflag&=~(IXON | IXOFF | IXANY); + config.c_iflag&=~(INLCR | IGNCR | ICRNL); + config.c_oflag&=~OPOST; + config.c_cc[VMIN]=0; + config.c_cc[VTIME]=100; + if(tcsetattr(this->serial_fd,TCSANOW,&config)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to set up the new attribute structure.\n",this->comm_id); + } + } + } + } +} + +void CRS232::hard_config(void *config) +{ + TRS232_config *serial_conf=(TRS232_config *)config; + + this->set_baudrate(serial_conf->baud); + this->set_num_bits(serial_conf->num_bits); + this->set_parity(serial_conf->parity); + this->set_stop_bits(serial_conf->stop_bits); +} + +int CRS232::hard_read(unsigned char *data, int len) +{ + int num_read=0; + + if((num_read=::read(this->serial_fd,data,len))==-1) + { + return -1; + } + + return num_read; +} + +int CRS232::hard_write(unsigned char *data, int len) +{ + int num_written=0; + + if((num_written=::write(this->serial_fd,data,len))==-1) + { + return -1; + } + + return num_written; +} + +int CRS232::hard_get_num_data(void) +{ + int num; + + if(ioctl(this->serial_fd,FIONREAD,&num)==-1) + { + return -1; + } + + return num; +} + +int CRS232::hard_wait_comm_event(void) +{ + fd_set receive_set,error_set; + int max_fd,wait_result=0; + + max_fd=this->serial_fd+1; + FD_ZERO(&receive_set); + FD_SET(this->serial_fd,&receive_set); + FD_ZERO(&error_set); + FD_SET(this->serial_fd,&error_set); + wait_result=select(max_fd,&receive_set,NULL,&error_set,NULL); + if(wait_result==-1) + return -1; + else + { + if(FD_ISSET(this->serial_fd,&receive_set))/* data has been received */ + { + return 1; + } + if(FD_ISSET(this->serial_fd,&error_set)) + { + return 2; + } + } + + return -1; +} + +void CRS232::hard_close(void) +{ + if(this->serial_fd!=-1) + { + ::close(this->serial_fd); + this->serial_fd=-1; + } +} + +void CRS232::set_control_signal(control_signals signal) +{ + int status; + + if(ioctl(this->serial_fd, TIOCMBIS, (const int *)&signal)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to get the control signals status.\n",this->comm_id); + } + + return; + + if(ioctl(this->serial_fd, TIOCMGET, &status)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to get the control signals status.\n",this->comm_id); + } + status|=signal; + if(ioctl(this->serial_fd, TIOCMSET, status)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to set the control signals status.\n",this->comm_id); + } +} + +void CRS232::clear_control_signal(control_signals signal) +{ + int status; + + if(ioctl(this->serial_fd, TIOCMBIC, (const int *)&signal)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to get the control signals status.\n",this->comm_id); + } + + return; + + if(ioctl(this->serial_fd, TIOCMGET, &status)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to get the control signals status.\n",this->comm_id); + } + status&=~signal; + if(ioctl(this->serial_fd, TIOCMSET, status)==-1) + { + /* handle exceptions */ + throw CRS232Exception(_HERE_,"Impossible to set the control signals status.\n",this->comm_id); + } +} + +bool CRS232::get_control_signal(control_signals signal) +{ + int status; + + ioctl(this->serial_fd, TIOCMGET, &status); + if(status&signal) + return true; + else + return false; +} + +CRS232::~CRS232() +{ + this->close(); +} diff --git a/src/serial/rs232.h b/src/serial/rs232.h new file mode 100644 index 0000000000000000000000000000000000000000..18d603d6e6a19b5b6e95dae6d45fb8cf5815bfb4 --- /dev/null +++ b/src/serial/rs232.h @@ -0,0 +1,424 @@ +#ifndef _RS232_H +#define _RS232_H + +#include "comm.h" +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <termios.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string> + +/** + * \brief Available types of parity + * + * This enumeration provides all the possible parity types available on the + * RS-232 serial port. The parity types are: + * + * * none: no parity bit is used and there is no way to detect errors. + * * even: adds an additional bit such that the number of ones in each packet + * is even. + * * odd: adds an additional bit such that the number of ones in each packet + * is odd. + * * mark: adds an additional bit with the fixed value of one. + * * space: adds an additional bit with the fixed value of zero. + */ +typedef enum {none,even,odd,mark,space} parity_type; + +/** + * \brief Available control signals of the RS232 port + * + * This enumeration provides identifiers for all the control signals of + * an standard serial port. These signals include the following: + * + * * DSR: Data Set Ready. This signal indicates the sending device that the + * receiving device is ready. + * * DTR: Data Terminal Ready. This signal indicates the receiving device that + * the sending device is ready. + * * RTS: Request To Send. This signal is used to perform hardware flow control + * and is used by the sending device to check if it is possible to start + * sending data. + * * CTS: Clear To Send. This signal is used to perform hardware flow control + * and it is used by the receiving device as an answer to the RTS signal + * to notify that the receiving device is ready to receive data. + * * CD: Data Carrier Detect. This signal was used in modems to notify that the + * modem was connected to the telephon line. + * * RI: Ring Indicator: This signal was used in modems to notify that the ring + * signal was detected on the telephone line. + * + * This is the standard function for each of the control signals of a serial port, + * However they can be used for other purposes. + * + */ +typedef enum {rs232_dsr=TIOCM_DSR, + rs232_dtr=TIOCM_DTR, + rs232_rts=TIOCM_RTS, + rs232_cts=TIOCM_CTS, + rs232_cd=TIOCM_CD, + rs232_ri=TIOCM_RI} control_signals; + +/** + * \brief Configuration structure for the serial port + */ +typedef struct{ + /** + * \brief Serial port baudrate + * + * This filed represents the desired speed in bits per second. Not all values are + * valid. The valid values for this parameter are listed below: + * + * - 50 + * - 75 + * - 110 + * - 134 + * - 150 + * - 200 + * - 300 + * - 600 + * - 1200 + * - 1800 + * - 2400 + * - 4800 + * - 9600 + * - 19200 + * - 38400 + * - 57600 + * - 115200 + * + */ + int baud; + /** + * \brief Number of bits per paquet + * + * This field represents the number of bits of each packet. Not all values + * are valid. The valid values for this parameter are 5,6,7 or 8. + */ + char num_bits; + /** + * \brief Parity type + * + * This field represents the desired parity type for each packet. This parameter + * must belong to the parity_type enumeration. See the documentation on this data + * type for more information on the possible values of this parameter. + */ + parity_type parity; + /** + * \brief Number of stop bits + * + * This field represents the desired number of stop bits per packet. The possible + * values for this parameter are only 1 or 2. + */ + char stop_bits; +}TRS232_config; + +/** + * \brief RS-232 Serial port driver + * + * This class implements a driver to use the standard RS-232 ports on any + * computer. It inherits from the CComm class which provides the basic + * interface to any communication device. + * + * This class overloads the open() and config() functions of the base class + * to suit the specific requirements of a serial port. Also, several + * functions are declared to configure the features of the serial port: + * baudrate, number of bits, parity and number of stop bits. + * + * For a more detailed description of the behavior of this class, see the + * documentation for the CComm base class. + */ +class CRS232 : public CComm +{ + private: + /** + * \brief write file descriptor of the communication device + * + * This variable is the write file descriptor associated to the communication + * device. By default it is initialized to -1, and it is the inherited class + * that must initialize it when actually opening the communication device by + * calling the open() function. The read and write file descriptors can be + * the same or different, depending on the particular communication device. + */ + int serial_fd; + protected: + /** + * \brief Function to set the desired baudrate + * + * This functions sets the speed of the srial port in bits per second. + * If the given speed is not valid this function throws a CRS232Exception. + * + * \param baud the desired speed in bits per second. Not all values are + * valid. The valid values for this parameter are listed below: + * + * - 50 + * - 75 + * - 110 + * - 134 + * - 150 + * - 200 + * - 300 + * - 600 + * - 1200 + * - 1800 + * - 2400 + * - 4800 + * - 9600 + * - 19200 + * - 38400 + * - 57600 + * - 115200 + * + */ + void set_baudrate(int baud); + /** + * \brief Function to set the number of bits per packet + * + * This function sets the number of data bits included in each packet. If + * the given number of bits is not valid, a CRS232Exception is thrown. + * + * \param num_bits The number of bits of each packet. Not all values are + * valid. The valid values for this parameter are 5,6,7 or + * 8. + * + */ + void set_num_bits(char num_bits); + /** + * \brief Function to set the parity + * + * This function sets the desired parity (if any) for each of the packets. + * If the given parity type is not valid, a CRS232Exception is thrown. + * + * \param parity the desired parity type for each packet. This parameter + * must belong to the parity_type enumeration. See the + * documentation on this data type for more information + * on the possible values of this parameter. + */ + void set_parity(parity_type parity); + /** + * \brief Function to set the number of stop bits + * + * This function sets the desired number of stop bits for each packet. If + * the given number of stop bits is not valid, a CRS232Exception is thrown. + * + * \param stop_bits the desired number of stop bits per packet. The possible + * values for this parameter are only 1 or 2. + * + */ + void set_stop_bits(char stop_bits); + /** + * \brief Function to open an RS232 serial port + * + * This function is called automatically when the base class open() function + * is called. It must create a handle to the communication device in order to + * be used in the future. The device handle must be provided by any inherited + * class since it is device dependant. + * + * This class accepts a generic parameter (void *) to allow the user to pass + * to the function any data structure. This parameter comes from the base + * class open() function, but no action is performed on it. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param comm_dev a generic pointer (void *) to the data structure needed to + * identify the communication device to open and any other + * required information. This parameter may be NULL if not + * needed. + * \verbatim + * /dev/ttyS0 + * \endverbatim + */ + virtual void hard_open(void *comm_dev); + /** + * \brief Function to configure the serial port + * + * This function is called automatically when the base class config() function + * is called. It must configure the communication device as required by the + * application. + * + * This class accepts a generic parameter (void *) to allow the user to pass + * to the function any data structure. This parameter comes from the base + * class open() function, but no action is performed on it. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param config a pointer to a TRS232_config structure already initialized + * with all teh configuration parameters of the serial port. + */ + virtual void hard_config(void *config); + /** + * \brief Function to actually read from the device + * + * This function is automatically called when the new data received is activated. + * The read() function from the base class gets data from the internal queue, so + * this function is not used. It must try to read the ammount of data specified + * and store it in the data buffer provided without blocking. Also, it must + * return the number of bytes actually read from the devicve, since they may be + * different than the desired value. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param data a reference to the buffer where the received data must be + * copied. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * read from the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually read. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + virtual int hard_read(unsigned char *data, int len); + /** + * \brief Function to actually write to the device + * + * This function is automatically called either when the base class write() + * function is called or when the data transmission end event is activated. + * It must try to write the desired ammount of data to the communication + * device without blocking. Also it must return the number of bytes actually + * written to the communication device since they may be different from the + * desired value. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param data a reference to the buffer with the data must be send to the + * device. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * written to the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually written. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + virtual int hard_write(unsigned char *data, int len); + /** + * \brief Function to actually get the number of bytes availables + * + * This function is called when the new data received event is activated. + * It must get the number of data bytes available from the communication + * device and return its value without blocking. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \return an integer with the number of bytes available in the receive queue. + * This value can be 0 if there is no data available, but there is ni upper + * limit in its value. + */ + virtual int hard_get_num_data(void); + /** + * \brief Function to actually wait for a given communication event + * + * This function is called in the internal communciation thread to wait for + * any event on the communuication device. It must check for any event on the + * device (reception, end of transmission or error) and return the + * corresponding identifier. When an event is activated, this function must + * return to allow the base class to handle it, and the it is called again + * to wait for the next event. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \return -1 if there has been any error or else the identifier of the + * communciation event: + * + * - 0 for the end of transmission event + * - 1 for the new data received event + * - 2 for the error event + */ + virtual int hard_wait_comm_event(void); + /** + * \brief Function to actually close the device + * + * This function is called when the base class close() funciton is called. It + * must free the device handle initialized by the open() function and also free + * any other resource allocated by the inherited class. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + */ + virtual void hard_close(void); + public: + /** + * \brief Constructor + * + * This constructor creates a new CRS232 object initialized by default, + * but it does not open any physical serial port. This constructor calls + * the base class constructor with the serial port unique identifier + * provided to the constructor. + * + * \param comm_id A null terminated string which identified the + * communication device. This string is used to create a + * unique identifier for all the threads and events of the + * class. + * + */ + CRS232(const std::string& comm_id); + /** + * \brief Function to set an RS232 control signal + * + * This function changes the state of an output control signal to its + * active state (negative voltage). See the documentation on the + * control_signals enumeration type for information on the possible control + * signals. + * + * If the state of the control signal can not be changed, an CRS232Exception + * object is thrown. + * + * \param signal an identifier of the signal to be activated. This parameter + * must be of the control_signals enumeration type. + * + */ + void set_control_signal(control_signals signal); + /** + * \brief Funciton to reset an RS232 control signal + * + * This function chnages the state of an output control signal to its inactive + * state (positive voltage). See the documentation on the control_signals + * enumeration type for information on the possible control signals. + * + * If the state of the control signal can not be changed, an CRS232Exception + * object is thrown. + * + * \param signal an identifier of the signal to be activated. This parameter + * must be of the control_signals enumeration type. + * + */ + void clear_control_signal(control_signals signal); + /** + * \brief Function to get the current state of an RS232 control signal + * + * This function returns the current state of an input control signal. See + * the documentation on the control_signals enumeration type for information + * on the possible control signals. + * + * If the state of the control signal can not be changed, an CRS232Exception + * object is thrown. + * + * \return the current state of the desired control signal. True is the + * current state is active (negative voltage) and false id the + * current state is inactive (positive voltage). + */ + bool get_control_signal(control_signals signal); + /** + * \brief destructor + * + * This destructor does nothing. The base class destructor is the one in + * charge of freeing all the allocated resources. + */ + ~CRS232(); +}; + +#endif diff --git a/src/serial/rs232exceptions.cpp b/src/serial/rs232exceptions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..369bdc7b4dd0560b303247130f9ad57f3173fd33 --- /dev/null +++ b/src/serial/rs232exceptions.cpp @@ -0,0 +1,11 @@ +#include "rs232exceptions.h" +#include <string.h> +#include <stdio.h> + +const std::string rs232_exception_msg="[CRS232 class] - "; + +CRS232Exception::CRS232Exception(const std::string& where,const std::string& error_msg,const std::string& comm_id):CCommException(where,rs232_exception_msg,error_msg) +{ + this->error_msg+=" - "; + this->error_msg+=comm_id; +} diff --git a/src/serial/rs232exceptions.h b/src/serial/rs232exceptions.h new file mode 100644 index 0000000000000000000000000000000000000000..afacce075a372c37c87f4addaf45dd56073c3680 --- /dev/null +++ b/src/serial/rs232exceptions.h @@ -0,0 +1,60 @@ +#ifndef RS232_EXCEPTIONS +#define RS232_EXCEPTIONS + +#include "commexceptions.h" + +/** + * \brief RS232 exception class + * + * This class implements the exceptions for the CRS232 communication class. + * In addition to the basic error message provided by the base class CException, + * this exception class provides also the unique identifier of the communication + * device that generated the exception. + * + * Also, similarly to other exception classes, it appends a class identifer + * string ("[CRS232 class] - ") to the error message in order to identify + * the class that generated the exception. + * + * The base class can be used to catch any exception thrown by the application + * or also, this class can be used in order to catch only exceptions generated + * by CS232 objects. + * + */ +class CRS232Exception : public CCommException +{ + public: + /** + * \brief Constructor + * + * The constructor calls the base class constructor to add the general + * exception identifier and then adds the class identifier string + * "[CRS232 class]" and the supplied error message. + * + * It also appends the unique identifier of the communication device + * that generated the exception. So, the total exception message will + * look like this: + * + * \verbatim + * [Exception caught] - <where> + * Error: [CComm class] - [CRS232 class] - <error message> - <comm id> + * \endverbatim + * + * \param where a null terminated string with the information about the name + * of the function, the source code filename and the line where + * the exception was generated. This string must be generated + * by the _HERE_ macro. + * + * \param error_msg a null terminated string that contains the error message. + * This string may have any valid character and there is no + * limit on its length. + * + * \param comm_id a null terminated string that contains the communication + * device unique identifier. This string must be the one used to + * create the object. + * + * + */ + CRS232Exception(const std::string& where,const std::string& error_msg,const std::string& comm_id); +}; + +#endif diff --git a/src/sockets/socket.cpp b/src/sockets/socket.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aaddeff9f5eb119bcb424ebd772ae41d17443078 --- /dev/null +++ b/src/sockets/socket.cpp @@ -0,0 +1,182 @@ +#include "socket.h" +#include "socketexceptions.h" +#include <string.h> + +CSocket::CSocket(const std::string &comm_id) : CComm(comm_id) +{ + struct hostent *host; + char name[1024]; + + this->connection_closed_event.clear(); + this->connection_closed_event+=comm_id; + this->connection_closed_event+="_closed"; + this->event_server->create_event(this->connection_closed_event); + this->cloned=false; +} + +CSocket::CSocket(const std::string &comm_id,const int fd) : CComm(comm_id) +{ + struct hostent *host; + char name[200]; + + if(fd<0) + { + /* handle exceptions */ + throw CSocketException(_HERE_, "Invalid file descriptor",comm_id); + } + else + { + this->connection_closed_event.clear(); + this->connection_closed_event+=comm_id; + this->connection_closed_event+="_closed"; + this->event_server->create_event(this->connection_closed_event); + this->cloned=true; + this->socket_fd=fd; + } +} + +CSocket *CSocket::create_socket(const std::string &comm_id, const int fd) +{ + CSocket *new_socket; + + new_socket=new CSocket(comm_id,fd); + + return new_socket; +} + +std::string CSocket::get_connection_closed_event(void) +{ + return this->connection_closed_event; +} + +void CSocket::hard_open(void *comm_dev) +{ + int yes=1; + + if(!this->cloned) + { + if((this->socket_fd=socket(AF_INET,SOCK_STREAM,0))<0) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Error opening socket", this->comm_id); + } + else + { + if(setsockopt(this->socket_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int))<0) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Impossible to change socket options",this->comm_id); + } + } + } +} + +void CSocket::hard_config(void *config) +{ + /* do nothing */ +} + +int CSocket::hard_read(unsigned char *data, int len) +{ + int num_read=0; + + if(!this->event_server->event_is_set(this->connection_closed_event)) + { + if((num_read=::read(this->socket_fd,data,len))==-1) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Error while reading from the socket.",this->comm_id); + } + } + + return num_read; +} + +int CSocket::hard_write(unsigned char *data, int len) +{ + int num_write=0; + + if(!this->event_server->event_is_set(this->connection_closed_event)) + { + if((num_write=::write(this->socket_fd,data,len))==-1) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Error while writing to the socket.",this->comm_id); + } + } + + return num_write; +} + +int CSocket::hard_get_num_data(void) +{ + int num; + + if(!this->event_server->event_is_set(this->connection_closed_event)) + { + if(ioctl(this->socket_fd,FIONREAD,&num)==-1) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Error while getting the number of bytes available from the socket.",this->comm_id); + } + } + + return num; +} + +int CSocket::hard_wait_comm_event(void) +{ + fd_set receive_set,error_set; + int max_fd,wait_result=0; + + max_fd=this->socket_fd+1; + FD_ZERO(&receive_set); + FD_SET(this->socket_fd,&receive_set); + FD_ZERO(&error_set); + FD_SET(this->socket_fd,&error_set); + wait_result=select(max_fd,&receive_set,NULL,&error_set,NULL); + if(wait_result==-1) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Error while waiting for socket events",this->comm_id); + } + else + { + if(FD_ISSET(this->socket_fd,&receive_set))/* data has been received */ + { + if(this->hard_get_num_data()==0) + { + if(!this->event_server->event_is_set(this->connection_closed_event)) + this->event_server->set_event(this->connection_closed_event); + return -1; + } + else + return 1; + } + if(FD_ISSET(this->socket_fd,&error_set)) + return 2; + } + + return -1; +} + +void CSocket::hard_close(void) +{ + if(this->socket_fd!=-1) + { + if(::close(this->socket_fd)<0) + throw CSocketException(_HERE_,"Error while closing the socket",this->comm_id); + this->socket_fd=-1; + } + this->cloned=false; +} + +CSocket::~CSocket() +{ + this->close(); + if(this->connection_closed_event.size()!=0) + { + this->event_server->delete_event(this->connection_closed_event); + this->connection_closed_event.clear(); + } +} diff --git a/src/sockets/socket.h b/src/sockets/socket.h new file mode 100644 index 0000000000000000000000000000000000000000..89f1763f23aa287c12e0936db0d3d486504af0ad --- /dev/null +++ b/src/sockets/socket.h @@ -0,0 +1,322 @@ +#ifndef _SOCKET_H +#define _SOCKET_H + +#include "comm.h" +#include "mutex.h" +#include <iostream> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <netdb.h> + +/** + * \brief Socket information structure + * + * This structure holds information about the remote IP address and the port + * where the socket is connected. + * + */ +typedef struct { + /** + * \brief address of the socket + * + * A string with the desired IP addres to assign to the socket. The IP address + * must be passed with the dot format, as shown here + * + * * 192.168.0.1 + */ + std::string address; + /** + * \brief port of the socket + * + * An integer with the port number to be assigned to the socket. This value can + * be any positive interger which is not used by any other application. + */ + int port; +}TSocket_info; + +/** + * \brief Socket driver + * + * This class implements a driver to use generic sockets using the TCP/IP protocol. + * It inherits from CComm to include all the basic functionality of a communication + * device and reimplements the socket specific functions. + * + * This class provides an event which is activated when the connection is remotely + * closed. This event can be used by the application using the socket to monitor + * the state iof the connection. To get the identifier of this event, use the + * get_connection_closed_event() function. + * + * A special constructor is provided to create a new CSocket object from an already + * initialized socket file descriptor provided by some system function of a + * manufacturer library. However, this constructor is no public, and it can only + * be accessed by the create_socket() function. + * + * In case of any error, this class throws an exceptions of the CSocketException class. + * + * For more detailed description of the behavior of this class, see the documentation + * for the CComm base class. + */ + +class CSocket : public CComm +{ + private: + /** + * \brief Connection closed event identifier + * + * This string holds the unique identifier of the event to indicate the + * closure of a connection. Its value is initialized at construction time + * with the name assigned to the class to create the unique identifier for + * the event. Use the get_connection_closed_event() function to get this + * string. + */ + std::string connection_closed_event; + /** + * \brief cloned socket flag + * + * This attribute is used to differentiate between a CSocket object created + * by the default constructor and those created by the create_socket() function. + * This flag is mainly used to avoid opening a new socket when the hard_open() + * function is called. The value of this attribute is changed internally and can + * not be accessed from the outside. + */ + bool cloned; + protected: + /** + * \brief socket file descriptor. + * + * This integer holds the file descriptor of the socket associated to the + * object. This attribute is initialized when either the open() or create_socket() + * functions are called and can not be modified afterwards. Its value is valid + * until the close() function is called. + * + */ + int socket_fd; + /** + * \brief Constructor with parameters + * + * This constructor is used only by the create_socket() function to create a + * new CSocket object and associate it to an already initialized socket file + * descriptor. Since it is protected, it can not be used as a regular + * constructor. + * + * \param comm_id a string with the unique identifier of the socket object. + * This identifier is used to create the unique identifiers + * of the internal threads and events, so it is imperative + * that each socket has a unique name. + * + * \param fd a positive integer with the value of the file descriptor of + * an already initialized socket. If the file descriptor provided + * to this function is not valid or it is not properly initialized, + * unexpected errors will ocurr. + */ + CSocket(const std::string &comm_id,const int fd); + /** + * \brief function to create a new socket object from a file descriptor + * + * This function is used in a server side application to create a new + * CSocket object (or any inherited class) from an already initialized + * socket file descriptor. This is required when the server socket + * accepts a new connection with the accept() function, which returns + * a file descriptor which is not associated to any CSocket object. + * + * This function is defined as static so that it can be called without + * having to create an object of this class, but it can not be accessed + * outside an object of this class, since it is defined as protected. + * + * The most common use of this function is in a server side of a connection + * just after the accept() function returns with a new client connection, in + * order to associate the new socket to a CSocket object. + * + * \param comm_id a string with the unique identifier of the socket object. + * This identifier is used to create the unique identifiers + * of the internal threads and events, so it is imperative + * that each socket has a unique name. + * + * \param fd a positive integer with the value of the file descriptor of + * an already initialized socket. If the file descriptor provided + * to this function is not valid or it is not properly initialized, + * unexpected errors will ocurr. + * + * \return a new CSocket object initialized with the parameters passed as + * arguments to the function. + */ + static CSocket *create_socket(const std::string &comm_id, const int fd); + /** + * \brief Function to actually open the device + * + * This function is called automatically when the base class open() function + * is called. By default, it initializes the socket file descriptor using the + * socket() system call. But, if the object has been created by the + * create_socket() function, this function does nothing. + * + * This class does not need any parameter since the socket is created by + * default using a TCP/IP protocol and the AF_INET socket family. So the + * argument passed to this function is ignored and may be NULL. + * + * This function can throw any CSocketException object exception or the generic + * CCommException class. + * + * \param comm_dev this parameter is ignored in this case and can be set to NULL. + * It is only keeped for backward compatiblity with the CComm + * class. + */ + virtual void hard_open(void *comm_dev=NULL); + /** + * \brief Function to actually configure the device + * + * This function is called automatically when the base class config() function + * is called. In this case this function does nothing, since all the necessay + * parameters are provide to the open() function. However, it is necessary to + * call the config() function to successfully change the internal state of the + * communication device. + * + * The provided parameter in this case can be NULL since no configuration is + * needed. This function can throw any CCommException object exception or else + * any exception class defined by the inherited class. + * + * \param config This parameter is not used since no configuration is required. + * It is only keeped for backward compatiblity with the CComm + * class. + */ + virtual void hard_config(void *config=NULL); + /** + * \brief Function to actually read from the device + * + * This function is automatically called when the new data received event is + * activated. The read() function from the base class gets data from the + * internal queue, so this function is not used. It must try to read the + * ammount of data specified and store it in the data buffer provided without + * blocking. Also, it must return the number of bytes actually read from the + * devicve, since they may be different than the desired value. + * + * In case of any error, this function throws a CSocketException exception. + * + * \param data a reference to the buffer where the received data must be + * copied. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * read from the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually read. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + virtual int hard_read(unsigned char *data, int len); + /** + * \brief Hard write function + * + * This function is automatically called when the base class write() function + * is called. It must try to write the desired ammount of data to the communication + * device without blocking. Also it must return the number of bytes actually + * written to the communication device since they may be different from the + * desired value. + * + * In case of any error, this function throws a CSocketException exception. + * + * \param data a reference to the buffer with the data must be send to the + * device. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * written to the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually written. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + virtual int hard_write(unsigned char *data, int len); + /** + * \brief Function to actually get the number of bytes availables + * + * This function is called when the new data received event is activated. + * It must get the number of data bytes available from the communication + * device and return its value without blocking. + * + * In case of any error, this function throws a CSocketException exception. + * + * \return an integer with the number of bytes available in the receive queue. + * This value can be 0 if there is no data available, but there is ni upper + * limit in its value. + */ + virtual int hard_get_num_data(void); + /** + * \brief Function to actually wait for a given communication event + * + * This function is called in the internal communciation thread to wait for + * any event on the communuication device. It must check for any event on the + * device (reception, end of transmission or error) and return the + * corresponding identifier. When an event is activated, this function must + * return to allow the base class to handle it, and the it is called again + * to wait for the next event. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \return -1 if there has been any error or else the identifier of the + * communciation event: + * + * - 1 for the new data received event + * - 2 for the error event + */ + virtual int hard_wait_comm_event(void); + /** + * \brief Hard close function + * + * This function is called when the base class close() funciton is called. It + * must free the device handle initialized by the open() function and also free + * any other resource allocated by the inherited class. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + */ + virtual void hard_close(void); + public: + /** + * \brief default constructor + * + * This constructor creates a new CSocket object. It does not open a physical + * socket, it only allocates the necessary resources to use it. + * + * \param comm_id A null terminated string which identifies the + * communications device. This string is used to created a + * unique identifier for all the threads and events of the + * class. + */ + CSocket(const std::string &comm_id); + /** + * \brief Function to get the name of Connection Closed Event + * + * This function is used to retrieve the identifier of the connection closed + * event. This event is initialized at contruction time, and it can be + * retrieved at any time. + * + * This function only returns a copy of the internal attribute, so the value + * returned by this function can be modified without affecting the proper + * function of the class. + * + * \return a string with a copy of the identifier of the connection closed + * event. This identifier can be used in the wait_first() or wait_all() + * functions of the CEventServer class to wait its activation. + */ + std::string get_connection_closed_event(void); + /** + * \brief Destructor + * + * This destructor does nothing. The base class destructor is the one in + * charge of freeing all the allocated resources. + */ + virtual ~CSocket(); +}; + +#endif diff --git a/src/sockets/socketclient.cpp b/src/sockets/socketclient.cpp new file mode 100644 index 0000000000000000000000000000000000000000..efb63cf52119da6e5dc075deb640c9d4f76343a0 --- /dev/null +++ b/src/sockets/socketclient.cpp @@ -0,0 +1,70 @@ +#include "socketclient.h" +#include "socketexceptions.h" +#include <string.h> + +CSocketClient::CSocketClient(const std::string &sock_id) : CSocket(sock_id) +{ + this->connected=false; + this->remote.address.clear(); + this->remote.port=-1; +} + +void CSocketClient::hard_close(void) +{ + CSocket::hard_close(); + this->connected=false; + this->remote.address.clear(); + this->remote.port=-1; +} + +void CSocketClient::hard_open(void *comm_dev) +{ + TSocket_info *remote=(TSocket_info *)comm_dev; + sockaddr_in sock; + + CSocket::hard_open(NULL); + memset(&sock,0,sizeof(sock)); + sock.sin_family=AF_INET; + if(inet_aton(remote->address.c_str(),&sock.sin_addr)==0) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Impossible to convert IP address",this->comm_id); + } + else + { + sock.sin_port=htons(remote->port); + if((::connect(this->socket_fd,(struct sockaddr *)&sock,sizeof(sock)))<0) + { + /* handle exceptions */ + if(errno==ECONNREFUSED) + throw CSocketNoConnectionException(_HERE_,"Nobody listening",this->comm_id); + else + throw CSocketException(_HERE_, "Error with connect function", this->comm_id); + } + else + { + this->connected=true; + this->remote.port=remote->port; + this->remote.address=remote->address; + } + } +} + +int CSocketClient::get_remote_port(void) +{ + return this->remote.port; +} + +std::string CSocketClient::get_remote_IP_address(void) +{ + return this->remote.address; +} + +bool CSocketClient::is_connected(void) +{ + return this->connected; +} + +CSocketClient::~CSocketClient() +{ +} diff --git a/src/sockets/socketclient.h b/src/sockets/socketclient.h new file mode 100644 index 0000000000000000000000000000000000000000..741a876b87c6963def291e63e341f918a1bd2f46 --- /dev/null +++ b/src/sockets/socketclient.h @@ -0,0 +1,152 @@ +#ifndef _SOCKETCLIENT_H +#define _SOCKETCLIENT_H + +#include "socket.h" + +/** + * \brief Client side socket + * + * This class implements the functionality of a client socket. It inherits from + * the socket class for the basic socket communication issues and adds the + * necessary resources to work as a client socket. + * + * The hard_open() function (called when the base class open() function is called) + * is extended to try to connect to the desired server. If the server is already + * listening, the connection is set. However, if the server is not yet listening, + * a special exception (CSocketNoConnection) is thrown. This special exception can + * be catch and used to iterate until the server is ready. + * + * The general procedure to work with client sockets is as follows: + * + * 1. Create a new CSocketClient object with a unique identifier. + * 2. Call the open() function with the server IP address and port + * 3. If a CSocketNoConnection is throw, either repeat step 2 or exit. + * 4. On success, call the config() function without any parameters + * 5. Perform any read or write operations on the socket + * 6. Call the close() function to finish the socket connection. + * + */ +class CSocketClient : public CSocket +{ + private: + /** + * \brief information on the remote server + * + * This structure holds the IP address and port of the remote server to which + * the client socket is connected. This structure is initialized after a + * successfull call to the open() function and can not be modified afterwards, + * until the socket is closed and reconnected to an other server. + * + * By default, the IP address string is empty and the port is initialized to -1. + * Use the get_remote_port() and get_remote_IP_address() to get the values + * of this structure at any time. + */ + TSocket_info remote; + /** + * \brief Connection flag + * + * This flag indicated whether the client socket is connected to a server or + * not. By default it is set to false, and it is only set to true after a + * successfull call to the open() function. When the socket is closed, and + * therefore the connection is severed, this flag is set back to false. + * + * Use the is_connected() function to check whether the socket is connected + * or not. + */ + bool connected; + protected: + /** + * \brief Connect function + * + * This function is called when the base class open() function is called, and + * it is used to perform the connection to a server. This function requires a + * TSocket_info structure as parameter which must be provided to the open() + * call. It also calls the hard_open() function of the CSocket class to + * initialize the base class attributes. + * + * The IP address and port within this structure are used to try to stablish + * a communication with a remote server. If the server is already listening, + * the function returns normally with all the internal attributes properly + * initialized. + * + * If the server is not yet listening, this function throws a CSocketNoConnection + * exception. This exception can be catched and used to iterate the process + * until the server is available or terminate the applcation. In case of any + * other error, this function throws a CSocketException. + * + * After the connection is set, it is still necessary to call the config() + * function to properly configure the object to send and receive information. + * + * \param comm_dev a valid pointer to a TSocket_info structure with the IP + * address and listening port of the desired server. See the + * documentation on the TSocket_info structure for more + * information on the IP address and port format. + */ + virtual void hard_open(void *comm_dev=NULL); + /** + * \brief function to close the client socket + * + * This function is called when the close() function of the base class is + * called. It calls the hard_close() function of the CSocket class to + * actually terminate the connection and change all the internal attributes + * to reflect that (the connection flag is set back to false and the server + * IP address and port are set to the default values. + * + * In case of any error, this function throws a CSocketException. + */ + virtual void hard_close(void); + public: + /** + * \brief Constructor + * + * This constructor creates a new client socket object with the provided + * identifier, but does not connect it to any server yet. To do that, it + * is necessary to call the open() function of the base class with a + * pointer to a TSocket_info structure as a parameter. + * + * \param sock_id a null terminated string with the unique identifier for + * the socket. This identifier is internally used to create + * the unique indentifiers for all threads and events, so + * it is important that a single identifier is not used more + * than once. + */ + CSocketClient(const std::string &sock_id); + /** + * \brief function to return the remote server port + * + * This function returns the port number of the current connection if the + * socket is connected to a server. Otherwise it returns -1. + * + * \return the server's port number to which it is connected, or -1 if the + * socket is not currently connected to any server. + */ + int get_remote_port(void); + /** + * \brief function to return the remote server IP address + * + * This function returns the IP address of the current connection if the + * socket is connected to a server. Otherwise it returns an empty string. + * + * \return the server'a IP address to which it is connected, or an empty + * string if the socket is not currently connected to any server. + */ + std::string get_remote_IP_address(void); + /** + * \brief function to check whether the socket is connected or not + * + * This function checks wether the client socket is connected to a server + * or not. + * + * \return true is the socket is connected to a server and false otherwise. + */ + bool is_connected(void); + /** + * \brief destructor + * + * When the object is destroyed, the connection to the server is lost (if + * it was not closed before) and all the resources of the client are freed. + */ + virtual ~CSocketClient(); +}; + +#endif diff --git a/src/sockets/socketexceptions.cpp b/src/sockets/socketexceptions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..02e7485ff4c42919f1e136a0a62462c63de464dc --- /dev/null +++ b/src/sockets/socketexceptions.cpp @@ -0,0 +1,15 @@ +#include "socketexceptions.h" +#include <string.h> +#include <stdio.h> + +const string socket_exception_msg="[CSocket class] - "; + +CSocketException::CSocketException(const string& where,const string& error_msg,const string& comm_id):CCommException(where,socket_exception_msg,error_msg) +{ + this->error_msg+=" - "; + this->error_msg+=comm_id; +} + +CSocketNoConnectionException::CSocketNoConnectionException(const string& where,const string& error_msg,const string& comm_id):CSocketException(where,error_msg,comm_id) +{ +} diff --git a/src/sockets/socketexceptions.h b/src/sockets/socketexceptions.h new file mode 100644 index 0000000000000000000000000000000000000000..75e97acfb6bd083f0fddfaf95a54e4b0b0d4eb44 --- /dev/null +++ b/src/sockets/socketexceptions.h @@ -0,0 +1,116 @@ +#ifndef SOCKET_EXCEPTIONS +#define SOCKET_EXCEPTIONS + +#include "commexceptions.h" + +using namespace std; + +/** + * \brief Socket exception class + * + * This class implements the exceptions for the CSocket communication class. + * In addition to the basic error message provided by the base class CException, + * this exception class provides also the unique identifier of the communication + * device that generated the exception. + * + * Also, similarly to other exception classes, it appends a class identifer + * string ("[CSocket class] - ") to the error message in order to identify + * the class that generated the exception. + * + * The base class can be used to catch any exception thrown by the application + * or also, this class can be used in order to catch only exceptions generated + * by CSocket objects. + * + */ +class CSocketException : public CCommException +{ + public: + /** + * \brief Constructor + * + * The constructor calls the base class constructor to add the general + * exception identifier and then adds the class identifier string + * "[CSocket class]" and the supplied error message. + * + * It also appends the unique identifier of the communication device + * that generated the exception. So, the total exception message will + * look like this: + * + * \verbatim + * [Exception caught] - <where> + * Error: [CComm class] - [CSocket class] - <error message> - <comm id> + * \endverbatim + * + * \param where a null terminated string with the information about the name + * of the function, the source code filename and the line where + * the exception was generated. This string must be generated + * by the _HERE_ macro. + * + * \param error_msg a null terminated string that contains the error message. + * This string may have any valid character and there is no + * limit on its length. + * + * \param comm_id a null terminated string that contains the communication + * device unique identifier. This string must be the one used to + * create the object. + * + * + */ + CSocketException(const string& where,const string& error_msg,const string& comm_id); +}; + +/** + * \brief No connection exception class + * + * This class implements a special exception for the CSocket class and its inherited + * classes that indicates that the connection has been refused. This event ocurrs + * when a client side application is trying to connect to a server, and it is still + * not ready to accept connections. + * + * This exception can be caught to prevent the client application to close when the + * server in not yet ready, and then used to iterate until it is. If functionality + * is not desired, this exception can be caught as a simple CSocketException or a + * basic CException. + * + * The error message and the information provided by this exception is the same as + * the information provided by the CSocketException class. + * + */ +class CSocketNoConnectionException : public CSocketException +{ + public: + /** + * \brief Constructor + * + * The constructor calls the base class constructor to add the general + * exception identifier and then adds the class identifier string + * "[CSocket class]" and the supplied error message. + * + * It also appends the unique identifier of the communication device + * that generated the exception. So, the total exception message will + * look like this: + * + * \verbatim + * [Exception caught] - <where> + * Error: [CComm class] - [CSocket class] - <error message> - <comm id> + * \endverbatim + * + * \param where a null terminated string with the information about the name + * of the function, the source code filename and the line where + * the exception was generated. This string must be generated + * by the _HERE_ macro. + * + * \param error_msg a null terminated string that contains the error message. + * This string may have any valid character and there is no + * limit on its length. + * + * \param comm_id a null terminated string that contains the communication + * device unique identifier. This string must be the one used to + * create the object. + * + * + */ + CSocketNoConnectionException(const string& where,const string& error_msg,const string& comm_id); +}; + +#endif diff --git a/src/sockets/socketserver.cpp b/src/sockets/socketserver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..599e8d0355263ca333d8b27be6a113d10f14788b --- /dev/null +++ b/src/sockets/socketserver.cpp @@ -0,0 +1,525 @@ +#include "socketserver.h" +#include "socketexceptions.h" +#include "eventexceptions.h" +#include <errno.h> +#include <iostream> +#include <sstream> +#include <string> +#include <string.h> + +int CSocketServer::counter=0; + +CSocketServer::CSocketServer(const std::string &sock_id) : CSocket(sock_id) +{ + this->current_clients=0; + this->max_clients=0; + this->state=server_created; + // clear the client list + this->client_list.clear(); + // clear the new connection event id + this->new_connection_event_id.clear(); + this->connect_thread_id.clear(); + this->disconnect_thread_id.clear(); + // set up server information + this->info.port=-1; + this->info.address.clear(); + // create the threads + this->connect_thread_id=sock_id + "_connect_thread"; + this->thread_server->create_thread(this->connect_thread_id); + this->thread_server->attach_thread(this->connect_thread_id,connect_thread,this); + this->disconnect_thread_id=sock_id + "_disconnect_thread"; + this->thread_server->create_thread(this->disconnect_thread_id); + this->thread_server->attach_thread(this->disconnect_thread_id,disconnect_thread,this); + // create the events + this->new_connection_event_id=sock_id+"_new_connection_event"; + this->event_server->create_event(this->new_connection_event_id); + this->update_list_event_id=sock_id+"_update_list_event"; + this->event_server->create_event(this->update_list_event_id); + this->finish_connect_thread_event_id=sock_id+"_finish_connect_thread_event"; + this->event_server->create_event(this->finish_connect_thread_event_id); + this->finish_disconnect_thread_event_id=sock_id+"_finish_disconnect_thread_event"; + this->event_server->create_event(this->finish_disconnect_thread_event_id); +} + +void CSocketServer::hard_open(void *comm_dev) +{ + TSocket_info *info=(TSocket_info *)comm_dev; + sockaddr_in sock; + + memset(&sock,0,sizeof(sock)); + CSocket::hard_open(); + if(comm_dev==NULL) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Invalid server information",this->comm_id); + } + else + { + // set non-blocking operation + if(fcntl(this->socket_fd,F_SETFL,O_NONBLOCK)<0) + { + /* handle exceptions */ + std::cout << "error" << std::endl; + } + else + { + if(this->state==server_created) + { + sock.sin_family=AF_INET; + if(inet_aton(info->address.c_str(),&sock.sin_addr)==0) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Impossible to convert IP address",this->comm_id); + } + else + { + sock.sin_port=htons(info->port); + if(::bind(this->socket_fd,(struct sockaddr *)&sock,sizeof(sock))<0) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Impossible to bind the socket to the desired IP address and port.",this->comm_id); + } + else + { + this->server_access.enter(); + this->state=server_binded; + this->info.port=info->port; + this->info.address=info->address; + this->server_access.exit(); + } + } + } + else + { + /* handle exceptions */ + throw CSocketException(_HERE_,"The server is not created.",this->comm_id); + } + } + } +} +void CSocketServer::hard_config(void *config) +{ + int num_listen=*((int *)config); + + CSocket::hard_config(); + if(config==NULL) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Invalid configuration information",this->comm_id); + } + else + { + if(this->state==server_binded) + { + if(::listen(this->socket_fd,num_listen)<0) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Impossible to listen to the desired IP address and port.",this->comm_id); + } + else + { + this->server_access.enter(); + this->state=server_listening; + this->server_access.exit(); + } + } + else + { + /* handle exceptions */ + throw CSocketException(_HERE_,"The server is not binded",this->comm_id); + } + } +} + +int CSocketServer::hard_wait_comm_event(void) +{ + std::string wait_event=this->comm_id+"_wait_event"; + std::list<std::string> events; + + this->event_server->create_event(wait_event); + events.push_back(wait_event); + + this->event_server->wait_first(events); + + return -1; +} + +void CSocketServer::hard_close(void) +{ + CSocket::hard_close(); + this->stop_server(); + this->state=server_created; + this->info.port=-1; + this->info.address.clear(); +} + +void CSocketServer::set_max_clients(int max_clients) +{ + this->server_access.enter(); + this->max_clients = max_clients; + this->server_access.exit(); +} + +int CSocketServer::get_max_clients(void) +{ + return this->max_clients; +} + +int CSocketServer::get_num_current_clients(void) +{ + return this->current_clients; +} + +void CSocketServer::start_server(void) +{ + if(this->state!=server_listening) + { + /* handle exceptions */ + throw CSocketException(_HERE_,"The server is not properly configured to accept connections",this->comm_id); + } + else + { + this->server_access.enter(); + this->state=server_on; + this->server_access.exit(); + this->thread_server->start_thread(this->connect_thread_id); + this->thread_server->start_thread(this->disconnect_thread_id); + } +} + +void CSocketServer::stop_server(void) +{ + std::list<TClient_info_int *>::iterator it; + + if (this->state==server_on) + { + this->event_server->set_event(this->finish_connect_thread_event_id); + this->thread_server->end_thread(this->connect_thread_id); + this->event_server->set_event(this->finish_disconnect_thread_event_id); + this->thread_server->end_thread(this->disconnect_thread_id); + this->server_access.enter(); + this->state=server_listening; + this->server_access.exit(); + // remove all clients from the list + // activate the disconnect event + for(it=this->client_list.begin();it!=this->client_list.end();it++) + this->event_server->set_event((*it)->disconnect_event_id); + } + else + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Server is not running", this->comm_id); + } +} + +void CSocketServer::broadcast(unsigned char *data, const int len) +{ + std::list<TClient_info_int *>::iterator it; + int written=0; + + try{ + this->server_access.enter(); + for(it=this->client_list.begin();it!=this->client_list.end();it++) + { + if((written=(*it)->socket->write(data,len))!=len) + { + this->server_access.exit(); + /* handle exceptions */ + throw CSocketException(_HERE_,"Error while writing to the client",(*it)->client_id); + } + } + this->server_access.exit(); + }catch(CException &e){ + /* handle exceptions */ + this->server_access.exit(); + throw; + } +} + +int CSocketServer::write_to(const std::string &sock_id, unsigned char *data, const int len) +{ + std::list<TClient_info_int *>::iterator it; + + try{ + this->server_access.enter(); + for(it=this->client_list.begin();it!=this->client_list.end();it++) + { + if((*it)->client_id==sock_id) + { + this->server_access.exit(); + return (*it)->socket->write(data,len); + } + } + this->server_access.exit(); + throw CSocketException(_HERE_,"Unknown socket identifier",sock_id); + }catch(CException &e){ + /* handle exceptions */ + this->server_access.exit(); + throw; + } +} + +int CSocketServer::read_from(const std::string &sock_id, unsigned char *data, const int len) +{ + std::list<TClient_info_int *>::iterator it; + + try{ + this->server_access.enter(); + for(it=this->client_list.begin();it!=this->client_list.end();it++) + { + if((*it)->client_id==sock_id) + { + this->server_access.exit(); + return (*it)->socket->read(data,len); + } + } + this->server_access.exit(); + throw CSocketException(_HERE_,"Unknown socket identifier",sock_id); + }catch(CException &e){ + /* handle exceptions */ + this->server_access.exit(); + throw; + } +} + +int CSocketServer::get_num_data_from(const std::string &sock_id) +{ + std::list<TClient_info_int *>::iterator it; + + try{ + this->server_access.enter(); + for(it=this->client_list.begin();it!=this->client_list.end();it++) + { + if((*it)->client_id==sock_id) + { + this->server_access.exit(); + return (*it)->socket->get_num_data(); + } + } + this->server_access.exit(); + throw CSocketException(_HERE_,"Unknown socket identifier",sock_id); + }catch(CException &e){ + /* handle exceptions */ + this->server_access.exit(); + throw; + } +} + +string CSocketServer::get_new_connection_event_id(void) +{ + return this->new_connection_event_id; +} + +TClient_info *CSocketServer::get_new_client(void) +{ + TClient_info *client,*client_queue; + + this->server_access.enter(); + if(this->new_clients.size()>0) + { + client_queue=this->new_clients.front(); + client=new TClient_info; + client->client_id=client_queue->client_id; + client->IP=client_queue->IP; + client->port=client_queue->port; + client->disconnect_event_id=client_queue->disconnect_event_id; + client->rx_event_id=client_queue->rx_event_id; + this->new_clients.pop(); + this->server_access.exit(); + return client; + } + else + { + this->server_access.exit(); + return NULL; + } +} + +void CSocketServer::free_client(TClient_info *info) +{ + std::list<TClient_info_int *>:: iterator it; + sockaddr_in sock; + + this->server_access.enter(); + for(it=this->client_list.begin();it!=this->client_list.end();it++) + { + if((*it)->client_id==info->client_id) + { + (*it)->socket->close(); + delete (*it)->socket; + this->current_clients--; + it=this->client_list.erase(it); + this->event_server->delete_event(info->disconnect_event_id); + delete info; + if(this->socket_fd==-1) + { + memset(&sock,0,sizeof(sock)); + sock.sin_family=AF_INET; + inet_aton(this->info.address.c_str(),&sock.sin_addr); + sock.sin_port=htons(this->info.port); + this->CSocket::hard_open(); + ::bind(this->socket_fd,(struct sockaddr *)&sock,sizeof(sock)); + ::listen(this->socket_fd,1); + this->state=server_on; + } + this->server_access.exit(); + return; + } + } + this->server_access.exit(); + throw CSocketException(_HERE_,"No such client",this->comm_id); +} + +void *CSocketServer::connect_thread(void *param) +{ + CSocketServer *server=(CSocketServer *)param; + TClient_info_int *client_info_int; + std::stringstream sock_id; + TClient_info *client_info; + struct sockaddr_in client; + int new_fd,len=0; + bool end=false; + + while(!end) + { + if(server->state==server_on) + { + server->server_access.enter(); + try{ + len=sizeof(client); + server->server_access.exit(); + if((new_fd=::accept(server->socket_fd,(struct sockaddr *)&client,(socklen_t *)&len))<0) + { + if(errno==EWOULDBLOCK) + { + if(server->event_server->event_is_set(server->finish_connect_thread_event_id)) + end=true; + else + usleep(100000); + } + else + { + /* handle exceptions */ + throw CSocketException(_HERE_,"Unexpected error while accepting new connections.",server->comm_id); + } + } + else + { + server->server_access.enter(); + server->counter++; + sock_id.str(""); + sock_id << "client_socket_" << server->counter; + // initialize the internal client info + client_info_int=new TClient_info_int; + client_info_int->socket=CSocket::create_socket(sock_id.str(),new_fd); + client_info_int->socket->open(NULL); + client_info_int->socket->config(NULL); + client_info_int->client_id=sock_id.str(); + client_info_int->disconnect_event_id=sock_id.str()+"_disconnect"; + server->event_server->create_event(client_info_int->disconnect_event_id); + // initialize the external client info + client_info=new TClient_info; + client_info->client_id=sock_id.str(); + client_info->IP=inet_ntoa(client.sin_addr); + client_info->port=(int)(ntohs(client.sin_port)); + client_info->disconnect_event_id=client_info_int->disconnect_event_id; + client_info->rx_event_id=client_info_int->socket->get_rx_event_id(); + // signal the connection of a new client + server->new_clients.push(client_info); + server->client_list.push_back(client_info_int); + server->current_clients++; + server->event_server->set_event(server->new_connection_event_id); + server->event_server->set_event(server->update_list_event_id); + if(server->current_clients==server->max_clients) + { + server->CSocket::hard_close(); + server->state=server_created; + } + server->server_access.exit(); + } + }catch(CException &e){ + server->server_access.exit(); + std::cout << e.what() << std::endl; + } + } + else + { + if(server->event_server->event_is_set(server->finish_connect_thread_event_id)) + end=true; + sleep(1); + } + } + + pthread_exit(NULL); +} + +void *CSocketServer::disconnect_thread(void *param) +{ + std::list<TClient_info_int *>:: iterator it_client; + CSocketServer *server=(CSocketServer *)param; + std::list<std::string>:: iterator it_event; + std::list<std::string> events; + int event_id=0,i=0; + bool end=false; + + events.clear(); + events.push_back(server->update_list_event_id); + events.push_back(server->finish_disconnect_thread_event_id); + while(!end) + { + try{ + event_id=server->event_server->wait_first(events); + server->server_access.enter(); + if(event_id==0) + { + events.clear(); + events.push_back(server->update_list_event_id); + events.push_back(server->finish_disconnect_thread_event_id); + for(it_client=server->client_list.begin();it_client!=server->client_list.end();it_client++) + events.push_back((*it_client)->socket->get_connection_closed_event()); + } + else + { + if(event_id==1) + end=true; + else + { + it_event=events.begin(); + it_client=server->client_list.begin(); + for(i=0;i<(event_id-2);i++) + { + it_event++; + it_client++; + } + it_event++; + if(!server->event_server->event_is_set((*it_client)->disconnect_event_id)) + server->event_server->set_event((*it_client)->disconnect_event_id); + events.erase(it_event); + } + } + server->server_access.exit(); + }catch(CException &e){ + std::cout << e.what() << std::endl; + } + } + + pthread_exit(NULL); +} + +CSocketServer::~CSocketServer() +{ + this->event_server->delete_event(this->new_connection_event_id); + this->new_connection_event_id=""; + this->event_server->delete_event(this->update_list_event_id); + this->update_list_event_id=""; + this->event_server->delete_event(this->finish_connect_thread_event_id); + this->finish_connect_thread_event_id=""; + this->event_server->delete_event(this->finish_disconnect_thread_event_id); + this->finish_disconnect_thread_event_id=""; + this->thread_server->delete_thread(this->connect_thread_id); + this->connect_thread_id=""; + this->thread_server->delete_thread(this->disconnect_thread_id); + this->disconnect_thread_id=""; + this->info.port=-1; + this->info.address.clear(); +} diff --git a/src/sockets/socketserver.h b/src/sockets/socketserver.h new file mode 100644 index 0000000000000000000000000000000000000000..228b23b9aab438a6f91cb6dcfe909279a7a6bc12 --- /dev/null +++ b/src/sockets/socketserver.h @@ -0,0 +1,677 @@ +#ifndef _SOCKETSERVER_H +#define _SOCKETSERVER_H + +#include <queue> +#include "socket.h" + +/** + * \brief possible server states + * + * This ennumeration type represents all the possible states of the server, which + * are used internally to control its flow. The possible states are: + * + * * server_created: It is the default state when it is created for the first time. + * * server_binded: after successfully calling the open() function, the server is + * attached to an IP address and port. + * * server_listening: after successfully calling the config() function the server + * is ready to listen to incomming connections. If the server + * is closed, it returns to the server_created state. + * * server_on: It is the main state when the server is started using the start() + * function. In this state, the server is capable of detecting new + * connections and disconnections, as well as send and receive data + * to and from any client. If the server is stopped it returns to the + * server_listening state. + * + * The next figure shows the server states and the possible transitions between them: + * + * \image html server_states.png + * + */ +typedef enum {server_created, server_binded, server_listening, server_on} server_state; + +/** + * \brief client information + * + * This structure holds all the information required by the server side application + * to handle a connection with a new client. This structure is initialized when + * a new connection is stablished, and the get_new_client() function should be used + * to retrieve it. Once the connection is closed, the free_client() function should + * be used to properly free all the allocated resources. + */ +typedef struct +{ + /** + * \brief client identifier + * + * This ideintifier is automatically assigned by the server when the connection is + * stablished. This identifier is unique for each connection and it is used to + * identify the client to which send or receive information. + */ + std::string client_id; + /** + * \brief client IP address + * + * This string holds the IP address of the client in dot format (192.168.0.1). This + * value can be used by the server side application to identify the different + * clients connected. This field is purely informative and it is not used for + * any operation. + */ + std::string IP; + /** + * \brief client port + * + * this integer is the port assigned to the client for the connection. This port + * is automatically assigned by the accept function and can not be modified + * afterwards. This filed is purely informative and it is not used by any operation. + */ + int port; + /** + * \brief identifier of the disconnect event. + * + * This events notifies the server side application that the connection to the + * client has been closed. This event is not the same as the connection closed + * event provided by the CSocket class. This event is used internally by the + * server, which activates this one when the disconnection is detected. + */ + std::string disconnect_event_id; + /** + * \brief identifier of the reception event + * + * This event notifies the reception of new data for the corresponding client. + * This event is the one provided by the CSocket base class and it is provided + * here because there is no direct access to the CSocket object from outside + * the class. + */ + std::string rx_event_id; +}TClient_info; + +/** + * \brief Internal client information + * + * This structure hold information of the client necessary for the server to + * manage the connection. This structure is only used internally by the server + * and can not be accessed from outside the class. + */ +typedef struct +{ + /** + * \brief client identifier + * + * This ideintifier is automatically assigned by the server when the connection is + * stablished. This identifier is unique for each connection and it is used to + * identify the client to which send or receive information. + */ + std::string client_id; + /** + * \brief identifier of the disconnect event. + * + * This events notifies the server side application that the connection to the + * client has been closed. This event is not the same as the connection closed + * event provided by the CSocket class. This event is used internally by the + * server, which activates this one when the disconnection is detected. + */ + std::string disconnect_event_id; + /** + * \brief communication soket + * + * This field points to the CSocket object used to communicate with the client. + * This object can only be accessed internally by the class, and the server + * side application must use the unique identifier assigned to it in order to + * access it throw the public interface of the server. + * + * This object is created and initialized automatically when the connection is + * stablished, and it is only closed and freed when the free_client() function + * is called. + */ + CSocket *socket; +}TClient_info_int; + +/** + * \brief Server side socket + * + * This class implemnt the server side of a TCP/IP socket communication. It + * inherits from the CSocket class for the basic socket communication issues and + * adds the necessary resources to provide connections to several clients + * simultaneously. + * + * After creation, it is necessary to call the open() function with the IP address + * and the desired listining port of the server socket. Then, the config() function + * is provided with the number of connections that can be buffered waiting for + * a connection. + * + * At this point the server is properly configured, but it will not accept new + * connections until it is started by calling the start() function. After these + * steps, the server socket is ready to accept connections and send and receive data + * to and from them. The server can be stoped at any time calling the stop() function + * and restarted. When the server is stopped, all connections are lost. + * + * This class provides an event (which can be retrieved with the get_new_connection_event() + * function) which is activated each time a new client is connected to the server. + * At this point, a server side application can get all the new client information + * using the get_new_client() function. + * + * This information includes the unique identifier of the socket used to send + * and receive information because the CSocket object itself is not accessible + * from outisde the class. It also includes the identifier of an event which is + * activated when the connection is closed by the client side (disconnect_event_id). + * + * When a connection is closed, most of the associated resources in the server + * side are freed, but it is imperative to call the free_client() function in + * order to properly free all the resources and avoid memory leaks. This function + * should only be called after the connection is closed. calling it before, will + * terminate the connection from the server side. + * + * When the maximum number of connections are reached, the server does not accept + * any more until one of the existing connections is closed. + * + */ +class CSocketServer : public CSocket +{ + private: + /** + * \brief current state of the server + * + * This attribute keeps the current state of the server. By default it is + * initialized to server_created, and its value automarically changes when + * the server is opend, configured and started. See the server_state + * ennumeration documentation for more information about the possible states + * of the server and the state transitions. + */ + server_state state; + /** + * \brief maximum number of simultaneous clients + * + * This integer has the maximum number of connections allowed for the server. + * By default its value is set to 1, but it can be changed at any time using + * the set_max_clients() function. The maximum number of clients can be + * retrieved at nay time with the get_max_clients() function. + */ + int max_clients; + /** + * \brief number of clients currently connected + * + * This integer has the current number of clients connected to the server. By + * default it is initialized to 0, and its value is automatically updated when + * new connections are created or existing connections are closed. The current + * number of connections can be retrieved with the set_num_current_connections() + * function. + */ + int current_clients; + /** + * \brief connection counter + * + * This static integer is used to assign a unique identifier to each new + * connection. This attribute is shared by all servers and it is incremented + * each time a new connection is set up in any of them, but it is never + * decremented. This value is only used internally, and can not be modified + * or accessed from outisde the class. + */ + static int counter; + /** + * \brief list of clients connected + * + * This list holds the internal information required by the server to manage + * each of the connections. By default the list is empty, and it is automatically + * updated each time a new connections is created or an existing connection + * is closed. This list is only used internally and can not be modified or + * accessed from outside class. + * + * See the documentation on the TClient_info_int data type for more + * information about the elements fo this list. + */ + std::list<TClient_info_int *> client_list; + /** + * \brief mutex object to access the server + * + * This object is used to avoid simultaneous access to the shared resources + * of the server (list of clients) from multiple threads (connection and + * disconnection threads and the main thread). This object is initialized at + * construction time and used internally in most of the functions. + */ + CMutex server_access; + /** + * \brief identifier of the connection thread + * + * This string holds the unique identifier of the connection thread. This + * identifier is created at contruction time attaching "_connect_thread" to + * the identifier assigned to the server. This identifier can not be modified + * or accessed outside the class. + */ + std::string connect_thread_id; + /** + * \brief identifier of the disconnection thread + * + * This string holds the unique identifier of the disconnection thread. This + * identifier is created at contruction time attaching "_disconnect_thread" to + * the identifier assigned to the server. This identifier can not be modified + * or accessed outside the class. + */ + std::string disconnect_thread_id; + /** + * \brief identifier of the new connection event + * + * This string holds the identifier of the new connection event. This identifier + * is created at construction time attaching "new_connection_event" to the + * identifier assigned to the server. This identifier can not be modified outside + * the class, but it can be retrieved with the get_new_connection_event_id() + * function to wait for new connections outside the class. + */ + std::string new_connection_event_id; + /** + * \brief identifier of the update list event + * + * This string holds the identifier of the update list event. This identifier + * is created at construction time attaching "update_list_event" to the + * identifier assigned to the server. This event is only used inside the class + * and can not be accessed or modified outside it. + */ + std::string update_list_event_id; + /** + * \brief identifier of the event to terminate the connect thread + * + * This event is used to signal the thread that is waiting for new connections + * to terminate its operation. This thread periodically checks the state of this + * event. + * + * This event is created when the server is created, but it is only activated + * when the stop_server() function is called or the object is destroyed. This + * event is for internal use only, and can not be accessed outside the class. + */ + std::string finish_connect_thread_event_id; + /** + * \brief identifier of the event to terminate the disconnect thread + * + * This event is used to signal the thread that is waiting for disconnections + * to terminate its operation. This thread periodically checks the state of this + * event. + * + * This event is created when the server is created, but it is only activated + * when the stop_server() function is called or the object is destroyed. This + * event is for internal use only, and can not be accessed outside the class. + */ + std::string finish_disconnect_thread_event_id; + /** + * \brief queue of clients waiting to be retrieved + * + * This queue temporary holds the information of the new connections stablished + * to the server. This queue is empty by default and it is filled each time a new + * connection is created. The queue is emptied by calling the get_new_client() + * function when the new_connection_event_id event is activated, which returns + * the necessary information to handle the connection to the server side + * application. This queue can not be modified or accessed outside the class. + * + * This event can be activated several times if multiple connections are created + * without being acknowledge by the server side application. + */ + std::queue<TClient_info *> new_clients; + protected: + /** + * \brief server connection IP address and port + * + * This strcture has the IP address and listening port of the server. By default + * the IP address uis empty and the port is set to -1. When the open() function is + * called successfully, this values are updated to the values provided to the + * function. + * + * These values can not be directly modified nor accessed outside the class. + */ + TSocket_info info; + /** + * \brief connection thread function + * + * This is the main function for the connection thread. This thread is waiting + * for new connections continuously. When a new connection is created, it first + * created a new CSocket object associated to the new socket file descriptor + * to handle all the communication issues and generates both the internal + * (TClient_info_int) and external (TClient_info) structures with all the + * necessary information. + * + * The CSocket object can only be accessed from inside the class, and the + * server side application must use the client identifier provided by the + * get_new_client() function to access it. + * + * After that, the new_connection event is activated to signal the server side + * application that a new connection is ready. If several connections are + * created without being acknowledge by the server side application, this event + * remains active until all the new client information is retrieved. + * + * This thread does not throw any exception. + * + * \param param a pointer to the CSocketServer object to which the thread is + * associated. This parameter is used to access the internal + * attriobutes and functions of the class since the thread itself + * is defined as static. + */ + static void *connect_thread(void *param); + /** + * \brief disconnection thread function + * + * This is the main function of the disconnection thread. This thread waits + * for the connection closed event of each of the active connections. When + * one or more of these events are activated, it frees most of the internal + * resources associated with the connection and activates the corresponding + * disconnect event to signal the server side application that the + * connection is no longer available. + * + * It is the server side application the one responsible of freeing the rest + * of the resources associated with the closed connection by calling the + * free_client() function when the disconnect event is activated. + * + * If there are no active connections, this thread is inactive. As new + * connections are created, this thread periodically updates the wait event + * list to match the current active connections. + * + * This thread does not throw any exception. + * + * \param param a pointer to the CSocketServer object to which the thread is + * associated. This parameter is used to access the internal + * attriobutes and functions of the class since the thread itself + * is defined as static. + */ + static void *disconnect_thread(void *param); + /** + * \brief function to bind the desired IP address and port to the socket + * + * This function is automatically called when the base class open() function + * is called. This function binds the server socket to the desired IP + * address and port provided to the function. The IP address must coincide + * with the IP address assigned to the computer where the server is being + * executed, and the port can not be used by nay other application. + * + * If successful, the internal state of the server is changed to server_binded. + * After calling this function, it is necesary to call the config() function + * to properly configure the server. + * + * \param comm_dev a pointer to a valid TSocket_info strcture with the server + * IP address and desired listening port. See the documentation + * on the TSocket_info structure for more information on the + * IP address and port format. + */ + virtual void hard_open(void *comm_dev=NULL); + /** + * \brief function to configure the length of the connection buffer + * + * This function is automatically called when the base class config() function + * is called. This function sets the maximum number of waiting connections + * that are allowed. + * + * If successfull, the internal state of the server is changed to server_listening. + * After calling this function, the server is properly configured, but it is still + * not ready to accept new connections or send and receive data. To do that, it + * is necessary to call the start() function. + * + * \param config a pointer to an integer value with the number of waiting + * connections allowed. This value must be a positive integer. + */ + virtual void hard_config(void *config=NULL); + /** + * \brief function to wait for communication events + * + * This function is only provided for compatibility since the server socket never + * receives data from a client (a new socket is created for that purpose). However, + * changes in the state of the socket may generate unexpected errors that the + * CSocket hard_wait_comm_event() function can not handle. + * + * Therefore, this function overrides the base class function and block the thread + * indefinetely. When the server is destroyed, the thread is killed and this + * function is aborted. + * + * \return this function will block and never return any value. + */ + virtual int hard_wait_comm_event(void); + /** + * \brief function to close the server + * + * This function will first stop the server and also close any connection with a + * client which is still active, freeing all the associated resources. If + * successfull this function changes the state of the server to server_created. + * + * If this function is called, in order to set up the server again, it will be + * necessary to call the open() and config() functions to reconfigure it and also + * the start() function to accept and handle new connections. + */ + virtual void hard_close(void); + public: + /** + * \brief constructor + * + * This constructor created the connect and disconnect threads and attach them + * to the corresponding functions, but does not start any of them. It also + * creates the new connection event. The socket identifier is used to initialize + * the identifier for both threads and events, and each server must have a + * different one. + * + * In this function all the internal attributes are initialized by default. + * + * \param sock_id a null terminated string with the identifier of the server. + * This string must not have any white spaces and each server + * must have a different one, since it is used to generate the + * unique identifier for both threads and events. + */ + CSocketServer(const std::string &sock_id); + /** + * \brief function to set the maximum number of simultaneous clients + * + * This function sets the maximum number of clients connected any given time. + * This function can be called any time, but if the new maximum value is + * smaller than the current number of connections, this function will throw + * a CSocket exception instead of closing the exceeding connections. + * + * If the new value is bigger than the previous one, there is no problem. + * However, it is better to set this value before calling the start() + * function and, if it is necessary to change the maximum number of clients + * connected, first stop the server and then change it. + * + * \param max_clients a positive integer with the maximum number of clients + * connected any given time. + */ + void set_max_clients(int max_clients); + /** + * \brief function to get the maximum number of simultaneous clients + * + * This function return the maximum number of clients that can be connected + * to the server at any given time. + * + * \return a positive integer with the maximum number of clients connected + * to the server any given time. + */ + int get_max_clients(void); + /** + * \brief function to get the number of clients currently connected + * + * This returns the number of clients currently connected to the server. This + * number is always between 0 and the maximum number of clients allowed. + * + * \return a non-negative integer with the number of clients currently + * connected to the server. + */ + int get_num_current_clients(void); + /** + * \brief function to start the server + * + * This function starts the internal threads which allow the server to detect + * new connections. Before calling this function, the server will not be able + * to accept any new connections. If successfull this function changes the state + * of the server to server_on. + * + * After calling this function, the server side application should wait for the + * activation of the new connection event. Once activated, the new client + * information can be retrieved by calling the get_new_client() function. + * + * At this point, the receive event and the disconnet event should be monitored to + * detect the reception of new data or the disconnection of the client, whatever + * happens first. If new data is received, the get_num_data_from() and read_from() + * functions can be used to get it. If the connection has been closed, the + * free_client() function should be called to free all the associated resources. + * + * If successfull, this function changes the state of the server to server_on. + * This function throws a CSocketException in case of any error. + */ + void start_server(void); + /** + * \brief function to stop the server + * + * This function stops the normal operation of the server. It closes any active + * connection that may still exists and frees all the associated resources. In + * this case, however, the server does not loose its configuration, so in order + * to start accepting connections again, it is only necessary to call the + * start() function. + * + * If successfull, this function changes the state of the server to + * server_listening. This function throws a CSocketException in case of any + * error; + */ + void stop_server(void); + /** + * \brief function to send some data to all clients + * + * This function is used to send any data to all the active connections on the + * server. In this case no client identifier is provided because the data is + * sequentially sent to all available clients. + * + * This function throws a CSocketException in case of any error. + * + * \param data a pointer to an unsigned char vector with the information to + * send. The memory for this parameter must be allocated before + * calling this function, and its length must coincide with the + * value of the second parameter. + * + * \param len a positive integer with the length of the data to be sent. This + * value must coincide with the actual length of the vector passed + * as first argument. + */ + void broadcast(unsigned char *data, const int len); + /** + * \brief function to send data to a single client + * + * This function is used to send data to a single client. It uses the client + * identifier returned by the get_new_client() function to identify the + * desired client connection. If the specified client does not exist, this + * function throws a CSocketException. + * + * \param sock_id the identifier of the desired client. This value must be + * one of the identifier returned by the get_new_client() + * function which is still active. + * + * \param data a pointer to an unsigned char vector with the information to + * send. The memory for this parameter must be allocated before + * calling this function, and its length must coincide with the + * value of the third parameter. + * + * \param len a positive integer with the length of the data to be sent. This + * value must coincide with the actual length of the vector passed + * as second argument. + * + * \return the number of bytes actually written to the client. In a standard + * case, this value should be equal to the value of the third parameter, + * otherwise, and error has ocurred. + */ + int write_to(const std::string &sock_id, unsigned char *data, const int len); + /** + * \brief function to get data from a single client + * + * This function is used to get data from a single client. It uses the client + * identifier returned by the get_new_client() function to identify the + * desired client connection. If the specified client does not exist, this + * function throws a CSocketException. + * + * \param sock_id the identifier of the desired client. This value must be + * one of the identifier returned by the get_new_client() + * function which is still active. + * + * \param data a pointer to an unsigned char vector where the information + * received is stored. The memory for this parameter must be + * allocated before calling this function, and its length must + * coincide with the value of the third parameter. + * + * \param len a positive integer with the length of the data to be read. This + * value must coincide with the actual length of the vector passed + * as second argument. The get_num_data_from() function can be + * used to know how many data is available at any time. + * + * \return the number of bytes actually read from the client. In a standard + * case, this value should be equal to the value of the third parameter, + * otherwise, and error has ocurred. + */ + int read_from(const std::string &sock_id, unsigned char *data, const int len); + /** + * \brief function to get the number of available data from a single client + * + * This function is used to get the ammount of data available from a single + * client. It uses the client identifier returned by the get_new_client() + * function to identify the desired client connection. If the specified + * client does not exist, this function throws a CSocketException. + * + * \param sock_id the identifier of the desired client. This value must be + * one of the identifier returned by the get_new_client() + * function which is still active. + * + * \return the number of bytes currently available at the desired client. This + * value can be used as the third parameter to the read_from() + * function. + */ + int get_num_data_from(const std::string &sock_id); + /** + * \brief function to get the identifier of the new connection event + * + * This function return the identifier of the new connection event. This + * event can be used to wait for new connections once the server has been + * started with the start() function. There exist a single event for each + * server that is activated as many times as new clients are pending to be + * retrieved. + * + * After this event is active, the server side application should call the + * get_new_client() function in order ro get all the necessary information + * to handle the new connection. + * + * \return a string with a copy of the new connection event identifier. + */ + std::string get_new_connection_event_id(void); + /** + * \brief function to get the information about the new connection + * + * This function is used to get all the important information about a new + * connection. See the documentation on the TClient_info structure for more + * information about the parameters returned by this function. + * + * This function should be called as long as the new connection event is + * active, which means that there is a new client connected and waiting. + * + * \return a pointer to an structure with information about the new + * connection. The memory necessary for this structure is allocated + * in this function, and it is necessary to call the free_client() + * function to properly free it and all its associated resources. + */ + TClient_info *get_new_client(void); + /** + * \brief function to free the resources associated with a client + * + * This function is used to properly free all the resources associated with a + * connection once is has been closed. This function should only be called + * after the disconnect event for the corresponding connection has been + * activated. Otherwise, the connection will be closed and the client will be + * disconnected. + * + * This function actually closes the socket accociated to the connection and + * destroys it, and also destroys all the events associated with it. Finally, + * it also, frees the information structure passed as an argument so the + * server side of the application does not have to do that. + * + * \param info a pointer to a valid TClient_info structure which corresponds + * to an active connection. This structure should be the one + * returned by the get_new_client() function. + */ + void free_client(TClient_info *info); + /** + * \brief destructor + * + * This destructor first stops the server, if it is running, and closes all + * the active connections with clients. This destructor does not free all the + * resources associated with each connection, so its better to first close all + * the connections using the free_client() function and then destroy the + * server object. + */ + virtual ~CSocketServer(); +}; + +#endif diff --git a/src/usb_ftdi/ftdiexceptions.cpp b/src/usb_ftdi/ftdiexceptions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e12766c164de6565efb57330b916b5b13efa0246 --- /dev/null +++ b/src/usb_ftdi/ftdiexceptions.cpp @@ -0,0 +1,37 @@ +#include "ftdiexceptions.h" +#include <string.h> +#include <stdio.h> + +const std::string ftdi_exception_msg="[CFTDI class] - "; +const std::string ftdiserver_exception_msg="[CFTDIServer class] - "; + +const std::string error_messages[]={"ok.\n", + "Invalid handle.\n", + "Device not found.\n", + "Device not opened.\n", + "Input/output error.\n", + "Insufficient resources.\n", + "Invalid parameter.\n", + "Invalid baudrate.\n", + "Device not opened for erase.\n", + "Device not opened for write.\n", + "Failed to write device.\n", + "EEPROM read failed.\n", + "EEPROM write failed.\n", + "EEPROM erase failed.\n", + "EEPROM not present.\n", + "EEPROM not programmes.\n" + "Invalid arguments.\n", + "Not supported.\n", + "Unknown error.\n"}; + +CFTDIException::CFTDIException(const std::string& where,const std::string& error_msg,const std::string& comm_id) : CCommException(where,ftdi_exception_msg,error_msg) +{ + this->error_msg+=" - "; + this->error_msg+=comm_id; +} + +CFTDIServerException::CFTDIServerException(const std::string& where,const std::string& error_msg) : CException(where,ftdi_exception_msg) +{ + this->error_msg+=error_msg; +} diff --git a/src/usb_ftdi/ftdiexceptions.h b/src/usb_ftdi/ftdiexceptions.h new file mode 100644 index 0000000000000000000000000000000000000000..53387f0c71ac89ce01a6893d1ec66ea49e0a473a --- /dev/null +++ b/src/usb_ftdi/ftdiexceptions.h @@ -0,0 +1,116 @@ +#ifndef FTDI_EXCEPTIONS +#define FTDI_EXCEPTIONS + +#include "commexceptions.h" +#include "ftd2xx.h" +#include <string> + +/** + * \brief vector with all the error messages + * + * This constant vector has the strings corresponding to all possible errors + * of any FTDI device. The value of the error can be used to index the table + * and get the corresponding string. + */ +extern const std::string error_messages[]; + +/** + * \brief FTDI exception class + * + * This class implements the exceptions for the FTDI communication class. + * In addition to the basic error message provided by the base class CException, + * this exception class provides also the unique identifier of the communication + * device that generated the exception. + * + * Also, similarly to other exception classes, it appends a class identifer + * string ("[CFTDI class] - ") to the error message in order to identify + * the class that generated the exception. + * + * The base class can be used to catch any exception thrown by the application + * or also, this class can be used in order to catch only exceptions generated + * by CFTDI objects. + * + */ +class CFTDIException : public CCommException +{ + public: + /** + * \brief Constructor + * + * The constructor calls the base class constructor to add the general + * exception identifier and then adds the class identifier string + * "[CFTDI class]" and the supplied error message. + * + * It also appends the unique identifier of the communication device + * that generated the exception. So, the total exception message will + * look like this: + * + * \verbatim + * [Exception caught] - <where> + * Error: [CComm class] - [CFTDI class] - <error message> - <comm id> + * \endverbatim + * + * \param where a null terminated string with the information about the name + * of the function, the source code filename and the line where + * the exception was generated. This string must be generated + * by the _HERE_ macro. + * + * \param error_msg a null terminated string that contains the error message. + * This string may have any valid character and there is no + * limit on its length. + * + * \param comm_id a null terminated string that contains the communication + * device unique identifier. This string must be the one used to + * create the object. + * + * + */ + CFTDIException(const std::string& where,const std::string& error_msg,const std::string& comm_id); +}; + +/** + * \brief FTDIServer exception class + * + * This class implements the exceptions for the FTDI Server class. + * Similarly to other exception classes, it appends a class identifer + * string ("[CFTDIServer class] - ") to the error message in order to identify + * the class that generated the exception. + * + * The base class can be used to catch any exception thrown by the application + * or also, this class can be used in order to catch only exceptions generated + * by CFTDIServer objects. + * + */ +class CFTDIServerException : public CException +{ + public: + /** + * \brief Constructor + * + * The constructor calls the base class constructor to add the general + * exception identifier and then adds the class identifier string + * "[CFTDIServer class]" and the supplied error message. + * + * It also appends the unique identifier of the communication device + * that generated the exception. So, the total exception message will + * look like this: + * + * \verbatim + * [Exception caught] - <where> + * [CComm class] - [CFTDIServer class] - <error message> + * \endverbatim + * + * \param where a null terminated string with the information about the name + * of the function, the source code filename and the line where + * the exception was generated. This string must be generated + * by the _HERE_ macro. + * + * \param error_msg a null terminated string that contains the error message. + * This string may have any valid character and there is no + * limit on its length. + * + */ + CFTDIServerException(const std::string& where,const std::string& error_msg); +}; + +#endif diff --git a/src/usb_ftdi/ftdimodule.cpp b/src/usb_ftdi/ftdimodule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7520d5d0e0374e201f39e7dd58235a1cccfc1b2b --- /dev/null +++ b/src/usb_ftdi/ftdimodule.cpp @@ -0,0 +1,210 @@ + +#include "ftdimodule.h" +#include "ftdiexceptions.h" +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <pthread.h> +#include <string> +#include <iostream> + +CFTDI::CFTDI(const std::string& comm_id) : CComm(comm_id) +{ + this->ft_handle = NULL; +} + +void CFTDI::hard_open(void* comm_dev) +{ + FT_STATUS ft_status; + std::string *serial_desc=(std::string *)comm_dev; + + ft_status=FT_OpenEx((void *)serial_desc->c_str(),FT_OPEN_BY_SERIAL_NUMBER,&(this->ft_handle)); + ft_status=FT_OpenEx((void *)serial_desc->c_str(),FT_OPEN_BY_DESCRIPTION,&(this->ft_handle)); + if(this->ft_handle==NULL) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,"Impossible to open the device.\n",this->comm_id); + } + if((ft_status=FT_ResetDevice(this->ft_handle))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + if((ft_status=FT_Purge(this->ft_handle,FT_PURGE_RX|FT_PURGE_TX))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + if((ft_status=FT_ResetDevice(this->ft_handle))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + pthread_cond_init(&this->event_handle.eCondVar, NULL); + pthread_mutex_init(&this->event_handle.eMutex, NULL); + pthread_mutex_lock(&this->event_handle.eMutex); + + //Set condition event here, before thread is started + if((ft_status = FT_SetEventNotification(this->ft_handle,FT_EVENT_RXCHAR,(PVOID)&this->event_handle))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } +} + +void CFTDI::hard_config(void *config) +{ + FT_STATUS ft_status; + TFTDIconfig *ftdi_config = (TFTDIconfig*) config; + + if(config==NULL) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,"Invalid configuration structure",this->comm_id); + } + //1. Set Baud Rate + if(ftdi_config->baud_rate!=-1) + { + if((ft_status=FT_SetBaudRate(this->ft_handle,ftdi_config->baud_rate))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + } + //2. Set the data characteristics + if(ftdi_config->word_length!=-1 && ftdi_config->stop_bits!=-1 && ftdi_config->parity!=-1) + { + if((ft_status=FT_SetDataCharacteristics(this->ft_handle,ftdi_config->word_length,ftdi_config->stop_bits,ftdi_config->parity))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + } + //3. set read and write timeouts + if((ft_status=FT_SetTimeouts(this->ft_handle,ftdi_config->read_timeout,ftdi_config->write_timeout))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + //4. Set latency timer + if((ft_status=FT_SetLatencyTimer(this->ft_handle,ftdi_config->latency_timer))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + if((ft_status=FT_SetFlowControl(this->ft_handle,FT_FLOW_NONE,0x00,0x00))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } +} + +void CFTDI::hard_close(void) +{ + FT_STATUS ft_status; + + if(this->ft_handle!=NULL) + { + if(pthread_mutex_trylock(&this->event_handle.eMutex)==EBUSY) + pthread_mutex_unlock(&this->event_handle.eMutex); + if((ft_status = FT_Close(this->ft_handle))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + this->ft_handle = NULL; // assing no opened value + } +} + +int CFTDI::hard_read(unsigned char *data, int len) +{ + FT_STATUS ft_status; + DWORD bytes_received; + + if((ft_status = FT_Read(this->ft_handle,data,len,&bytes_received))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + + return bytes_received; +} + +int CFTDI::hard_write(unsigned char *data, int len) +{ + FT_STATUS ft_status; + DWORD bytes_written; + + if((ft_status = FT_Write(this->ft_handle, data,len,&bytes_written))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + + return bytes_written; +} + +int CFTDI::hard_get_num_data(void) +{ + FT_STATUS ft_status; + DWORD bytes_rx; + + if((ft_status=FT_GetQueueStatus(this->ft_handle,&bytes_rx))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + + return bytes_rx; +} + +int CFTDI::hard_wait_comm_event(void) +{ + FT_STATUS ft_status; + DWORD rx_bytes=0,tx_bytes=0,event_id=0; + + while(rx_bytes < 1) + { + pthread_cond_wait(&event_handle.eCondVar, &event_handle.eMutex); + if((ft_status = FT_GetStatus(this->ft_handle,&rx_bytes,&tx_bytes,&event_id))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],this->comm_id); + } + } + if(rx_bytes < 1) + return 2; // error + else + { + return 1; // ready to read data + } +} + +std::ostream& operator<< (std::ostream& out,CFTDI& ftdi) +{ + FT_STATUS ft_status; + DWORD type; + DWORD id; + char serial_number[16]; + char description[64]; + + out << "****************** FTDI Devices Info ***********************" << std::endl; + if((ft_status=FT_GetDeviceInfo(ftdi.ft_handle,&type,&id,serial_number,description,NULL))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIException(_HERE_,error_messages[ft_status],ftdi.comm_id); + } + out << "Device name: " << ftdi.comm_id << std::endl; + out << "Type: " << type << std::endl; + out << "Device id: " << id << std::endl; + out << "Serial Number: " << serial_number << std::endl; + out << "Description: " << description << std::endl; + + return out; +} + +CFTDI::~CFTDI(){ + this->close(); +} + + diff --git a/src/usb_ftdi/ftdimodule.h b/src/usb_ftdi/ftdimodule.h new file mode 100644 index 0000000000000000000000000000000000000000..720d56e9af021340c19672619198fbce57f45f88 --- /dev/null +++ b/src/usb_ftdi/ftdimodule.h @@ -0,0 +1,334 @@ +#ifndef _FTDI_MODULE_H +#define _FTDI_MODULE_H + +#include <ostream> +#include "ftd2xx.h" +#include "comm.h" + +/** + \brief structure to hold the configuration parameters of the FTDI device + + The necessary configuration parameters of the supported FTDI devices are: + + - baud rate (only used on serial devices) + - number of bits per word (only used on serial devices) + - number of stop bits (only used on serial devices) + - parity type (only used on serial devices) + - read and write timeouts (used on all devices) + - latency timer (used on all devices) + + These parameters must be configured after the device has been opened by + calling the open() function and before reading or writing data from or to + the device. + +*/ +typedef struct TFTDIconfig +{ + /** + * \brief desired baud rate + * + * This parameter specifies the desired baud rate in bits per second of the + * device. This parameter only has meaning when the FTDI device is of the + * serial kind. Otherwise it is not used and it can be set to -1 to avoid + * its initialization on the driver. + * + * The possible values for this parameter are: + * + * - 300 + * - 600 + * - 1200 + * - 2400 + * - 4800 + * - 9600 + * - 14400 + * - 19200 + * - 38400 + * - 57600 + * - 115200 + * - 230400 + * - 460800 + * - 921600 + * + */ + int baud_rate; + /** + * \brief desired word length + * + * This parameter specifies the desired length of each packet in bits. This + * parameter only has meaning when the FTDI device is of the serial kind. + * Otherwise it is not used and it can be set to -1 to avoid its initialization + * on the driver. + * + * The possible values for this parameter are integers from 5 to 8. + */ + int word_length; + /** + * \brief desired number of stop bits + * + * This parameter specifies the desired number of stop bits of each packet. This + * parameter only has meaning when the FTDI device is of the serial kind. + * Otherwise it is not used and it can be set to -1 to avoid its initialization + * on the driver. + * + * The possible values for this parameter are: + * + * - 0 for 1 stop bits + * - 1 for 1.5 stop bits + * - 2 for 2 stop bits + */ + int stop_bits; + /** + * \brief desired parity type + * + * This parameter specifies the desired kind of parity to be used. This parameter + * only has meaning when the FTDI device is of the serial kind. Otherwise it is + * not used and it can be set to -1 to avoid its initialization on the driver. + * + * The possible values for this parameter are: + * + * - 0 for no parity + * - 1 for odd parity + * - 2 for even parity + * - 3 for mark parity + * - 4 for space parity + */ + int parity; + /** + * \brief desired timeout for read operations + * + * This parameter specifies the maximum time to wait for a read operation to end in + * miliseconds. + */ + int read_timeout; + /** + * \brief desired timeout for write operations + * + * This parameter specifies the maximum time to wait for a write operation to end in + * miliseconds. + */ + int write_timeout; + /** + * \brief rate at which the receive buffer is flushed + * + * This parameter specifies the period in miliseconds at which the receive buffer is + * flushed. This value can be set to any value between 2 and 255. + */ + unsigned char latency_timer; +}TFTDIconfig; + +/** + * \brief FTDI driver class using the D2XX library + * + * This class implements the access to any FTDI device supported by the D2XX driver + * provided by the manufacturer. This class inherits from the CComm class to have + * all the basic communications issues solved and it only has to implement the + * specific functions to open the FTDI device (hatd_open()), config (hard_config()), + * read from (hard_read()), write to (hard_write()) and close it (hard_close()). + * Also it implements a function to assynchronously wait for communication events + * to happen (hard_wait_comm_event()). + * + * This class allows access to any external device that uses one of the supported + * FTDI chips. The supported FTDI devices are: + * + * - FT2232H + * - FT4232H + * - FT232R + * - FT245R + * - FT2232 + * - FT232B + * - FT245B + * - FT8U232AM + * - FT8U245AM + * + * This driver must be used when the used VID and PID values do not coincide with + * the dafualt values used by the manufacturer. Otherwise, it is possible to use + * a standard serial port driver (CRS232) to access the device. However, this + * driver provides faster transfer rates to and from the device than the virtual + * COM port option. + * + * This class uses exceptions to report errors. The name of the exception + * class associated to this class is CFTDIException. For a more detailes + * descritpion, see the corresponding documentation. + * + */ +class CFTDI : public CComm +{ + private: + /** + * \brief Handle to the FTDI device + * + * This handle is used to access the associated FTDI device. By default it is set + * to NULL when the object is created, and it is initialized when the device is + * opened by calling the open() function. This handle remains valid until the object + * is destroyed or the FTDI device is disconnected + */ + FT_HANDLE ft_handle; + + EVENT_HANDLE event_handle; + protected: + /** + * \brief Function to actually open the device + * + * This function is called automatically when the base class open() function + * is called. It must create a handle to the communication device in order to + * be used in the future. The device handle must be provided by any inherited + * class since it is device dependant. + * + * This class accepts a generic parameter (void *) to allow the user to pass + * to the function any data structure. This parameter comes from the base + * class open() function, but no action is performed on it. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param comm_dev a generic pointer (void *) to the data structure needed to + * identify the communication device to open and any other + * required information. This parameter may be NULL if not + * needed. + */ + void hard_open(void* comm_dev); + /** + * \brief Function to actually configure the device + * + * This function is called automatically when the base class config() function + * is called. It must configure the communication device as required by the + * application. + * + * This class accepts a generic parameter (void *) to allow the user to pass + * to the function any data structure. This parameter comes from the base + * class open() function, but no action is performed on it. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param config a generic pointer (void *) to the data structure needed to + * configure the communicationd device. This parameter may be + * NULL if not needed. + */ + void hard_config(void *config); + /** + * \brief Function to actually read from the device + * + * This function is automatically called when the new data received is activated. + * The read() function from the base class gets data from the internal queue, so + * this function is not used. It must try to read the ammount of data specified + * and store it in the data buffer provided without blocking. Also, it must + * return the number of bytes actually read from the devicve, since they may be + * different than the desired value. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param data a reference to the buffer where the received data must be + * copied. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * read from the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually read. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + int hard_read(unsigned char *data, int len); + /** + * \brief Function to actually write to the device + * + * This function is automatically called when the base class write() function + * is called. It must try to write the desired ammount of data to the communication + * device without blocking. Also it must return the number of bytes actually + * written to the communication device since they may be different from the + * desired value. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \param data a reference to the buffer with the data must be send to the + * device. The necessary memory for this buffer must be allocated before + * calling this function and have enough size to store all the desired data. + * If this buffer is not initialized, the function throws an exception. + * + * \param len a positive interger that indicates the number of byte to be + * written to the communication device. This value must be at most the length + * of the data buffer provided to the function. + * + * \return an integer with the number of bytes actually written. These number + * coincide with the desired number if there is enough data in the internal + * queue, but it could be smaller if not. + */ + int hard_write(unsigned char *data, int len); + /** + * \brief Function to actually get the number of bytes availables + * + * This function is called when the new data received event is activated. + * It must get the number of data bytes available from the communication + * device and return its value without blocking. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \return an integer with the number of bytes available in the receive queue. + * This value can be 0 if there is no data available, but there is ni upper + * limit in its value. + */ + void hard_close(void); + /** + * \brief Function to actually wait for a given communication event + * + * This function is called in the internal communciation thread to wait for + * any event on the communuication device. It must check for any event on the + * device (reception or error) and return the corresponding identifier. When + * an event is activated, this function must return to allow the base class + * to handle it, and the it is called again to wait for the next event. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + * + * \return -1 if there has been any error or else the identifier of the + * communciation event: + * + * - 1 for the new data received event + * - 2 for the error event + */ + int hard_get_num_data(void); + /** + * \brief Function to actually close the device + * + * This function is called when the base class close() funciton is called. It + * must free the device handle initialized by the open() function and also free + * any other resource allocated by the inherited class. + * + * This function can throw any CCommException object exception or else any + * exception class defined by the inherited class. + */ + int hard_wait_comm_event(void); + public: + /** + * \brief Constructor + * + * + */ + CFTDI(const std::string& comm_id); + /** + * \brief operator << overloading + * + * This operator allow to show important information about the FTDI device + * associated to the class on any ostream obejct (the standard output, a file, + * etc...). + * + * \param out A reference to an output device in which to show the desired + * information. + * + * \param ftdi A reference to a CFTDIServer object with the information to be + * displayed. + * + * \return an object with the desired information already formatted. In this case + * it has the number of devices and the information on each of them. + */ + friend std::ostream& operator<< (std::ostream& out,CFTDI& ftdi); + ~CFTDI(); +}; + +#endif diff --git a/src/usb_ftdi/ftdiserver.cpp b/src/usb_ftdi/ftdiserver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a0c9acc81c7ca04372358d513be2d86ff2318cd4 --- /dev/null +++ b/src/usb_ftdi/ftdiserver.cpp @@ -0,0 +1,247 @@ +#include "ftdiexceptions.h" +#include "ftdiserver.h" +#include "ftd2xx.h" + +CFTDIServer *CFTDIServer::pinstance=NULL; + +CFTDIServer::CFTDIServer() +{ + this->scan_bus(); +} + +CFTDIServer::CFTDIServer(const CFTDIServer& object) +{ +} + +CFTDIServer& CFTDIServer::operator = (const CFTDIServer& object) +{ + return *this->pinstance; +} + +CFTDIServer *CFTDIServer::instance(void) +{ + if (CFTDIServer::pinstance == NULL) + { + CFTDIServer::pinstance = new CFTDIServer(); // Creamos la instancia + } + return CFTDIServer::pinstance; // Retornamos la dirección de la instancia +} + +void CFTDIServer::add_custom_PID(int PID) +{ + FT_STATUS status; + int i=0; + + for(i=0;i<this->custom_PID.size();i++) + { + if(this->custom_PID[i]==PID) + return; + } + this->custom_PID.push_back(PID); + if((status=FT_SetVIDPID(FTDI_VID,PID))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,error_messages[status]); + } + else + { + this->scan_bus(); + } +} + +void CFTDIServer::scan_bus(void) +{ + FT_DEVICE_LIST_INFO_NODE *dev; + TDevice_info dev_info; + DWORD num_devs=0; + FT_STATUS status; + int i=0; + + if((status=FT_CreateDeviceInfoList(&num_devs))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,error_messages[status]); + } + else + { + this->devices.clear(); + if(num_devs>0) + { + dev=new FT_DEVICE_LIST_INFO_NODE[num_devs]; + if((status=FT_GetDeviceInfoList(dev,&num_devs))!=FT_OK) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,error_messages[status]); + } + for(i=0;i<num_devs;i++) + { + if(dev[i].Flags&0x00000001) + dev_info.opened=true; + else + dev_info.opened=false; + if(dev[i].Flags&0x00000002) + dev_info.high_speed=true; + else + dev_info.high_speed=false; + dev_info.type=dev[i].Type; + dev_info.id=dev[i].ID; + dev_info.location=dev[i].LocId; + dev_info.serial_number=dev[i].SerialNumber; + dev_info.description=dev[i].Description; + this->devices.push_back(dev_info); + } + } + } +} + +int CFTDIServer::get_num_devices(void) +{ + return this->devices.size(); +} + +std::vector<int> CFTDIServer::get_ids_by_description(const std::string& desc) +{ + std::string description; + std::vector<int> ids; + int num=0,i=0; + + num=this->get_num_devices(); + for(i=0;i<num;i++) + { + description=this->get_description(i); + if(description.find(desc)!=std::string::npos) + ids.push_back(i); + } + + return ids; +} + +bool CFTDIServer::is_opened(int index) +{ + if(index<0 || index>this->devices.size()) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,"There exist no USB device with the given index.\n"); + } + else + return this->devices[index].opened; +} + +bool CFTDIServer::is_high_speed(int index) +{ + if(index<0 || index>this->devices.size()) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,"There exist no USB device with the given index.\n"); + } + else + return this->devices[index].high_speed; +} + +unsigned long int CFTDIServer::get_type(int index) +{ + if(index<0 || index>this->devices.size()) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,"There exist no USB device with the given index.\n"); + } + else + return this->devices[index].type; +} + +unsigned long int CFTDIServer::get_id(int index) +{ + if(index<0 || index>this->devices.size()) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,"There exist no USB device with the given index.\n"); + } + else + return this->devices[index].id; +} + +unsigned long int CFTDIServer::get_location(int index) +{ + if(index<0 || index>this->devices.size()) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,"There exist no USB device with the given index.\n"); + } + else + return this->devices[index].location; +} + +std::string& CFTDIServer::get_serial_number(int index) +{ + if(index<0 || index>this->devices.size()) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,"There exist no USB device with the given index.\n"); + } + else + return this->devices[index].serial_number; +} + +std::string& CFTDIServer::get_description(int index) +{ + if(index<0 || index>this->devices.size()) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,"There exist no USB device with the given index.\n"); + } + else + return this->devices[index].description; +} + +CFTDI *CFTDIServer::get_device(std::string& serial_desc) +{ + std::string dev_name; + CFTDI *ftdi_device; + int i=0; + + if(serial_desc.size()==0) + { + /* handle exceptions */ + throw CFTDIServerException(_HERE_,"Invalid serial number or description - empty string.\n"); + } + else + { + for(i=0;i<this->devices.size();i++) + { + if(this->devices[i].serial_number==serial_desc || this->devices[i].description==serial_desc) + { + dev_name="FTDI_"; + dev_name+=serial_desc; + ftdi_device=new CFTDI(dev_name); + ftdi_device->open((void *)&serial_desc); + return ftdi_device; + } + } + } +} + +std::ostream& operator<< (std::ostream &out,CFTDIServer &server) +{ + int i=0; + + out << "Number of FTDI devices detected: " << server.devices.size() << std::endl; + for(i=0;i<server.devices.size();i++) + { + out << "Device " << i << std::endl; + if(server.devices[i].opened) + out << "The device is opened" << std::endl; + else + out << "The device is closed" << std::endl; + if(server.devices[i].high_speed) + out << "The device is high speed" << std::endl; + else + out << "The device is full speed" << std::endl; + out << "Type: " << server.devices[i].type << std::endl; + out << "Device id: " << server.devices[i].id << std::endl; + out << "Location: " << server.devices[i].location << std::endl; + out << "Serial number: " << server.devices[i].serial_number << std::endl; + out << "Description: " << server.devices[i].description << std::endl; + } + + return out; +} diff --git a/src/usb_ftdi/ftdiserver.h b/src/usb_ftdi/ftdiserver.h new file mode 100644 index 0000000000000000000000000000000000000000..0897388ffa7a178d203a8e5341d60478e8f7fdb1 --- /dev/null +++ b/src/usb_ftdi/ftdiserver.h @@ -0,0 +1,395 @@ +#ifndef _FTDI_SERVER +#define _FTDI_SERVER + +#include <vector> +#include <iostream> +#include "ftdimodule.h" +#include "mutex.h" + +const int FTDI_VID=0x0403; +const int DEFAULT_FTDI_PID[]={0x6001,0x6010,0x6006}; + +/** + * \brief structure with information about a device + * + * This structure holds information about one of the devices available on a system. + * This strcucture is first initialized when the device is first detected, and then + * it is stored in the internal device list. + */ +typedef struct +{ + /** + * \brief opened flag + * + * This flag indicates if the USB device is opened by the system or any other + * application. It will anly be possible to use devices that are not currently + * opened by any othe process. Trying to do so will throw an exception. + */ + bool opened; + /** + * \brief High speed flag + * + * This flag indicates if the corresponding device supports the high speed + * specification of the USB (true) or not (false). + */ + bool high_speed; + /** + * \brief Type of the USB device + */ + unsigned long int type; + /** + * \brief USB device identification + */ + unsigned long int id; + /** + * \brief location of the device on the bus + */ + unsigned long int location; + /** + * \brief Serial number of the device + */ + std::string serial_number; + /** + * \brief Decription of the device + */ + std::string description; +}TDevice_info; + +/** + * \brief Global event server + * + * This class implements an FTDI device server which is global to the application + * and also only one instance exist that is shared by all obects requiring + * access to an FTDI device. + * + * Each FTDI device is identified by a VID and PID combination. At construction + * time this server searches the system for devices with the default VID and PID + * combinations provided by the manufacturer. If there exist devices with + * different VID and PID combinations it is necessary to call the add_custom_PID() + * function, which rescans the system for available devices and adds them into + * the internal list. + * + * The get_num_devices() function can be used to retrieve the number of FTDI + * devices available on the system. To actually get one CFTDI class object to + * handle the desired device it is necessary to call the get_device() function, + * either providing the location of the desired device, the serial number or + * its description. It will be impossible to get an object of a device that it + * is already in use by the system or any other application. + * + * This class uses exceptions to report errors. The name of the exception + * class associated to this class is CFTDIServerException. For a more detailes + * descritpion, see the corresponding documentation. + * + */ +class CFTDIServer +{ + private: + /** + * \brief Reference to the unique instance of this class + * + * This reference points to the unique instance of this class. By default + * it is set to NULL, and it is initialized in the first call to the + * instance() function. after that, successive calls to rhat function + * will return the pointer to the previously created object. + */ + static CFTDIServer* pinstance; + /** + * \brief Information on all FTDI devices available + * + * This list have important information on all FTDI devices available on + * the system with one of the default or custom PID values. The information + * include: + * + * - Opened flag (as a boolean) + * - High speed flag (as a boolean) + * - Device type (as an unsigned long int) + * - Device id (as an unisgned long int) + * - Device location (as an int) + * - Serial number (as a string) + * - Description (as a string) + * + * This list if first initialized at construction time with the devices + * presnt with the default PID values. When new PID values are added and + * FTDI devices with those PID values are present, the list is expanded + * wiht the information of the new devices. + * + */ + std::vector<TDevice_info> devices; + /** + * \brief List of all custom PID values + * + * This list has all the PID added by the user. Initially it is empty, + * and it is updated any time the add_custom_PID() function is called. + */ + std::vector<int> custom_PID; + protected: + /** + * \brief Default constructor + * + * This constructor initializes the available FTDI devices list with the + * default VID and PID combinations. This list can only be modified when + * a new PID is added and the sustem is rescaned for new devices. + * + * The reference to the newly created object is not modified. This constructor + * is only called once and from inside the instance() function. It can not + * be called directly by the user since it is declared as protected. + * + */ + CFTDIServer(); + /** + * \brief Copy constructor + * + * This constructor is used to initialize a new object with the contents of + * an existing one. Since there could be only one instance of this class, + * only the pinstance attribute must be copied, but since it is static + * nothing is to be done in this constructor. + * + * \param object an existing instance of a CFTDIServer class which has been + * already initialized. + * + */ + CFTDIServer(const CFTDIServer& object); + /** + * \brief assign operator overloading + * + * This function overloads the assign operator for this class. Since there + * could be only one instance of this class, only the pinstance attribute + * must be copied, but since it is static, nothing is to be done. + * + * \param object an existing instance of a CFTDIServer class which has been + * already initialized. + */ + CFTDIServer& operator = (const CFTDIServer& object); + public: + /** + * \brief Function to get a reference to the unique instance + * + * This function returns a reference to the only instance of the singleton. + * When it is first called, a new object of this class is created, and the + * successive calls only return a reference to the previously created + * object. + * + * Since this function is static, it can be call anywhere in the code so + * all objects can access the unique event handler. + * + * \return A reference to the only instance of this class. This reference + * must not be freed until the application ends. + * + */ + static CFTDIServer* instance(void); + /** + * \brief Function to add new PID values + * + * This function can be used to include new VID and PID combinations. By + * default the FTDI driver only recognises devices with the default PID + * values. Some manufacturers provide their own PID, so it is necessary to + * include it using this function. + * + * When this function is called, the system is rescaned to find any available + * FTDI device with the provided PID. In case there is any, the internal + * device list is expanded with the new information. If there is no device + * with the provided PID this function does nothing. + * + * This class uses exceptions to report errors. The name of the exception + * class associated to this class is CFTDIServerException. For a more detailes + * descritpion, see the corresponding documentation. + * + * \param PID a positive value corresponding to the desired PID value. This + * value must be the one provided by the manufacturer. + */ + void add_custom_PID(int PID); + /** + * \brief function to scan the bus + * + * This function can be used to scan the USB bus for FTDI devices. When it is + * called, it automatically updates all the internal information regarding any + * FTDI devices connected to the USB port of a computer. + * + * This function is also called internally at contruction time and when a new + * PID is added. It can also be called by the user to update the local information + * when there are changes on the bus (a device has been connected or disconnected). + * + * This class uses exceptions to report errors. The name of the exception + * class associated to this class is CFTDIServerException. For a more detailes + * descritpion, see the corresponding documentation. + */ + void scan_bus(void); + /** + * \brief Function to get the number of available FTDI devices + * + * This function return to total number of FTDI devices present in the system + * with any of the default or custom PID values. Some of the devices may be + * used by the system or other applications. In this case, they can not be + * accessed, but they are listed anyway. + * + * This class uses exceptions to report errors. The name of the exception + * class associated to this class is CFTDIServerException. For a more detailes + * descritpion, see the corresponding documentation. + * + * \return The number of devices available on the system + */ + int get_num_devices(void); + /** + * \brief Function to check if teh device is opened + * + * This function returna wether the device located at the given position of the + * internal list is opened or not. + * + * \param index is an integer with the position on the internal list of the + * desired device. This value must be between 0 and the number of + * devices on the list -1. + * + * \return a boolean that indicates if the device is opened (true) or not (false). + */ + bool is_opened(int index); + /** + * \brief Function to check the maximum speed supported + * + * This function returns wether the device located at the given position of the + * internal list supports high speed modes or not. + * + * \param index is an integer with the position on the internal list of the + * desired device. This value must be between 0 and the number of + * devices on the list -1. + * + * \return a boolean that indicates if the device supports high speed modes (true) + * or not (false). + */ + bool is_high_speed(int index); + /** + * \brief Function to get the type of the device + * + * This function returns the type of the USB device located at the given position + * of the internal list. + * + * \param index is an integer with the position on the internal list of the + * desired device. This value must be between 0 and the number of + * devices on the list -1. + * + * \return an integer which represents the type of the USB device. + */ + unsigned long int get_type(int index); + /** + * \brief Function to get the identifiers of the devices with a given description + * + * This function returns a vector with the absolute indexes of all the FTDI + * devices that matches a given description or that contain a given string in + * their description. This function is usefull when trying to access devices + * of a given type. + * + * If no devices are present with the given description, an empty vector is + * returned + * + * \param desc a null terminated string with the whole desired description or + * a subset of it. + * + * \return a vector with the absolute indexes of all the devices matching + * the given description. If no devices are found, the returned + * vector is empty. + * + */ + std::vector<int> get_ids_by_description(const std::string &desc); + /** + * \brief Function to get the device identifier + * + * This function returns the identifier of the USB device located at the given + * position of the internal list. + * + * \param index is an integer with the position on the internal list of the + * desired device. This value must be between 0 and the number of + * devices on the list -1. + * + * \return an integer which represents the identifier of the USB device. + * + */ + unsigned long int get_id(int index); + /** + * \brief Function to get the location of the device on teh bus + * + * This function returns the location of the USB device on the USB bus, which is + * located at the given position of the internal list. + * + * \param index is an integer with the position on the internal list of the + * desired device. This value must be between 0 and the number of + * devices on the list -1. + * + * \return an integer which represents the location of the USB device on the bus. + */ + unsigned long int get_location(int index); + /** + * \brief Function to get the serial number of the device + * + * This function return the serial number of the USB device located at the given + * position of the internal list as a string. + * + * \param index is an integer with the position on the internal list of the + * desired device. This value must be between 0 and the number of + * devices on the list -1. + * + * \return a string with the serial number of the USB device. + */ + std::string& get_serial_number(int index); + /** + * \brief Function to get the description of the device + * + * This function return the description of the USB device located at the given + * position of the internal list as a string. + * + * \param index is an integer with the position on the internal list of the + * desired device. This value must be between 0 and the number of + * devices on the list -1. + * + * \return a string with the description of the USB device. + */ + std::string& get_description(int index); + /** + * \brief Function to get a new CFTDI object + * + * Function to get a CFTDI class object associated to the desired FTDI device. + * In this case the desired FTDI device is identified by either its serial number + * or its description. The serial number is in general the best way to select + * the desired device since it is unique. + * + * However, the description may be shared by several devices of the same kind. + * In this second case, the first device found on the list is returned. + * If the desired device is already being used by the system or any other + * application this function will fail throwing an exception of the + * CFTDIServer class. Otherwise, a new CFTDI object is returned. + * + * The CFTDI object returned by this function is already opened using the + * serial number or description provided to the function. The name of the + * returned object is created by appending the provided serial number or + * description to "FTDI_" to have a unique identifier for each device. + * + * This class uses exceptions to report errors. The name of the exception + * class associated to this class is CFTDIServerException. For a more detailes + * descritpion, see the corresponding documentation. + * + * \param serial_desc a string with either the serial number or the description + * of the desired FTDI device. + * + * \return on success this function returns a pointer a newly created CFTDI + * object associated to the desired FTDI device. The calling process + * is responsible for destroying the returned obejct. + */ + CFTDI *get_device(std::string& serial_desc); + /** + * \brief operator << overloading + * + * This operator allow to show important information about the class and the + * information of all FTDI devices available on any ostream obejct (the standard + * output, a file, etc...). + * + * \param out A reference to an output device in which to show the desired + * information. + * + * \param server A reference to a CFTDIServer object with the information to be + * displayed. + * + * \return an object with the desired information already formatted. In this case + * it has the number of devices and the information on each of them. + */ + friend std::ostream& operator<< (std::ostream &out,CFTDIServer &server); +}; + +#endif