1.Introduction
2.Preparation
2.1 Development
2.2 Test
2.3 Delivery
2.4 Development rules
3.Libraries loader
3.1 Logger loader
3.2 Serial Driver loader
3.3 Protocol Loader
4.Logger
5.Serial driver
5.1 Serial driver C implementations
5.2 Serial driver JNI
6.Multithread and Multistate model
7.Modify the list of published API
8.Versioning
This Guide describes the architecture and functionalities of each module that composes the rs232 driver C implementation. The implementation is divided in 3 modules: loader, logger, and serial driver. Each of them is described in details below.
Before compilation of com_coronis_sdk_serialdriver_rs232driver or com_coronis_sdk_serialdriver_rs232driver_test
projects you should make following steps:
com_coronis_sdk_serialdriver_rs232driver project's compilation guide.
com_coronis_sdk_serialdriver_rs232driver_test project's compilation guide.
Libraries loader is located into com_coronis_sdk_domain project, src/external/loader_/*.c
The Loader is divided into 3 parts, one per library it loads. The function addresses are loaded In a structure that represents the a collection of pointers of functions type. Each time a new API function is added to a serial driver, logger or protocol, developer must update loader.
Loads all APIs published by logger. Exported functions can be found in domain project, /include/i_logger_export_types.h. In fact the loader loads references to intialisation and log write functions.
Loads all APIs published by serial driver. A list of exported functions are located in serial driver api header file com_coronis_sdk_serialdriver_api/i_serial_driver_export_types.h. At the beginning are defined symbolic names of the exported API. These names are used by system library loader to find functions. Next are specified the list of APIs types (return type and parameters).
A more detailed description can be found in i_serial_driver_service.h . In this header file are defined the exported functions.
Serial driver loader loads serial driver library into memory and all known exported functions to structure named SerialDriverLoader and which is defined in file loader_serial_driver.h
Serial drier loading is managed by two functions:
void free_serial_driver( SerialDriverLoader* serialDriver ) – informs the OS that library is not used, so the system will be able to unload it from memory when nobody uses it.
int load_serial_driver( const char* serialDriverName, SerialDriverLoader* serialDriver ) – load library to memory, and all exported APIs references to structure passed as a second paramenter.
In case if a new function is added to the API set, then this function must be declared in structure SerialDriverLoader. The loading of new function must be added in file loader_serial_driver.c in load_serial_driver.
Loads all protocol exported API in a structure which is defined in . The API types are defined in eclipse project com_coronis_sdk_protocol_api the file i_protocol_export_types.h. A more detailed information about each API are available in file i_protocol_service.h.
Logger is a part of domain project. It’s purpose is to provide a thread safe tool to collect debug/info or error messages to a location that is defined in csloger.config
Logger is organized as 2 initialization functions and 5 logging functions. Internally logger uses one single function for actual logging called log_msg(const char *msg). This function formats and writes data to log file using a global buffer which is locked by interface logging functions.
The logger properties file contain following fields:
“logger.loggerName=log.txt” – the logger file name (and location path if specified)
“logger.loggerLevel=debug” – the logging level -“debug” means that all messages will be shown. Following logging modes are available: DEBUG, INFO, WARN, ERROR, FATAL.
“logger.loggerMaxSize=50” – represents the maximal allowed log file size. If this size is exceeded the current log file is renamed to < logger.loggerName>.n (where n is a number that is incremented each time log file is splitted), and a new file with name < logger.loggerName> is created.
Initialization is performed by calling one of initialization functions:
void init_log( ) – initialize logger with default cslogger.config property file which is located in the same folder with library.
void init_log_from_property_file( const char* propertyFile ) – is called to initialize logger with custom properties file.
Writing of log messages are pergformed by one of the following functions:
void debug( const char* format, ... ); - debug messages
void info( const char* format, ... ); - info messages
void warn( const char* format, ... ); - warning messages
void error( const char* format, ... ); - error messages
void fatal( const char* format, ... ); - fatal error messages
All these logging functions uses a global buffer and a lock mechanism to synchronize write/read access to this buffer:
static char logBuffer[LOG_MAX_BUFFER_SIZE] = "";
By default the LOG_MAX_BUFFER_SIZE is 5k, and access to this buffer is strictly controlled, to avoid buffer overflow error. The buffer is mainly used to format log messages by expanding variable arguments lists passed to logging functions.
Access to logger functions from Java is provided through JNI interface functions which are located in domain package src/JNI/ com_coronis_sdk_domain_framework_Logger.c
Serial driver is composed from 2 basic parts located in project com_coronis_sdk_serialdriver_rs232driver :
The native C API implementation
The JNI translation of Java to C and C to Java calls.
Serial driver c APIs list are published in project “com_coronis_sdk_serialdriver_api” file “i_serial_driver_service.h”
The serial driver is implemented to perform asynchronous calls to callback functions (i.e. to listeners) using a pool of threads, which are allocated per each event. This approach avoids driver blocking when a user performs some time consuming operations.
The main driver loop is located in file serial.c in function “main_thread”. The function will iterate trough all opened drivers and will call process_port for each opened driver instance.
The implementation of these APIs are located in project “com_coronis_sdk_serialdriver_rs232driver” the file “driver.c”
APIs implemented in driver.c performs direct calls to functions located in following files:
• serial.c – this is high level serial port access routines which is independent from underlying platform and uses u_serial or w_serial to access hardware on unix or windows based platforms. In this file is concentrated all serial driver basic functions (read/write open and close).
Additional description needs function “process_port” which is a state machine, that processes different changes in the current instance of driver, and continuously update the internal state and calls registered listeners for a specific event. The event handlers are called in separated threads (from a latent thread pool) .
• listeners.c - to assign listeners for different event types. These functions are callbacks and are indirectly called by the serial driver logic when a specific event occurs. The main idea is to notify a listener by creating a thread in pool and detach from main thread without waiting for the registered listener to finish its job.
Three functions to mention here:
int serial_driver_listeners( void* param, const SerialFrame* frame ) – function is called by the driver to create a thread into thread’s pool and detach from main thread. In this case, the user code will be executed in a thread different from main thread.
void* listener_thread( void *param ) – this is a separate thread function, which performs the actual call of registered listener according with occurred event. The data are prepared here to be passed to callback function. After callback function exits, the thread update the pool to signal that it is ready for removal (ltp->threadId = 0;)
void cleanup() - cleanup of the threads pool. The function will scan thread pool and will detect all dead threads (by threadId).
This function is mandatory called before a new thread is created.
• serial_properties.c – The file concentrates the logic that reads and parse properties file. The driver will parse only known properties and will store their values in a driver descriptor structure, named by convention static standard and expert parameters. Those parameters which are named "dynamic” are specified at runtime and passed through function calls.
A particularity of property reader, it is that it will use default values for properties that are missing or cannot be parsed.
Serial driver uses a set of binary flags to store the status of each property (is set or not set). This approach is widely used when managing priorities of properties to be used.
Priorities are in descending order: dynamic properties, static properties and default values. In order to use a property value, the driver is checking if this value has been read or not from property file or it has been set by a respective API function.
Serial driver Java implementation is located in serial driver project src/JNI/. All calls from java are mapped to C implemented functions. The main role of JNI functions are to convert data passed between java and C implementations. The second role, is to prepare and make a correct call of java functions from C.
A very important thing to mention here is about data types, functions names and class names in Java when called from C trough JNI: the names are literal, and cannot be verified at compile time, just at runtime (using reflection). As a result, any changes in naming on Java part (classes, member names, package types of parameters of a function, order or number of parameters), will require an additional refactoring of JNI part, to adapt code to new model.
Each JNI function call will receive a pointer to calling Java environment (JNIEnv )* which is used to get data from Java and other information necessary to create a Java object to be passed back to Java.
In order to develop new functionalities, use one of the existing functions as a template, to get data from Java and create new classed to send back to Java.
As an additional recommendation, after each JNI invocation (from C to Java), check the environment for a java Exception. In case of occurrence, log it as an error with all associated information. It will help to find errors that can occur when an invalid class name or method signature is used in reflection.
The serial driver is implemented as a multithreaded and multistate machine which makes it very hard to debug using a debugger. Instead, it is recommended to use logger to analyze the problem.
Once driver is initialized, it runs a loop that iterates through all opened driver instances (an array of driver descriptors) and processes their current status one by one sequentially.
This is the linear part of the algorithm.
The asynchronous part of serial driver begins when an event occurs and there are registered listeners for this event. In this case, the linear part of the algorithm creates a new thread and passes to it all the necessary information (like event type and received data).
Data sending are performed also in a linear part of the algorithm. The main loop is pooling the opened driver instances and check if they have something to send. The low level ACK (confirmation from modem, that data have been received and understood by the modem) is processed locally and are not passed to the upper layer (this is specific for Coronis Waveport implementation).
Add, remove or edit the list of serial driver published API will require a small refactoring in several places:
1.com_coronis_sdk_serialdriver_api/i_serial_driver_service.h – the file contains all exported APIs
2.com_coronis_sdk_serialdriver_api/i_serial_driver_export_types.h – contains function types required be loader and driver descriptor. These data types are pointers types to functions. And must be strictly correspond to functions they will point to. This file contains the symbolic names of each function, which are required by the loader to find the function name in library.
3.com_coronis_sdk_serialdriver_api/i_serial_driver_listener_types.h – update this file if a new spy type is required and in parallel, if a new event spy will be associated with a new event type, update the file “com_coronis_sdk_serialdriver_api/serial_driver_event.h”.
4.com_coronis_sdk_serialdriver_api/serial_port_constants.h – will be updated if a newly added API required some additional status code. In case if an API function is removed, probably some status codes will not be used anymore. In this case, it’s better to remove them.
5.com_coronis_sdk_domain/include/ loader_serial_driver.h – the serial driver descriptor SerialDriverLoader must be updated.
6.com_coronis_sdk_domain/src/external/ loader_serial_driver.c – update code to add loading of added or modified function. The loading of removed function must be removed; otherwise the driver loading will fail. The loader uses functions symbolic name to get its address from library, and the function type, to store it into the SerialDriverLoader structure.
7.com_coronis_sdk_serialdriver_rs232driver/src/driver.c - the place where API functions are defined.
Note: modification of API part will require some refactoring in Java code (the native functions call), but this is a subject of another article.
Versioning is used to control the binary version of the used serial driver. This control guaranties that the driver implements the expected API set with the expected behavior.
Wiki: C-CPP_SerialDriver_UserGuide
Wiki: Home
Wiki: Java_SerialDriver_UserGuide
Wiki: RS232Driver_DevelopperPrerequisites