From 285dc9fd4b95673639fa6091632e7fb629c02fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillem=20Aleny=C3=A0=20Ribas?= <galenya@iri.upc.edu> Date: Mon, 18 Apr 2011 13:38:36 +0000 Subject: [PATCH] trunk/tags/branches creation --- CMakeLists.txt | 84 ++++ Findcomm.cmake | 21 + ReadMe.txt | 17 + doc/doxygen.conf | 251 ++++++++++ doc/doxygen_project_name.conf | 1 + doc/images/comm_states.eps | 506 +++++++++++++++++++ doc/images/comm_states.odp | Bin 0 -> 12467 bytes doc/images/comm_states.png | Bin 0 -> 22446 bytes doc/main.dox | 143 ++++++ src/CMakeLists.txt | 57 +++ src/comm.cpp | 328 +++++++++++++ src/comm.h | 649 ++++++++++++++++++++++++ src/commexceptions.cpp | 12 + src/commexceptions.h | 58 +++ src/examples/CMakeLists.txt | 35 ++ src/examples/test_ftdi.cpp | 73 +++ src/examples/test_multiple_client.cpp | 51 ++ src/examples/test_multiple_server.cpp | 84 ++++ src/examples/test_rs232.cpp | 119 +++++ src/examples/test_simple_client.cpp | 44 ++ src/examples/test_simple_server.cpp | 65 +++ src/serial/rs232.cpp | 357 ++++++++++++++ src/serial/rs232.h | 424 ++++++++++++++++ src/serial/rs232exceptions.cpp | 11 + src/serial/rs232exceptions.h | 60 +++ src/sockets/socket.cpp | 182 +++++++ src/sockets/socket.h | 322 ++++++++++++ src/sockets/socketclient.cpp | 70 +++ src/sockets/socketclient.h | 152 ++++++ src/sockets/socketexceptions.cpp | 15 + src/sockets/socketexceptions.h | 116 +++++ src/sockets/socketserver.cpp | 525 ++++++++++++++++++++ src/sockets/socketserver.h | 677 ++++++++++++++++++++++++++ src/usb_ftdi/ftdiexceptions.cpp | 37 ++ src/usb_ftdi/ftdiexceptions.h | 116 +++++ src/usb_ftdi/ftdimodule.cpp | 210 ++++++++ src/usb_ftdi/ftdimodule.h | 334 +++++++++++++ src/usb_ftdi/ftdiserver.cpp | 247 ++++++++++ src/usb_ftdi/ftdiserver.h | 395 +++++++++++++++ 39 files changed, 6848 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Findcomm.cmake create mode 100644 ReadMe.txt create mode 100644 doc/doxygen.conf create mode 100644 doc/doxygen_project_name.conf create mode 100644 doc/images/comm_states.eps create mode 100644 doc/images/comm_states.odp create mode 100644 doc/images/comm_states.png create mode 100644 doc/main.dox create mode 100644 src/CMakeLists.txt create mode 100644 src/comm.cpp create mode 100644 src/comm.h create mode 100644 src/commexceptions.cpp create mode 100644 src/commexceptions.h create mode 100644 src/examples/CMakeLists.txt create mode 100644 src/examples/test_ftdi.cpp create mode 100644 src/examples/test_multiple_client.cpp create mode 100644 src/examples/test_multiple_server.cpp create mode 100644 src/examples/test_rs232.cpp create mode 100644 src/examples/test_simple_client.cpp create mode 100644 src/examples/test_simple_server.cpp create mode 100644 src/serial/rs232.cpp create mode 100644 src/serial/rs232.h create mode 100644 src/serial/rs232exceptions.cpp create mode 100644 src/serial/rs232exceptions.h create mode 100644 src/sockets/socket.cpp create mode 100644 src/sockets/socket.h create mode 100644 src/sockets/socketclient.cpp create mode 100644 src/sockets/socketclient.h create mode 100644 src/sockets/socketexceptions.cpp create mode 100644 src/sockets/socketexceptions.h create mode 100644 src/sockets/socketserver.cpp create mode 100644 src/sockets/socketserver.h create mode 100644 src/usb_ftdi/ftdiexceptions.cpp create mode 100644 src/usb_ftdi/ftdiexceptions.h create mode 100644 src/usb_ftdi/ftdimodule.cpp create mode 100644 src/usb_ftdi/ftdimodule.h create mode 100644 src/usb_ftdi/ftdiserver.cpp create mode 100644 src/usb_ftdi/ftdiserver.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..212386f --- /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 0000000..8408e22 --- /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 0000000..6095168 --- /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 0000000..347af2e --- /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 0000000..b8e4e36 --- /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 0000000..0eb51e7 --- /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 GIT binary patch literal 12467 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 I{oUFB0l{sKQ2+n{ literal 0 HcmV?d00001 diff --git a/doc/images/comm_states.png b/doc/images/comm_states.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a452f08de48c6415ee98a66a1c8bc346d6ab27 GIT binary patch literal 22446 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 literal 0 HcmV?d00001 diff --git a/doc/main.dox b/doc/main.dox new file mode 100644 index 0000000..73bc774 --- /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 0000000..6fc247f --- /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 0000000..6437535 --- /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 0000000..27919ea --- /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 0000000..1c17119 --- /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 0000000..30b535b --- /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 0000000..e90ba12 --- /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 0000000..ad031e3 --- /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 0000000..bfc5a00 --- /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 0000000..376491d --- /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 0000000..4598df3 --- /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 0000000..488d181 --- /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 0000000..4c6a7a2 --- /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 0000000..ceffa0a --- /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 0000000..18d603d --- /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 0000000..369bdc7 --- /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 0000000..afacce0 --- /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 0000000..aaddeff --- /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 0000000..89f1763 --- /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 0000000..efb63cf --- /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 0000000..741a876 --- /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 0000000..02e7485 --- /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 0000000..75e97ac --- /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 0000000..599e8d0 --- /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 0000000..228b23b --- /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 0000000..e12766c --- /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 0000000..53387f0 --- /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 0000000..7520d5d --- /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 0000000..720d56e --- /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 0000000..a0c9acc --- /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 0000000..0897388 --- /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 -- GitLab