diff --git a/examples/enumerate.c b/examples/enumerate.c index dc00c2a..358bd9b 100644 --- a/examples/enumerate.c +++ b/examples/enumerate.c @@ -8,19 +8,17 @@ This example shows how to enumerate the attached devices. int main( void ) { - char *error = NULL; - if ( !tempered_init( &error ) ) + char error[256]; + if ( !tempered_init( error, sizeof(error) ) ) { fprintf( stderr, "%s\n", error ); - free( error ); return 1; } - struct tempered_device_list *list = tempered_enumerate( &error ); + struct tempered_device_list *list = tempered_enumerate( error, sizeof(error) ); if ( list == NULL ) { fprintf( stderr, "%s\n", error ); - free( error ); } else { @@ -39,10 +37,9 @@ int main( void ) tempered_free_device_list( list ); } - if ( !tempered_exit( &error ) ) + if ( !tempered_exit( error, sizeof(error) ) ) { fprintf( stderr, "%s\n", error ); - free( error ); return 1; } return 0; diff --git a/examples/read-all.c b/examples/read-all.c index 2069df4..2a1964f 100644 --- a/examples/read-all.c +++ b/examples/read-all.c @@ -60,12 +60,11 @@ void read_device( struct tempered_device_list *dev ) dev->interface_number, dev->type_name ); - char *error = NULL; - tempered_device *device = tempered_open( dev, &error ); + char error[256]; + tempered_device *device = tempered_open( dev, error, sizeof(error) ); if ( device == NULL ) { printf( "\tOpen failed, error: %s\n", error ); - free( error ); return; } printf( @@ -96,15 +95,14 @@ void read_device( struct tempered_device_list *dev ) int main( void ) { - char *error = NULL; - if ( !tempered_init( &error ) ) + char error[256]; + if ( !tempered_init( error, sizeof(error) ) ) { fprintf( stderr, "Failed to initialize libtempered: %s\n", error ); - free( error ); return 1; } - struct tempered_device_list *list = tempered_enumerate( &error ); + struct tempered_device_list *list = tempered_enumerate( error, sizeof(error) ); if ( list == NULL ) { if ( error == NULL ) @@ -114,7 +112,6 @@ int main( void ) else { fprintf( stderr, "Failed to enumerate devices: %s\n", error ); - free( error ); } } else @@ -127,10 +124,9 @@ int main( void ) tempered_free_device_list( list ); } - if ( !tempered_exit( &error ) ) + if ( !tempered_exit( error, sizeof(error) ) ) { fprintf( stderr, "Failed to shut down libtempered: %s\n", error ); - free( error ); return 1; } return 0; diff --git a/examples/read-repeat.c b/examples/read-repeat.c index 8fb404f..21630ac 100644 --- a/examples/read-repeat.c +++ b/examples/read-repeat.c @@ -55,11 +55,11 @@ void read_device_sensor( tempered_device *device, int sensor ) void read_repeatedly( tempered_device *device ) { int i; - for ( i = 0; i < 10; i++ ) + for ( i = 0; i < 500; i++ ) { if ( i > 0 ) { - sleep( 5 ); + sleep( 1 ); } if ( !tempered_read_sensors( device ) ) { @@ -82,19 +82,11 @@ void read_repeatedly( tempered_device *device ) /** Open the device with the given device path. */ tempered_device* open_device( char *dev_path ) { - char *error = NULL; - struct tempered_device_list *list = tempered_enumerate( &error ); + char error[256]; + struct tempered_device_list *list = tempered_enumerate( error, sizeof(error) ); if ( list == NULL ) { - if ( error == NULL ) - { - printf( "No devices were found.\n" ); - } - else - { - fprintf( stderr, "Failed to enumerate devices: %s\n", error ); - free( error ); - } + fprintf( stderr, "Failed to enumerate devices: %s\n", error ); return NULL; } tempered_device *device = NULL; @@ -105,7 +97,7 @@ tempered_device* open_device( char *dev_path ) if ( strcmp( dev->path, dev_path ) == 0 ) { found = true; - device = tempered_open( dev, &error ); + device = tempered_open( dev, error, sizeof(error) ); break; } } @@ -118,7 +110,6 @@ tempered_device* open_device( char *dev_path ) stderr, "Opening %s failed, error: %s\n", dev_path, error ); - free( error ); } else { @@ -135,11 +126,10 @@ int main( int argc, char *argv[] ) fprintf( stderr, "Usage: read-repeat \n" ); return 1; } - char *error = NULL; - if ( !tempered_init( &error ) ) + char error[256]; + if ( !tempered_init( error, sizeof(error) ) ) { fprintf( stderr, "Failed to initialize libtempered: %s\n", error ); - free( error ); return 1; } @@ -150,10 +140,9 @@ int main( int argc, char *argv[] ) tempered_close( device ); } - if ( !tempered_exit( &error ) ) + if ( !tempered_exit( error, sizeof(error) ) ) { fprintf( stderr, "Failed to shut down libtempered: %s\n", error ); - free( error ); return 1; } return 0; diff --git a/libtempered/CMakeLists.txt b/libtempered/CMakeLists.txt index b989315..34b4120 100644 --- a/libtempered/CMakeLists.txt +++ b/libtempered/CMakeLists.txt @@ -8,6 +8,7 @@ endif() if (BUILD_SHARED_LIB) add_library(tempered-shared SHARED ${libtempered_FILES}) + target_link_libraries(tempered-shared ${HIDAPI_LINK_LIBS}) set_target_properties(tempered-shared PROPERTIES OUTPUT_NAME tempered SOVERSION 0 @@ -22,6 +23,7 @@ endif() if (BUILD_STATIC_LIB) add_library(tempered-static STATIC ${libtempered_FILES}) + target_link_libraries(tempered-static ${HIDAPI_LINK_LIBS}) set_target_properties(tempered-static PROPERTIES OUTPUT_NAME tempered ) diff --git a/libtempered/core.c b/libtempered/core.c index 73ad0b0..783a80b 100644 --- a/libtempered/core.c +++ b/libtempered/core.c @@ -23,21 +23,59 @@ void tempered_set_error( tempered_device *device, char *error ) } /** Initialize the TEMPered library. */ -bool tempered_init( char **error ) +bool tempered_init( char *error, size_t err_size ) { - return temper_type_init( error ); + if ( error == NULL ) //don't need to handle error messages + { + return temper_type_init( NULL ); + } + + char *err; + if ( !temper_type_init( &err ) ) + { + snprintf(error, err_size, "%s", err); + free(err); //free the damn thing + + return false; + } + return true; } /** Finalize the TEMPered library. */ -bool tempered_exit( char **error ) +bool tempered_exit( char *error, size_t err_size ) { - return temper_type_exit( error ); + if ( error == NULL ) //don't need to handle error messages + { + return temper_type_init( NULL ); + } + + char *err; + if ( !temper_type_exit( &err ) ) + { + snprintf(error, err_size, "%s", err); + free(err); //free the damn thing + + return false; + } + return true; } /** Enumerate the TEMPer devices. */ -struct tempered_device_list* tempered_enumerate( char **error ) +struct tempered_device_list* tempered_enumerate( char *error, size_t err_size ) { - return temper_type_enumerate( error ); + if ( error == NULL ) //don't need to handle error messages + { + return temper_type_enumerate( NULL ); + } + + char *err; + struct tempered_device_list *list = temper_type_enumerate( &err ); + if ( list == NULL ) + { + snprintf(error, err_size, "%s", err); + free(err); //free the damn thing + } + return list; } /** Free the memory used by the given device list. */ @@ -95,13 +133,13 @@ static bool tempered_open__find_subtype( tempered_device *device ) } /** Open a given device from the list. */ -tempered_device* tempered_open( struct tempered_device_list *list, char **error ) +tempered_device* tempered_open( struct tempered_device_list *list, char *error, size_t err_size ) { if ( list == NULL ) { if ( error != NULL ) { - *error = strdup( "Invalid device given." ); + snprintf( error, err_size, "Invalid device given." ); } return NULL; } @@ -112,7 +150,7 @@ tempered_device* tempered_open( struct tempered_device_list *list, char **error { if ( error != NULL ) { - *error = strdup( "Invalid device given (type not found)." ); + snprintf( error, err_size, "Invalid device given (type not found)." ); } return NULL; } @@ -120,7 +158,7 @@ tempered_device* tempered_open( struct tempered_device_list *list, char **error { if ( error != NULL ) { - *error = strdup( "Device type has no open method." ); + snprintf( error, err_size, "Device type has no open method." ); } return NULL; } @@ -129,7 +167,7 @@ tempered_device* tempered_open( struct tempered_device_list *list, char **error { if ( error != NULL ) { - *error = strdup( "Could not allocate memory for the device." ); + snprintf( error, err_size, "Could not allocate memory for the device." ); } return NULL; } @@ -142,7 +180,7 @@ tempered_device* tempered_open( struct tempered_device_list *list, char **error { if ( error != NULL ) { - *error = strdup( "Could not allocate memory for the path." ); + snprintf( error, err_size, "Could not allocate memory for the path." ); } free( device ); return NULL; @@ -153,11 +191,13 @@ tempered_device* tempered_open( struct tempered_device_list *list, char **error { if ( device->error != NULL ) { - *error = device->error; + snprintf( error, err_size, + "Type-specific device open failed with error: %s", device->error + ); } else { - *error = strdup( + snprintf( error, err_size, "Type-specific device open failed with no error message." ); } @@ -174,7 +214,8 @@ tempered_device* tempered_open( struct tempered_device_list *list, char **error { if ( error != NULL ) { - *error = device->error; + snprintf( error, err_size, "%s", device->error ); + free( device->error ); device->error = NULL; } tempered_close( device ); @@ -188,12 +229,15 @@ tempered_device* tempered_open( struct tempered_device_list *list, char **error { if ( device->error != NULL ) { - *error = device->error; + snprintf( error, err_size, + "Type-specific device open failed with error: %s", device->error + ); + free( device->error ); device->error = NULL; } else { - *error = strdup( + snprintf( error, err_size, "Type-specific device open failed with no error message." ); } diff --git a/libtempered/temper_type.c b/libtempered/temper_type.c index 1b00b20..7bbe47e 100644 --- a/libtempered/temper_type.c +++ b/libtempered/temper_type.c @@ -10,6 +10,8 @@ #include "type_hid/sht1x.h" #include "type_hid/ntc.h" #include "type_hid/si7005.h" +#include "type_hid/si7021.h" +#include "type_hid/simple.h" // This is an array of known TEMPer types. struct temper_type known_temper_types[]={ @@ -37,6 +39,8 @@ struct temper_type known_temper_types[]={ .subtype_strings = (char *[]){ "TEMPerHumV1.0rHu", "TEMPerHumM12V1.0", + "TEMPerHumM12V1.2", + "TEMPerHumM12V1.3", NULL } }, @@ -103,6 +107,68 @@ struct temper_type known_temper_types[]={ } } }, + (struct temper_subtype*)&(struct temper_subtype_hid){ + .base = { + .id = 2, + .name = "TEMPerHumM12V1.2", + .open = tempered_type_hid_subtype_open, + .read_sensors = tempered_type_hid_read_sensors, + .get_temperature = tempered_type_hid_get_temperature, + .get_humidity = tempered_type_hid_get_humidity + }, + .sensor_group_count = 1, + .sensor_groups = (struct tempered_type_hid_sensor_group[]){ + { + .query = { + .length = 9, + .data = (unsigned char[]){ 0, 1, 0x80, 0x33, 1, 0, 0, 0, 0 } + }, + .read_sensors = tempered_type_hid_read_sensor_group, + .sensor_count = 1, + .sensors = (struct tempered_type_hid_sensor[]){ + { + .get_temperature = tempered_type_hid_get_temperature_si7021, + .get_humidity = tempered_type_hid_get_humidity_si7021, + .temperature_high_byte_offset = 2, + .temperature_low_byte_offset = 3, + .humidity_high_byte_offset = 4, + .humidity_low_byte_offset = 5 + } + } + } + } + }, + (struct temper_subtype*)&(struct temper_subtype_hid){ + .base = { + .id = 3, + .name = "TEMPerHumM12V1.3", + .open = tempered_type_hid_subtype_open, + .read_sensors = tempered_type_hid_read_sensors, + .get_temperature = tempered_type_hid_get_temperature, + .get_humidity = tempered_type_hid_get_humidity + }, + .sensor_group_count = 1, + .sensor_groups = (struct tempered_type_hid_sensor_group[]){ + { + .query = { + .length = 9, + .data = (unsigned char[]){ 0, 1, 0x80, 0x33, 1, 0, 0, 0, 0 } + }, + .read_sensors = tempered_type_hid_read_sensor_group, + .sensor_count = 1, + .sensors = (struct tempered_type_hid_sensor[]){ + { + .get_temperature = tempered_type_hid_get_temperature_si7021, + .get_humidity = tempered_type_hid_get_humidity_si7021, + .temperature_high_byte_offset = 2, + .temperature_low_byte_offset = 3, + .humidity_high_byte_offset = 4, + .humidity_low_byte_offset = 5 + } + } + } + } + }, NULL // List terminator for subtypes } }, @@ -407,6 +473,85 @@ struct temper_type known_temper_types[]={ NULL // List terminator for subtypes } }, + { + .name="TEMPer2V3.1 or TEMPer2V3.3", + .vendor_id=0x413d, + .product_id=0x2107, + .interface_number=1, + .open = tempered_type_hid_open, + .close = tempered_type_hid_close, + .get_subtype_id = tempered_type_hid_get_subtype_id_from_string, + .get_subtype_data = &(struct tempered_type_hid_subtype_from_string_data) + { + .query = { + .length = 9, + .data = (unsigned char[]){ 0, 1, 0x86, 0xFF, 1, 0, 0, 0, 0 } + }, + .response_count = 2, + .subtype_strings = (char *[]){ + "TEMPerGold_V3.1 ", + "TEMPerX_V3.3 ", + NULL + } + }, + .subtypes = (struct temper_subtype*[]){ + (struct temper_subtype*)&(struct temper_subtype_hid){ + .base = { + .id = 0, + .name = "TEMPerGold_V3.1", + .open = tempered_type_hid_subtype_open, + .read_sensors = tempered_type_hid_read_sensors, + .get_temperature = tempered_type_hid_get_temperature + }, + .sensor_group_count = 1, + .sensor_groups = (struct tempered_type_hid_sensor_group[]){ + { + .query = { + .length = 9, + .data = (unsigned char[]){ 0, 1, 0x80, 0x33, 1, 0, 0, 0, 0 } + }, + .read_sensors = tempered_type_hid_read_sensor_group, + .sensor_count = 1, + .sensors = (struct tempered_type_hid_sensor[]){ + { + .get_temperature = tempered_type_hid_get_temperature_simple, + .temperature_high_byte_offset = 2, + .temperature_low_byte_offset = 3 + } + } + } + } + }, + (struct temper_subtype*)&(struct temper_subtype_hid){ + .base = { + .id = 1, + .name = "TEMPerX_V3.3", + .open = tempered_type_hid_subtype_open, + .read_sensors = tempered_type_hid_read_sensors, + .get_temperature = tempered_type_hid_get_temperature + }, + .sensor_group_count = 1, + .sensor_groups = (struct tempered_type_hid_sensor_group[]){ + { + .query = { + .length = 9, + .data = (unsigned char[]){ 0, 1, 0x80, 0x33, 1, 0, 0, 0, 0 } + }, + .read_sensors = tempered_type_hid_read_sensor_group, + .sensor_count = 1, + .sensors = (struct tempered_type_hid_sensor[]){ + { + .get_temperature = tempered_type_hid_get_temperature_simple, + .temperature_high_byte_offset = 2, + .temperature_low_byte_offset = 3 + } + } + } + } + }, + NULL // List terminator for subtypes + } + }, { .name=NULL } // List terminator for temper types }; diff --git a/libtempered/tempered.h b/libtempered/tempered.h index 0ba3338..cb33442 100644 --- a/libtempered/tempered.h +++ b/libtempered/tempered.h @@ -5,6 +5,7 @@ extern "C" { #endif +#include #include /** This file contains the headers that comprise the public API of the TEMPered @@ -69,37 +70,40 @@ typedef struct tempered_device_ tempered_device; * necessary, as it will be called automatically when needed, but should be * called at the start of execution if there's a chance the library will be * used by multiple threads simultaneously. - * @param error If an error occurs and this is not NULL, it will be set to the - * error message. The returned string is dynamically allocated, and should be - * freed when you're done with it. + * @param error If an error occurs and this is not NULL, the error message will + * be written to the given buffer. + * @param err_size If the buffer is too small for the error message it will be + * truncated. * @return true on success, false on error. */ -bool tempered_init( char **error ); +bool tempered_init( char *error, size_t err_size ); /** Finalize the TEMPered library. * * This function should be called at the end of execution to avoid memory leaks. - * @param error If an error occurs and this is not NULL, it will be set to the - * error message. The returned string is dynamically allocated, and should be - * freed when you're done with it. + * @param error If an error occurs and this is not NULL, the error message will + * be written to the given buffer. + * @param err_size If the buffer is too small for the error message it will be + * truncated. * @return true on success, false on error. */ -bool tempered_exit( char **error ); +bool tempered_exit( char *error, size_t err_size ); /** Enumerate the TEMPer devices. * * This function returns a linked list of all the recognized TEMPer devices * attached to the system (excluding the ones that are ignored). * - * @param error If an error occurs and this is not NULL, it will be set to the - * error message. The returned string is dynamically allocated, and should be - * freed when you're done with it. + * @param error If an error occurs and this is not NULL, the error message will + * be written to the given buffer. + * @param err_size If the buffer is too small for the error message it will be + * truncated. * @return A pointer to the first device in the enumerated list, or NULL on * error. This list should be freed with tempered_free_device_list when you * are done with it. * If no devices were found, NULL is returned and the error remains unset. */ -struct tempered_device_list* tempered_enumerate( char **error ); +struct tempered_device_list* tempered_enumerate( char *error, size_t err_size ); /** Free the memory used by the given device list. * @@ -115,14 +119,15 @@ void tempered_free_device_list( struct tempered_device_list *list ); * The returned handle should be closed with tempered_close() when you are done * using the device. * @param list The device list entry that should be opened. - * @param error If an error occurs and this is not NULL, it will be set to the - * error message. The returned string is dynamically allocated, and should be - * freed when you're done with it. + * @param error If an error occurs and this is not NULL, the error message will + * be written to the given buffer. + * @param err_size If the buffer is too small for the error message it will be + * truncated. * @return The opened device, or NULL on error. * @see tempered_close() * @see tempered_enumerate() */ -tempered_device* tempered_open( struct tempered_device_list *list, char **error ); +tempered_device* tempered_open( struct tempered_device_list *list, char *error, size_t err_size ); /** Close an open device. * diff --git a/libtempered/type_hid/si7021.c b/libtempered/type_hid/si7021.c new file mode 100644 index 0000000..0bf1506 --- /dev/null +++ b/libtempered/type_hid/si7021.c @@ -0,0 +1,69 @@ +#include +#include + +#include "type-info.h" +#include "../tempered-internal.h" + +bool tempered_type_hid_get_temperature_si7021( + tempered_device *device, struct tempered_type_hid_sensor *sensor, + struct tempered_type_hid_query_result *group_data, float *tempC +) { + if ( + group_data->length <= sensor->temperature_high_byte_offset || + group_data->length <= sensor->temperature_low_byte_offset + ) { + tempered_set_error( + device, strdup( "Not enough data was read from the sensor." ) + ); + return false; + } + + // Convert from two separate data bytes to a single integer. + // The result should be an unsigned int between 0x0000 and 0xFFFF. + int low_byte_offset = sensor->temperature_low_byte_offset; + int high_byte_offset = sensor->temperature_high_byte_offset; + int temp = ( group_data->data[low_byte_offset] & 0xFF ) + + ( ( group_data->data[high_byte_offset] & 0xFF ) << 8 ) + ; + + // These formulas and values are based on the Silicon Labs Si7021 datasheet + *tempC = 175.72*temp/65536 - 46.85; + + return true; +} + +bool tempered_type_hid_get_humidity_si7021( + tempered_device *device, struct tempered_type_hid_sensor *sensor, + struct tempered_type_hid_query_result *group_data, float *rel_hum +) { + float tempC; + if ( + !tempered_type_hid_get_temperature_si7021( + device, sensor, group_data, &tempC + ) + ) { + return false; + } + + if ( + group_data->length <= sensor->humidity_high_byte_offset || + group_data->length <= sensor->humidity_low_byte_offset + ) + { + tempered_set_error( + device, strdup( "Not enough data was read from the sensor." ) + ); + return false; + } + + int low_byte_offset = sensor->humidity_low_byte_offset; + int high_byte_offset = sensor->humidity_high_byte_offset; + int rh = ( group_data->data[low_byte_offset] & 0xFF ) + + ( ( group_data->data[high_byte_offset] & 0xFF ) << 8 ) + ; + + // These formulas and values are based on the Silicon Labs Si7021 datasheet + *rel_hum = 125.*rh/65536 - 6; + + return true; +} diff --git a/libtempered/type_hid/si7021.h b/libtempered/type_hid/si7021.h new file mode 100644 index 0000000..7b5de85 --- /dev/null +++ b/libtempered/type_hid/si7021.h @@ -0,0 +1,20 @@ +#ifndef TEMPERED__TYPE_HID__SI7021_H +#define TEMPERED__TYPE_HID__SI7021_H + +#include + +#include "type-info.h" + +// These methods are for the Silicon Labs Si7021 chip. + +bool tempered_type_hid_get_temperature_si7021( + tempered_device *device, struct tempered_type_hid_sensor *sensor, + struct tempered_type_hid_query_result *group_data, float *tempC +); + +bool tempered_type_hid_get_humidity_si7021( + tempered_device *device, struct tempered_type_hid_sensor *sensor, + struct tempered_type_hid_query_result *group_data, float *rel_hum +); + +#endif diff --git a/libtempered/type_hid/simple.c b/libtempered/type_hid/simple.c new file mode 100644 index 0000000..7620660 --- /dev/null +++ b/libtempered/type_hid/simple.c @@ -0,0 +1,30 @@ +#include +#include + +#include "type-info.h" +#include "../tempered-internal.h" + +bool tempered_type_hid_get_temperature_simple( + tempered_device *device, struct tempered_type_hid_sensor *sensor, + struct tempered_type_hid_query_result *group_data, float *tempC +) { + if ( + group_data->length <= sensor->temperature_high_byte_offset || + group_data->length <= sensor->temperature_low_byte_offset + ) { + tempered_set_error( + device, strdup( "Not enough data was read from the sensor." ) + ); + return false; + } + + int low_byte_offset = sensor->temperature_low_byte_offset; + int high_byte_offset = sensor->temperature_high_byte_offset; + int temp = ( group_data->data[low_byte_offset] & 0xFF ) + + ( (signed char)group_data->data[high_byte_offset] << 8 ) + ; + + *tempC = temp / 100.0; + + return true; +} diff --git a/libtempered/type_hid/simple.h b/libtempered/type_hid/simple.h new file mode 100644 index 0000000..7d0b5ad --- /dev/null +++ b/libtempered/type_hid/simple.h @@ -0,0 +1,13 @@ +#ifndef TEMPERED__TYPE_HID__SIMPLE_H +#define TEMPERED__TYPE_HID__SIMPLE_H + +#include + +#include "type-info.h" + +bool tempered_type_hid_get_temperature_simple( + tempered_device *device, struct tempered_type_hid_sensor *sensor, + struct tempered_type_hid_query_result *group_data, float *tempC +); + +#endif diff --git a/utils/tempered.c b/utils/tempered.c index 65b72c5..689a4c8 100644 --- a/utils/tempered.c +++ b/utils/tempered.c @@ -245,15 +245,14 @@ void print_device( ); return; } - char *error = NULL; - tempered_device *device = tempered_open( dev, &error ); + char error[256]; + tempered_device *device = tempered_open( dev, error, sizeof(error) ); if ( device == NULL ) { fprintf( stderr, "%s: Could not open device: %s\n", dev->path, error ); - free( error ); return; } if ( !tempered_read_sensors( device ) ) @@ -282,20 +281,18 @@ int main( int argc, char *argv[] ) { return 1; } - char *error = NULL; - if ( !tempered_init( &error ) ) + char error[256]; + if ( !tempered_init( error, sizeof(error) ) ) { fprintf( stderr, "Failed to initialize libtempered: %s\n", error ); - free( error ); free_options( options ); return 1; } - struct tempered_device_list *list = tempered_enumerate( &error ); + struct tempered_device_list *list = tempered_enumerate( error, sizeof(error) ); if ( list == NULL ) { fprintf( stderr, "Failed to enumerate devices: %s\n", error ); - free( error ); } else { @@ -337,10 +334,9 @@ int main( int argc, char *argv[] ) tempered_free_device_list( list ); } - if ( !tempered_exit( &error ) ) + if ( !tempered_exit( error, sizeof(error) ) ) { fprintf( stderr, "%s\n", error ); - free( error ); free_options( options ); return 1; }