Compare commits
1 commit
go-rewrite
...
main
Author | SHA1 | Date | |
---|---|---|---|
5afff53301 |
60 changed files with 45543 additions and 170 deletions
|
@ -20,7 +20,9 @@ steps:
|
||||||
api_key:
|
api_key:
|
||||||
from_secret: gitea_token
|
from_secret: gitea_token
|
||||||
base_url: https://git.serguzim.me
|
base_url: https://git.serguzim.me
|
||||||
title: ${DRONE_TAG}
|
files:
|
||||||
|
- build/controller
|
||||||
|
- build/controller.ini
|
||||||
when:
|
when:
|
||||||
event: tag
|
event: tag
|
||||||
|
|
||||||
|
|
67
CMakeLists.txt
Normal file
67
CMakeLists.txt
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
cmake_minimum_required (VERSION 3.7)
|
||||||
|
project(controller
|
||||||
|
VERSION 0.3.7
|
||||||
|
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 "$ENV{CFLAGS}")
|
||||||
|
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra -ffile-prefix-map=${CMAKE_SOURCE_DIR}/src/=")
|
||||||
|
|
||||||
|
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage")
|
||||||
|
|
||||||
|
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("controller.ini" "controller.ini" COPYONLY)
|
||||||
|
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 ./controller start
|
||||||
|
DEPENDS controller
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
|
)
|
||||||
|
add_custom_target(debug
|
||||||
|
COMMAND valgrind ./controller start
|
||||||
|
DEPENDS controller
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
|
)
|
||||||
|
add_custom_target(debug-full
|
||||||
|
COMMAND valgrind --leak-check=full --show-leak-kinds=all ./controller start
|
||||||
|
DEPENDS controller
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
|
)
|
||||||
|
add_custom_target(docs
|
||||||
|
COMMAND doxygen
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
)
|
69
controller.ini
Normal file
69
controller.ini
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
[controller]
|
||||||
|
name = new emgauwa device
|
||||||
|
|
||||||
|
: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
|
||||||
|
discovery-port = 4421
|
||||||
|
: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
|
||||||
|
mqtt-port = 1885
|
||||||
|
mqtt-host = localhost
|
||||||
|
|
||||||
|
relay-count = 10
|
||||||
|
relays-init = 1
|
||||||
|
|
||||||
|
database = controller.sqlite
|
||||||
|
log-level = debug
|
||||||
|
log-file = stdout
|
||||||
|
|
||||||
|
[relay-0]
|
||||||
|
driver = piface
|
||||||
|
pin = 0
|
||||||
|
inverted = 0
|
||||||
|
init = 0
|
||||||
|
|
||||||
|
[relay-1]
|
||||||
|
driver = piface
|
||||||
|
pin = 1
|
||||||
|
inverted = 0
|
||||||
|
init = 1
|
||||||
|
|
||||||
|
[relay-2]
|
||||||
|
driver = gpio
|
||||||
|
pin = 5
|
||||||
|
inverted = 1
|
||||||
|
|
||||||
|
[relay-3]
|
||||||
|
driver = gpio
|
||||||
|
pin = 4
|
||||||
|
inverted = 1
|
||||||
|
|
||||||
|
[relay-4]
|
||||||
|
driver = gpio
|
||||||
|
pin = 3
|
||||||
|
inverted = 1
|
||||||
|
|
||||||
|
[relay-5]
|
||||||
|
driver = gpio
|
||||||
|
pin = 2
|
||||||
|
inverted = 1
|
||||||
|
|
||||||
|
[relay-6]
|
||||||
|
driver = gpio
|
||||||
|
pin = 1
|
||||||
|
inverted = 1
|
||||||
|
pulse-duration = 3
|
||||||
|
|
||||||
|
[relay-7]
|
||||||
|
driver = gpio
|
||||||
|
pin = 0
|
||||||
|
inverted = 1
|
||||||
|
pulse-duration = 3
|
||||||
|
|
||||||
|
[relay-8]
|
||||||
|
driver = gpio
|
||||||
|
pin = 16
|
||||||
|
inverted = 1
|
||||||
|
|
||||||
|
[relay-9]
|
||||||
|
driver = gpio
|
||||||
|
pin = 15
|
||||||
|
inverted = 1
|
|
@ -1,66 +0,0 @@
|
||||||
[controller]
|
|
||||||
database = "emgauwa-controller.sqlite"
|
|
||||||
|
|
||||||
mqtt-host = "127.0.0.1"
|
|
||||||
|
|
||||||
[ports]
|
|
||||||
# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
|
|
||||||
discovery = 4421
|
|
||||||
# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
|
|
||||||
mqtt = 1885
|
|
||||||
|
|
||||||
[logging]
|
|
||||||
level = "debug"
|
|
||||||
file = "stdout"
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "piface"
|
|
||||||
pin = 0
|
|
||||||
inverted = 0
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "piface"
|
|
||||||
pin = 1
|
|
||||||
inverted = 0
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 5
|
|
||||||
inverted = 1
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 4
|
|
||||||
inverted = 1
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 3
|
|
||||||
inverted = 1
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 2
|
|
||||||
inverted = 1
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 1
|
|
||||||
inverted = 1
|
|
||||||
pulse-duration = 3
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 0
|
|
||||||
inverted = 1
|
|
||||||
pulse-duration = 3
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 16
|
|
||||||
inverted = 1
|
|
||||||
|
|
||||||
[[relays]]
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 15
|
|
||||||
inverted = 1
|
|
Binary file not shown.
3
go.mod
3
go.mod
|
@ -1,3 +0,0 @@
|
||||||
module emgauwa.app/controller
|
|
||||||
|
|
||||||
go 1.15
|
|
18
include/colors.h
Normal file
18
include/colors.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#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
|
42
include/config.h
Normal file
42
include/config.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef CONTROLLER_CONFIG_H
|
||||||
|
#define CONTROLLER_CONFIG_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <confini.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;
|
||||||
|
|
||||||
|
int
|
||||||
|
config_load(IniDispatch *disp, void *config_void);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char *file;
|
||||||
|
char database[256];
|
||||||
|
char user[256];
|
||||||
|
char group[256];
|
||||||
|
int log_level;
|
||||||
|
FILE *log_file;
|
||||||
|
run_type_t run_type;
|
||||||
|
char name[MAX_NAME_LENGTH + 1];
|
||||||
|
uint16_t discovery_port;
|
||||||
|
uint16_t mqtt_port;
|
||||||
|
char mqtt_host[256];
|
||||||
|
uint8_t relay_count;
|
||||||
|
int relays_init;
|
||||||
|
config_relay_t *relay_configs;
|
||||||
|
} config_t;
|
||||||
|
|
||||||
|
extern config_t global_config;
|
||||||
|
|
||||||
|
#endif //CONTROLLER_CONFIG_H
|
17
include/connections.h
Normal file
17
include/connections.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#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 */
|
31
include/constants.h
Normal file
31
include/constants.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#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 Maximum number of dbs for the databases for the MDB_env
|
||||||
|
*
|
||||||
|
* Used when calling mdb_env_set_maxdbs() in database_setup()
|
||||||
|
*/
|
||||||
|
#define MDB_MAXDBS 8
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief How many milli seconds to wait until poll timeout in main loop
|
||||||
|
*/
|
||||||
|
#define ACCEPT_TIMEOUT_MSECONDS 1000
|
||||||
|
|
||||||
|
#define PIFACE_GPIO_BASE 200
|
||||||
|
|
||||||
|
#endif /* CONTROLLER_CONTANTS_H */
|
40
include/database.h
Normal file
40
include/database.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#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 */
|
13
include/drivers.h
Normal file
13
include/drivers.h
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#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 */
|
31
include/enums.h
Normal file
31
include/enums.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#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;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RUN_TYPE_START,
|
||||||
|
RUN_TYPE_TEST,
|
||||||
|
} run_type_t;
|
||||||
|
|
||||||
|
#endif /* CONTROLLER_ENUMS_H */
|
32
include/handlers.h
Normal file
32
include/handlers.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#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 */
|
37
include/helpers.h
Normal file
37
include/helpers.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#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);
|
||||||
|
|
||||||
|
void
|
||||||
|
helper_parse_cli(int argc, const char **argv, config_t *config);
|
||||||
|
|
||||||
|
int
|
||||||
|
helper_get_weekday(const struct tm *time_struct);
|
||||||
|
|
||||||
|
int
|
||||||
|
helper_drop_privileges();
|
||||||
|
|
||||||
|
#endif /* CONTROLLER_HELPERS_H */
|
22
include/logger.h
Normal file
22
include/logger.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#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
|
6
include/macros.h
Normal file
6
include/macros.h
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef CONTROLLER_MACROS_H
|
||||||
|
#define CONTROLLER_MACROS_H
|
||||||
|
|
||||||
|
#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
|
||||||
|
|
||||||
|
#endif /* CONTROLLER_MACROS_H */
|
106
include/models/controller.h
Normal file
106
include/models/controller.h
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#ifndef CONTROLLER_CONTROLLER_H
|
||||||
|
#define CONTROLLER_CONTROLLER_H
|
||||||
|
|
||||||
|
#include <uuid/uuid.h>
|
||||||
|
#include <stdint.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
|
16
include/models/junction_relay_schedule.h
Normal file
16
include/models/junction_relay_schedule.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#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 */
|
16
include/models/period.h
Normal file
16
include/models/period.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#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 */
|
53
include/models/relay.h
Normal file
53
include/models/relay.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#ifndef CONTROLLER_RELAY_H
|
||||||
|
#define CONTROLLER_RELAY_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.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
|
57
include/models/schedule.h
Normal file
57
include/models/schedule.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#ifndef CONTROLLER_SCHEDULE_H
|
||||||
|
#define CONTROLLER_SCHEDULE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <uuid/uuid.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 */
|
11
include/runners.h
Normal file
11
include/runners.h
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef CONTROLLER_RUNNERS_H
|
||||||
|
#define CONTROLLER_RUNNERS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <models/controller.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
runner_test();
|
||||||
|
|
||||||
|
#endif /* CONTROLLER_RUNNERS_H */
|
0
include/sql/.gitkeep
Normal file
0
include/sql/.gitkeep
Normal file
16
include/wiring_debug.h
Normal file
16
include/wiring_debug.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#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
100
main.go
|
@ -1,100 +0,0 @@
|
||||||
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"))
|
|
||||||
}
|
|
49
sql/migration_0.sql
Normal file
49
sql/migration_0.sql
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
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');
|
191
src/config.c
Normal file
191
src/config.c
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <helpers.h>
|
||||||
|
#include <config.h>
|
||||||
|
#include <logger.h>
|
||||||
|
#include <confini.h>
|
||||||
|
|
||||||
|
#define CONFINI_IS_KEY(SECTION, KEY) \
|
||||||
|
(ini_array_match(SECTION, disp->append_to, '.', disp->format) && \
|
||||||
|
ini_string_match_ii(KEY, disp->data, disp->format))
|
||||||
|
|
||||||
|
static int
|
||||||
|
config_load_log_level(IniDispatch *disp, config_t *config)
|
||||||
|
{
|
||||||
|
if(strcasecmp(disp->value, "debug") == 0)
|
||||||
|
{
|
||||||
|
setlogmask(LOG_UPTO(LOG_DEBUG));
|
||||||
|
config->log_level = LOG_DEBUG;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(strcasecmp(disp->value, "info") == 0)
|
||||||
|
{
|
||||||
|
setlogmask(LOG_UPTO(LOG_INFO));
|
||||||
|
config->log_level = LOG_INFO;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(strcasecmp(disp->value, "notice") == 0)
|
||||||
|
{
|
||||||
|
setlogmask(LOG_UPTO(LOG_NOTICE));
|
||||||
|
config->log_level = LOG_NOTICE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(strcasecmp(disp->value, "warning") == 0)
|
||||||
|
{
|
||||||
|
setlogmask(LOG_UPTO(LOG_WARNING));
|
||||||
|
config->log_level = LOG_WARNING;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(strcasecmp(disp->value, "err") == 0)
|
||||||
|
{
|
||||||
|
setlogmask(LOG_UPTO(LOG_ERR));
|
||||||
|
config->log_level = LOG_ERR;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(strcasecmp(disp->value, "crit") == 0)
|
||||||
|
{
|
||||||
|
setlogmask(LOG_UPTO(LOG_CRIT));
|
||||||
|
config->log_level = LOG_CRIT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(strcasecmp(disp->value, "emerg") == 0)
|
||||||
|
{
|
||||||
|
setlogmask(LOG_UPTO(LOG_EMERG));
|
||||||
|
config->log_level = LOG_EMERG;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LOGGER_WARNING("invalid log-level '%s'\n", disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
config_load_log_file(IniDispatch *disp, config_t *config)
|
||||||
|
{
|
||||||
|
if(strcasecmp(disp->value, "stdout") == 0)
|
||||||
|
{
|
||||||
|
config->log_file = stdout;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(strcasecmp(disp->value, "stderr") == 0)
|
||||||
|
{
|
||||||
|
config->log_file = stderr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
config->log_file = fopen(disp->value, "a+");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
config_load(IniDispatch *disp, void *config_void)
|
||||||
|
{
|
||||||
|
config_t *config = (config_t*)config_void;
|
||||||
|
char relay_section_name[10]; // "relay-255\0" is longest name
|
||||||
|
|
||||||
|
if(disp->type == INI_KEY)
|
||||||
|
{
|
||||||
|
if(CONFINI_IS_KEY("controller", "name"))
|
||||||
|
{
|
||||||
|
strncpy(config->name, disp->value, MAX_NAME_LENGTH);
|
||||||
|
config->name[MAX_NAME_LENGTH] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "database"))
|
||||||
|
{
|
||||||
|
strcpy(config->database, disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "user"))
|
||||||
|
{
|
||||||
|
strcpy(config->user, disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "group"))
|
||||||
|
{
|
||||||
|
strcpy(config->group, disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "log-level"))
|
||||||
|
{
|
||||||
|
return config_load_log_level(disp, config);
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "log-file"))
|
||||||
|
{
|
||||||
|
return config_load_log_file(disp, config);
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "discovery-port"))
|
||||||
|
{
|
||||||
|
config->discovery_port = atoi(disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "mqtt-port"))
|
||||||
|
{
|
||||||
|
config->mqtt_port = atoi(disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "mqtt-host"))
|
||||||
|
{
|
||||||
|
strcpy(config->mqtt_host, disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "relays-init"))
|
||||||
|
{
|
||||||
|
config->relays_init = atoi(disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY("controller", "relay-count"))
|
||||||
|
{
|
||||||
|
config->relay_count = atoi(disp->value);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for(uint8_t i = 0; i < config->relay_count; ++i)
|
||||||
|
{
|
||||||
|
sprintf(relay_section_name, "relay-%u", i);
|
||||||
|
if(CONFINI_IS_KEY(relay_section_name, "pin"))
|
||||||
|
{
|
||||||
|
config->relay_configs[i].pin = atoi(disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY(relay_section_name, "inverted"))
|
||||||
|
{
|
||||||
|
config->relay_configs[i].inverted = atoi(disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY(relay_section_name, "init"))
|
||||||
|
{
|
||||||
|
config->relay_configs[i].init = atoi(disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY(relay_section_name, "pulse-duration"))
|
||||||
|
{
|
||||||
|
config->relay_configs[i].pulse_duration = atoi(disp->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(CONFINI_IS_KEY(relay_section_name, "driver"))
|
||||||
|
{
|
||||||
|
if(strcasecmp(disp->value, "gpio") == 0)
|
||||||
|
{
|
||||||
|
config->relay_configs[i].driver = RELAY_DRIVER_GPIO;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(strcasecmp(disp->value, "piface") == 0)
|
||||||
|
{
|
||||||
|
config->relay_configs[i].driver = RELAY_DRIVER_PIFACE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LOGGER_WARNING("invalid driver '%s' in section '%s'\n", disp->value, relay_section_name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
35
src/connections.c
Normal file
35
src/connections.c
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#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.discovery_port);
|
||||||
|
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.mqtt_port);
|
||||||
|
struct mg_connection *c = mg_connect(mgr, address, handler_mqtt);
|
||||||
|
return c;
|
||||||
|
}
|
264
src/database.c
Normal file
264
src/database.c
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
#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;
|
||||||
|
}
|
11
src/drivers/gpio.c
Normal file
11
src/drivers/gpio.c
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include <wiringPi.h>
|
||||||
|
#include <piFace.h>
|
||||||
|
#include <wiring_debug.h>
|
||||||
|
|
||||||
|
#include <drivers.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
driver_gpio_set(int pin, int value)
|
||||||
|
{
|
||||||
|
digitalWrite(pin, value);
|
||||||
|
}
|
11
src/drivers/piface.c
Normal file
11
src/drivers/piface.c
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#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);
|
||||||
|
}
|
251
src/handlers/command.c
Normal file
251
src/handlers/command.c
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
#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;
|
||||||
|
}
|
95
src/handlers/discovery.c
Normal file
95
src/handlers/discovery.c
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
92
src/handlers/loop.c
Normal file
92
src/handlers/loop.c
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/handlers/mqtt.c
Normal file
43
src/handlers/mqtt.c
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
52
src/helpers/bind_server.c
Normal file
52
src/helpers/bind_server.c
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
#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;
|
||||||
|
}
|
39
src/helpers/connect_server.c
Normal file
39
src/helpers/connect_server.c
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#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;
|
||||||
|
}
|
81
src/helpers/drop_privileges.c
Normal file
81
src/helpers/drop_privileges.c
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
24
src/helpers/get_port.c
Normal file
24
src/helpers/get_port.c
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
11
src/helpers/get_weekday.c
Normal file
11
src/helpers/get_weekday.c
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#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;
|
||||||
|
}
|
57
src/helpers/open_discovery_socket.c
Normal file
57
src/helpers/open_discovery_socket.c
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#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;
|
||||||
|
}
|
70
src/helpers/parse_cli.c
Normal file
70
src/helpers/parse_cli.c
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <argparse.h>
|
||||||
|
#include <config.h>
|
||||||
|
#include <logger.h>
|
||||||
|
#include <helpers.h>
|
||||||
|
#include <version.h>
|
||||||
|
|
||||||
|
static const char *const usage[] = {
|
||||||
|
"controller [options] [[--] args]",
|
||||||
|
"controller [options]",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PERM_READ (1<<0)
|
||||||
|
#define PERM_WRITE (1<<1)
|
||||||
|
#define PERM_EXEC (1<<2)
|
||||||
|
|
||||||
|
void
|
||||||
|
helper_parse_cli(int argc, const char **argv, config_t *config)
|
||||||
|
{
|
||||||
|
int version = 0;
|
||||||
|
struct argparse_option options[] =
|
||||||
|
{
|
||||||
|
OPT_HELP(),
|
||||||
|
OPT_GROUP("Basic options"),
|
||||||
|
OPT_STRING('c', "config", &config->file, "path to config file", 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."
|
||||||
|
);
|
||||||
|
argc = argparse_parse(&argparse, argc, argv);
|
||||||
|
|
||||||
|
if(version)
|
||||||
|
{
|
||||||
|
printf("%s\n", EMGAUWA_CONTROLLER_VERSION);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argc == 1)
|
||||||
|
{
|
||||||
|
if(strcmp(argv[0], "start") == 0)
|
||||||
|
{
|
||||||
|
config->run_type = RUN_TYPE_START;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(strcmp(argv[0], "test") == 0)
|
||||||
|
{
|
||||||
|
config->run_type = RUN_TYPE_TEST;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOGGER_CRIT("bad action '%s' given ('start', 'test')\n", argv[0]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER_CRIT("no action given ('start', 'test')\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
85
src/logger.c
Normal file
85
src/logger.c
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
#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.log_level < level)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
|
||||||
|
char *buffer = malloc(sizeof(char) * (128 + strlen(msg)));
|
||||||
|
sprintf(buffer, "%s[%5s] %s:%d:%s " COLOR_NONE "%s", color, level_str, filename, line, func, msg);
|
||||||
|
|
||||||
|
//fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, msg);
|
||||||
|
vsyslog(level, buffer, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
char *buffer_timed = malloc(sizeof(char) * (strlen(timestamp_str) + strlen(buffer) + 2));
|
||||||
|
sprintf(buffer_timed, "%s %s", timestamp_str, buffer);
|
||||||
|
va_start(args, msg);
|
||||||
|
vfprintf(global_config.log_file, buffer_timed, args);
|
||||||
|
fflush(global_config.log_file);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
free(buffer_timed);
|
||||||
|
}
|
207
src/main.c
Normal file
207
src/main.c
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <syslog.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>
|
||||||
|
#include <confini.h>
|
||||||
|
|
||||||
|
config_t global_config;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
setlogmask(LOG_UPTO(LOG_INFO));
|
||||||
|
|
||||||
|
|
||||||
|
/******************** LOAD CONFIG ********************/
|
||||||
|
|
||||||
|
global_config.file = "controller.ini";
|
||||||
|
global_config.discovery_port = 4421;
|
||||||
|
global_config.mqtt_port = 1885;
|
||||||
|
|
||||||
|
global_config.relay_count = 0;
|
||||||
|
global_config.relays_init = -1;
|
||||||
|
|
||||||
|
global_config.log_level = LOG_INFO;
|
||||||
|
global_config.log_file = stdout;
|
||||||
|
|
||||||
|
strcpy(global_config.user, "");
|
||||||
|
strcpy(global_config.group, "");
|
||||||
|
|
||||||
|
helper_parse_cli(argc, argv, &global_config);
|
||||||
|
|
||||||
|
FILE * const ini_file = fopen(global_config.file, "rb");
|
||||||
|
if(ini_file == NULL)
|
||||||
|
{
|
||||||
|
LOGGER_CRIT("config file '%s' was not found\n", global_config.file);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, config_load, &global_config))
|
||||||
|
{
|
||||||
|
LOGGER_CRIT("unable to parse ini file\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(ini_file);
|
||||||
|
|
||||||
|
if(global_config.log_file == NULL)
|
||||||
|
{
|
||||||
|
global_config.log_file = stdout;
|
||||||
|
}
|
||||||
|
openlog("emgauwa-controller", 0, LOG_USER);
|
||||||
|
|
||||||
|
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(global_config.run_type == RUN_TYPE_TEST)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
256
src/models/controller.c
Normal file
256
src/models/controller.c
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
#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]);
|
||||||
|
}
|
||||||
|
}
|
102
src/models/junction_relay_schedule.c
Normal file
102
src/models/junction_relay_schedule.c
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
#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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
37
src/models/period.c
Normal file
37
src/models/period.c
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#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;
|
||||||
|
}
|
293
src/models/relay.c
Normal file
293
src/models/relay.c
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
#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;
|
||||||
|
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
322
src/models/schedule.c
Normal file
322
src/models/schedule.c
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
#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);
|
||||||
|
}
|
35
src/runners/test.c
Normal file
35
src/runners/test.c
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#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
Normal file
384
vendor/argparse.c
vendored
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
/**
|
||||||
|
* 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
Normal file
130
vendor/argparse.h
vendored
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
/**
|
||||||
|
* 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
|
5016
vendor/confini.c
vendored
Normal file
5016
vendor/confini.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
547
vendor/confini.h
vendored
Normal file
547
vendor/confini.h
vendored
Normal file
|
@ -0,0 +1,547 @@
|
||||||
|
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
@file confini.h
|
||||||
|
@brief libconfini header
|
||||||
|
@author Stefano Gioffré
|
||||||
|
@copyright GNU General Public License, version 3 or any later version
|
||||||
|
@version 1.14.0
|
||||||
|
@date 2016-2020
|
||||||
|
@see https://madmurphy.github.io/libconfini
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _LIBCONFINI_HEADER_
|
||||||
|
#define _LIBCONFINI_HEADER_
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* PRIVATE (HEADER-SCOPED) MACROS */
|
||||||
|
|
||||||
|
|
||||||
|
#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \
|
||||||
|
unsigned char NAME:SIZE;
|
||||||
|
#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL,
|
||||||
|
#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0,
|
||||||
|
#define _LIBCONFINI_INIFORMAT_TYPE_ \
|
||||||
|
struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) }
|
||||||
|
#define _LIBCONFINI_DEFAULT_FORMAT_ \
|
||||||
|
{ INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) }
|
||||||
|
#define _LIBCONFINI_UNIXLIKE_FORMAT_ \
|
||||||
|
{ INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* PUBLIC MACROS */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Calls a user-given macro (that accepts four arguments) for each row
|
||||||
|
of the table
|
||||||
|
**/
|
||||||
|
/*
|
||||||
|
NOTE: The following table and the order of its rows **define** (and link
|
||||||
|
together) both the #IniFormat and #IniFormatNum data types declared in this
|
||||||
|
header
|
||||||
|
*/
|
||||||
|
#define INIFORMAT_TABLE_AS(_____) /* IniFormat table *\
|
||||||
|
|
||||||
|
NAME BIT SIZE DEFAULT
|
||||||
|
*/\
|
||||||
|
_____( delimiter_symbol, 0, 7, INI_EQUALS ) \
|
||||||
|
_____( case_sensitive, 7, 1, false )/*
|
||||||
|
*/\
|
||||||
|
_____( semicolon_marker, 8, 2, INI_DISABLED_OR_COMMENT ) \
|
||||||
|
_____( hash_marker, 10, 2, INI_DISABLED_OR_COMMENT ) \
|
||||||
|
_____( section_paths, 12, 2, INI_ABSOLUTE_AND_RELATIVE ) \
|
||||||
|
_____( multiline_nodes, 14, 2, INI_MULTILINE_EVERYWHERE )/*
|
||||||
|
*/\
|
||||||
|
_____( no_single_quotes, 16, 1, false ) \
|
||||||
|
_____( no_double_quotes, 17, 1, false ) \
|
||||||
|
_____( no_spaces_in_names, 18, 1, false ) \
|
||||||
|
_____( implicit_is_not_empty, 19, 1, false ) \
|
||||||
|
_____( do_not_collapse_values, 20, 1, false ) \
|
||||||
|
_____( preserve_empty_quotes, 21, 1, false ) \
|
||||||
|
_____( disabled_after_space, 22, 1, false ) \
|
||||||
|
_____( disabled_can_be_implicit, 23, 1, false )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Checks whether a format does **not** support escape sequences
|
||||||
|
**/
|
||||||
|
#define INIFORMAT_HAS_NO_ESC(FORMAT) \
|
||||||
|
(FORMAT.multiline_nodes == INI_NO_MULTILINE && \
|
||||||
|
FORMAT.no_double_quotes && FORMAT.no_single_quotes)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* PUBLIC TYPEDEFS */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief 24-bit bitfield representing the format of an INI file (INI
|
||||||
|
dialect)
|
||||||
|
**/
|
||||||
|
typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Global statistics about an INI file
|
||||||
|
**/
|
||||||
|
typedef struct IniStatistics {
|
||||||
|
const IniFormat format;
|
||||||
|
const size_t bytes;
|
||||||
|
const size_t members;
|
||||||
|
} IniStatistics;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Dispatch of a single INI node
|
||||||
|
**/
|
||||||
|
typedef struct IniDispatch {
|
||||||
|
const IniFormat format;
|
||||||
|
uint8_t type;
|
||||||
|
char * data;
|
||||||
|
char * value;
|
||||||
|
const char * append_to;
|
||||||
|
size_t d_len;
|
||||||
|
size_t v_len;
|
||||||
|
size_t at_len;
|
||||||
|
size_t dispatch_id;
|
||||||
|
} IniDispatch;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief The unique ID of an INI format (24-bit maximum)
|
||||||
|
**/
|
||||||
|
typedef uint32_t IniFormatNum;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Callback function for handling an #IniStatistics structure
|
||||||
|
**/
|
||||||
|
typedef int (* IniStatsHandler) (
|
||||||
|
IniStatistics * statistics,
|
||||||
|
void * user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Callback function for handling an #IniDispatch structure
|
||||||
|
**/
|
||||||
|
typedef int (* IniDispHandler) (
|
||||||
|
IniDispatch * dispatch,
|
||||||
|
void * user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Callback function for handling an INI string belonging to a
|
||||||
|
sequence of INI strings
|
||||||
|
**/
|
||||||
|
typedef int (* IniStrHandler) (
|
||||||
|
char * ini_string,
|
||||||
|
size_t string_length,
|
||||||
|
size_t string_num,
|
||||||
|
IniFormat format,
|
||||||
|
void * user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Callback function for handling a selected fragment of an INI string
|
||||||
|
**/
|
||||||
|
typedef int (* IniSubstrHandler) (
|
||||||
|
const char * ini_string,
|
||||||
|
size_t fragm_offset,
|
||||||
|
size_t fragm_length,
|
||||||
|
size_t fragm_num,
|
||||||
|
IniFormat format,
|
||||||
|
void * user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* PUBLIC FUNCTIONS */
|
||||||
|
|
||||||
|
|
||||||
|
extern int strip_ini_cache (
|
||||||
|
register char * const ini_source,
|
||||||
|
const size_t ini_length,
|
||||||
|
const IniFormat format,
|
||||||
|
const IniStatsHandler f_init,
|
||||||
|
const IniDispHandler f_foreach,
|
||||||
|
void * const user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern int load_ini_file (
|
||||||
|
FILE * const ini_file,
|
||||||
|
const IniFormat format,
|
||||||
|
const IniStatsHandler f_init,
|
||||||
|
const IniDispHandler f_foreach,
|
||||||
|
void * const user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern int load_ini_path (
|
||||||
|
const char * const path,
|
||||||
|
const IniFormat format,
|
||||||
|
const IniStatsHandler f_init,
|
||||||
|
const IniDispHandler f_foreach,
|
||||||
|
void * const user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern bool ini_string_match_ss (
|
||||||
|
const char * const simple_string_a,
|
||||||
|
const char * const simple_string_b,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern bool ini_string_match_si (
|
||||||
|
const char * const simple_string,
|
||||||
|
const char * const ini_string,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern bool ini_string_match_ii (
|
||||||
|
const char * const ini_string_a,
|
||||||
|
const char * const ini_string_b,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern bool ini_array_match (
|
||||||
|
const char * const ini_string_a,
|
||||||
|
const char * const ini_string_b,
|
||||||
|
const char delimiter,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern size_t ini_unquote (
|
||||||
|
char * const ini_string,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern size_t ini_string_parse (
|
||||||
|
char * const ini_string,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern size_t ini_array_get_length (
|
||||||
|
const char * const ini_string,
|
||||||
|
const char delimiter,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern int ini_array_foreach (
|
||||||
|
const char * const ini_string,
|
||||||
|
const char delimiter,
|
||||||
|
const IniFormat format,
|
||||||
|
const IniSubstrHandler f_foreach,
|
||||||
|
void * const user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern size_t ini_array_shift (
|
||||||
|
const char ** const ini_strptr,
|
||||||
|
const char delimiter,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern size_t ini_array_collapse (
|
||||||
|
char * const ini_string,
|
||||||
|
const char delimiter,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern char * ini_array_break (
|
||||||
|
char * const ini_string,
|
||||||
|
const char delimiter,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern char * ini_array_release (
|
||||||
|
char ** const ini_strptr,
|
||||||
|
const char delimiter,
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern int ini_array_split (
|
||||||
|
char * const ini_string,
|
||||||
|
const char delimiter,
|
||||||
|
const IniFormat format,
|
||||||
|
const IniStrHandler f_foreach,
|
||||||
|
void * const user_data
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern void ini_global_set_lowercase_mode (
|
||||||
|
const bool lowercase
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern void ini_global_set_implicit_value (
|
||||||
|
char * const implicit_value,
|
||||||
|
const size_t implicit_v_len
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern IniFormatNum ini_fton (
|
||||||
|
const IniFormat format
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern IniFormat ini_ntof (
|
||||||
|
const IniFormatNum format_id
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern int ini_get_bool (
|
||||||
|
const char * const ini_string,
|
||||||
|
const int when_fail
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* PUBLIC LINKS */
|
||||||
|
|
||||||
|
|
||||||
|
extern int (* const ini_get_int) (
|
||||||
|
const char * ini_string
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern long int (* const ini_get_lint) (
|
||||||
|
const char * ini_string
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern long long int (* const ini_get_llint) (
|
||||||
|
const char * ini_string
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
extern double (* const ini_get_double) (
|
||||||
|
const char * ini_string
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Legacy support, soon to be replaced with a `float` data type --
|
||||||
|
please **do not use `ini_get_float()`!**
|
||||||
|
**/
|
||||||
|
#define ini_get_float \
|
||||||
|
_Pragma("GCC warning \"function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead\"") \
|
||||||
|
ini_get_double
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* PUBLIC CONSTANTS AND VARIABLES */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Error mask (flags not present in user-generated interruptions)
|
||||||
|
**/
|
||||||
|
#define CONFINI_ERROR 252
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Error codes
|
||||||
|
**/
|
||||||
|
enum ConfiniInterruptNo {
|
||||||
|
CONFINI_SUCCESS = 0, /**< There have been no interruptions, everything
|
||||||
|
went well [value=0] **/
|
||||||
|
CONFINI_IINTR = 1, /**< Interrupted by the user during `f_init()`
|
||||||
|
[value=1] **/
|
||||||
|
CONFINI_FEINTR = 2, /**< Interrupted by the user during `f_foreach()`
|
||||||
|
[value=2] **/
|
||||||
|
CONFINI_ENOENT = 4, /**< File inaccessible [value=4] **/
|
||||||
|
CONFINI_ENOMEM = 5, /**< Error allocating virtual memory [value=5] **/
|
||||||
|
CONFINI_EIO = 6, /**< Error reading the file [value=6] **/
|
||||||
|
CONFINI_EOOR = 7, /**< Out-of-range error: callbacks are more than
|
||||||
|
expected [value=7] **/
|
||||||
|
CONFINI_EBADF = 8, /**< The stream specified is not a seekable stream
|
||||||
|
[value=8] **/
|
||||||
|
CONFINI_EFBIG = 9, /**< File too large [value=9] **/
|
||||||
|
CONFINI_EROADDR = 10 /**< Address is read-only [value=10] **/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief INI node types
|
||||||
|
**/
|
||||||
|
enum IniNodeType {
|
||||||
|
INI_UNKNOWN = 0, /**< This is a node impossible to categorize
|
||||||
|
[value=0] **/
|
||||||
|
INI_VALUE = 1, /**< Not used by **libconfini** (values are
|
||||||
|
dispatched together with keys) -- but
|
||||||
|
available for user's implementations
|
||||||
|
[value=1] **/
|
||||||
|
INI_KEY = 2, /**< This is a key [value=2] **/
|
||||||
|
INI_SECTION = 3, /**< This is a section or a section path
|
||||||
|
[value=3] **/
|
||||||
|
INI_COMMENT = 4, /**< This is a comment [value=4] **/
|
||||||
|
INI_INLINE_COMMENT = 5, /**< This is an inline comment [value=5] **/
|
||||||
|
INI_DISABLED_KEY = 6, /**< This is a disabled key [value=6] **/
|
||||||
|
INI_DISABLED_SECTION = 7 /**< This is a disabled section path
|
||||||
|
[value=7] **/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Common array and key-value delimiters (but a delimiter may also be
|
||||||
|
any other ASCII character not present in this list)
|
||||||
|
**/
|
||||||
|
enum IniDelimiters {
|
||||||
|
INI_ANY_SPACE = 0, /**< In multi-line INIs:
|
||||||
|
`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in
|
||||||
|
non-multi-line INIs: `/[\t \v\f])+/` **/
|
||||||
|
INI_EQUALS = '=', /**< Equals character (`=`) **/
|
||||||
|
INI_COLON = ':', /**< Colon character (`:`) **/
|
||||||
|
INI_DOT = '.', /**< Dot character (`.`) **/
|
||||||
|
INI_COMMA = ',' /**< Comma character (`,`) **/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Possible values of #IniFormat::semicolon_marker and
|
||||||
|
#IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in
|
||||||
|
respect to a format)
|
||||||
|
**/
|
||||||
|
enum IniCommentMarker {
|
||||||
|
INI_DISABLED_OR_COMMENT = 0, /**< This marker opens a comment or a
|
||||||
|
disabled entry **/
|
||||||
|
INI_ONLY_COMMENT = 1, /**< This marker opens a comment **/
|
||||||
|
INI_IGNORE = 2, /**< This marker opens a comment that has
|
||||||
|
been marked for deletion and must not
|
||||||
|
be dispatched or counted **/
|
||||||
|
INI_IS_NOT_A_MARKER = 3 /**< This is not a marker at all, but a
|
||||||
|
normal character instead **/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Possible values of #IniFormat::section_paths
|
||||||
|
**/
|
||||||
|
enum IniSectionPaths {
|
||||||
|
INI_ABSOLUTE_AND_RELATIVE = 0, /**< Section paths starting with a dot
|
||||||
|
express nesting to the current parent,
|
||||||
|
to root otherwise **/
|
||||||
|
INI_ABSOLUTE_ONLY = 1, /**< Section paths starting with a dot will
|
||||||
|
be cleaned of their leading dot and
|
||||||
|
appended to root **/
|
||||||
|
INI_ONE_LEVEL_ONLY = 2, /**< Format supports sections, but the dot
|
||||||
|
does not express nesting and is not a
|
||||||
|
meta-character **/
|
||||||
|
INI_NO_SECTIONS = 3 /**< Format does *not* support sections --
|
||||||
|
`/\[[^\]]*\]/g`, if any, will be
|
||||||
|
treated as keys! **/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Possible values of #IniFormat::multiline_nodes
|
||||||
|
**/
|
||||||
|
enum IniMultiline {
|
||||||
|
INI_MULTILINE_EVERYWHERE = 0, /**< Comments, section paths and keys
|
||||||
|
-- disabled or not -- are allowed
|
||||||
|
to be multi-line **/
|
||||||
|
INI_BUT_COMMENTS = 1, /**< Only section paths and keys --
|
||||||
|
disabled or not -- are allowed to
|
||||||
|
be multi-line **/
|
||||||
|
INI_BUT_DISABLED_AND_COMMENTS = 2, /**< Only active section paths and
|
||||||
|
active keys are allowed to be
|
||||||
|
multi-line **/
|
||||||
|
INI_NO_MULTILINE = 3 /**< Multi-line escape sequences are
|
||||||
|
disabled **/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief A model format for standard INI files
|
||||||
|
**/
|
||||||
|
static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief A model format for Unix-like .conf files (where space characters
|
||||||
|
are delimiters between keys and values)
|
||||||
|
**/
|
||||||
|
/* All fields are set to `0` here. */
|
||||||
|
static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief If set to `true`, key and section names in case-insensitive INI
|
||||||
|
formats will be dispatched lowercase, verbatim otherwise (default
|
||||||
|
value: `false`)
|
||||||
|
**/
|
||||||
|
extern bool INI_GLOBAL_LOWERCASE_MODE;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Value to be assigned to implicit keys (default value: `NULL`)
|
||||||
|
**/
|
||||||
|
extern char * INI_GLOBAL_IMPLICIT_VALUE;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Length of the value assigned to implicit keys (default value: `0`)
|
||||||
|
**/
|
||||||
|
extern size_t INI_GLOBAL_IMPLICIT_V_LEN;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* CLEAN THE PRIVATE ENVIRONMENT */
|
||||||
|
|
||||||
|
|
||||||
|
#undef _LIBCONFINI_UNIXLIKE_FORMAT_
|
||||||
|
#undef _LIBCONFINI_DEFAULT_FORMAT_
|
||||||
|
#undef _LIBCONFINI_INIFORMAT_TYPE_
|
||||||
|
#undef __INIFORMAT_TABLE_CB_ZERO__
|
||||||
|
#undef __INIFORMAT_TABLE_CB_DEFAULT__
|
||||||
|
#undef __INIFORMAT_TABLE_CB_FIELDS__
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* END OF `_LIBCONFINI_HEADER_` */
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* EOF */
|
||||||
|
|
16173
vendor/mongoose.c
vendored
Normal file
16173
vendor/mongoose.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
6285
vendor/mongoose.h
vendored
Normal file
6285
vendor/mongoose.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
6440
vendor/mpack.c
vendored
Normal file
6440
vendor/mpack.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
7151
vendor/mpack.h
vendored
Normal file
7151
vendor/mpack.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
version.h.in
Normal file
1
version.h.in
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#define EMGAUWA_CONTROLLER_VERSION "@CMAKE_PROJECT_VERSION@"
|
Loading…
Reference in a new issue