Prepare go-rewrite
This commit is contained in:
		
							parent
							
								
									c49ada88c3
								
							
						
					
					
						commit
						c515f55b8b
					
				
					 57 changed files with 103 additions and 42639 deletions
				
			
		| 
						 | 
				
			
			@ -1,94 +0,0 @@
 | 
			
		|||
cmake_minimum_required (VERSION 3.7)
 | 
			
		||||
project(controller
 | 
			
		||||
        VERSION 0.4.0
 | 
			
		||||
        LANGUAGES C)
 | 
			
		||||
 | 
			
		||||
add_executable(controller src/main.c)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(controller -lwiringPi)
 | 
			
		||||
target_link_libraries(controller -lwiringPiDev)
 | 
			
		||||
target_link_libraries(controller -lsqlite3)
 | 
			
		||||
target_link_libraries(controller -luuid)
 | 
			
		||||
 | 
			
		||||
option(WIRING_PI_DEBUG "Use WiringPi Debugging Tool (OFF)" OFF)
 | 
			
		||||
 | 
			
		||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}")
 | 
			
		||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D'__FILENAME__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR}/src/)/,,$(abspath $<))\"'")
 | 
			
		||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra")
 | 
			
		||||
 | 
			
		||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage")
 | 
			
		||||
 | 
			
		||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
file(GLOB_RECURSE ALL_SOURCE_FILES src/*.c)
 | 
			
		||||
 | 
			
		||||
if(WIRING_PI_DEBUG)
 | 
			
		||||
    message("Showing wiringPi calls as debug")
 | 
			
		||||
    add_definitions("-DWIRING_PI_DEBUG")
 | 
			
		||||
endif(WIRING_PI_DEBUG)
 | 
			
		||||
 | 
			
		||||
aux_source_directory(src/ SRC_DIR)
 | 
			
		||||
aux_source_directory(src/models MODELS_SRC)
 | 
			
		||||
aux_source_directory(src/helpers HELPERS_SRC)
 | 
			
		||||
aux_source_directory(src/handlers HANDLERS_SRC)
 | 
			
		||||
aux_source_directory(src/drivers DRIVERS_SRC)
 | 
			
		||||
aux_source_directory(src/runners RUNNERS_SRC)
 | 
			
		||||
aux_source_directory(vendor VENDOR_SRC)
 | 
			
		||||
 | 
			
		||||
add_dependencies(controller sql)
 | 
			
		||||
 | 
			
		||||
configure_file("version.h.in" "version.h" @ONLY)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
target_sources(controller PRIVATE ${SRC_DIR} ${MODELS_SRC} ${HELPERS_SRC} ${HANDLERS_SRC} ${DRIVERS_SRC} ${RUNNERS_SRC} ${VENDOR_SRC})
 | 
			
		||||
target_include_directories(controller PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
 | 
			
		||||
target_include_directories(controller PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor)
 | 
			
		||||
target_include_directories(controller PRIVATE ${CMAKE_BINARY_DIR})
 | 
			
		||||
 | 
			
		||||
add_custom_target(sql
 | 
			
		||||
    COMMAND ./compile_sql.sh
 | 
			
		||||
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_target(run
 | 
			
		||||
    COMMAND ${CMAKE_BINARY_DIR}/controller
 | 
			
		||||
    DEPENDS controller
 | 
			
		||||
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_custom_target(docs
 | 
			
		||||
    COMMAND doxygen
 | 
			
		||||
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
IF(CMAKE_BUILD_TYPE MATCHES Debug)
 | 
			
		||||
    message(STATUS "loading debug targets")
 | 
			
		||||
    add_custom_target(debug
 | 
			
		||||
        COMMAND gdb ${CMAKE_BINARY_DIR}/controller
 | 
			
		||||
        DEPENDS controller
 | 
			
		||||
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
 | 
			
		||||
    )
 | 
			
		||||
    add_custom_target(valgrind
 | 
			
		||||
        COMMAND valgrind -s ${CMAKE_BINARY_DIR}/controller
 | 
			
		||||
        DEPENDS controller
 | 
			
		||||
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
 | 
			
		||||
    )
 | 
			
		||||
    add_custom_target(valgrind-leak
 | 
			
		||||
        COMMAND valgrind --leak-check=full --show-leak-kinds=all ${CMAKE_BINARY_DIR}/controller
 | 
			
		||||
        DEPENDS controller
 | 
			
		||||
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    add_custom_target(
 | 
			
		||||
        clang-tidy
 | 
			
		||||
        COMMAND /usr/bin/clang-tidy
 | 
			
		||||
        ${ALL_SOURCE_FILES}
 | 
			
		||||
        --header-filter=${CMAKE_SOURCE_DIR}/include/*
 | 
			
		||||
        --
 | 
			
		||||
        -std=gnu99
 | 
			
		||||
        -I${CMAKE_SOURCE_DIR}/include
 | 
			
		||||
        -I${CMAKE_SOURCE_DIR}/vendor
 | 
			
		||||
        -I${CMAKE_BINARY_DIR}
 | 
			
		||||
    )
 | 
			
		||||
ENDIF(CMAKE_BUILD_TYPE MATCHES Debug)
 | 
			
		||||
							
								
								
									
										3
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
module emgauwa.app/controller
 | 
			
		||||
 | 
			
		||||
go 1.15
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_CLI_H
 | 
			
		||||
#define CONTROLLER_CLI_H
 | 
			
		||||
 | 
			
		||||
typedef struct cli
 | 
			
		||||
{
 | 
			
		||||
    const char *config_file;
 | 
			
		||||
    int demo_mode;
 | 
			
		||||
} cli_t;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
cli_parse(int argc, const char **argv, cli_t *cli);
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_CLI_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_COLORS_H
 | 
			
		||||
#define CONTROLLER_COLORS_H
 | 
			
		||||
 | 
			
		||||
#define COLOR_RED "\033[0;31m"
 | 
			
		||||
#define COLORB_RED "\033[1;31m"
 | 
			
		||||
#define COLOR_GREEN "\033[0;32m"
 | 
			
		||||
#define COLORB_GREEN "\033[1;32m"
 | 
			
		||||
#define COLOR_YELLOW "\033[0;33m"
 | 
			
		||||
#define COLORB_YELLOW "\033[1;33m"
 | 
			
		||||
#define COLOR_BLUE "\033[0;34m"
 | 
			
		||||
#define COLORB_BLUE "\033[1;34m"
 | 
			
		||||
#define COLOR_MAGENTA "\033[0;35m"
 | 
			
		||||
#define COLORB_MAGENTA "\033[1;35m"
 | 
			
		||||
#define COLOR_CYAN "\033[0;36m"
 | 
			
		||||
#define COLORB_CYAN "\033[1;36m"
 | 
			
		||||
#define COLOR_NONE "\033[0m"
 | 
			
		||||
 | 
			
		||||
#endif //CONTROLLER_COLORS_H
 | 
			
		||||
| 
						 | 
				
			
			@ -1,66 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_CONFIG_H
 | 
			
		||||
#define CONTROLLER_CONFIG_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <toml.h>
 | 
			
		||||
 | 
			
		||||
#include <constants.h>
 | 
			
		||||
#include <enums.h>
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
    uint8_t pin;
 | 
			
		||||
    int inverted;
 | 
			
		||||
    int init;
 | 
			
		||||
    relay_driver_t driver;
 | 
			
		||||
    uint8_t pulse_duration;
 | 
			
		||||
} config_relay_t;
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
    char *name;
 | 
			
		||||
    char *database;
 | 
			
		||||
    char *user;
 | 
			
		||||
    char *group;
 | 
			
		||||
    char *mqtt_host;
 | 
			
		||||
    char *include;
 | 
			
		||||
    int relays_init;
 | 
			
		||||
    uint8_t relay_count;
 | 
			
		||||
 | 
			
		||||
    struct
 | 
			
		||||
    {
 | 
			
		||||
        int level;
 | 
			
		||||
        FILE *file;
 | 
			
		||||
    } logging;
 | 
			
		||||
 | 
			
		||||
    struct
 | 
			
		||||
    {
 | 
			
		||||
        uint16_t discovery;
 | 
			
		||||
        uint16_t mqtt;
 | 
			
		||||
    } ports;
 | 
			
		||||
 | 
			
		||||
    config_relay_t *relay_configs;
 | 
			
		||||
} config_t;
 | 
			
		||||
 | 
			
		||||
extern config_t *global_config;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_init();
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_free();
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_load_string(char **holder, const char *value);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_load(config_t *config, const char *cli_config_file);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_load_file(config_t *config, const char *file_name);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_load_directory(config_t *config, const char *directory_name);
 | 
			
		||||
 | 
			
		||||
#endif //CONTROLLER_CONFIG_H
 | 
			
		||||
| 
						 | 
				
			
			@ -1,17 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_CONNECTIONS_H
 | 
			
		||||
#define CONTROLLER_CONNECTIONS_H
 | 
			
		||||
 | 
			
		||||
#include <mongoose.h>
 | 
			
		||||
 | 
			
		||||
struct mg_connection*
 | 
			
		||||
connection_discovery_bind(struct mg_mgr *mgr);
 | 
			
		||||
 | 
			
		||||
struct mg_connection*
 | 
			
		||||
connection_command_bind(struct mg_mgr *mgr);
 | 
			
		||||
 | 
			
		||||
struct mg_connection*
 | 
			
		||||
connection_mqtt_connect(struct mg_mgr *mgr);
 | 
			
		||||
 | 
			
		||||
extern struct mg_connection *global_connection_mqtt;
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_CONNECTIONS_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,32 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_CONTANTS_H
 | 
			
		||||
#define CONTROLLER_CONTANTS_H
 | 
			
		||||
 | 
			
		||||
#define SECONDS_PER_DAY 86400 // 60 * 60 * 24
 | 
			
		||||
 | 
			
		||||
#define SECONDS_PER_MINUTE 60
 | 
			
		||||
 | 
			
		||||
#define POLL_FDS_COUNT 2
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Limit the maximum length of a controller/relay/etc name
 | 
			
		||||
 *
 | 
			
		||||
 * The NULL terminator is not included. Arrays of length #MAX_NAME_LENGTH + 1 are required.
 | 
			
		||||
 */
 | 
			
		||||
#define MAX_NAME_LENGTH 128
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief How many milli seconds to wait until poll timeout in main loop
 | 
			
		||||
 */
 | 
			
		||||
#define ACCEPT_TIMEOUT_MSECONDS 1000
 | 
			
		||||
 | 
			
		||||
#define PIFACE_GPIO_BASE 200
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_CONTROLLER_NAME "emgauwa-controller"
 | 
			
		||||
#define DEFAULT_CONFIG_PATH "emgauwa-controller.conf"
 | 
			
		||||
#define DEFAULT_GLOBAL_CONFIG_PATH "/etc/emgauwa/controller.conf"
 | 
			
		||||
#define DEFAULT_DATABASE_PATH "emgauwa-controller.sqlite"
 | 
			
		||||
#define DEFAULT_DISCOVERY_PORT 4421
 | 
			
		||||
#define DEFAULT_MQTT_PORT 1885
 | 
			
		||||
#define DEFAULT_MQTT_HOST "127.0.0.1"
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_CONTANTS_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,40 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_DATABASE_H
 | 
			
		||||
#define CONTROLLER_DATABASE_H
 | 
			
		||||
 | 
			
		||||
#include <sqlite3.h>
 | 
			
		||||
 | 
			
		||||
extern sqlite3 *global_database;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_init();
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_free();
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_migrate();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
database_transaction_begin();
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_transaction_commit();
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_transaction_rollback();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
database_helper_get_id(sqlite3_stmt *stmt);
 | 
			
		||||
 | 
			
		||||
int*
 | 
			
		||||
database_helper_get_ids(sqlite3_stmt *stmt);
 | 
			
		||||
 | 
			
		||||
char*
 | 
			
		||||
database_helper_get_string(sqlite3_stmt *stmt);
 | 
			
		||||
 | 
			
		||||
char**
 | 
			
		||||
database_helper_get_strings(sqlite3_stmt *stmt);
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_DATABASE_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_DRIVERS_H
 | 
			
		||||
#define CONTROLLER_DRIVERS_H
 | 
			
		||||
 | 
			
		||||
#include <models/relay.h>
 | 
			
		||||
#include <enums.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
driver_piface_set(int pin, int value);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
driver_gpio_set(int pin, int value);
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_DRIVERS_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,25 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_ENUMS_H
 | 
			
		||||
#define CONTROLLER_ENUMS_H
 | 
			
		||||
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    POLL_FDS_DISCOVERY,
 | 
			
		||||
    POLL_FDS_COMMAND
 | 
			
		||||
} poll_fds_t;
 | 
			
		||||
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    DISCOVERY_MAPPING_ID = 0,
 | 
			
		||||
    DISCOVERY_MAPPING_NAME = 1,
 | 
			
		||||
    DISCOVERY_MAPPING_COMMAND_PORT = 2,
 | 
			
		||||
    DISCOVERY_MAPPING_RELAY_COUNT = 3,
 | 
			
		||||
} discovery_mapping_t;
 | 
			
		||||
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    RELAY_DRIVER_NONE,
 | 
			
		||||
    RELAY_DRIVER_GPIO,
 | 
			
		||||
    RELAY_DRIVER_PIFACE,
 | 
			
		||||
} relay_driver_t;
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_ENUMS_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,32 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_HANDLERS_H
 | 
			
		||||
#define CONTROLLER_HANDLERS_H
 | 
			
		||||
 | 
			
		||||
#include <mongoose.h>
 | 
			
		||||
 | 
			
		||||
#include <models/controller.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Handle the command processing
 | 
			
		||||
 *
 | 
			
		||||
 * @param fd File descriptor to receive initial data from
 | 
			
		||||
 * @param controller Controller to use for answering command
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
handler_command(struct mg_connection *c, int ev, void *ev_data);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Handle the discovery processing
 | 
			
		||||
 *
 | 
			
		||||
 * @param fd File descriptor to receive initial data from
 | 
			
		||||
 * @param controller Controller to use for answering discovery
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
handler_discovery(struct mg_connection *c, int ev, void *ev_data);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
handler_mqtt(struct mg_connection *c, int ev, void *ev_data);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
handler_loop(struct mg_connection *c_mqtt);
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_HANDLERS_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,34 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_HELPERS_H
 | 
			
		||||
#define CONTROLLER_HELPERS_H
 | 
			
		||||
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <config.h>
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_connect_tcp_server(char* host, uint16_t port);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_bind_tcp_server(char* addr, uint16_t port, int max_client_backlog);
 | 
			
		||||
 | 
			
		||||
uint16_t
 | 
			
		||||
helper_get_port(int sock);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Open socket for discovery
 | 
			
		||||
 *
 | 
			
		||||
 * Will exit program when unable to open socket.
 | 
			
		||||
 *
 | 
			
		||||
 * @param discovery_port Port number to listen on for discovery broadcasts
 | 
			
		||||
 *
 | 
			
		||||
 * @return Open socket to accept discovery broadcasts on
 | 
			
		||||
 */
 | 
			
		||||
int
 | 
			
		||||
helper_open_discovery_socket(uint16_t discovery_port);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_get_weekday(const struct tm *time_struct);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_drop_privileges();
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_HELPERS_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,22 +0,0 @@
 | 
			
		|||
#ifndef EMGAUWA_LOGGER_H
 | 
			
		||||
#define EMGAUWA_LOGGER_H
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <syslog.h>
 | 
			
		||||
 | 
			
		||||
#include <colors.h>
 | 
			
		||||
#include <config.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...);
 | 
			
		||||
 | 
			
		||||
#define LOGGER_DEBUG(...)   logger_log(LOG_DEBUG  , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 | 
			
		||||
#define LOGGER_INFO(...)    logger_log(LOG_INFO   , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 | 
			
		||||
#define LOGGER_NOTICE(...)  logger_log(LOG_NOTICE , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 | 
			
		||||
#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 | 
			
		||||
#define LOGGER_ERR(...)     logger_log(LOG_ERR    , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 | 
			
		||||
#define LOGGER_CRIT(...)    logger_log(LOG_CRIT   , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 | 
			
		||||
#define LOGGER_EMERG(...)   logger_log(LOG_EMERG  , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 | 
			
		||||
 | 
			
		||||
#endif //EMGAUWA_LOGGER_H
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_MACROS_H
 | 
			
		||||
#define CONTROLLER_MACROS_H
 | 
			
		||||
 | 
			
		||||
#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_MACROS_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,107 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_CONTROLLER_H
 | 
			
		||||
#define CONTROLLER_CONTROLLER_H
 | 
			
		||||
 | 
			
		||||
#include <uuid/uuid.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <lmdb.h>
 | 
			
		||||
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <models/relay.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Information about this controller
 | 
			
		||||
 */
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
    int id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief A unique UUID for this controller
 | 
			
		||||
     */
 | 
			
		||||
    uuid_t uid;
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief The name of this controller
 | 
			
		||||
     *
 | 
			
		||||
     * Includes a \0 terminator.
 | 
			
		||||
     */
 | 
			
		||||
    char name[MAX_NAME_LENGTH + 1];
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief The command port the controller was bound to
 | 
			
		||||
     */
 | 
			
		||||
    uint16_t command_port;
 | 
			
		||||
 | 
			
		||||
    relay_t **relays;
 | 
			
		||||
 | 
			
		||||
} controller_t;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Key to save controller information in database
 | 
			
		||||
 */
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    DB_KEY_CONTROLLER_ID = 0,
 | 
			
		||||
    DB_KEY_CONTROLLER_NAME = 1,
 | 
			
		||||
    DB_KEY_CONTROLLER_COMMAND_PORT = 2,
 | 
			
		||||
    DB_KEY_CONTROLLER_DISCOVERY_PORT = 3,
 | 
			
		||||
} db_key_controller_e;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Create a new instance of controller
 | 
			
		||||
 *
 | 
			
		||||
 * This should not fail. The instance will be created with malloc and genric default values
 | 
			
		||||
 *
 | 
			
		||||
 * @return A new instance of #controller
 | 
			
		||||
 */
 | 
			
		||||
controller_t*
 | 
			
		||||
controller_create(void);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Load a controller for database or create a new one
 | 
			
		||||
 *
 | 
			
		||||
 * Will return NULL when transaction can't start.
 | 
			
		||||
 *
 | 
			
		||||
 * @param mdb_env An opened MDB_env to load from
 | 
			
		||||
 *
 | 
			
		||||
 * @return A loaded or new instance of controller or NULL on database error
 | 
			
		||||
 */
 | 
			
		||||
controller_t*
 | 
			
		||||
controller_load();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Save a controller to the database
 | 
			
		||||
 *
 | 
			
		||||
 * @param controller Instance of a controller
 | 
			
		||||
 * @param mdb_env Already created MDB_env
 | 
			
		||||
 *
 | 
			
		||||
 * @return Indicator to show success (0) or failure (!0)
 | 
			
		||||
 */
 | 
			
		||||
int
 | 
			
		||||
controller_save();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Sets a name to a controller.
 | 
			
		||||
 * This function won't perform any checks (e.g. no NULL checks)
 | 
			
		||||
 *
 | 
			
		||||
 * @param controller Set the name to this controller
 | 
			
		||||
 * @param name Name to be set
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
controller_set_name(controller_t *controller, const char *name);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
controller_free(controller_t *controller);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Debug an instance of #controller
 | 
			
		||||
 *
 | 
			
		||||
 * Will use #LOG_DEBUG to log. So log will be depending on #LOG_LEVEL
 | 
			
		||||
 *
 | 
			
		||||
 * @param cntrlr #controller to debug
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
controller_debug(controller_t *controller);
 | 
			
		||||
 | 
			
		||||
extern controller_t *global_controller;
 | 
			
		||||
 | 
			
		||||
#endif //CONTROLLER_CONTROLLER_H
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H
 | 
			
		||||
#define CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
junction_relay_schedule_remove_for_relay(int relay_id);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids);
 | 
			
		||||
 | 
			
		||||
int*
 | 
			
		||||
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id);
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_PERIOD_H
 | 
			
		||||
#define CONTROLLER_PERIOD_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
    uint16_t start;
 | 
			
		||||
    uint16_t end;
 | 
			
		||||
} period_t;
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
period_includes_time(period_t period, struct tm *time_struct);
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_PERIOD_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,54 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_RELAY_H
 | 
			
		||||
#define CONTROLLER_RELAY_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <lmdb.h>
 | 
			
		||||
 | 
			
		||||
#include <constants.h>
 | 
			
		||||
#include <models/schedule.h>
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
    int id;
 | 
			
		||||
    uint8_t number;
 | 
			
		||||
    int is_on;
 | 
			
		||||
    int pulse_timer;
 | 
			
		||||
    int sent_to_broker;
 | 
			
		||||
    char name[MAX_NAME_LENGTH + 1];
 | 
			
		||||
    schedule_t *schedules[7];
 | 
			
		||||
} relay_t;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Key to save relay information in database
 | 
			
		||||
 */
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    DB_KEY_RELAY_NAME = 0,
 | 
			
		||||
} db_key_relay_e;
 | 
			
		||||
 | 
			
		||||
relay_t*
 | 
			
		||||
relay_create(uint8_t number);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
relay_set_name(relay_t *relay, const char *name);
 | 
			
		||||
 | 
			
		||||
relay_t*
 | 
			
		||||
relay_load(uint8_t number);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
relay_save(relay_t *relay);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
relay_reload_schedules(relay_t *relay);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
relay_is_on_schedule(relay_t *relay, struct tm *time_struct);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
relay_free(relay_t *relay);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
relay_debug(relay_t *relay);
 | 
			
		||||
 | 
			
		||||
#endif //CONTROLLER_RELAY_H
 | 
			
		||||
| 
						 | 
				
			
			@ -1,58 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_SCHEDULE_H
 | 
			
		||||
#define CONTROLLER_SCHEDULE_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <uuid/uuid.h>
 | 
			
		||||
#include <lmdb.h>
 | 
			
		||||
 | 
			
		||||
#include <models/period.h>
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
    int id;
 | 
			
		||||
    uuid_t uid;
 | 
			
		||||
    uint8_t weekday;
 | 
			
		||||
    uint16_t periods_count;
 | 
			
		||||
    period_t *periods;
 | 
			
		||||
} schedule_t;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Key to save schedule information in database
 | 
			
		||||
 */
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    DB_KEY_SCHEDULE_ID = 0,
 | 
			
		||||
    DB_KEY_SCHEDULE_PERIODS = 1,
 | 
			
		||||
} db_key_schedule_e;
 | 
			
		||||
 | 
			
		||||
schedule_t*
 | 
			
		||||
schedule_create(uuid_t uid, uint16_t length, uint16_t *periods_blob);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
schedule_save(schedule_t *schedule);
 | 
			
		||||
 | 
			
		||||
schedule_t*
 | 
			
		||||
schedule_get_by_uid(uuid_t uid);
 | 
			
		||||
 | 
			
		||||
schedule_t**
 | 
			
		||||
schedule_get_relay_weekdays(int relay_id);
 | 
			
		||||
 | 
			
		||||
uint16_t*
 | 
			
		||||
schedule_periods_to_blob(schedule_t *schedule);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
schedule_free(schedule_t *schedule);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
schedule_free_list(schedule_t **schedules_list);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
schedule_debug(schedule_t *schedule);
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
schedule_uid_parse(const char *uid_str, uuid_t result);
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
schedule_uid_unparse(const uuid_t uid, char *result);
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_SCHEDULE_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_RUNNERS_H
 | 
			
		||||
#define CONTROLLER_RUNNERS_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <models/controller.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
runner_test();
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_RUNNERS_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
#ifndef CONTROLLER_WIRING_DEBUG_H
 | 
			
		||||
#define CONTROLLER_WIRING_DEBUG_H
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
 | 
			
		||||
#ifdef WIRING_PI_DEBUG
 | 
			
		||||
    #define LOG_WIRING_PI LOGGER_DEBUG
 | 
			
		||||
    #define wiringPiSetup() LOG_WIRING_PI("wiringPi wiringPiSetup()\n")
 | 
			
		||||
    #define wiringPiSetupSys() LOG_WIRING_PI("wiringPi wiringPiSetupSys()\n")
 | 
			
		||||
    #define pinMode(x,y) LOG_WIRING_PI("wiringPi pinMode(%d, %d)\n", x, y)
 | 
			
		||||
    #define digitalWrite(x,y) LOG_WIRING_PI("wiringPi digitalWrite(%d, %d)\n", x, y)
 | 
			
		||||
 | 
			
		||||
    #define piFaceSetup(x) LOG_WIRING_PI("wiringPi piFaceSetup(%d)\n", x)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif /* CONTROLLER_WIRING_DEBUG_H */
 | 
			
		||||
							
								
								
									
										100
									
								
								main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,100 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
#cgo CFLAGS: -g -Wall
 | 
			
		||||
#cgo LDFLAGS: -lwiringPi -lwiringPiDev
 | 
			
		||||
#include <wiringPi.h>
 | 
			
		||||
*/
 | 
			
		||||
import "C"
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-playground/validator/v10"
 | 
			
		||||
	"github.com/spf13/viper"
 | 
			
		||||
 | 
			
		||||
	"emgauwa.app/emgauwa-core/models"
 | 
			
		||||
	"emgauwa.app/emgauwa-core/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type (
 | 
			
		||||
	EmgauwaValidator struct {
 | 
			
		||||
		validator *validator.Validate
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (cv *EmgauwaValidator) Validate(i interface{}) error {
 | 
			
		||||
	return cv.validator.Struct(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SkipperSwagger(c echo.Context) bool {
 | 
			
		||||
	if strings.Contains(c.Request().URL.Path, "swagger") {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	utils.ConfigInit()
 | 
			
		||||
	utils.LoggerInit()
 | 
			
		||||
	models.DatabaseInit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// @title Emgauwa API
 | 
			
		||||
// @version 1.0
 | 
			
		||||
// @description This is a sample server server.
 | 
			
		||||
// @termsOfService http://swagger.io/terms/
 | 
			
		||||
 | 
			
		||||
// @contact.name Tobias Reisinger
 | 
			
		||||
// @contact.url https://serguzim.me
 | 
			
		||||
// @contact.email tobias@msrg.cc
 | 
			
		||||
func main() {
 | 
			
		||||
 | 
			
		||||
	C.wiringPiSetup()
 | 
			
		||||
 | 
			
		||||
	e := echo.New()
 | 
			
		||||
 | 
			
		||||
	v := validator.New()
 | 
			
		||||
	v.RegisterValidation("hhmmformat", utils.ValidatorsHHMMFormat)
 | 
			
		||||
	e.Validator = &EmgauwaValidator{validator: v}
 | 
			
		||||
 | 
			
		||||
	e.Pre(middleware.RemoveTrailingSlash())
 | 
			
		||||
 | 
			
		||||
	e.Use(middleware.CORS())
 | 
			
		||||
	e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
 | 
			
		||||
		Skipper: SkipperSwagger,
 | 
			
		||||
	}))
 | 
			
		||||
	e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
 | 
			
		||||
		Format: "${time_rfc3339} [${method}] ${uri} > ${status} (${latency_human})\n",
 | 
			
		||||
	}))
 | 
			
		||||
	e.Use(middleware.Recover())
 | 
			
		||||
	e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
 | 
			
		||||
		Skipper: SkipperSwagger,
 | 
			
		||||
		Root:    viper.GetString("content-dir"),
 | 
			
		||||
		HTML5:   true,
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	e.GET("api/swagger/*", echoSwagger.WrapHandler)
 | 
			
		||||
	e.GET("api/swagger", func(c echo.Context) error {
 | 
			
		||||
		return c.Redirect(http.StatusMovedPermanently, "/api/swagger/index.html")
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	r_api_v1 := e.Group("api/v1")
 | 
			
		||||
	{
 | 
			
		||||
 | 
			
		||||
		r_api_v1.GET("/schedules", api_v1.Schedules_GET)
 | 
			
		||||
		r_api_v1.POST("/schedules", api_v1.Schedules_POST)
 | 
			
		||||
		r_api_v1.GET("/schedules/:schedule_uid", api_v1.Schedules_UID_GET)
 | 
			
		||||
		r_api_v1.PUT("/schedules/:schedule_uid", api_v1.Schedules_UID_PUT)
 | 
			
		||||
		r_api_v1.DELETE("/schedules/:schedule_uid", api_v1.Schedules_UID_DELETE)
 | 
			
		||||
		r_api_v1.GET("/schedules/tag/:tag", api_v1.Schedules_Tag_TAG_GET)
 | 
			
		||||
		r_api_v1.POST("/schedules/list", api_v1.Schedules_List_POST)
 | 
			
		||||
 | 
			
		||||
		r_api_v1.GET("/controllers", api_v1.Controllers_GET)
 | 
			
		||||
 | 
			
		||||
		r_api_v1.GET("/tags", api_v1.Tags_GET)
 | 
			
		||||
		r_api_v1.GET("/ws/relays", api_v1.WS_Relays_GET)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	e.Start(viper.GetString("bind.http"))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,49 +0,0 @@
 | 
			
		|||
create table controllers
 | 
			
		||||
(
 | 
			
		||||
    id      INTEGER
 | 
			
		||||
            PRIMARY KEY
 | 
			
		||||
            AUTOINCREMENT,
 | 
			
		||||
    uid     BLOB
 | 
			
		||||
            NOT NULL
 | 
			
		||||
            UNIQUE,
 | 
			
		||||
    name            VARCHAR(128),
 | 
			
		||||
    command_port    INTEGER
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
create table relays
 | 
			
		||||
(
 | 
			
		||||
    id      INTEGER
 | 
			
		||||
            PRIMARY KEY
 | 
			
		||||
            AUTOINCREMENT,
 | 
			
		||||
    number  INTEGER
 | 
			
		||||
            NOT NULL,
 | 
			
		||||
    name    VARCHAR(128)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
create table schedules
 | 
			
		||||
(
 | 
			
		||||
    id      INTEGER
 | 
			
		||||
            PRIMARY KEY
 | 
			
		||||
            AUTOINCREMENT,
 | 
			
		||||
    uid     BLOB
 | 
			
		||||
            NOT NULL
 | 
			
		||||
            UNIQUE,
 | 
			
		||||
    name    VARCHAR(128),
 | 
			
		||||
    periods BLOB
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
create table junction_relay_schedule
 | 
			
		||||
(
 | 
			
		||||
    weekday         SMALLINT
 | 
			
		||||
                    NOT NULL,
 | 
			
		||||
    relay_id        INTEGER
 | 
			
		||||
                    REFERENCES relays (id)
 | 
			
		||||
                    ON DELETE CASCADE,
 | 
			
		||||
    schedule_id     INTEGER
 | 
			
		||||
                    DEFAULT 1
 | 
			
		||||
                    REFERENCES schedules (id)
 | 
			
		||||
                    ON DELETE SET DEFAULT
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00');
 | 
			
		||||
INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000',  'on', x'010000009F05');
 | 
			
		||||
							
								
								
									
										49
									
								
								src/cli.c
									
										
									
									
									
								
							
							
						
						
									
										49
									
								
								src/cli.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,49 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
#include <argparse.h>
 | 
			
		||||
 | 
			
		||||
#include <cli.h>
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
#include <version.h>
 | 
			
		||||
 | 
			
		||||
static const char *const usage[] = {
 | 
			
		||||
    "controller [options]",
 | 
			
		||||
    NULL,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
cli_parse(int argc, const char **argv, cli_t *cli)
 | 
			
		||||
{
 | 
			
		||||
    cli->config_file = NULL;
 | 
			
		||||
    cli->demo_mode = 0;
 | 
			
		||||
 | 
			
		||||
    int version = 0;
 | 
			
		||||
    struct argparse_option options[] =
 | 
			
		||||
    {
 | 
			
		||||
        OPT_HELP(),
 | 
			
		||||
        OPT_GROUP("Basic options"),
 | 
			
		||||
        OPT_STRING('c', "config", &cli->config_file, "path to config file", NULL, 0, OPT_NONEG),
 | 
			
		||||
        OPT_BOOLEAN('d', "demo", &cli->demo_mode, "demo mode", NULL, 0, OPT_NONEG),
 | 
			
		||||
        OPT_BOOLEAN('v', "version", &version, "print version", NULL, 0, OPT_NONEG),
 | 
			
		||||
        OPT_END(),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct argparse argparse;
 | 
			
		||||
    argparse_init(&argparse, options, usage, 0);
 | 
			
		||||
    argparse_describe(
 | 
			
		||||
            &argparse,
 | 
			
		||||
            "\nA brief description of what the program does and how it works.",
 | 
			
		||||
            "\nAdditional description of the program after the description of the arguments."
 | 
			
		||||
    );
 | 
			
		||||
    argparse_parse(&argparse, argc, argv);
 | 
			
		||||
 | 
			
		||||
    if(version)
 | 
			
		||||
    {
 | 
			
		||||
        printf("%s\n", EMGAUWA_CONTROLLER_VERSION);
 | 
			
		||||
        exit(0);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										442
									
								
								src/config.c
									
										
									
									
									
								
							
							
						
						
									
										442
									
								
								src/config.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,442 +0,0 @@
 | 
			
		|||
#include <ctype.h>
 | 
			
		||||
#include <dirent.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <constants.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
 | 
			
		||||
config_t *global_config;
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
config_load_log_level(config_t *config, char *value)
 | 
			
		||||
{
 | 
			
		||||
    if(strcmp(value, "debug") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        setlogmask(LOG_UPTO(LOG_DEBUG));
 | 
			
		||||
        config->logging.level = LOG_DEBUG;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if(strcmp(value, "info") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        setlogmask(LOG_UPTO(LOG_INFO));
 | 
			
		||||
        config->logging.level = LOG_INFO;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if(strcmp(value, "notice") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        setlogmask(LOG_UPTO(LOG_NOTICE));
 | 
			
		||||
        config->logging.level = LOG_NOTICE;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if(strcmp(value, "warning") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        setlogmask(LOG_UPTO(LOG_WARNING));
 | 
			
		||||
        config->logging.level = LOG_WARNING;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if(strcmp(value, "err") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        setlogmask(LOG_UPTO(LOG_ERR));
 | 
			
		||||
        config->logging.level = LOG_ERR;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if(strcmp(value, "crit") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        setlogmask(LOG_UPTO(LOG_CRIT));
 | 
			
		||||
        config->logging.level = LOG_CRIT;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if(strcmp(value, "emerg") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        setlogmask(LOG_UPTO(LOG_EMERG));
 | 
			
		||||
        config->logging.level = LOG_EMERG;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    LOGGER_WARNING("invalid log-level '%s'\n", value);
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
config_load_log_file(config_t *config, char *value)
 | 
			
		||||
{
 | 
			
		||||
    if(strcmp(value, "stdout") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        config->logging.file = stdout;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if(strcmp(value, "stderr") == 0)
 | 
			
		||||
    {
 | 
			
		||||
        config->logging.file = stderr;
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    config->logging.file = fopen(value, "a+");
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
config_init_relay_configs(config_t *config)
 | 
			
		||||
{
 | 
			
		||||
    config->relay_configs = malloc(sizeof(config_relay_t) * config->relay_count);
 | 
			
		||||
    for(uint8_t i = 0; i < config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        config->relay_configs[i].driver = RELAY_DRIVER_NONE;
 | 
			
		||||
        config->relay_configs[i].inverted = 0;
 | 
			
		||||
        config->relay_configs[i].init = -1;
 | 
			
		||||
        config->relay_configs[i].pin = 0;
 | 
			
		||||
        config->relay_configs[i].pulse_duration = 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
config_load_section_controller(config_t *config, toml_table_t* controller)
 | 
			
		||||
{
 | 
			
		||||
    toml_datum_t config_entry;
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(controller, "name");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_string(&config->name, config_entry.u.s);
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(controller, "database");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_string(&config->database, config_entry.u.s);
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(controller, "user");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_string(&config->user, config_entry.u.s);
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(controller, "group");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_string(&config->group, config_entry.u.s);
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(controller, "mqtt-host");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_string(&config->mqtt_host, config_entry.u.s);
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_int_in(controller, "relays-init");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config->relays_init = config_entry.u.i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(controller, "include");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_string(&config->include, config_entry.u.s);
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
config_load_section_logging(config_t *config, toml_table_t* logging)
 | 
			
		||||
{
 | 
			
		||||
    toml_datum_t config_entry;
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(logging, "level");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_log_level(config, config_entry.u.s);
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(logging, "file");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_log_file(config, config_entry.u.s);
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
config_load_section_ports(config_t *config, toml_table_t* ports)
 | 
			
		||||
{
 | 
			
		||||
    toml_datum_t config_entry;
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_int_in(ports, "discovery");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config->ports.discovery = config_entry.u.i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_int_in(ports, "mqtt");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config->ports.mqtt = config_entry.u.i;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
config_load_section_relay(config_t *config, toml_table_t* relay, int relay_num)
 | 
			
		||||
{
 | 
			
		||||
    toml_datum_t config_entry;
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_int_in(relay, "pin");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config->relay_configs[relay_num].pin = config_entry.u.i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_int_in(relay, "inverted");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config->relay_configs[relay_num].inverted = config_entry.u.i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_int_in(relay, "init");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config->relay_configs[relay_num].init = config_entry.u.i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_int_in(relay, "pulse-duration");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        config->relay_configs[relay_num].pulse_duration = config_entry.u.i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config_entry = toml_string_in(relay, "driver");
 | 
			
		||||
    if(config_entry.ok)
 | 
			
		||||
    {
 | 
			
		||||
        for(int i = 0; config_entry.u.s[i] != '\0'; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            config_entry.u.s[i] = tolower(config_entry.u.s[i]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(strcmp(config_entry.u.s, "gpio") == 0)
 | 
			
		||||
        {
 | 
			
		||||
            config->relay_configs[relay_num].driver = RELAY_DRIVER_GPIO;
 | 
			
		||||
        }
 | 
			
		||||
        else if(strcmp(config_entry.u.s, "piface") == 0)
 | 
			
		||||
        {
 | 
			
		||||
            config->relay_configs[relay_num].driver = RELAY_DRIVER_PIFACE;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_WARNING("invalid driver '%s' in section for relay %d\n", config_entry.u.s, relay_num);
 | 
			
		||||
        }
 | 
			
		||||
        free(config_entry.u.s);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_init()
 | 
			
		||||
{
 | 
			
		||||
    global_config = calloc(1, sizeof(config_t));
 | 
			
		||||
 | 
			
		||||
    config_load_string(&global_config->name, DEFAULT_CONTROLLER_NAME);
 | 
			
		||||
 | 
			
		||||
    config_load_string(&global_config->database, DEFAULT_DATABASE_PATH);
 | 
			
		||||
 | 
			
		||||
    config_load_string(&global_config->mqtt_host, DEFAULT_MQTT_HOST);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    global_config->user = NULL;
 | 
			
		||||
    global_config->group = NULL;
 | 
			
		||||
 | 
			
		||||
    global_config->include = NULL;
 | 
			
		||||
 | 
			
		||||
    global_config->relays_init = 0;
 | 
			
		||||
    global_config->relay_count = 0;
 | 
			
		||||
 | 
			
		||||
    config_load_string(&global_config->mqtt_host, "127.0.0.1");
 | 
			
		||||
 | 
			
		||||
    global_config->ports.mqtt = DEFAULT_MQTT_PORT;
 | 
			
		||||
    global_config->ports.discovery = DEFAULT_DISCOVERY_PORT;
 | 
			
		||||
 | 
			
		||||
    global_config->logging.level = LOG_DEBUG;
 | 
			
		||||
    global_config->logging.file = stdout;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_free()
 | 
			
		||||
{
 | 
			
		||||
    free(global_config->name);
 | 
			
		||||
    free(global_config->database);
 | 
			
		||||
    free(global_config->user);
 | 
			
		||||
    free(global_config->group);
 | 
			
		||||
    free(global_config->mqtt_host);
 | 
			
		||||
    free(global_config->include);
 | 
			
		||||
 | 
			
		||||
    free(global_config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_load_string(char **holder, const char *value)
 | 
			
		||||
{
 | 
			
		||||
    if(*holder)
 | 
			
		||||
    {
 | 
			
		||||
        free(*holder);
 | 
			
		||||
    }
 | 
			
		||||
    size_t value_len = strlen(value);
 | 
			
		||||
 | 
			
		||||
    char *new_holder = malloc(sizeof(char) * (value_len + 1));
 | 
			
		||||
    strcpy(new_holder, value);
 | 
			
		||||
    new_holder[value_len] = '\0';
 | 
			
		||||
 | 
			
		||||
    *holder = new_holder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
config_try_file(const char *path)
 | 
			
		||||
{
 | 
			
		||||
    if(access(path, F_OK) != 0)
 | 
			
		||||
    {
 | 
			
		||||
        return 1; 
 | 
			
		||||
    }
 | 
			
		||||
    if(access(path, R_OK) != 0)
 | 
			
		||||
    {
 | 
			
		||||
        return 1; 
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_load(config_t *config, const char *cli_config_file)
 | 
			
		||||
{
 | 
			
		||||
    if(cli_config_file)
 | 
			
		||||
    {
 | 
			
		||||
        if(config_try_file(cli_config_file) == 0)
 | 
			
		||||
        {
 | 
			
		||||
            config_load_file(config, cli_config_file);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        LOGGER_CRIT("unable to open the passed config file '%s'\n", cli_config_file);
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    if(config_try_file(DEFAULT_CONFIG_PATH) == 0)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_file(config, DEFAULT_CONFIG_PATH);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if(config_try_file(DEFAULT_GLOBAL_CONFIG_PATH) == 0)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_file(config, DEFAULT_GLOBAL_CONFIG_PATH);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_load_file(config_t *config, const char *file_name)
 | 
			
		||||
{
 | 
			
		||||
    FILE *fp;
 | 
			
		||||
    toml_table_t* config_toml;
 | 
			
		||||
    char errbuf[256];
 | 
			
		||||
 | 
			
		||||
    /* Open the file and parse content */
 | 
			
		||||
    fp = fopen(file_name, "r");
 | 
			
		||||
    if(fp == NULL) {
 | 
			
		||||
        LOGGER_CRIT("unable to open config file '%s'\n", file_name);
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    config_toml = toml_parse_file(fp, errbuf, sizeof(errbuf));
 | 
			
		||||
    fclose(fp);
 | 
			
		||||
    if(config_toml == NULL) {
 | 
			
		||||
        LOGGER_CRIT("unable to parse config file '%s': %s\n", file_name, errbuf);
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toml_table_t* controller = toml_table_in(config_toml, "controller");
 | 
			
		||||
    if(controller)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_section_controller(config, controller);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toml_table_t* logging = toml_table_in(config_toml, "logging");
 | 
			
		||||
    if(logging)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_section_logging(config, logging);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toml_table_t* ports = toml_table_in(config_toml, "ports");
 | 
			
		||||
    if(ports)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_section_ports(config, ports);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toml_array_t* relays = toml_array_in(config_toml, "relays");
 | 
			
		||||
    if(relays)
 | 
			
		||||
    {
 | 
			
		||||
        config->relay_count = toml_array_nelem(relays);
 | 
			
		||||
        config_init_relay_configs(config);
 | 
			
		||||
 | 
			
		||||
        for(int i = 0; i < config->relay_count; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            config_load_section_relay(config, toml_table_at(relays, i), i);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toml_free(config_toml);
 | 
			
		||||
 | 
			
		||||
    LOGGER_DEBUG("Loaded config from %s\n", file_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
config_load_directory(config_t *config, const char *directory_name)
 | 
			
		||||
{
 | 
			
		||||
    struct dirent *directory_entry;
 | 
			
		||||
    DIR *directory;
 | 
			
		||||
 | 
			
		||||
    (void)config;
 | 
			
		||||
 | 
			
		||||
    directory = opendir(directory_name);
 | 
			
		||||
    if(directory == NULL)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("cannot open directory '%s': %s\n", directory_name, strerror(errno));
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while((directory_entry = readdir(directory)) != NULL)
 | 
			
		||||
    {
 | 
			
		||||
        struct stat sb;
 | 
			
		||||
        const char *entry_name = directory_entry->d_name;
 | 
			
		||||
 | 
			
		||||
        size_t copied = 0;
 | 
			
		||||
 | 
			
		||||
        // Add 2 for '/' and '\0'.
 | 
			
		||||
        size_t path_len = strlen(directory_name) + strlen(entry_name) + 1;
 | 
			
		||||
        char *path = malloc(sizeof(char) * (path_len + 1));
 | 
			
		||||
        path[0] = '\0';
 | 
			
		||||
 | 
			
		||||
        strncat(path + copied, directory_name, path_len - copied);
 | 
			
		||||
        copied = strlen(path);
 | 
			
		||||
        if(path[copied - 1] != '/')
 | 
			
		||||
        {
 | 
			
		||||
            strncat(path + copied, "/", path_len - copied);
 | 
			
		||||
            copied = strlen(path);
 | 
			
		||||
        }
 | 
			
		||||
        strncat(path + copied, entry_name, path_len - copied);
 | 
			
		||||
 | 
			
		||||
        if(stat(path, &sb))
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_WARNING("failed to get info for '%s': %s\n", path, strerror(errno));
 | 
			
		||||
        }
 | 
			
		||||
        if(S_ISREG(sb.st_mode))
 | 
			
		||||
        {
 | 
			
		||||
            config_load_file(config, path);
 | 
			
		||||
        }
 | 
			
		||||
        free(path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    closedir(directory);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +0,0 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
#include <connections.h>
 | 
			
		||||
#include <models/controller.h>
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <handlers.h>
 | 
			
		||||
 | 
			
		||||
struct mg_connection *global_connection_mqtt;
 | 
			
		||||
 | 
			
		||||
struct mg_connection*
 | 
			
		||||
connection_discovery_bind(struct mg_mgr *mgr)
 | 
			
		||||
{
 | 
			
		||||
    char address[32];
 | 
			
		||||
    sprintf(address, "udp://0.0.0.0:%u", global_config->ports.discovery);
 | 
			
		||||
    struct mg_connection *c = mg_bind(mgr, address, handler_discovery);
 | 
			
		||||
    return c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct mg_connection*
 | 
			
		||||
connection_command_bind(struct mg_mgr *mgr)
 | 
			
		||||
{
 | 
			
		||||
    char address[32];
 | 
			
		||||
    sprintf(address, "tcp://0.0.0.0:%u", global_controller->command_port);
 | 
			
		||||
    struct mg_connection *c = mg_bind(mgr, address, handler_command);
 | 
			
		||||
    return c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct mg_connection*
 | 
			
		||||
connection_mqtt_connect(struct mg_mgr *mgr)
 | 
			
		||||
{
 | 
			
		||||
    char address[512];
 | 
			
		||||
    sprintf(address, "tcp://%s:%u", global_config->mqtt_host, global_config->ports.mqtt);
 | 
			
		||||
    struct mg_connection *c = mg_connect(mgr, address, handler_mqtt);
 | 
			
		||||
    return c;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										264
									
								
								src/database.c
									
										
									
									
									
								
							
							
						
						
									
										264
									
								
								src/database.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,264 +0,0 @@
 | 
			
		|||
#include <stdint.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <database.h>
 | 
			
		||||
 | 
			
		||||
#include <sql/migration_0.h>
 | 
			
		||||
 | 
			
		||||
sqlite3 *global_database;
 | 
			
		||||
static int in_transaction;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_init()
 | 
			
		||||
{
 | 
			
		||||
    int rc = sqlite3_open(global_config->database, &global_database);
 | 
			
		||||
 | 
			
		||||
    if(rc)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("can't open database: %s\n", sqlite3_errmsg(global_database));
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    database_migrate();
 | 
			
		||||
 | 
			
		||||
    sqlite3_exec(global_database, "PRAGMA foreign_keys = ON", 0, 0, 0);
 | 
			
		||||
    in_transaction = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_free()
 | 
			
		||||
{
 | 
			
		||||
    sqlite3_close(global_database);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_migrate()
 | 
			
		||||
{
 | 
			
		||||
    uint16_t version_num = 0;
 | 
			
		||||
    int s, rc;
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "PRAGMA user_version;", -1, &stmt, NULL);
 | 
			
		||||
    s = sqlite3_step(stmt);
 | 
			
		||||
    if (s == SQLITE_ROW)
 | 
			
		||||
    {
 | 
			
		||||
        version_num = sqlite3_column_int(stmt, 0);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        version_num = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint16_t new_version_num = version_num;
 | 
			
		||||
    char* err_msg;
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    switch(version_num)
 | 
			
		||||
    {
 | 
			
		||||
        case 0:
 | 
			
		||||
            LOGGER_INFO("migrating LEVEL 0\n");
 | 
			
		||||
            rc = sqlite3_exec(global_database, (const char *)sql_migration_0_sql, NULL, NULL, &err_msg);
 | 
			
		||||
            if(rc)
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_CRIT("couldn't migrate LEVEL 0 (%s)\n", err_msg);
 | 
			
		||||
                exit(1);
 | 
			
		||||
            }
 | 
			
		||||
            new_version_num = 1;
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    char pragma_query[32];
 | 
			
		||||
    sprintf(pragma_query, "PRAGMA user_version=%d;", new_version_num);
 | 
			
		||||
    sqlite3_exec(global_database, pragma_query, 0, 0, 0);
 | 
			
		||||
    LOGGER_DEBUG("storing new user_version %d\n", new_version_num);
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
database_transaction_begin()
 | 
			
		||||
{
 | 
			
		||||
    if(!in_transaction)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_DEBUG("beginning transaction\n");
 | 
			
		||||
        sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
 | 
			
		||||
        in_transaction = 1;
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_transaction_commit()
 | 
			
		||||
{
 | 
			
		||||
    LOGGER_DEBUG("commiting transaction\n");
 | 
			
		||||
    sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
 | 
			
		||||
    in_transaction = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
database_transaction_rollback()
 | 
			
		||||
{
 | 
			
		||||
    LOGGER_DEBUG("rolling back transaction\n");
 | 
			
		||||
    sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
 | 
			
		||||
    in_transaction = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
database_helper_get_id(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    int result = 0;
 | 
			
		||||
 | 
			
		||||
    for(;;)
 | 
			
		||||
    {
 | 
			
		||||
        int s;
 | 
			
		||||
 | 
			
		||||
        s = sqlite3_step(stmt);
 | 
			
		||||
        if (s == SQLITE_ROW)
 | 
			
		||||
        {
 | 
			
		||||
            result = sqlite3_column_int(stmt, 0);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (s == SQLITE_DONE)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s));
 | 
			
		||||
                sqlite3_finalize(stmt);
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int*
 | 
			
		||||
database_helper_get_ids(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    int *result = malloc(sizeof(int));
 | 
			
		||||
    int new_id;
 | 
			
		||||
 | 
			
		||||
    int row = 0;
 | 
			
		||||
 | 
			
		||||
    for(;;)
 | 
			
		||||
    {
 | 
			
		||||
        int s;
 | 
			
		||||
 | 
			
		||||
        s = sqlite3_step(stmt);
 | 
			
		||||
        if (s == SQLITE_ROW)
 | 
			
		||||
        {
 | 
			
		||||
            new_id = sqlite3_column_int(stmt, 0);
 | 
			
		||||
            if(new_id != 0) // found row for other target (relay <> schedule)
 | 
			
		||||
            {
 | 
			
		||||
                row++;
 | 
			
		||||
 | 
			
		||||
                result = (int*)realloc(result, sizeof(int) * (row + 1));
 | 
			
		||||
                result[row - 1] = new_id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (s == SQLITE_DONE)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s));
 | 
			
		||||
                sqlite3_finalize(stmt);
 | 
			
		||||
                return NULL;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
    result[row] = 0;
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char*
 | 
			
		||||
database_helper_get_string(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    char *result = NULL;
 | 
			
		||||
 | 
			
		||||
    for(;;)
 | 
			
		||||
    {
 | 
			
		||||
        int s;
 | 
			
		||||
 | 
			
		||||
        s = sqlite3_step(stmt);
 | 
			
		||||
        if (s == SQLITE_ROW)
 | 
			
		||||
        {
 | 
			
		||||
            const char *found_string = (const char *)sqlite3_column_text(stmt, 0);
 | 
			
		||||
            result = (char*)malloc(sizeof(char) * (strlen(found_string) + 1));
 | 
			
		||||
            strcpy(result, found_string);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (s == SQLITE_DONE)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s));
 | 
			
		||||
                sqlite3_finalize(stmt);
 | 
			
		||||
                return NULL;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char**
 | 
			
		||||
database_helper_get_strings(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    char **result = malloc(sizeof(char*));
 | 
			
		||||
 | 
			
		||||
    int row = 0;
 | 
			
		||||
 | 
			
		||||
    for(;;)
 | 
			
		||||
    {
 | 
			
		||||
        int s;
 | 
			
		||||
 | 
			
		||||
        s = sqlite3_step(stmt);
 | 
			
		||||
        if (s == SQLITE_ROW)
 | 
			
		||||
        {
 | 
			
		||||
            const char *new_string = (const char *)sqlite3_column_text(stmt, 0);
 | 
			
		||||
            int new_string_len = strlen(new_string);
 | 
			
		||||
            row++;
 | 
			
		||||
 | 
			
		||||
            result = (char**)realloc(result, sizeof(char*) * (row + 1));
 | 
			
		||||
            result[row - 1] = malloc(sizeof(char) * (new_string_len + 1));
 | 
			
		||||
            strcpy(result[row - 1], new_string);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if(s == SQLITE_DONE)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s));
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
    result[row] = NULL;
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
#include <wiringPi.h>
 | 
			
		||||
#include <piFace.h>
 | 
			
		||||
#include <wiring_debug.h>
 | 
			
		||||
 | 
			
		||||
#include <drivers.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
driver_gpio_set(int pin, int value)
 | 
			
		||||
{
 | 
			
		||||
    digitalWrite(pin, value);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
#include <wiringPi.h>
 | 
			
		||||
#include <piFace.h>
 | 
			
		||||
#include <wiring_debug.h>
 | 
			
		||||
 | 
			
		||||
#include <drivers.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
driver_piface_set(int pin, int value)
 | 
			
		||||
{
 | 
			
		||||
    digitalWrite(PIFACE_GPIO_BASE + pin, value);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,251 +0,0 @@
 | 
			
		|||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <uuid/uuid.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <database.h>
 | 
			
		||||
#include <handlers.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
#include <mpack.h>
 | 
			
		||||
#include <models/controller.h>
 | 
			
		||||
#include <models/relay.h>
 | 
			
		||||
#include <models/schedule.h>
 | 
			
		||||
#include <models/junction_relay_schedule.h>
 | 
			
		||||
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    COMMAND_CODE_CONTROLLER_ID_GET = 0,
 | 
			
		||||
    COMMAND_CODE_CONTROLLER_TIME_GET = 1,
 | 
			
		||||
    COMMAND_CODE_CONTROLLER_NAME_SET = 2,
 | 
			
		||||
    COMMAND_CODE_CONTROLLER_NAME_GET = 3,
 | 
			
		||||
 | 
			
		||||
    COMMAND_CODE_RELAY_SCHEDULES_SET = 100,
 | 
			
		||||
    COMMAND_CODE_RELAY_SCHEDULES_GET = 101,
 | 
			
		||||
    COMMAND_CODE_RELAY_NAME_SET = 102,
 | 
			
		||||
    COMMAND_CODE_RELAY_NAME_GET = 103,
 | 
			
		||||
    COMMAND_CODE_RELAY_PULSE = 200,
 | 
			
		||||
 | 
			
		||||
    COMMAND_CODE_SCHEDULE_UPDATE = 300
 | 
			
		||||
} command_code_t;
 | 
			
		||||
 | 
			
		||||
typedef enum
 | 
			
		||||
{
 | 
			
		||||
    COMMAND_MAPPING_CODE = 0,
 | 
			
		||||
    COMMAND_MAPPING_NAME = 1,
 | 
			
		||||
    COMMAND_MAPPING_RELAY_NUM = 2,
 | 
			
		||||
    COMMAND_MAPPING_SCHEDULES_ARRAY = 3,
 | 
			
		||||
    COMMAND_MAPPING_SCHEDULE_ID = 4,
 | 
			
		||||
    COMMAND_MAPPING_PERIODS_COUNT = 5,
 | 
			
		||||
    COMMAND_MAPPING_PERIODS_BLOB = 6,
 | 
			
		||||
    COMMAND_MAPPING_PULSE_DURATION = 7,
 | 
			
		||||
} control_mapping_t;
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
handler_command_relay_pulse(mpack_node_t map)
 | 
			
		||||
{
 | 
			
		||||
    uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
 | 
			
		||||
 | 
			
		||||
    if(relay_num > global_config->relay_count)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    relay_t *target_relay = global_controller->relays[relay_num];
 | 
			
		||||
    (void)target_relay;
 | 
			
		||||
 | 
			
		||||
    int duration = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_PULSE_DURATION));
 | 
			
		||||
    if(duration == 0)
 | 
			
		||||
    {
 | 
			
		||||
        duration = global_config->relay_configs[relay_num].pulse_duration;
 | 
			
		||||
    }
 | 
			
		||||
    target_relay->pulse_timer = duration;
 | 
			
		||||
    LOGGER_DEBUG("pulsing relay %d for %ds\n", relay_num, duration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
handler_command_controller_name_set(mpack_node_t map)
 | 
			
		||||
{
 | 
			
		||||
    char name_buffer[MAX_NAME_LENGTH + 1];
 | 
			
		||||
    mpack_node_copy_cstr(mpack_node_map_uint(map, COMMAND_MAPPING_NAME), name_buffer, MAX_NAME_LENGTH + 1);
 | 
			
		||||
    controller_set_name(global_controller, name_buffer);
 | 
			
		||||
    LOGGER_DEBUG("setting new name %s for controller\n", name_buffer);
 | 
			
		||||
    controller_save();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
handler_command_relay_name_set(mpack_node_t map)
 | 
			
		||||
{
 | 
			
		||||
    uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
 | 
			
		||||
    const char *relay_name = mpack_node_str(mpack_node_map_uint(map, COMMAND_MAPPING_NAME));
 | 
			
		||||
 | 
			
		||||
    if(relay_num > global_config->relay_count)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    relay_set_name(global_controller->relays[relay_num], relay_name);
 | 
			
		||||
    LOGGER_DEBUG("setting new name %s for relay %d\n", relay_name, relay_num);
 | 
			
		||||
    relay_save(global_controller->relays[relay_num]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
handler_command_schedule_update(mpack_node_t map)
 | 
			
		||||
{
 | 
			
		||||
    uuid_t schedule_uid;
 | 
			
		||||
    memcpy(schedule_uid, mpack_node_data(mpack_node_map_uint(map, COMMAND_MAPPING_SCHEDULE_ID)), sizeof(uuid_t));
 | 
			
		||||
 | 
			
		||||
    uint16_t periods_count = mpack_node_u16(mpack_node_map_uint(map, COMMAND_MAPPING_PERIODS_COUNT));
 | 
			
		||||
    uint16_t *periods = (uint16_t*)mpack_node_bin_data(mpack_node_map_uint(map, COMMAND_MAPPING_PERIODS_BLOB));
 | 
			
		||||
 | 
			
		||||
    schedule_t *schedule = schedule_get_by_uid(schedule_uid);
 | 
			
		||||
 | 
			
		||||
    schedule_t *new_schedule = schedule_create(schedule_uid, periods_count, periods);
 | 
			
		||||
 | 
			
		||||
    if(schedule)
 | 
			
		||||
    {
 | 
			
		||||
        new_schedule->id = schedule->id;
 | 
			
		||||
        schedule_free(schedule);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    schedule_save(new_schedule);
 | 
			
		||||
 | 
			
		||||
    int *relay_ids = junction_relay_schedule_get_relay_ids_with_schedule(new_schedule->id);
 | 
			
		||||
 | 
			
		||||
    for(int i = 0; i < global_config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        for(int j = 0; relay_ids[j] != 0; ++j)
 | 
			
		||||
        {
 | 
			
		||||
            if(global_controller->relays[i]->id == relay_ids[j])
 | 
			
		||||
            {
 | 
			
		||||
                relay_reload_schedules(global_controller->relays[i]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    free(relay_ids);
 | 
			
		||||
    schedule_free(new_schedule);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
handler_command_relay_schedules_set(mpack_node_t map)
 | 
			
		||||
{
 | 
			
		||||
    uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
 | 
			
		||||
 | 
			
		||||
    if(relay_num > global_config->relay_count)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LOGGER_DEBUG("setting schedules for relay %d\n", relay_num);
 | 
			
		||||
    relay_t *target_relay = global_controller->relays[relay_num];
 | 
			
		||||
 | 
			
		||||
    database_transaction_begin();
 | 
			
		||||
 | 
			
		||||
    junction_relay_schedule_remove_for_relay(target_relay->id);
 | 
			
		||||
 | 
			
		||||
    mpack_node_t schedules_array = mpack_node_map_uint(map, COMMAND_MAPPING_SCHEDULES_ARRAY);
 | 
			
		||||
 | 
			
		||||
    for(int i = 0; i < 7; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        mpack_node_t schedule_map = mpack_node_array_at(schedules_array, i);
 | 
			
		||||
 | 
			
		||||
        uuid_t schedule_uid;
 | 
			
		||||
        memcpy(schedule_uid, mpack_node_data(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_SCHEDULE_ID)), sizeof(uuid_t));
 | 
			
		||||
 | 
			
		||||
        uint16_t periods_count = mpack_node_u16(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_PERIODS_COUNT));
 | 
			
		||||
        uint16_t *periods = (uint16_t*)mpack_node_bin_data(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_PERIODS_BLOB));
 | 
			
		||||
 | 
			
		||||
        schedule_t *schedule = schedule_get_by_uid(schedule_uid);
 | 
			
		||||
 | 
			
		||||
        schedule_t *new_schedule = schedule_create(schedule_uid, periods_count, periods);
 | 
			
		||||
 | 
			
		||||
        if(schedule)
 | 
			
		||||
        {
 | 
			
		||||
            new_schedule->id = schedule->id;
 | 
			
		||||
            schedule_free(schedule);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        schedule_save(new_schedule);
 | 
			
		||||
 | 
			
		||||
        junction_relay_schedule_insert(i, target_relay->id, new_schedule->id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    relay_reload_schedules(target_relay);
 | 
			
		||||
 | 
			
		||||
    database_transaction_commit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
handler_command(struct mg_connection *c, int ev, void *ev_data)
 | 
			
		||||
{
 | 
			
		||||
    (void)ev_data;
 | 
			
		||||
    if(ev != MG_EV_RECV)
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    uint32_t payload_length = *((uint32_t*)c->recv_mbuf.buf);
 | 
			
		||||
 | 
			
		||||
    if(c->recv_mbuf.len < payload_length + sizeof(payload_length))
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    char *payload = c->recv_mbuf.buf + sizeof(payload_length);
 | 
			
		||||
 | 
			
		||||
    mpack_tree_t tree;
 | 
			
		||||
    mpack_tree_init_data(&tree, payload, payload_length);
 | 
			
		||||
    mpack_tree_parse(&tree);
 | 
			
		||||
    mpack_node_t root = mpack_tree_root(&tree);
 | 
			
		||||
 | 
			
		||||
    uint16_t command_code = mpack_node_u16(mpack_node_map_uint(root, COMMAND_MAPPING_CODE));
 | 
			
		||||
 | 
			
		||||
    LOGGER_DEBUG("received command %d\n", command_code);
 | 
			
		||||
 | 
			
		||||
    switch(command_code)
 | 
			
		||||
    {
 | 
			
		||||
        case COMMAND_CODE_CONTROLLER_ID_GET:
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_CONTROLLER_TIME_GET:
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_CONTROLLER_NAME_SET:
 | 
			
		||||
            handler_command_controller_name_set(root);
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_CONTROLLER_NAME_GET:
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_RELAY_SCHEDULES_SET:
 | 
			
		||||
            handler_command_relay_schedules_set(root);
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_RELAY_SCHEDULES_GET:
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_RELAY_NAME_SET:
 | 
			
		||||
            handler_command_relay_name_set(root);
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_RELAY_NAME_GET:
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_RELAY_PULSE:
 | 
			
		||||
            handler_command_relay_pulse(root);
 | 
			
		||||
            break;
 | 
			
		||||
        case COMMAND_CODE_SCHEDULE_UPDATE:
 | 
			
		||||
            handler_command_schedule_update(root);
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            LOGGER_ERR("received invalid command\n");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(mpack_tree_destroy(&tree) != mpack_ok)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_WARNING("error when destroying mpack tree\n");
 | 
			
		||||
    }
 | 
			
		||||
    LOGGER_DEBUG("done with command %d - closing connection\n", command_code);
 | 
			
		||||
    c->flags |= MG_F_SEND_AND_CLOSE;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,95 +0,0 @@
 | 
			
		|||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <uuid/uuid.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <mongoose.h>
 | 
			
		||||
#include <handlers.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
#include <mpack.h>
 | 
			
		||||
#include <enums.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
handler_discovery(struct mg_connection *c, int ev, void *ev_data)
 | 
			
		||||
{
 | 
			
		||||
    (void)ev_data;
 | 
			
		||||
    if(ev == MG_EV_RECV)
 | 
			
		||||
    {
 | 
			
		||||
        uint16_t discovery_answer_port;
 | 
			
		||||
        char ip_buf[32];
 | 
			
		||||
        mg_conn_addr_to_str(c, ip_buf, sizeof(ip_buf), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_REMOTE);
 | 
			
		||||
 | 
			
		||||
        if(c->recv_mbuf.len != sizeof(discovery_answer_port))
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("received invalid discovery from %s\n", ip_buf);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        discovery_answer_port = *((uint16_t*)c->recv_mbuf.buf);
 | 
			
		||||
 | 
			
		||||
        LOGGER_INFO("received discovery from %s:%d\n", ip_buf, discovery_answer_port);
 | 
			
		||||
 | 
			
		||||
        if(discovery_answer_port == 0)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("invalid port received\n");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        char* payload;
 | 
			
		||||
        size_t payload_size;
 | 
			
		||||
        mpack_writer_t writer;
 | 
			
		||||
        mpack_writer_init_growable(&writer, &payload, &payload_size);
 | 
			
		||||
 | 
			
		||||
        mpack_start_map(&writer, 4);
 | 
			
		||||
        mpack_write_uint(&writer, DISCOVERY_MAPPING_ID);
 | 
			
		||||
        mpack_write_bin(&writer, (char*)global_controller->uid, sizeof(uuid_t));
 | 
			
		||||
        mpack_write_uint(&writer, DISCOVERY_MAPPING_COMMAND_PORT);
 | 
			
		||||
        mpack_write_u16(&writer, global_controller->command_port);
 | 
			
		||||
        mpack_write_uint(&writer, DISCOVERY_MAPPING_RELAY_COUNT);
 | 
			
		||||
        mpack_write_u8(&writer, global_config->relay_count);
 | 
			
		||||
        mpack_write_uint(&writer, DISCOVERY_MAPPING_NAME);
 | 
			
		||||
        mpack_write_cstr(&writer, global_controller->name);
 | 
			
		||||
        mpack_finish_map(&writer);
 | 
			
		||||
 | 
			
		||||
        // finish writing
 | 
			
		||||
        if(mpack_writer_destroy(&writer) != mpack_ok)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error writing discovery answer payload\n");
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        size_t bytes_transferred;
 | 
			
		||||
        int fd_answer = helper_connect_tcp_server(ip_buf, discovery_answer_port);
 | 
			
		||||
        if(fd_answer == -1)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error during connecting\n");
 | 
			
		||||
            free(payload);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if((bytes_transferred = send(fd_answer, &payload_size, sizeof(payload_size), 0)) <= 0)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error during sending\n");
 | 
			
		||||
            free(payload);
 | 
			
		||||
            close(fd_answer);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if((bytes_transferred = send(fd_answer, payload, payload_size, 0)) <= 0)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error during sending\n");
 | 
			
		||||
            free(payload);
 | 
			
		||||
            close(fd_answer);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        free(payload);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,92 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <uuid/uuid.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <models/controller.h>
 | 
			
		||||
#include <handlers.h>
 | 
			
		||||
#include <drivers.h>
 | 
			
		||||
#include <enums.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
#include <wiringPi.h>
 | 
			
		||||
#include <wiring_debug.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
handler_loop(struct mg_connection *c_mqtt)
 | 
			
		||||
{
 | 
			
		||||
    char topic_buf[100];
 | 
			
		||||
    char payload_buf[2];
 | 
			
		||||
    char controller_uid[UUID_STR_LEN];
 | 
			
		||||
    uuid_unparse(global_controller->uid, controller_uid);
 | 
			
		||||
 | 
			
		||||
    struct tm time_last, time_now;
 | 
			
		||||
    time_t timestamp;
 | 
			
		||||
 | 
			
		||||
    timestamp = time(NULL) - 1;
 | 
			
		||||
    localtime_r(×tamp, &time_last);
 | 
			
		||||
    timestamp = time(NULL);
 | 
			
		||||
    localtime_r(×tamp, &time_now);
 | 
			
		||||
    for(uint_fast8_t i = 0; i < global_config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        relay_t *relay = global_controller->relays[i];
 | 
			
		||||
        int is_on = 0;
 | 
			
		||||
        
 | 
			
		||||
        int is_on_schedule = relay_is_on_schedule(relay, &time_now);
 | 
			
		||||
        int pulse_relay = global_config->relay_configs[i].pulse_duration;
 | 
			
		||||
 | 
			
		||||
        if(is_on_schedule)
 | 
			
		||||
        {
 | 
			
		||||
            if(!pulse_relay)
 | 
			
		||||
            {
 | 
			
		||||
                is_on = 1;
 | 
			
		||||
                LOGGER_DEBUG("relay %d is active\n", i);
 | 
			
		||||
            }
 | 
			
		||||
            if(pulse_relay && relay->pulse_timer)
 | 
			
		||||
            {
 | 
			
		||||
                is_on = 1;
 | 
			
		||||
                --relay->pulse_timer;
 | 
			
		||||
                LOGGER_DEBUG("relay %d is pulsing for %d more seconds\n", i, relay->pulse_timer);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            relay->pulse_timer = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(relay->is_on != is_on)
 | 
			
		||||
        {
 | 
			
		||||
            relay->sent_to_broker = 0;
 | 
			
		||||
        }
 | 
			
		||||
        if(!relay->sent_to_broker && c_mqtt)
 | 
			
		||||
        {
 | 
			
		||||
            sprintf(topic_buf, "controller/%s/relay/%u", controller_uid, i);
 | 
			
		||||
            sprintf(payload_buf, "%u", is_on);
 | 
			
		||||
            mg_mqtt_publish(c_mqtt, topic_buf, 0, MG_MQTT_QOS(0), payload_buf, strlen(payload_buf));
 | 
			
		||||
            relay->sent_to_broker = (rand() % 45) + 15;
 | 
			
		||||
            LOGGER_DEBUG("sent relay %d status (%d) to mqtt broker\n", i, is_on);
 | 
			
		||||
        }
 | 
			
		||||
        if(relay->sent_to_broker)
 | 
			
		||||
        {
 | 
			
		||||
            --relay->sent_to_broker;
 | 
			
		||||
        }
 | 
			
		||||
        relay->is_on = is_on;
 | 
			
		||||
 | 
			
		||||
        if(global_config->relay_configs[i].inverted)
 | 
			
		||||
        {
 | 
			
		||||
            is_on = !is_on;
 | 
			
		||||
        }
 | 
			
		||||
        switch(global_config->relay_configs[i].driver)
 | 
			
		||||
        {
 | 
			
		||||
            case RELAY_DRIVER_GPIO:
 | 
			
		||||
                driver_gpio_set(global_config->relay_configs[i].pin, is_on);
 | 
			
		||||
                break;
 | 
			
		||||
            case RELAY_DRIVER_PIFACE:
 | 
			
		||||
                driver_piface_set(global_config->relay_configs[i].pin, is_on);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                LOGGER_WARNING("relay %d is not using a driver\n", i);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,43 +0,0 @@
 | 
			
		|||
#include <logger.h>
 | 
			
		||||
#include <handlers.h>
 | 
			
		||||
#include <connections.h>
 | 
			
		||||
#include <models/controller.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
handler_mqtt(struct mg_connection *nc, int ev, void *p) {
 | 
			
		||||
    struct mg_mqtt_message *msg = (struct mg_mqtt_message *) p;
 | 
			
		||||
    (void) nc;
 | 
			
		||||
 | 
			
		||||
    switch (ev)
 | 
			
		||||
    {
 | 
			
		||||
        case MG_EV_CONNECT:
 | 
			
		||||
            {
 | 
			
		||||
                struct mg_send_mqtt_handshake_opts opts;
 | 
			
		||||
                memset(&opts, 0, sizeof(opts));
 | 
			
		||||
                // TODO add password
 | 
			
		||||
 | 
			
		||||
                mg_set_protocol_mqtt(nc);
 | 
			
		||||
                mg_send_mqtt_handshake_opt(nc, global_controller->name, opts);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        case MG_EV_MQTT_CONNACK:
 | 
			
		||||
            if(msg->connack_ret_code != MG_EV_MQTT_CONNACK_ACCEPTED)
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_INFO("Got MQTT connection error: %d\n", msg->connack_ret_code);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            if(!global_connection_mqtt)
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_DEBUG("connected to MQTT server\n");
 | 
			
		||||
                global_connection_mqtt = nc;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case MG_EV_CLOSE:
 | 
			
		||||
            if(global_connection_mqtt)
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_DEBUG("disconnected from MQTT server\n");
 | 
			
		||||
            }
 | 
			
		||||
            global_connection_mqtt = NULL;
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,52 +0,0 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_bind_tcp_server(char* addr, uint16_t port, int max_client_backlog)
 | 
			
		||||
{
 | 
			
		||||
    char port_str[6];
 | 
			
		||||
    sprintf(port_str, "%d", port);
 | 
			
		||||
 | 
			
		||||
    struct addrinfo hints, *res;
 | 
			
		||||
    int fd;
 | 
			
		||||
    int status;
 | 
			
		||||
 | 
			
		||||
    memset(&hints, 0, sizeof hints);
 | 
			
		||||
    hints.ai_family = AF_UNSPEC;
 | 
			
		||||
    hints.ai_socktype = SOCK_STREAM;
 | 
			
		||||
    hints.ai_flags = AI_PASSIVE;
 | 
			
		||||
 | 
			
		||||
    if ((status = getaddrinfo(addr, port_str, &hints, &res)) != 0)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_ERR("getaddrinfo: %s\n", gai_strerror(status));
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
 | 
			
		||||
 | 
			
		||||
    if ((status = bind(fd, res->ai_addr, res->ai_addrlen)) == -1)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_ERR("error binding socket: %s\n", strerror(errno));
 | 
			
		||||
        freeaddrinfo(res);
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((status = listen(fd, max_client_backlog)) == -1)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_ERR("error setting up listener: %s\n", strerror(errno));
 | 
			
		||||
        freeaddrinfo(res);
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    freeaddrinfo(res);
 | 
			
		||||
 | 
			
		||||
    return fd;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,39 +0,0 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_connect_tcp_server(char* host, uint16_t port)
 | 
			
		||||
{
 | 
			
		||||
    char port_str[6];
 | 
			
		||||
    sprintf(port_str, "%d", port);
 | 
			
		||||
 | 
			
		||||
    int s, status;
 | 
			
		||||
    struct addrinfo hints, *res;
 | 
			
		||||
    memset(&hints, 0, sizeof hints);
 | 
			
		||||
    hints.ai_family = AF_INET; //set IP Protocol flag (IPv4 or IPv6 - we don't care)
 | 
			
		||||
    hints.ai_socktype = SOCK_STREAM; //set socket flag
 | 
			
		||||
 | 
			
		||||
    if ((status = getaddrinfo(host, port_str, &hints, &res)) != 0) { //getaddrinfo() will evaluate the given address, using the hints-flags and port, and return an IP address and other server infos
 | 
			
		||||
        LOGGER_ERR("getaddrinfo: %s\n", gai_strerror(status));
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //res got filled out by getaddrinfo() for us
 | 
			
		||||
    s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
 | 
			
		||||
 | 
			
		||||
    if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
 | 
			
		||||
        LOGGER_ERR("connect() failed\n");
 | 
			
		||||
        freeaddrinfo(res);
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    freeaddrinfo(res);
 | 
			
		||||
 | 
			
		||||
    return s;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,81 +0,0 @@
 | 
			
		|||
#include <sys/types.h>
 | 
			
		||||
#include <pwd.h>
 | 
			
		||||
#include <grp.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
 | 
			
		||||
static uid_t
 | 
			
		||||
get_uid_for_user(char *user)
 | 
			
		||||
{
 | 
			
		||||
    if(user == NULL || user[0] == '\0')
 | 
			
		||||
    {
 | 
			
		||||
        return getuid();
 | 
			
		||||
    }
 | 
			
		||||
    struct passwd *pwd = calloc(1, sizeof(struct passwd));
 | 
			
		||||
    size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
 | 
			
		||||
    char *buffer = malloc(buffer_len);
 | 
			
		||||
    getpwnam_r(user, pwd, buffer, buffer_len, &pwd);
 | 
			
		||||
 | 
			
		||||
    if(pwd == NULL)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("couldn't find user to drop privileges\n");
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uid_t result = pwd->pw_uid;
 | 
			
		||||
    free(buffer);
 | 
			
		||||
    free(pwd);
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gid_t
 | 
			
		||||
get_gid_for_group(char *group)
 | 
			
		||||
{
 | 
			
		||||
    if(group == NULL || group[0] == '\0')
 | 
			
		||||
    {
 | 
			
		||||
        return getgid();
 | 
			
		||||
    }
 | 
			
		||||
    struct group *grp = calloc(1, sizeof(struct group));
 | 
			
		||||
    size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
 | 
			
		||||
    char *buffer = malloc(buffer_len);
 | 
			
		||||
    getgrnam_r(group, grp, buffer, buffer_len, &grp);
 | 
			
		||||
 | 
			
		||||
    if(grp == NULL)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("couldn't find group to drop privileges\n");
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    gid_t result = grp->gr_gid;
 | 
			
		||||
    free(buffer);
 | 
			
		||||
    free(grp);
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_drop_privileges()
 | 
			
		||||
{
 | 
			
		||||
    uid_t uid = get_uid_for_user(global_config->user);
 | 
			
		||||
    gid_t gid = get_gid_for_group(global_config->group);
 | 
			
		||||
 | 
			
		||||
    LOGGER_DEBUG("drop privileges to %lu:%lu\n", uid, gid);
 | 
			
		||||
 | 
			
		||||
    if (setgid(gid) == -1)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("failed to drop group privileges\n");
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    if (setuid(uid) == -1)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("failed to drop user privileges\n");
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1,24 +0,0 @@
 | 
			
		|||
#include <sys/socket.h>
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
 | 
			
		||||
uint16_t
 | 
			
		||||
helper_get_port(int sock)
 | 
			
		||||
{
 | 
			
		||||
    struct sockaddr_in sin;
 | 
			
		||||
    socklen_t len = sizeof(sin);
 | 
			
		||||
    if (getsockname(sock, (struct sockaddr *)&sin, &len) == -1)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_ERR("could not get socket name for port: %s\n", strerror(errno));
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        return ntohs(sin.sin_port);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
#include <time.h>
 | 
			
		||||
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_get_weekday(const struct tm *time_struct)
 | 
			
		||||
{
 | 
			
		||||
    int wday_sun_sat = time_struct->tm_wday;
 | 
			
		||||
    int wday_mon_sun = (wday_sun_sat + 6) % 7;
 | 
			
		||||
    return wday_mon_sun;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,57 +0,0 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
helper_open_discovery_socket(uint16_t discovery_port)
 | 
			
		||||
{
 | 
			
		||||
    struct addrinfo hints, *res;
 | 
			
		||||
    int fd, status;
 | 
			
		||||
 | 
			
		||||
    memset(&hints, 0, sizeof hints);
 | 
			
		||||
    hints.ai_family = AF_INET; // use ipv4
 | 
			
		||||
    hints.ai_socktype = SOCK_DGRAM; //set socket flag
 | 
			
		||||
    hints.ai_flags = AI_PASSIVE; // get my IP
 | 
			
		||||
 | 
			
		||||
    char discovery_port_str[6];
 | 
			
		||||
    sprintf(discovery_port_str, "%u", discovery_port);
 | 
			
		||||
 | 
			
		||||
    //get connection info for our computer
 | 
			
		||||
    if ((status = getaddrinfo(NULL, discovery_port_str, &hints, &res)) != 0)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("getaddrinfo: %s\n", gai_strerror(status));
 | 
			
		||||
        freeaddrinfo(res);
 | 
			
		||||
        exit(EXIT_FAILURE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //creating socket
 | 
			
		||||
    fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
 | 
			
		||||
    int yes = 1;
 | 
			
		||||
 | 
			
		||||
    // lose the pesky "Address already in use" error message
 | 
			
		||||
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("setsockopt: %s\n", strerror(errno));
 | 
			
		||||
        freeaddrinfo(res);
 | 
			
		||||
        exit(EXIT_FAILURE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (bind(fd, res->ai_addr, res->ai_addrlen) == -1)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_CRIT("bind: %s\n", strerror(errno));
 | 
			
		||||
        freeaddrinfo(res);
 | 
			
		||||
        exit(EXIT_FAILURE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    freeaddrinfo(res);
 | 
			
		||||
 | 
			
		||||
    LOGGER_INFO("opened discovery socket on port %u\n", discovery_port);
 | 
			
		||||
 | 
			
		||||
    return fd;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								src/logger.c
									
										
									
									
									
								
							
							
						
						
									
										96
									
								
								src/logger.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,96 +0,0 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
 | 
			
		||||
const char *COLOR_DEBUG = COLOR_GREEN;
 | 
			
		||||
const char *COLOR_INFO = COLOR_CYAN;
 | 
			
		||||
const char *COLOR_NOTICE = COLOR_CYAN;
 | 
			
		||||
const char *COLOR_WARNING = COLOR_YELLOW;
 | 
			
		||||
const char *COLOR_ERR = COLOR_RED;
 | 
			
		||||
const char *COLOR_CRIT = COLOR_MAGENTA;
 | 
			
		||||
const char *COLOR_EMERG = COLOR_MAGENTA;
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...)
 | 
			
		||||
{
 | 
			
		||||
    if(global_config->logging.level < level)
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    va_list args;
 | 
			
		||||
    const char *level_str;
 | 
			
		||||
    const char *color;
 | 
			
		||||
 | 
			
		||||
    switch(level)
 | 
			
		||||
    {
 | 
			
		||||
        case LOG_DEBUG:
 | 
			
		||||
            color = COLOR_DEBUG;
 | 
			
		||||
            level_str = "DEBUG";
 | 
			
		||||
            break;
 | 
			
		||||
        case LOG_INFO:
 | 
			
		||||
            color = COLOR_INFO;
 | 
			
		||||
            level_str = "INFO";
 | 
			
		||||
            break;
 | 
			
		||||
        case LOG_NOTICE:
 | 
			
		||||
            color = COLOR_NOTICE;
 | 
			
		||||
            level_str = "NOTE";
 | 
			
		||||
            break;
 | 
			
		||||
        case LOG_WARNING:
 | 
			
		||||
            color = COLOR_WARNING;
 | 
			
		||||
            level_str = "WARN";
 | 
			
		||||
            break;
 | 
			
		||||
        case LOG_ERR:
 | 
			
		||||
            color = COLOR_ERR;
 | 
			
		||||
            level_str = "ERROR";
 | 
			
		||||
            break;
 | 
			
		||||
        case LOG_CRIT:
 | 
			
		||||
            color = COLOR_CRIT;
 | 
			
		||||
            level_str = "CRIT";
 | 
			
		||||
            break;
 | 
			
		||||
        case LOG_EMERG:
 | 
			
		||||
            color = COLOR_EMERG;
 | 
			
		||||
            level_str = "EMERG";
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    char timestamp_str[32];
 | 
			
		||||
    time_t rawtime;
 | 
			
		||||
    time(&rawtime);
 | 
			
		||||
    strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime));
 | 
			
		||||
    size_t timestamp_len = strlen(timestamp_str);
 | 
			
		||||
 | 
			
		||||
    size_t buffer_size = 128;
 | 
			
		||||
    buffer_size += timestamp_len;
 | 
			
		||||
    buffer_size += strlen(filename);
 | 
			
		||||
    buffer_size += strlen(func);
 | 
			
		||||
    buffer_size += strlen(msg);
 | 
			
		||||
 | 
			
		||||
    char *buffer = malloc(sizeof(char) * (buffer_size));
 | 
			
		||||
    sprintf(buffer, "%s %s[%5s] %s:%d:%s " COLOR_NONE "%s", timestamp_str, color, level_str, filename, line, func, msg);
 | 
			
		||||
 | 
			
		||||
    // start arg va_list and find log_len
 | 
			
		||||
    va_start(args, msg);
 | 
			
		||||
    size_t log_len = vsnprintf(NULL, 0, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
 | 
			
		||||
    va_end(args);
 | 
			
		||||
 | 
			
		||||
    char *log_line = malloc(sizeof(char) * (log_len + 1));
 | 
			
		||||
 | 
			
		||||
    // start arg va_list again and write log_line
 | 
			
		||||
    va_start(args, msg);
 | 
			
		||||
    vsprintf(log_line, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
 | 
			
		||||
    va_end(args);
 | 
			
		||||
 | 
			
		||||
    syslog(level, "%s", log_line + timestamp_len + 1);
 | 
			
		||||
 | 
			
		||||
    fprintf(global_config->logging.file, "%s", log_line);
 | 
			
		||||
    fflush(global_config->logging.file);
 | 
			
		||||
 | 
			
		||||
    free(buffer);
 | 
			
		||||
    free(log_line);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										189
									
								
								src/main.c
									
										
									
									
									
								
							
							
						
						
									
										189
									
								
								src/main.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,189 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <lmdb.h>
 | 
			
		||||
#include <signal.h>
 | 
			
		||||
#include <syslog.h>
 | 
			
		||||
 | 
			
		||||
#include <cli.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <mongoose.h>
 | 
			
		||||
#include <models/controller.h>
 | 
			
		||||
#include <database.h>
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <connections.h>
 | 
			
		||||
#include <constants.h>
 | 
			
		||||
#include <handlers.h>
 | 
			
		||||
#include <drivers.h>
 | 
			
		||||
#include <enums.h>
 | 
			
		||||
#include <runners.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
#include <wiringPi.h>
 | 
			
		||||
#include <piFace.h>
 | 
			
		||||
#include <wiring_debug.h>
 | 
			
		||||
 | 
			
		||||
static struct mg_mgr mgr;
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
terminate(int signum)
 | 
			
		||||
{
 | 
			
		||||
    LOGGER_INFO("terminating controller (%d)\n", signum);
 | 
			
		||||
 | 
			
		||||
    // TODO fix mg_mgr_free() causing loop (can't terminate)
 | 
			
		||||
    //LOGGER_DEBUG("freeing mongoose manager\n");
 | 
			
		||||
    //mg_mgr_free(&mgr);
 | 
			
		||||
 | 
			
		||||
    LOGGER_DEBUG("closing database\n");
 | 
			
		||||
    database_free();
 | 
			
		||||
 | 
			
		||||
    LOGGER_DEBUG("freeing global controller\n");
 | 
			
		||||
    controller_free(global_controller);
 | 
			
		||||
 | 
			
		||||
    LOGGER_DEBUG("freeing relay configs config\n");
 | 
			
		||||
    free(global_config->relay_configs);
 | 
			
		||||
 | 
			
		||||
    exit(signum);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The main function
 | 
			
		||||
 *
 | 
			
		||||
 * @param argc UNUSED
 | 
			
		||||
 * @param argv UNUSED
 | 
			
		||||
 *
 | 
			
		||||
 * @return Statuscode to indicate success (0) or failure (!0)
 | 
			
		||||
 */
 | 
			
		||||
int
 | 
			
		||||
main(int argc, const char** argv)
 | 
			
		||||
{
 | 
			
		||||
    (void)argc;
 | 
			
		||||
    (void)argv;
 | 
			
		||||
 | 
			
		||||
    signal(SIGINT, terminate);
 | 
			
		||||
    signal(SIGABRT, terminate);
 | 
			
		||||
    signal(SIGTERM, terminate);
 | 
			
		||||
 | 
			
		||||
    openlog("emgauwa-controller", 0, LOG_USER);
 | 
			
		||||
    setlogmask(LOG_UPTO(LOG_INFO));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /******************** LOAD CONFIG ********************/
 | 
			
		||||
 | 
			
		||||
    config_init();
 | 
			
		||||
 | 
			
		||||
    cli_t cli;
 | 
			
		||||
    cli_parse(argc, argv, &cli);
 | 
			
		||||
 | 
			
		||||
    config_load(global_config, cli.config_file);
 | 
			
		||||
 | 
			
		||||
    if(global_config->logging.file == NULL)
 | 
			
		||||
    {
 | 
			
		||||
        global_config->logging.file = stdout;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(global_config->include)
 | 
			
		||||
    {
 | 
			
		||||
        config_load_directory(global_config, global_config->include);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(sizeof(time_t) < 8)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_WARNING("this system is not using 8-bit time\n");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /******************** INIT DATABASE, SOCKETS AND THIS CONTROLLER ********************/
 | 
			
		||||
 | 
			
		||||
    mg_mgr_init(&mgr, NULL);
 | 
			
		||||
 | 
			
		||||
    database_init();
 | 
			
		||||
 | 
			
		||||
    global_controller = controller_load();
 | 
			
		||||
    if(!global_controller)
 | 
			
		||||
    {
 | 
			
		||||
        global_controller = controller_create();
 | 
			
		||||
        controller_save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connection_discovery_bind(&mgr);
 | 
			
		||||
    connection_mqtt_connect(&mgr);
 | 
			
		||||
    struct mg_connection *c_command = connection_command_bind(&mgr);
 | 
			
		||||
 | 
			
		||||
    if(global_controller->command_port == 0)
 | 
			
		||||
    {
 | 
			
		||||
        global_controller->command_port = helper_get_port(c_command->sock);
 | 
			
		||||
        controller_save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    controller_debug(global_controller);
 | 
			
		||||
 | 
			
		||||
    helper_drop_privileges();
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /******************** SETUP WIRINGPI ********************/
 | 
			
		||||
 | 
			
		||||
    wiringPiSetup();
 | 
			
		||||
 | 
			
		||||
    int piface_setup = 0;
 | 
			
		||||
 | 
			
		||||
    for(uint_fast8_t i = 0; i < global_config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        int relay_default = global_config->relay_configs[i].init;
 | 
			
		||||
        if(relay_default == -1)
 | 
			
		||||
        {
 | 
			
		||||
            relay_default = global_config->relays_init;
 | 
			
		||||
        }
 | 
			
		||||
        if(relay_default == -1)
 | 
			
		||||
        {
 | 
			
		||||
            relay_default = global_config->relay_configs[i].inverted;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(global_config->relay_configs[i].driver == RELAY_DRIVER_GPIO)
 | 
			
		||||
        {
 | 
			
		||||
            pinMode(global_config->relay_configs[i].pin, OUTPUT);
 | 
			
		||||
            driver_gpio_set(global_config->relay_configs[i].pin, relay_default);
 | 
			
		||||
        }
 | 
			
		||||
        if(global_config->relay_configs[i].driver == RELAY_DRIVER_PIFACE)
 | 
			
		||||
        {
 | 
			
		||||
            if(!piface_setup)
 | 
			
		||||
            {
 | 
			
		||||
                piFaceSetup(PIFACE_GPIO_BASE);
 | 
			
		||||
                piface_setup = 1;
 | 
			
		||||
            }
 | 
			
		||||
            driver_piface_set(global_config->relay_configs[i].pin, relay_default);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /******************** CHECK FOR TESTING RUN ********************/
 | 
			
		||||
 | 
			
		||||
    if(cli.demo_mode)
 | 
			
		||||
    {
 | 
			
		||||
        runner_test(global_controller);
 | 
			
		||||
        terminate(0);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /******************** START MAIN LOOP ********************/
 | 
			
		||||
 | 
			
		||||
    time_t timer = 0;
 | 
			
		||||
 | 
			
		||||
    for (;;)
 | 
			
		||||
    {
 | 
			
		||||
        mg_mgr_poll(&mgr, 200);
 | 
			
		||||
        if(time(NULL) - timer >= 1)
 | 
			
		||||
        {
 | 
			
		||||
            if(!global_connection_mqtt)
 | 
			
		||||
            {
 | 
			
		||||
                connection_mqtt_connect(&mgr);
 | 
			
		||||
            }
 | 
			
		||||
            handler_loop(global_connection_mqtt);
 | 
			
		||||
            timer = time(NULL);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    terminate(0);
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,256 +0,0 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <uuid/uuid.h>
 | 
			
		||||
 | 
			
		||||
#include <sqlite3.h>
 | 
			
		||||
#include <models/controller.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#include <constants.h>
 | 
			
		||||
#include <database.h>
 | 
			
		||||
 | 
			
		||||
controller_t *global_controller;
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
db_update_insert(controller_t *controller, sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    LOGGER_DEBUG("saving controller '%s' into database (id: %d)\n", controller->name, controller->id);
 | 
			
		||||
    int rc;
 | 
			
		||||
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, controller->id);
 | 
			
		||||
    sqlite3_bind_blob(stmt, 2, controller->uid, sizeof(uuid_t), SQLITE_STATIC);
 | 
			
		||||
    sqlite3_bind_text(stmt, 3, controller->name, -1, SQLITE_STATIC);
 | 
			
		||||
    sqlite3_bind_int(stmt, 4, controller->command_port);
 | 
			
		||||
 | 
			
		||||
    rc = sqlite3_step(stmt);
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    return rc != SQLITE_DONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static controller_t*
 | 
			
		||||
controller_db_select_mapper(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    controller_t *new_controller = malloc(sizeof(controller_t));
 | 
			
		||||
    for(int i = 0; i < sqlite3_column_count(stmt); i++)
 | 
			
		||||
    {
 | 
			
		||||
        const char *name = sqlite3_column_name(stmt, i);
 | 
			
		||||
        switch(name[0])
 | 
			
		||||
        {
 | 
			
		||||
            case 'i': // id
 | 
			
		||||
                new_controller->id = sqlite3_column_int(stmt, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'c': // command_port
 | 
			
		||||
                new_controller->command_port = sqlite3_column_int(stmt, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'n': // name
 | 
			
		||||
                strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'u': // uid
 | 
			
		||||
                uuid_copy(new_controller->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
 | 
			
		||||
                break;
 | 
			
		||||
            default: // ignore columns not implemented
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    new_controller->relays = malloc(sizeof(relay_t) * global_config->relay_count);
 | 
			
		||||
    uint8_t i;
 | 
			
		||||
    for(i = 0; i < global_config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        new_controller->relays[i] = relay_load(i);
 | 
			
		||||
        if(!new_controller->relays[i])
 | 
			
		||||
        {
 | 
			
		||||
            new_controller->relays[i] = relay_create(i);
 | 
			
		||||
            relay_save(new_controller->relays[i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return new_controller;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static controller_t**
 | 
			
		||||
controller_db_select(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    controller_t **all_controllers = malloc(sizeof(controller_t*));
 | 
			
		||||
 | 
			
		||||
    int row = 0;
 | 
			
		||||
 | 
			
		||||
    for(;;)
 | 
			
		||||
    {
 | 
			
		||||
        int s;
 | 
			
		||||
 | 
			
		||||
        s = sqlite3_step(stmt);
 | 
			
		||||
        if (s == SQLITE_ROW)
 | 
			
		||||
        {
 | 
			
		||||
            controller_t *new_controller = controller_db_select_mapper(stmt);
 | 
			
		||||
            row++;
 | 
			
		||||
 | 
			
		||||
            all_controllers = (controller_t**)realloc(all_controllers, sizeof(controller_t*) * (row + 1));
 | 
			
		||||
            all_controllers[row - 1] = new_controller;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if(s == SQLITE_DONE)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
    all_controllers[row] = NULL;
 | 
			
		||||
    return all_controllers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
controller_save()
 | 
			
		||||
{
 | 
			
		||||
    int opened_transaction = database_transaction_begin();
 | 
			
		||||
     
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
    if(global_controller->id)
 | 
			
		||||
    {
 | 
			
		||||
        sqlite3_prepare_v2(global_database, "UPDATE controllers set uid = ?2, name = ?3, command_port = ?4 WHERE id = ?1;", -1, &stmt, NULL);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        sqlite3_prepare_v2(global_database, "INSERT INTO controllers(uid, name, command_port) values (?2, ?3, ?4);", -1, &stmt, NULL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int result = db_update_insert(global_controller, stmt);
 | 
			
		||||
 | 
			
		||||
    if(result)
 | 
			
		||||
    {
 | 
			
		||||
        if(global_controller->id)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(opened_transaction)
 | 
			
		||||
        {
 | 
			
		||||
            database_transaction_rollback();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        if(!global_controller->id)
 | 
			
		||||
        {
 | 
			
		||||
            global_controller->id = sqlite3_last_insert_rowid(global_database);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(opened_transaction)
 | 
			
		||||
        {
 | 
			
		||||
            database_transaction_commit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
controller_t*
 | 
			
		||||
controller_load()
 | 
			
		||||
{
 | 
			
		||||
    LOGGER_DEBUG("getting controller from database\n");
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "SELECT * FROM controllers LIMIT 1;", -1, &stmt, NULL);
 | 
			
		||||
 | 
			
		||||
    controller_t **sql_result = controller_db_select(stmt);
 | 
			
		||||
 | 
			
		||||
    controller_t *result = sql_result[0];
 | 
			
		||||
    free(sql_result);
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
controller_remove(controller_t *controller)
 | 
			
		||||
{
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
    if(!controller->id)
 | 
			
		||||
    {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "DELETE FROM controllers WHERE id=?1;", -1, &stmt, NULL);
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, controller->id);
 | 
			
		||||
 | 
			
		||||
    int rc = sqlite3_step(stmt);
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    return rc != SQLITE_DONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
controller_t*
 | 
			
		||||
controller_create(void)
 | 
			
		||||
{
 | 
			
		||||
    controller_t *new_controller = malloc(sizeof(*new_controller));
 | 
			
		||||
    new_controller->id = 0;
 | 
			
		||||
    uuid_generate(new_controller->uid);
 | 
			
		||||
 | 
			
		||||
    strncpy(new_controller->name, global_config->name, MAX_NAME_LENGTH);
 | 
			
		||||
    new_controller->name[MAX_NAME_LENGTH] = '\0';
 | 
			
		||||
 | 
			
		||||
    new_controller->command_port = 0;
 | 
			
		||||
 | 
			
		||||
    new_controller->relays = malloc(sizeof(relay_t) * global_config->relay_count);
 | 
			
		||||
    uint8_t i;
 | 
			
		||||
    for(i = 0; i < global_config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        new_controller->relays[i] = relay_load(i);
 | 
			
		||||
        if(!new_controller->relays[i])
 | 
			
		||||
        {
 | 
			
		||||
            new_controller->relays[i] = relay_create(i);
 | 
			
		||||
            relay_save(new_controller->relays[i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new_controller;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
controller_set_name(controller_t *controller, const char *name)
 | 
			
		||||
{
 | 
			
		||||
    strncpy(controller->name, name, MAX_NAME_LENGTH);
 | 
			
		||||
    controller->name[MAX_NAME_LENGTH] = '\0';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
controller_free(controller_t *controller)
 | 
			
		||||
{
 | 
			
		||||
    for(int i = 0; i < global_config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        relay_free(controller->relays[i]);
 | 
			
		||||
    }
 | 
			
		||||
    free(controller->relays);
 | 
			
		||||
    free(controller);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
controller_debug(controller_t *controller)
 | 
			
		||||
{
 | 
			
		||||
    if(controller == NULL)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_DEBUG("controller is NULL\n");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    char uuid_str[37];
 | 
			
		||||
    uuid_unparse(controller->uid, uuid_str);
 | 
			
		||||
    LOGGER_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)controller);
 | 
			
		||||
    LOGGER_DEBUG("(2/3) name: %s\n", controller->name);
 | 
			
		||||
    LOGGER_DEBUG("(3/3) relays @ %p:\n", (void*)controller->relays);
 | 
			
		||||
    for(int i = 0; i < global_config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        relay_debug(controller->relays[i]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,102 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <models/junction_relay_schedule.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <macros.h>
 | 
			
		||||
#include <database.h>
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id)
 | 
			
		||||
{
 | 
			
		||||
    int rc;
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "INSERT INTO junction_relay_schedule(weekday, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, NULL);
 | 
			
		||||
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, weekday);
 | 
			
		||||
    sqlite3_bind_int(stmt, 2, schedule_id);
 | 
			
		||||
    sqlite3_bind_int(stmt, 3, relay_id);
 | 
			
		||||
 | 
			
		||||
    rc = sqlite3_step(stmt);
 | 
			
		||||
    if (rc != SQLITE_DONE)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids)
 | 
			
		||||
{
 | 
			
		||||
    int rc;
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
 | 
			
		||||
    static const char query_base[] = "INSERT INTO junction_relay_schedule (weekday, schedule_id, relay_id) VALUES";
 | 
			
		||||
    static const char query_extender[] = " (?, ?, ?)";
 | 
			
		||||
 | 
			
		||||
    size_t query_len = STRLEN(query_base) + (7 * (STRLEN(query_extender) + 1)) + 1;
 | 
			
		||||
    char *query = malloc(sizeof(char) * query_len + 1);
 | 
			
		||||
    strncpy(query, query_base, query_len);
 | 
			
		||||
    query_len -= STRLEN(query_base);
 | 
			
		||||
    for(int i = 0; i < 7; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        strncat(query, query_extender, query_len);
 | 
			
		||||
        query_len -= STRLEN(query_extender);
 | 
			
		||||
        char *query_divider = (i < 7 - 1) ? "," : ";";
 | 
			
		||||
        strncat(query, query_divider, query_len);
 | 
			
		||||
        query_len -= 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, query, -1, &stmt, NULL);
 | 
			
		||||
 | 
			
		||||
    for(int i = 0; i < 7; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        sqlite3_bind_int(stmt, i * 3 + 1, i);
 | 
			
		||||
        sqlite3_bind_int(stmt, i * 3 + 2, schedule_ids[i]);
 | 
			
		||||
        sqlite3_bind_int(stmt, i * 3 + 3, relay_id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rc = sqlite3_step(stmt);
 | 
			
		||||
    if (rc != SQLITE_DONE)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_ERR("error inserting data: %s", sqlite3_errmsg(global_database));
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
junction_relay_schedule_remove_for_relay(int relay_id)
 | 
			
		||||
{
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
    int rc;
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "DELETE FROM junction_relay_schedule WHERE relay_id=?1;", -1, &stmt, NULL);
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, relay_id);
 | 
			
		||||
    rc = sqlite3_step(stmt);
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    return rc == SQLITE_DONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int*
 | 
			
		||||
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id)
 | 
			
		||||
{
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "SELECT DISTINCT relay_id FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, NULL);
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, schedule_id);
 | 
			
		||||
 | 
			
		||||
    return database_helper_get_ids(stmt);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,37 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <constants.h>
 | 
			
		||||
#include <models/period.h>
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
period_includes_time(period_t period, struct tm *time_struct)
 | 
			
		||||
{
 | 
			
		||||
    uint16_t start = period.start;
 | 
			
		||||
    uint16_t end = period.end;
 | 
			
		||||
 | 
			
		||||
    time_t timestamp = time_struct->tm_hour * 60;
 | 
			
		||||
    timestamp += time_struct->tm_min;
 | 
			
		||||
 | 
			
		||||
    // "normal" timespan
 | 
			
		||||
    if(start < end)
 | 
			
		||||
    {
 | 
			
		||||
        if(start <= timestamp && end > timestamp)
 | 
			
		||||
        {
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // timespan goes through 00:00
 | 
			
		||||
    if(end < start)
 | 
			
		||||
    {
 | 
			
		||||
        if(start >= timestamp && end < timestamp)
 | 
			
		||||
        {
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,293 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <uuid/uuid.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <helpers.h>
 | 
			
		||||
#include <database.h>
 | 
			
		||||
#include <models/relay.h>
 | 
			
		||||
#include <models/junction_relay_schedule.h>
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
db_update_insert(relay_t *relay, sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    LOGGER_DEBUG("saving relay '%s' into database (id: %d)\n", relay->name, relay->id);
 | 
			
		||||
    int rc;
 | 
			
		||||
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, relay->id);
 | 
			
		||||
    sqlite3_bind_int(stmt, 2, relay->number);
 | 
			
		||||
    sqlite3_bind_text(stmt, 3, relay->name, -1, SQLITE_STATIC);
 | 
			
		||||
 | 
			
		||||
    rc = sqlite3_step(stmt);
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    return rc != SQLITE_DONE;
 | 
			
		||||
}
 | 
			
		||||
static relay_t*
 | 
			
		||||
relay_db_select_mapper(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    relay_t *new_relay = malloc(sizeof(relay_t));
 | 
			
		||||
    new_relay->is_on = 0;
 | 
			
		||||
 | 
			
		||||
    for(int i = 0; i < sqlite3_column_count(stmt); i++)
 | 
			
		||||
    {
 | 
			
		||||
        const char *name = sqlite3_column_name(stmt, i);
 | 
			
		||||
        switch(name[0])
 | 
			
		||||
        {
 | 
			
		||||
            case 'i':
 | 
			
		||||
                new_relay->id = sqlite3_column_int(stmt, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'n':
 | 
			
		||||
                switch(name[1])
 | 
			
		||||
                {
 | 
			
		||||
                    case 'a': // name
 | 
			
		||||
                        strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'u': // number
 | 
			
		||||
                        new_relay->number = sqlite3_column_int(stmt, i);
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            default: // ignore columns not implemented
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    memset(new_relay->schedules, 0, sizeof(schedule_t*) * 7);
 | 
			
		||||
    relay_reload_schedules(new_relay);
 | 
			
		||||
 | 
			
		||||
    new_relay->is_on = -1;
 | 
			
		||||
    new_relay->pulse_timer = 0;
 | 
			
		||||
    new_relay->sent_to_broker = 0;
 | 
			
		||||
 | 
			
		||||
    return new_relay;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static relay_t**
 | 
			
		||||
relay_db_select(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    relay_t **all_relays = malloc(sizeof(relay_t*));
 | 
			
		||||
 | 
			
		||||
    int row = 0;
 | 
			
		||||
 | 
			
		||||
    for(;;)
 | 
			
		||||
    {
 | 
			
		||||
        int s;
 | 
			
		||||
 | 
			
		||||
        s = sqlite3_step(stmt);
 | 
			
		||||
        if (s == SQLITE_ROW)
 | 
			
		||||
        {
 | 
			
		||||
            relay_t *new_relay = relay_db_select_mapper(stmt);
 | 
			
		||||
            row++;
 | 
			
		||||
 | 
			
		||||
            all_relays = (relay_t**)realloc(all_relays, sizeof(relay_t*) * (row + 1));
 | 
			
		||||
            all_relays[row - 1] = new_relay;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if(s == SQLITE_DONE)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_ERR("error selecting relays from database: %s\n", sqlite3_errstr(s));
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
    all_relays[row] = NULL;
 | 
			
		||||
    return all_relays;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
relay_save(relay_t *relay)
 | 
			
		||||
{
 | 
			
		||||
    int opened_transaction = database_transaction_begin();
 | 
			
		||||
     
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
    if(relay->id)
 | 
			
		||||
    {
 | 
			
		||||
        sqlite3_prepare_v2(global_database, "UPDATE relays set number = ?2, name = ?3 WHERE id = ?1;", -1, &stmt, NULL);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        sqlite3_prepare_v2(global_database, "INSERT INTO relays(number, name) values (?2, ?3);", -1, &stmt, NULL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int result = db_update_insert(relay, stmt);
 | 
			
		||||
 | 
			
		||||
    if(result)
 | 
			
		||||
    {
 | 
			
		||||
        if(relay->id)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(opened_transaction)
 | 
			
		||||
        {
 | 
			
		||||
            database_transaction_rollback();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        if(relay->id == 0)
 | 
			
		||||
        {
 | 
			
		||||
            relay->id = sqlite3_last_insert_rowid(global_database);
 | 
			
		||||
            LOGGER_DEBUG("new relay - new id: %d\n", relay->id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        LOGGER_DEBUG("cleaning relay_schedule junction\n");
 | 
			
		||||
        junction_relay_schedule_remove_for_relay(relay->id);
 | 
			
		||||
 | 
			
		||||
        LOGGER_DEBUG("rebuilding relay_schedule junction\n");
 | 
			
		||||
        int schedule_ids[7];
 | 
			
		||||
        for(int i = 0; i < 7; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            schedule_ids[i] = relay->schedules[i]->id;
 | 
			
		||||
        }
 | 
			
		||||
        junction_relay_schedule_insert_weekdays(relay->id, schedule_ids);
 | 
			
		||||
 | 
			
		||||
        if(opened_transaction)
 | 
			
		||||
        {
 | 
			
		||||
            database_transaction_commit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
relay_t*
 | 
			
		||||
relay_create(uint8_t number)
 | 
			
		||||
{
 | 
			
		||||
    relay_t *new_relay = malloc(sizeof(relay_t));
 | 
			
		||||
 | 
			
		||||
    new_relay->id = 0;
 | 
			
		||||
    new_relay->number = number;
 | 
			
		||||
    new_relay->name[0] = '\0';
 | 
			
		||||
 | 
			
		||||
    new_relay->is_on = -1;
 | 
			
		||||
    new_relay->pulse_timer = 0;
 | 
			
		||||
    new_relay->sent_to_broker = 0;
 | 
			
		||||
 | 
			
		||||
    uuid_t off_id;
 | 
			
		||||
    memset(off_id, 0, sizeof(uuid_t));
 | 
			
		||||
    memcpy(off_id, "off", 3);
 | 
			
		||||
 | 
			
		||||
    for(int i = 0; i < 7; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        new_relay->schedules[i] = schedule_get_by_uid(off_id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new_relay;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
relay_t*
 | 
			
		||||
relay_load(uint8_t number)
 | 
			
		||||
{
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE number = ?1;", -1, &stmt, NULL);
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, number);
 | 
			
		||||
 | 
			
		||||
    relay_t **sql_result = relay_db_select(stmt);
 | 
			
		||||
 | 
			
		||||
    relay_t *result = sql_result[0];
 | 
			
		||||
    free(sql_result);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
relay_reload_schedules(relay_t *relay)
 | 
			
		||||
{
 | 
			
		||||
    schedule_t **schedules = schedule_get_relay_weekdays(relay->id);
 | 
			
		||||
 | 
			
		||||
    uuid_t off_id;
 | 
			
		||||
    memset(off_id, 0, sizeof(uuid_t));
 | 
			
		||||
    memcpy(off_id, "off", 3);
 | 
			
		||||
 | 
			
		||||
    int fill_with_off = 0;
 | 
			
		||||
    for(int i = 0; i < 7; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        if(schedules[i] == NULL || fill_with_off)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_WARNING("got only %d/7 schedules for relay_id %d\n", i, relay->id);
 | 
			
		||||
            relay->schedules[i] = schedule_get_by_uid(off_id);
 | 
			
		||||
 | 
			
		||||
            fill_with_off = 1;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if(relay->schedules[i])
 | 
			
		||||
            {
 | 
			
		||||
                schedule_free(relay->schedules[i]);
 | 
			
		||||
            }
 | 
			
		||||
            relay->schedules[i] = schedules[i];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    free(schedules); // don't free list, because contents are kept in relay->schedules
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
relay_set_name(relay_t *relay, const char *name)
 | 
			
		||||
{
 | 
			
		||||
    strncpy(relay->name, name, MAX_NAME_LENGTH);
 | 
			
		||||
    relay->name[MAX_NAME_LENGTH] = '\0';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
relay_is_on_schedule(relay_t *relay, struct tm *time_struct)
 | 
			
		||||
{
 | 
			
		||||
    schedule_t *schedule = relay->schedules[helper_get_weekday(time_struct)];
 | 
			
		||||
    if(schedule->periods_count == 0)
 | 
			
		||||
    {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for(uint16_t i = 0; i < schedule->periods_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        if(period_includes_time(schedule->periods[i], time_struct))
 | 
			
		||||
        {
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
relay_debug(relay_t *relay)
 | 
			
		||||
{
 | 
			
		||||
    if(relay == NULL)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_DEBUG("relay is NULL\n");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    LOGGER_DEBUG("(1/3) %3d @ %p\n", relay->number, (void*)relay);
 | 
			
		||||
    LOGGER_DEBUG("(2/3) id: %3d; name: %s\n", relay->id, relay->name);
 | 
			
		||||
    LOGGER_DEBUG("(3/3) schedules @ %p:\n", (void*)relay->schedules);
 | 
			
		||||
    for(int i = 0; i < 7; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        schedule_debug(relay->schedules[i]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
relay_free(relay_t *relay)
 | 
			
		||||
{
 | 
			
		||||
    for(int i = 0; i < 7; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        schedule_free(relay->schedules[i]);
 | 
			
		||||
    }
 | 
			
		||||
    free(relay);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,322 +0,0 @@
 | 
			
		|||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <database.h>
 | 
			
		||||
#include <models/schedule.h>
 | 
			
		||||
#include <models/junction_relay_schedule.h>
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
db_update_insert(schedule_t *schedule, sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    char uuid_str[UUID_STR_LEN];
 | 
			
		||||
    uuid_unparse(schedule->uid, uuid_str);
 | 
			
		||||
    LOGGER_DEBUG("saving schedule '%s' into database (id: %d)\n", uuid_str, schedule->id);
 | 
			
		||||
 | 
			
		||||
    int rc;
 | 
			
		||||
    uint16_t *periods_blob = schedule_periods_to_blob(schedule);
 | 
			
		||||
    int blob_size = (int)sizeof(uint16_t) * ((periods_blob[0] * 2) + 1);
 | 
			
		||||
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, schedule->id);
 | 
			
		||||
    sqlite3_bind_blob(stmt, 2, schedule->uid, sizeof(uuid_t), SQLITE_STATIC);
 | 
			
		||||
    sqlite3_bind_blob(stmt, 3, periods_blob, blob_size, SQLITE_STATIC);
 | 
			
		||||
 | 
			
		||||
    rc = sqlite3_step(stmt);
 | 
			
		||||
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
    free(periods_blob);
 | 
			
		||||
 | 
			
		||||
    return rc != SQLITE_DONE;
 | 
			
		||||
}
 | 
			
		||||
static schedule_t*
 | 
			
		||||
schedule_db_select_mapper(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    const uint16_t *periods_blob;
 | 
			
		||||
    schedule_t *new_schedule = malloc(sizeof(schedule_t));
 | 
			
		||||
    for(int i = 0; i < sqlite3_column_count(stmt); i++)
 | 
			
		||||
    {
 | 
			
		||||
        const char *name = sqlite3_column_name(stmt, i);
 | 
			
		||||
        switch(name[0])
 | 
			
		||||
        {
 | 
			
		||||
            case 'i': // id
 | 
			
		||||
                new_schedule->id = sqlite3_column_int(stmt, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'p': // periods
 | 
			
		||||
                periods_blob = sqlite3_column_blob(stmt, i);
 | 
			
		||||
                new_schedule->periods_count = periods_blob[0];
 | 
			
		||||
                new_schedule->periods = malloc(sizeof(period_t) * periods_blob[0]);
 | 
			
		||||
 | 
			
		||||
                for(int i = 0; i < periods_blob[0]; ++i)
 | 
			
		||||
                {
 | 
			
		||||
                    new_schedule->periods[i].start = periods_blob[(i * 2) + 1];
 | 
			
		||||
                    new_schedule->periods[i].end   = periods_blob[(i * 2) + 2];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
            case 'u': // uid
 | 
			
		||||
                uuid_copy(new_schedule->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
 | 
			
		||||
                break;
 | 
			
		||||
            default: // ignore columns not implemented
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return new_schedule;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static schedule_t**
 | 
			
		||||
schedule_db_select(sqlite3_stmt *stmt)
 | 
			
		||||
{
 | 
			
		||||
    schedule_t **all_schedules = malloc(sizeof(schedule_t*));
 | 
			
		||||
 | 
			
		||||
    int row = 0;
 | 
			
		||||
 | 
			
		||||
    for(;;)
 | 
			
		||||
    {
 | 
			
		||||
        int s;
 | 
			
		||||
 | 
			
		||||
        s = sqlite3_step(stmt);
 | 
			
		||||
        if (s == SQLITE_ROW)
 | 
			
		||||
        {
 | 
			
		||||
            schedule_t *new_schedule = schedule_db_select_mapper(stmt);
 | 
			
		||||
            row++;
 | 
			
		||||
 | 
			
		||||
            all_schedules = (schedule_t**)realloc(all_schedules, sizeof(schedule_t*) * (row + 1));
 | 
			
		||||
            all_schedules[row - 1] = new_schedule;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if(s == SQLITE_DONE)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s));
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    sqlite3_finalize(stmt);
 | 
			
		||||
    all_schedules[row] = NULL;
 | 
			
		||||
    return all_schedules;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
schedule_save(schedule_t *schedule)
 | 
			
		||||
{
 | 
			
		||||
    int opened_transaction = database_transaction_begin();
 | 
			
		||||
     
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
    if(schedule->id)
 | 
			
		||||
    {
 | 
			
		||||
        sqlite3_prepare_v2(global_database, "UPDATE schedules SET uid = ?2, periods = ?3 WHERE id=?1;", -1, &stmt, NULL);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        sqlite3_prepare_v2(global_database, "INSERT INTO schedules(uid, periods) values (?2, ?3);", -1, &stmt, NULL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int result = db_update_insert(schedule, stmt);
 | 
			
		||||
 | 
			
		||||
    if(result)
 | 
			
		||||
    {
 | 
			
		||||
        if(schedule->id)
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(opened_transaction)
 | 
			
		||||
        {
 | 
			
		||||
            database_transaction_rollback();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        if(!schedule->id)
 | 
			
		||||
        {
 | 
			
		||||
            schedule->id = sqlite3_last_insert_rowid(global_database);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(opened_transaction)
 | 
			
		||||
        {
 | 
			
		||||
            database_transaction_commit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
schedule_t**
 | 
			
		||||
schedule_get_relay_weekdays(int relay_id)
 | 
			
		||||
{
 | 
			
		||||
    LOGGER_DEBUG("getting schedules [relay_id=%d] from database\n", relay_id);
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "SELECT schedules.* FROM schedules INNER JOIN junction_relay_schedule ON schedules.id == junction_relay_schedule.schedule_id WHERE junction_relay_schedule.relay_id = ?1 ORDER BY junction_relay_schedule.weekday ASC", -1, &stmt, NULL);
 | 
			
		||||
    sqlite3_bind_int(stmt, 1, relay_id);
 | 
			
		||||
 | 
			
		||||
    return schedule_db_select(stmt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
schedule_t*
 | 
			
		||||
schedule_get_by_uid(uuid_t uid)
 | 
			
		||||
{
 | 
			
		||||
    char uuid_str[UUID_STR_LEN];
 | 
			
		||||
    schedule_uid_unparse(uid, uuid_str);
 | 
			
		||||
    LOGGER_DEBUG("getting schedule [uid=%s] from database\n", uuid_str);
 | 
			
		||||
    sqlite3_stmt *stmt;
 | 
			
		||||
 | 
			
		||||
    sqlite3_prepare_v2(global_database, "SELECT * FROM schedules WHERE uid = ?1;", -1, &stmt, NULL);
 | 
			
		||||
    sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
 | 
			
		||||
 | 
			
		||||
    schedule_t **sql_result = schedule_db_select(stmt);
 | 
			
		||||
 | 
			
		||||
    schedule_t *result = sql_result[0];
 | 
			
		||||
    free(sql_result);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
schedule_t*
 | 
			
		||||
schedule_create(uuid_t uid, uint16_t length, uint16_t *periods_blob)
 | 
			
		||||
{
 | 
			
		||||
    schedule_t *new_schedule = malloc(sizeof(schedule_t));
 | 
			
		||||
 | 
			
		||||
    new_schedule->id = 0;
 | 
			
		||||
    memmove(new_schedule->uid, uid, sizeof(uuid_t));
 | 
			
		||||
 | 
			
		||||
    new_schedule->periods_count = length;
 | 
			
		||||
    new_schedule->periods = NULL;
 | 
			
		||||
 | 
			
		||||
    if(length)
 | 
			
		||||
    {
 | 
			
		||||
        new_schedule->periods = malloc(sizeof(period_t) * length);
 | 
			
		||||
 | 
			
		||||
        for(uint16_t i = 0; i < length; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            new_schedule->periods[i].start = periods_blob[0 + (i * 2)];
 | 
			
		||||
            new_schedule->periods[i].end = periods_blob[1 + (i * 2)];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new_schedule;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t*
 | 
			
		||||
schedule_periods_to_blob(schedule_t *schedule)
 | 
			
		||||
{
 | 
			
		||||
    uint16_t *periods_blob = malloc(sizeof(uint16_t) * ((2 * schedule->periods_count) + 1));
 | 
			
		||||
    periods_blob[0] = schedule->periods_count;
 | 
			
		||||
 | 
			
		||||
    for(uint16_t i = 0; i < schedule->periods_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        periods_blob[1 + (i * 2)] = schedule->periods[i].start;
 | 
			
		||||
        periods_blob[2 + (i * 2)] = schedule->periods[i].end;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return periods_blob;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
schedule_uid_parse(const char *uid_str, uuid_t result)
 | 
			
		||||
{
 | 
			
		||||
    if(strcmp("off", uid_str) == 0)
 | 
			
		||||
    {
 | 
			
		||||
        memset(result, 0, sizeof(uuid_t));
 | 
			
		||||
        memcpy(result, "off", 3);
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if(strcmp("on", uid_str) == 0)
 | 
			
		||||
    {
 | 
			
		||||
        memset(result, 0, sizeof(uuid_t));
 | 
			
		||||
        memcpy(result, "on", 2);
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(uuid_parse(uid_str, result))
 | 
			
		||||
    {
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
schedule_uid_unparse(const uuid_t uid, char *result)
 | 
			
		||||
{
 | 
			
		||||
    uuid_t tmp_uuid;
 | 
			
		||||
 | 
			
		||||
    memset(tmp_uuid, 0, sizeof(uuid_t));
 | 
			
		||||
    memcpy(tmp_uuid, "off", 3);
 | 
			
		||||
    if(uuid_compare(uid, tmp_uuid) == 0)
 | 
			
		||||
    {
 | 
			
		||||
        strcpy(result, "off");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    memset(tmp_uuid, 0, sizeof(uuid_t));
 | 
			
		||||
    memcpy(tmp_uuid, "on", 2);
 | 
			
		||||
    if(uuid_compare(uid, tmp_uuid) == 0)
 | 
			
		||||
    {
 | 
			
		||||
        strcpy(result, "on");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uuid_unparse(uid, result);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
schedule_free(schedule_t *schedule)
 | 
			
		||||
{
 | 
			
		||||
    free(schedule->periods);
 | 
			
		||||
    free(schedule);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
schedule_free_list(schedule_t **schedules)
 | 
			
		||||
{
 | 
			
		||||
    for(int i = 0; schedules[i] != NULL; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        schedule_free(schedules[i]);
 | 
			
		||||
    }
 | 
			
		||||
    free(schedules);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
schedule_debug(schedule_t *schedule)
 | 
			
		||||
{
 | 
			
		||||
    if(schedule == NULL)
 | 
			
		||||
    {
 | 
			
		||||
        LOGGER_DEBUG("schedule is NULL\n");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    char uuid_str[UUID_STR_LEN];
 | 
			
		||||
    schedule_uid_unparse(schedule->uid, uuid_str);
 | 
			
		||||
    LOGGER_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)schedule);
 | 
			
		||||
    LOGGER_DEBUG("(2/3) id: %3d; period count: %3d\n", schedule->id, schedule->periods_count);
 | 
			
		||||
 | 
			
		||||
    // one block: "HH:MM-HH:MM, " --> size: 13 (14 with '\0')
 | 
			
		||||
    char *periods_debug_str = malloc(sizeof(char) * ((schedule->periods_count * 13) + 1));
 | 
			
		||||
    periods_debug_str[0] = '\0';
 | 
			
		||||
 | 
			
		||||
    for(uint16_t i = 0; i < schedule->periods_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        sprintf(
 | 
			
		||||
            periods_debug_str + (13 * i),
 | 
			
		||||
            "%02d:%02d-%02d:%02d, ",
 | 
			
		||||
            schedule->periods[i].start / 60,
 | 
			
		||||
            schedule->periods[i].start % 60,
 | 
			
		||||
            schedule->periods[i].end / 60,
 | 
			
		||||
            schedule->periods[i].end % 60
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LOGGER_DEBUG("(3/3) periods: %s\n", periods_debug_str);
 | 
			
		||||
 | 
			
		||||
    free(periods_debug_str);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +0,0 @@
 | 
			
		|||
#include <unistd.h>   
 | 
			
		||||
 | 
			
		||||
#include <runners.h>
 | 
			
		||||
#include <logger.h>
 | 
			
		||||
#include <drivers.h>
 | 
			
		||||
#include <drivers.h>
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
runner_test()
 | 
			
		||||
{
 | 
			
		||||
    // from x down to 0 to turn all relays off in the end
 | 
			
		||||
    for(uint_fast8_t i = 0; i < global_config->relay_count; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        for(int test_run = 2; test_run >= 0; --test_run)
 | 
			
		||||
        {
 | 
			
		||||
            int is_active = test_run % 2;
 | 
			
		||||
            if(global_config->relay_configs[i].inverted)
 | 
			
		||||
            {
 | 
			
		||||
                is_active = !is_active;
 | 
			
		||||
            }
 | 
			
		||||
            switch(global_config->relay_configs[i].driver)
 | 
			
		||||
            {
 | 
			
		||||
                case RELAY_DRIVER_GPIO:
 | 
			
		||||
                    driver_gpio_set(global_config->relay_configs[i].pin, is_active);
 | 
			
		||||
                    break;
 | 
			
		||||
                case RELAY_DRIVER_PIFACE:
 | 
			
		||||
                    driver_piface_set(global_config->relay_configs[i].pin, is_active);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    LOGGER_WARNING("relay %d is not using a driver\n", i);
 | 
			
		||||
            }
 | 
			
		||||
            sleep(1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										384
									
								
								vendor/argparse.c
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										384
									
								
								vendor/argparse.c
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,384 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
 | 
			
		||||
 * All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by a MIT-style license that can be found
 | 
			
		||||
 * in the LICENSE file.
 | 
			
		||||
 */
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include "argparse.h"
 | 
			
		||||
 | 
			
		||||
#define OPT_UNSET 1
 | 
			
		||||
#define OPT_LONG  (1 << 1)
 | 
			
		||||
 | 
			
		||||
static const char *
 | 
			
		||||
prefix_skip(const char *str, const char *prefix)
 | 
			
		||||
{
 | 
			
		||||
    size_t len = strlen(prefix);
 | 
			
		||||
    return strncmp(str, prefix, len) ? NULL : str + len;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
prefix_cmp(const char *str, const char *prefix)
 | 
			
		||||
{
 | 
			
		||||
    for (;; str++, prefix++)
 | 
			
		||||
        if (!*prefix) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        } else if (*str != *prefix) {
 | 
			
		||||
            return (unsigned char)*prefix - (unsigned char)*str;
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
argparse_error(struct argparse *self, const struct argparse_option *opt,
 | 
			
		||||
               const char *reason, int flags)
 | 
			
		||||
{
 | 
			
		||||
    (void)self;
 | 
			
		||||
    if (flags & OPT_LONG) {
 | 
			
		||||
        fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
 | 
			
		||||
    } else {
 | 
			
		||||
        fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
 | 
			
		||||
    }
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
argparse_getvalue(struct argparse *self, const struct argparse_option *opt,
 | 
			
		||||
                  int flags)
 | 
			
		||||
{
 | 
			
		||||
    const char *s = NULL;
 | 
			
		||||
    if (!opt->value)
 | 
			
		||||
        goto skipped;
 | 
			
		||||
    switch (opt->type) {
 | 
			
		||||
    case ARGPARSE_OPT_BOOLEAN:
 | 
			
		||||
        if (flags & OPT_UNSET) {
 | 
			
		||||
            *(int *)opt->value = *(int *)opt->value - 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            *(int *)opt->value = *(int *)opt->value + 1;
 | 
			
		||||
        }
 | 
			
		||||
        if (*(int *)opt->value < 0) {
 | 
			
		||||
            *(int *)opt->value = 0;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    case ARGPARSE_OPT_BIT:
 | 
			
		||||
        if (flags & OPT_UNSET) {
 | 
			
		||||
            *(int *)opt->value &= ~opt->data;
 | 
			
		||||
        } else {
 | 
			
		||||
            *(int *)opt->value |= opt->data;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    case ARGPARSE_OPT_STRING:
 | 
			
		||||
        if (self->optvalue) {
 | 
			
		||||
            *(const char **)opt->value = self->optvalue;
 | 
			
		||||
            self->optvalue             = NULL;
 | 
			
		||||
        } else if (self->argc > 1) {
 | 
			
		||||
            self->argc--;
 | 
			
		||||
            *(const char **)opt->value = *++self->argv;
 | 
			
		||||
        } else {
 | 
			
		||||
            argparse_error(self, opt, "requires a value", flags);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    case ARGPARSE_OPT_INTEGER:
 | 
			
		||||
        errno = 0;
 | 
			
		||||
        if (self->optvalue) {
 | 
			
		||||
            *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
 | 
			
		||||
            self->optvalue     = NULL;
 | 
			
		||||
        } else if (self->argc > 1) {
 | 
			
		||||
            self->argc--;
 | 
			
		||||
            *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
 | 
			
		||||
        } else {
 | 
			
		||||
            argparse_error(self, opt, "requires a value", flags);
 | 
			
		||||
        }
 | 
			
		||||
        if (errno)
 | 
			
		||||
            argparse_error(self, opt, strerror(errno), flags);
 | 
			
		||||
        if (s[0] != '\0')
 | 
			
		||||
            argparse_error(self, opt, "expects an integer value", flags);
 | 
			
		||||
        break;
 | 
			
		||||
    case ARGPARSE_OPT_FLOAT:
 | 
			
		||||
        errno = 0;
 | 
			
		||||
        if (self->optvalue) {
 | 
			
		||||
            *(float *)opt->value = strtof(self->optvalue, (char **)&s);
 | 
			
		||||
            self->optvalue       = NULL;
 | 
			
		||||
        } else if (self->argc > 1) {
 | 
			
		||||
            self->argc--;
 | 
			
		||||
            *(float *)opt->value = strtof(*++self->argv, (char **)&s);
 | 
			
		||||
        } else {
 | 
			
		||||
            argparse_error(self, opt, "requires a value", flags);
 | 
			
		||||
        }
 | 
			
		||||
        if (errno)
 | 
			
		||||
            argparse_error(self, opt, strerror(errno), flags);
 | 
			
		||||
        if (s[0] != '\0')
 | 
			
		||||
            argparse_error(self, opt, "expects a numerical value", flags);
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        assert(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
skipped:
 | 
			
		||||
    if (opt->callback) {
 | 
			
		||||
        return opt->callback(self, opt);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
argparse_options_check(const struct argparse_option *options)
 | 
			
		||||
{
 | 
			
		||||
    for (; options->type != ARGPARSE_OPT_END; options++) {
 | 
			
		||||
        switch (options->type) {
 | 
			
		||||
        case ARGPARSE_OPT_END:
 | 
			
		||||
        case ARGPARSE_OPT_BOOLEAN:
 | 
			
		||||
        case ARGPARSE_OPT_BIT:
 | 
			
		||||
        case ARGPARSE_OPT_INTEGER:
 | 
			
		||||
        case ARGPARSE_OPT_FLOAT:
 | 
			
		||||
        case ARGPARSE_OPT_STRING:
 | 
			
		||||
        case ARGPARSE_OPT_GROUP:
 | 
			
		||||
            continue;
 | 
			
		||||
        default:
 | 
			
		||||
            fprintf(stderr, "wrong option type: %d", options->type);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
argparse_short_opt(struct argparse *self, const struct argparse_option *options)
 | 
			
		||||
{
 | 
			
		||||
    for (; options->type != ARGPARSE_OPT_END; options++) {
 | 
			
		||||
        if (options->short_name == *self->optvalue) {
 | 
			
		||||
            self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
 | 
			
		||||
            return argparse_getvalue(self, options, 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return -2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
argparse_long_opt(struct argparse *self, const struct argparse_option *options)
 | 
			
		||||
{
 | 
			
		||||
    for (; options->type != ARGPARSE_OPT_END; options++) {
 | 
			
		||||
        const char *rest;
 | 
			
		||||
        int opt_flags = 0;
 | 
			
		||||
        if (!options->long_name)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        rest = prefix_skip(self->argv[0] + 2, options->long_name);
 | 
			
		||||
        if (!rest) {
 | 
			
		||||
            // negation disabled?
 | 
			
		||||
            if (options->flags & OPT_NONEG) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // only OPT_BOOLEAN/OPT_BIT supports negation
 | 
			
		||||
            if (options->type != ARGPARSE_OPT_BOOLEAN && options->type !=
 | 
			
		||||
                ARGPARSE_OPT_BIT) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (prefix_cmp(self->argv[0] + 2, "no-")) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
 | 
			
		||||
            if (!rest)
 | 
			
		||||
                continue;
 | 
			
		||||
            opt_flags |= OPT_UNSET;
 | 
			
		||||
        }
 | 
			
		||||
        if (*rest) {
 | 
			
		||||
            if (*rest != '=')
 | 
			
		||||
                continue;
 | 
			
		||||
            self->optvalue = rest + 1;
 | 
			
		||||
        }
 | 
			
		||||
        return argparse_getvalue(self, options, opt_flags | OPT_LONG);
 | 
			
		||||
    }
 | 
			
		||||
    return -2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
argparse_init(struct argparse *self, struct argparse_option *options,
 | 
			
		||||
              const char *const *usages, int flags)
 | 
			
		||||
{
 | 
			
		||||
    memset(self, 0, sizeof(*self));
 | 
			
		||||
    self->options     = options;
 | 
			
		||||
    self->usages      = usages;
 | 
			
		||||
    self->flags       = flags;
 | 
			
		||||
    self->description = NULL;
 | 
			
		||||
    self->epilog      = NULL;
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
argparse_describe(struct argparse *self, const char *description,
 | 
			
		||||
                  const char *epilog)
 | 
			
		||||
{
 | 
			
		||||
    self->description = description;
 | 
			
		||||
    self->epilog      = epilog;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
argparse_parse(struct argparse *self, int argc, const char **argv)
 | 
			
		||||
{
 | 
			
		||||
    self->argc = argc - 1;
 | 
			
		||||
    self->argv = argv + 1;
 | 
			
		||||
    self->out  = argv;
 | 
			
		||||
 | 
			
		||||
    argparse_options_check(self->options);
 | 
			
		||||
 | 
			
		||||
    for (; self->argc; self->argc--, self->argv++) {
 | 
			
		||||
        const char *arg = self->argv[0];
 | 
			
		||||
        if (arg[0] != '-' || !arg[1]) {
 | 
			
		||||
            if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
 | 
			
		||||
                goto end;
 | 
			
		||||
            }
 | 
			
		||||
            // if it's not option or is a single char '-', copy verbatim
 | 
			
		||||
            self->out[self->cpidx++] = self->argv[0];
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        // short option
 | 
			
		||||
        if (arg[1] != '-') {
 | 
			
		||||
            self->optvalue = arg + 1;
 | 
			
		||||
            switch (argparse_short_opt(self, self->options)) {
 | 
			
		||||
            case -1:
 | 
			
		||||
                break;
 | 
			
		||||
            case -2:
 | 
			
		||||
                goto unknown;
 | 
			
		||||
            }
 | 
			
		||||
            while (self->optvalue) {
 | 
			
		||||
                switch (argparse_short_opt(self, self->options)) {
 | 
			
		||||
                case -1:
 | 
			
		||||
                    break;
 | 
			
		||||
                case -2:
 | 
			
		||||
                    goto unknown;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        // if '--' presents
 | 
			
		||||
        if (!arg[2]) {
 | 
			
		||||
            self->argc--;
 | 
			
		||||
            self->argv++;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        // long option
 | 
			
		||||
        switch (argparse_long_opt(self, self->options)) {
 | 
			
		||||
        case -1:
 | 
			
		||||
            break;
 | 
			
		||||
        case -2:
 | 
			
		||||
            goto unknown;
 | 
			
		||||
        }
 | 
			
		||||
        continue;
 | 
			
		||||
 | 
			
		||||
unknown:
 | 
			
		||||
        fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
 | 
			
		||||
        argparse_usage(self);
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
end:
 | 
			
		||||
    memmove(self->out + self->cpidx, self->argv,
 | 
			
		||||
            self->argc * sizeof(*self->out));
 | 
			
		||||
    self->out[self->cpidx + self->argc] = NULL;
 | 
			
		||||
 | 
			
		||||
    return self->cpidx + self->argc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
argparse_usage(struct argparse *self)
 | 
			
		||||
{
 | 
			
		||||
    if (self->usages) {
 | 
			
		||||
        fprintf(stdout, "Usage: %s\n", *self->usages++);
 | 
			
		||||
        while (*self->usages && **self->usages)
 | 
			
		||||
            fprintf(stdout, "   or: %s\n", *self->usages++);
 | 
			
		||||
    } else {
 | 
			
		||||
        fprintf(stdout, "Usage:\n");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // print description
 | 
			
		||||
    if (self->description)
 | 
			
		||||
        fprintf(stdout, "%s\n", self->description);
 | 
			
		||||
 | 
			
		||||
    fputc('\n', stdout);
 | 
			
		||||
 | 
			
		||||
    const struct argparse_option *options;
 | 
			
		||||
 | 
			
		||||
    // figure out best width
 | 
			
		||||
    size_t usage_opts_width = 0;
 | 
			
		||||
    size_t len;
 | 
			
		||||
    options = self->options;
 | 
			
		||||
    for (; options->type != ARGPARSE_OPT_END; options++) {
 | 
			
		||||
        len = 0;
 | 
			
		||||
        if ((options)->short_name) {
 | 
			
		||||
            len += 2;
 | 
			
		||||
        }
 | 
			
		||||
        if ((options)->short_name && (options)->long_name) {
 | 
			
		||||
            len += 2;           // separator ", "
 | 
			
		||||
        }
 | 
			
		||||
        if ((options)->long_name) {
 | 
			
		||||
            len += strlen((options)->long_name) + 2;
 | 
			
		||||
        }
 | 
			
		||||
        if (options->type == ARGPARSE_OPT_INTEGER) {
 | 
			
		||||
            len += strlen("=<int>");
 | 
			
		||||
        }
 | 
			
		||||
        if (options->type == ARGPARSE_OPT_FLOAT) {
 | 
			
		||||
            len += strlen("=<flt>");
 | 
			
		||||
        } else if (options->type == ARGPARSE_OPT_STRING) {
 | 
			
		||||
            len += strlen("=<str>");
 | 
			
		||||
        }
 | 
			
		||||
        len = (len + 3) - ((len + 3) & 3);
 | 
			
		||||
        if (usage_opts_width < len) {
 | 
			
		||||
            usage_opts_width = len;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    usage_opts_width += 4;      // 4 spaces prefix
 | 
			
		||||
 | 
			
		||||
    options = self->options;
 | 
			
		||||
    for (; options->type != ARGPARSE_OPT_END; options++) {
 | 
			
		||||
        size_t pos = 0;
 | 
			
		||||
        int pad    = 0;
 | 
			
		||||
        if (options->type == ARGPARSE_OPT_GROUP) {
 | 
			
		||||
            fputc('\n', stdout);
 | 
			
		||||
            fprintf(stdout, "%s", options->help);
 | 
			
		||||
            fputc('\n', stdout);
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        pos = fprintf(stdout, "    ");
 | 
			
		||||
        if (options->short_name) {
 | 
			
		||||
            pos += fprintf(stdout, "-%c", options->short_name);
 | 
			
		||||
        }
 | 
			
		||||
        if (options->long_name && options->short_name) {
 | 
			
		||||
            pos += fprintf(stdout, ", ");
 | 
			
		||||
        }
 | 
			
		||||
        if (options->long_name) {
 | 
			
		||||
            pos += fprintf(stdout, "--%s", options->long_name);
 | 
			
		||||
        }
 | 
			
		||||
        if (options->type == ARGPARSE_OPT_INTEGER) {
 | 
			
		||||
            pos += fprintf(stdout, "=<int>");
 | 
			
		||||
        } else if (options->type == ARGPARSE_OPT_FLOAT) {
 | 
			
		||||
            pos += fprintf(stdout, "=<flt>");
 | 
			
		||||
        } else if (options->type == ARGPARSE_OPT_STRING) {
 | 
			
		||||
            pos += fprintf(stdout, "=<str>");
 | 
			
		||||
        }
 | 
			
		||||
        if (pos <= usage_opts_width) {
 | 
			
		||||
            pad = usage_opts_width - pos;
 | 
			
		||||
        } else {
 | 
			
		||||
            fputc('\n', stdout);
 | 
			
		||||
            pad = usage_opts_width;
 | 
			
		||||
        }
 | 
			
		||||
        fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // print epilog
 | 
			
		||||
    if (self->epilog)
 | 
			
		||||
        fprintf(stdout, "%s\n", self->epilog);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int
 | 
			
		||||
argparse_help_cb(struct argparse *self, const struct argparse_option *option)
 | 
			
		||||
{
 | 
			
		||||
    (void)option;
 | 
			
		||||
    argparse_usage(self);
 | 
			
		||||
    exit(0);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										130
									
								
								vendor/argparse.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										130
									
								
								vendor/argparse.h
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,130 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
 | 
			
		||||
 * All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by a MIT-style license that can be found
 | 
			
		||||
 * in the LICENSE file.
 | 
			
		||||
 */
 | 
			
		||||
#ifndef ARGPARSE_H
 | 
			
		||||
#define ARGPARSE_H
 | 
			
		||||
 | 
			
		||||
/* For c++ compatibility */
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
struct argparse;
 | 
			
		||||
struct argparse_option;
 | 
			
		||||
 | 
			
		||||
typedef int argparse_callback (struct argparse *self,
 | 
			
		||||
                               const struct argparse_option *option);
 | 
			
		||||
 | 
			
		||||
enum argparse_flag {
 | 
			
		||||
    ARGPARSE_STOP_AT_NON_OPTION = 1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum argparse_option_type {
 | 
			
		||||
    /* special */
 | 
			
		||||
    ARGPARSE_OPT_END,
 | 
			
		||||
    ARGPARSE_OPT_GROUP,
 | 
			
		||||
    /* options with no arguments */
 | 
			
		||||
    ARGPARSE_OPT_BOOLEAN,
 | 
			
		||||
    ARGPARSE_OPT_BIT,
 | 
			
		||||
    /* options with arguments (optional or required) */
 | 
			
		||||
    ARGPARSE_OPT_INTEGER,
 | 
			
		||||
    ARGPARSE_OPT_FLOAT,
 | 
			
		||||
    ARGPARSE_OPT_STRING,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum argparse_option_flags {
 | 
			
		||||
    OPT_NONEG = 1,              /* disable negation */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *  argparse option
 | 
			
		||||
 *
 | 
			
		||||
 *  `type`:
 | 
			
		||||
 *    holds the type of the option, you must have an ARGPARSE_OPT_END last in your
 | 
			
		||||
 *    array.
 | 
			
		||||
 *
 | 
			
		||||
 *  `short_name`:
 | 
			
		||||
 *    the character to use as a short option name, '\0' if none.
 | 
			
		||||
 *
 | 
			
		||||
 *  `long_name`:
 | 
			
		||||
 *    the long option name, without the leading dash, NULL if none.
 | 
			
		||||
 *
 | 
			
		||||
 *  `value`:
 | 
			
		||||
 *    stores pointer to the value to be filled.
 | 
			
		||||
 *
 | 
			
		||||
 *  `help`:
 | 
			
		||||
 *    the short help message associated to what the option does.
 | 
			
		||||
 *    Must never be NULL (except for ARGPARSE_OPT_END).
 | 
			
		||||
 *
 | 
			
		||||
 *  `callback`:
 | 
			
		||||
 *    function is called when corresponding argument is parsed.
 | 
			
		||||
 *
 | 
			
		||||
 *  `data`:
 | 
			
		||||
 *    associated data. Callbacks can use it like they want.
 | 
			
		||||
 *
 | 
			
		||||
 *  `flags`:
 | 
			
		||||
 *    option flags.
 | 
			
		||||
 */
 | 
			
		||||
struct argparse_option {
 | 
			
		||||
    enum argparse_option_type type;
 | 
			
		||||
    const char short_name;
 | 
			
		||||
    const char *long_name;
 | 
			
		||||
    void *value;
 | 
			
		||||
    const char *help;
 | 
			
		||||
    argparse_callback *callback;
 | 
			
		||||
    intptr_t data;
 | 
			
		||||
    int flags;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * argpparse
 | 
			
		||||
 */
 | 
			
		||||
struct argparse {
 | 
			
		||||
    // user supplied
 | 
			
		||||
    const struct argparse_option *options;
 | 
			
		||||
    const char *const *usages;
 | 
			
		||||
    int flags;
 | 
			
		||||
    const char *description;    // a description after usage
 | 
			
		||||
    const char *epilog;         // a description at the end
 | 
			
		||||
    // internal context
 | 
			
		||||
    int argc;
 | 
			
		||||
    const char **argv;
 | 
			
		||||
    const char **out;
 | 
			
		||||
    int cpidx;
 | 
			
		||||
    const char *optvalue;       // current option value
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// built-in callbacks
 | 
			
		||||
int argparse_help_cb(struct argparse *self,
 | 
			
		||||
                     const struct argparse_option *option);
 | 
			
		||||
 | 
			
		||||
// built-in option macros
 | 
			
		||||
#define OPT_END()        { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 }
 | 
			
		||||
#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ }
 | 
			
		||||
#define OPT_BIT(...)     { ARGPARSE_OPT_BIT, __VA_ARGS__ }
 | 
			
		||||
#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ }
 | 
			
		||||
#define OPT_FLOAT(...)   { ARGPARSE_OPT_FLOAT, __VA_ARGS__ }
 | 
			
		||||
#define OPT_STRING(...)  { ARGPARSE_OPT_STRING, __VA_ARGS__ }
 | 
			
		||||
#define OPT_GROUP(h)     { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 }
 | 
			
		||||
#define OPT_HELP()       OPT_BOOLEAN('h', "help", NULL,                 \
 | 
			
		||||
                                     "show this help message and exit", \
 | 
			
		||||
                                     argparse_help_cb, 0, OPT_NONEG)
 | 
			
		||||
 | 
			
		||||
int argparse_init(struct argparse *self, struct argparse_option *options,
 | 
			
		||||
                  const char *const *usages, int flags);
 | 
			
		||||
void argparse_describe(struct argparse *self, const char *description,
 | 
			
		||||
                       const char *epilog);
 | 
			
		||||
int argparse_parse(struct argparse *self, int argc, const char **argv);
 | 
			
		||||
void argparse_usage(struct argparse *self);
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										16173
									
								
								vendor/mongoose.c
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										16173
									
								
								vendor/mongoose.c
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										6285
									
								
								vendor/mongoose.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6285
									
								
								vendor/mongoose.h
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										6440
									
								
								vendor/mpack.c
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6440
									
								
								vendor/mpack.c
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										7151
									
								
								vendor/mpack.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										7151
									
								
								vendor/mpack.h
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										2274
									
								
								vendor/toml.c
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2274
									
								
								vendor/toml.c
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										175
									
								
								vendor/toml.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										175
									
								
								vendor/toml.h
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,175 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
  MIT License
 | 
			
		||||
  
 | 
			
		||||
  Copyright (c) 2017 - 2019 CK Tan
 | 
			
		||||
  https://github.com/cktan/tomlc99
 | 
			
		||||
  
 | 
			
		||||
  Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
  of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
  in the Software without restriction, including without limitation the rights
 | 
			
		||||
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
  copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
  furnished to do so, subject to the following conditions:
 | 
			
		||||
  
 | 
			
		||||
  The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
  copies or substantial portions of the Software.
 | 
			
		||||
  
 | 
			
		||||
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
  SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
#ifndef TOML_H
 | 
			
		||||
#define TOML_H
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
#define TOML_EXTERN extern "C"
 | 
			
		||||
#else
 | 
			
		||||
#define TOML_EXTERN extern
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
typedef struct toml_timestamp_t toml_timestamp_t;
 | 
			
		||||
typedef struct toml_table_t toml_table_t;
 | 
			
		||||
typedef struct toml_array_t toml_array_t;
 | 
			
		||||
typedef struct toml_datum_t toml_datum_t;
 | 
			
		||||
 | 
			
		||||
/* Parse a file. Return a table on success, or 0 otherwise. 
 | 
			
		||||
 * Caller must toml_free(the-return-value) after use.
 | 
			
		||||
 */
 | 
			
		||||
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp, 
 | 
			
		||||
										  char* errbuf,
 | 
			
		||||
										  int errbufsz);
 | 
			
		||||
 | 
			
		||||
/* Parse a string containing the full config. 
 | 
			
		||||
 * Return a table on success, or 0 otherwise.
 | 
			
		||||
 * Caller must toml_free(the-return-value) after use.
 | 
			
		||||
 */
 | 
			
		||||
TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
 | 
			
		||||
									 char* errbuf,
 | 
			
		||||
									 int errbufsz);
 | 
			
		||||
 | 
			
		||||
/* Free the table returned by toml_parse() or toml_parse_file(). Once 
 | 
			
		||||
 * this function is called, any handles accessed through this tab 
 | 
			
		||||
 * directly or indirectly are no longer valid.
 | 
			
		||||
 */
 | 
			
		||||
TOML_EXTERN void toml_free(toml_table_t* tab);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Timestamp types. The year, month, day, hour, minute, second, z 
 | 
			
		||||
 * fields may be NULL if they are not relevant. e.g. In a DATE
 | 
			
		||||
 * type, the hour, minute, second and z fields will be NULLs.
 | 
			
		||||
 */
 | 
			
		||||
struct toml_timestamp_t {
 | 
			
		||||
	struct { /* internal. do not use. */
 | 
			
		||||
		int year, month, day;
 | 
			
		||||
		int hour, minute, second, millisec;
 | 
			
		||||
		char z[10];
 | 
			
		||||
	} __buffer;
 | 
			
		||||
	int *year, *month, *day;
 | 
			
		||||
	int *hour, *minute, *second, *millisec;
 | 
			
		||||
	char* z;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*-----------------------------------------------------------------
 | 
			
		||||
 *  Enhanced access methods 
 | 
			
		||||
 */
 | 
			
		||||
struct toml_datum_t {
 | 
			
		||||
	int ok;
 | 
			
		||||
	union {
 | 
			
		||||
		toml_timestamp_t* ts; /* ts must be freed after use */
 | 
			
		||||
		char*   s; /* string value. s must be freed after use */
 | 
			
		||||
		int     b; /* bool value */
 | 
			
		||||
		int64_t i; /* int value */
 | 
			
		||||
		double  d; /* double value */
 | 
			
		||||
	} u;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* on arrays: */
 | 
			
		||||
/* ... retrieve size of array. */
 | 
			
		||||
TOML_EXTERN int toml_array_nelem(const toml_array_t* arr);
 | 
			
		||||
/* ... retrieve values using index. */
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx);
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx);
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx);
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx);
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx);
 | 
			
		||||
/* ... retrieve array or table using index. */
 | 
			
		||||
TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx);
 | 
			
		||||
TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx);
 | 
			
		||||
 | 
			
		||||
/* on tables: */
 | 
			
		||||
/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
 | 
			
		||||
TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx);
 | 
			
		||||
/* ... retrieve values using key. */
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key);
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key);
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key);
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key);
 | 
			
		||||
TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key);
 | 
			
		||||
/* .. retrieve array or table using key. */
 | 
			
		||||
TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab,
 | 
			
		||||
										const char* key);
 | 
			
		||||
TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
 | 
			
		||||
										const char* key);
 | 
			
		||||
 | 
			
		||||
/*-----------------------------------------------------------------
 | 
			
		||||
 * lesser used 
 | 
			
		||||
 */
 | 
			
		||||
/* Return the array kind: 't'able, 'a'rray, 'v'alue */
 | 
			
		||||
TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
 | 
			
		||||
 | 
			
		||||
/* For array kind 'v'alue, return the type of values 
 | 
			
		||||
   i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
 | 
			
		||||
   0 if unknown
 | 
			
		||||
*/
 | 
			
		||||
TOML_EXTERN char toml_array_type(const toml_array_t* arr);
 | 
			
		||||
 | 
			
		||||
/* Return the key of an array */
 | 
			
		||||
TOML_EXTERN const char* toml_array_key(const toml_array_t* arr);
 | 
			
		||||
 | 
			
		||||
/* Return the number of key-values in a table */
 | 
			
		||||
TOML_EXTERN int toml_table_nkval(const toml_table_t* tab);
 | 
			
		||||
 | 
			
		||||
/* Return the number of arrays in a table */
 | 
			
		||||
TOML_EXTERN int toml_table_narr(const toml_table_t* tab);
 | 
			
		||||
 | 
			
		||||
/* Return the number of sub-tables in a table */
 | 
			
		||||
TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
 | 
			
		||||
 | 
			
		||||
/* Return the key of a table*/
 | 
			
		||||
TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
 | 
			
		||||
 | 
			
		||||
/*--------------------------------------------------------------
 | 
			
		||||
 * misc 
 | 
			
		||||
 */
 | 
			
		||||
TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
 | 
			
		||||
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
 | 
			
		||||
TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
 | 
			
		||||
								  void	(*xxfree)(void*));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*--------------------------------------------------------------
 | 
			
		||||
 *  deprecated 
 | 
			
		||||
 */
 | 
			
		||||
/* A raw value, must be processed by toml_rto* before using. */
 | 
			
		||||
typedef const char* toml_raw_t;
 | 
			
		||||
TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key);
 | 
			
		||||
TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx);
 | 
			
		||||
TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret);
 | 
			
		||||
TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret);
 | 
			
		||||
TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret);
 | 
			
		||||
TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret);
 | 
			
		||||
TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen);
 | 
			
		||||
TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* TOML_H */
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
#define EMGAUWA_CONTROLLER_VERSION "@CMAKE_PROJECT_VERSION@"
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue