Compare commits

...

5 commits

60 changed files with 170 additions and 45547 deletions

View file

@ -20,9 +20,7 @@ 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
files: title: ${DRONE_TAG}
- build/controller
- build/controller.ini
when: when:
event: tag event: tag

View file

@ -1,67 +0,0 @@
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}
)

View file

@ -1,69 +0,0 @@
[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

66
emgauwa-controller.conf Normal file
View file

@ -0,0 +1,66 @@
[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

BIN
emgauwa-controller.sqlite Normal file

Binary file not shown.

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module emgauwa.app/controller
go 1.15

View file

@ -1,18 +0,0 @@
#ifndef CONTROLLER_COLORS_H
#define CONTROLLER_COLORS_H
#define COLOR_RED "\033[0;31m"
#define COLORB_RED "\033[1;31m"
#define COLOR_GREEN "\033[0;32m"
#define COLORB_GREEN "\033[1;32m"
#define COLOR_YELLOW "\033[0;33m"
#define COLORB_YELLOW "\033[1;33m"
#define COLOR_BLUE "\033[0;34m"
#define COLORB_BLUE "\033[1;34m"
#define COLOR_MAGENTA "\033[0;35m"
#define COLORB_MAGENTA "\033[1;35m"
#define COLOR_CYAN "\033[0;36m"
#define COLORB_CYAN "\033[1;36m"
#define COLOR_NONE "\033[0m"
#endif //CONTROLLER_COLORS_H

View file

@ -1,42 +0,0 @@
#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

View file

@ -1,17 +0,0 @@
#ifndef CONTROLLER_CONNECTIONS_H
#define CONTROLLER_CONNECTIONS_H
#include <mongoose.h>
struct mg_connection*
connection_discovery_bind(struct mg_mgr *mgr);
struct mg_connection*
connection_command_bind(struct mg_mgr *mgr);
struct mg_connection*
connection_mqtt_connect(struct mg_mgr *mgr);
extern struct mg_connection *global_connection_mqtt;
#endif /* CONTROLLER_CONNECTIONS_H */

View file

@ -1,31 +0,0 @@
#ifndef CONTROLLER_CONTANTS_H
#define CONTROLLER_CONTANTS_H
#define SECONDS_PER_DAY 86400 // 60 * 60 * 24
#define SECONDS_PER_MINUTE 60
#define POLL_FDS_COUNT 2
/**
* @brief Limit the maximum length of a controller/relay/etc name
*
* The NULL terminator is not included. Arrays of length #MAX_NAME_LENGTH + 1 are required.
*/
#define MAX_NAME_LENGTH 128
/**
* @brief 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 */

View file

@ -1,40 +0,0 @@
#ifndef CONTROLLER_DATABASE_H
#define CONTROLLER_DATABASE_H
#include <sqlite3.h>
extern sqlite3 *global_database;
void
database_init();
void
database_free();
void
database_migrate();
int
database_transaction_begin();
void
database_transaction_commit();
void
database_transaction_rollback();
int
database_helper_get_id(sqlite3_stmt *stmt);
int*
database_helper_get_ids(sqlite3_stmt *stmt);
char*
database_helper_get_string(sqlite3_stmt *stmt);
char**
database_helper_get_strings(sqlite3_stmt *stmt);
#endif /* CONTROLLER_DATABASE_H */

View file

@ -1,13 +0,0 @@
#ifndef CONTROLLER_DRIVERS_H
#define CONTROLLER_DRIVERS_H
#include <models/relay.h>
#include <enums.h>
void
driver_piface_set(int pin, int value);
void
driver_gpio_set(int pin, int value);
#endif /* CONTROLLER_DRIVERS_H */

View file

@ -1,31 +0,0 @@
#ifndef CONTROLLER_ENUMS_H
#define CONTROLLER_ENUMS_H
typedef enum
{
POLL_FDS_DISCOVERY,
POLL_FDS_COMMAND
} poll_fds_t;
typedef enum
{
DISCOVERY_MAPPING_ID = 0,
DISCOVERY_MAPPING_NAME = 1,
DISCOVERY_MAPPING_COMMAND_PORT = 2,
DISCOVERY_MAPPING_RELAY_COUNT = 3,
} discovery_mapping_t;
typedef enum
{
RELAY_DRIVER_NONE,
RELAY_DRIVER_GPIO,
RELAY_DRIVER_PIFACE,
} relay_driver_t;
typedef enum
{
RUN_TYPE_START,
RUN_TYPE_TEST,
} run_type_t;
#endif /* CONTROLLER_ENUMS_H */

View file

@ -1,32 +0,0 @@
#ifndef CONTROLLER_HANDLERS_H
#define CONTROLLER_HANDLERS_H
#include <mongoose.h>
#include <models/controller.h>
/**
* @brief Handle the command processing
*
* @param fd File descriptor to receive initial data from
* @param controller Controller to use for answering command
*/
void
handler_command(struct mg_connection *c, int ev, void *ev_data);
/**
* @brief Handle the discovery processing
*
* @param fd File descriptor to receive initial data from
* @param controller Controller to use for answering discovery
*/
void
handler_discovery(struct mg_connection *c, int ev, void *ev_data);
void
handler_mqtt(struct mg_connection *c, int ev, void *ev_data);
void
handler_loop(struct mg_connection *c_mqtt);
#endif /* CONTROLLER_HANDLERS_H */

View file

@ -1,37 +0,0 @@
#ifndef CONTROLLER_HELPERS_H
#define CONTROLLER_HELPERS_H
#include <time.h>
#include <config.h>
int
helper_connect_tcp_server(char* host, uint16_t port);
int
helper_bind_tcp_server(char* addr, uint16_t port, int max_client_backlog);
uint16_t
helper_get_port(int sock);
/**
* @brief Open socket for discovery
*
* Will exit program when unable to open socket.
*
* @param discovery_port Port number to listen on for discovery broadcasts
*
* @return Open socket to accept discovery broadcasts on
*/
int
helper_open_discovery_socket(uint16_t discovery_port);
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 */

View file

@ -1,22 +0,0 @@
#ifndef EMGAUWA_LOGGER_H
#define EMGAUWA_LOGGER_H
#include <stdio.h>
#include <time.h>
#include <syslog.h>
#include <colors.h>
#include <config.h>
void
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...);
#define LOGGER_DEBUG(...) logger_log(LOG_DEBUG , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_INFO(...) logger_log(LOG_INFO , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_NOTICE(...) logger_log(LOG_NOTICE , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_ERR(...) logger_log(LOG_ERR , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_CRIT(...) logger_log(LOG_CRIT , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_EMERG(...) logger_log(LOG_EMERG , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#endif //EMGAUWA_LOGGER_H

View file

@ -1,6 +0,0 @@
#ifndef CONTROLLER_MACROS_H
#define CONTROLLER_MACROS_H
#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
#endif /* CONTROLLER_MACROS_H */

View file

@ -1,107 +0,0 @@
#ifndef CONTROLLER_CONTROLLER_H
#define CONTROLLER_CONTROLLER_H
#include <uuid/uuid.h>
#include <stdint.h>
#include <lmdb.h>
#include <config.h>
#include <models/relay.h>
/**
* @brief Information about this controller
*/
typedef struct
{
int id;
/**
* @brief A unique UUID for this controller
*/
uuid_t uid;
/**
* @brief The name of this controller
*
* Includes a \0 terminator.
*/
char name[MAX_NAME_LENGTH + 1];
/**
* @brief The command port the controller was bound to
*/
uint16_t command_port;
relay_t **relays;
} controller_t;
/**
* @brief Key to save controller information in database
*/
typedef enum
{
DB_KEY_CONTROLLER_ID = 0,
DB_KEY_CONTROLLER_NAME = 1,
DB_KEY_CONTROLLER_COMMAND_PORT = 2,
DB_KEY_CONTROLLER_DISCOVERY_PORT = 3,
} db_key_controller_e;
/**
* @brief Create a new instance of controller
*
* This should not fail. The instance will be created with malloc and genric default values
*
* @return A new instance of #controller
*/
controller_t*
controller_create(void);
/**
* @brief Load a controller for database or create a new one
*
* Will return NULL when transaction can't start.
*
* @param mdb_env An opened MDB_env to load from
*
* @return A loaded or new instance of controller or NULL on database error
*/
controller_t*
controller_load();
/**
* @brief Save a controller to the database
*
* @param controller Instance of a controller
* @param mdb_env Already created MDB_env
*
* @return Indicator to show success (0) or failure (!0)
*/
int
controller_save();
/**
* @brief Sets a name to a controller.
* This function won't perform any checks (e.g. no NULL checks)
*
* @param controller Set the name to this controller
* @param name Name to be set
*/
void
controller_set_name(controller_t *controller, const char *name);
void
controller_free(controller_t *controller);
/**
* @brief Debug an instance of #controller
*
* Will use #LOG_DEBUG to log. So log will be depending on #LOG_LEVEL
*
* @param cntrlr #controller to debug
*/
void
controller_debug(controller_t *controller);
extern controller_t *global_controller;
#endif //CONTROLLER_CONTROLLER_H

View file

@ -1,16 +0,0 @@
#ifndef CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H
#define CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H
int
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id);
int
junction_relay_schedule_remove_for_relay(int relay_id);
int
junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids);
int*
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id);
#endif /* CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H */

View file

@ -1,16 +0,0 @@
#ifndef CONTROLLER_PERIOD_H
#define CONTROLLER_PERIOD_H
#include <stdint.h>
#include <time.h>
typedef struct
{
uint16_t start;
uint16_t end;
} period_t;
int
period_includes_time(period_t period, struct tm *time_struct);
#endif /* CONTROLLER_PERIOD_H */

View file

@ -1,54 +0,0 @@
#ifndef CONTROLLER_RELAY_H
#define CONTROLLER_RELAY_H
#include <stdint.h>
#include <time.h>
#include <lmdb.h>
#include <constants.h>
#include <models/schedule.h>
typedef struct
{
int id;
uint8_t number;
int is_on;
int pulse_timer;
int sent_to_broker;
char name[MAX_NAME_LENGTH + 1];
schedule_t *schedules[7];
} relay_t;
/**
* @brief Key to save relay information in database
*/
typedef enum
{
DB_KEY_RELAY_NAME = 0,
} db_key_relay_e;
relay_t*
relay_create(uint8_t number);
void
relay_set_name(relay_t *relay, const char *name);
relay_t*
relay_load(uint8_t number);
int
relay_save(relay_t *relay);
void
relay_reload_schedules(relay_t *relay);
int
relay_is_on_schedule(relay_t *relay, struct tm *time_struct);
void
relay_free(relay_t *relay);
void
relay_debug(relay_t *relay);
#endif //CONTROLLER_RELAY_H

View file

@ -1,58 +0,0 @@
#ifndef CONTROLLER_SCHEDULE_H
#define CONTROLLER_SCHEDULE_H
#include <stdint.h>
#include <uuid/uuid.h>
#include <lmdb.h>
#include <models/period.h>
typedef struct
{
int id;
uuid_t uid;
uint8_t weekday;
uint16_t periods_count;
period_t *periods;
} schedule_t;
/**
* @brief Key to save schedule information in database
*/
typedef enum
{
DB_KEY_SCHEDULE_ID = 0,
DB_KEY_SCHEDULE_PERIODS = 1,
} db_key_schedule_e;
schedule_t*
schedule_create(uuid_t uid, uint16_t length, uint16_t *periods_blob);
int
schedule_save(schedule_t *schedule);
schedule_t*
schedule_get_by_uid(uuid_t uid);
schedule_t**
schedule_get_relay_weekdays(int relay_id);
uint16_t*
schedule_periods_to_blob(schedule_t *schedule);
void
schedule_free(schedule_t *schedule);
void
schedule_free_list(schedule_t **schedules_list);
void
schedule_debug(schedule_t *schedule);
int
schedule_uid_parse(const char *uid_str, uuid_t result);
void
schedule_uid_unparse(const uuid_t uid, char *result);
#endif /* CONTROLLER_SCHEDULE_H */

View file

@ -1,11 +0,0 @@
#ifndef CONTROLLER_RUNNERS_H
#define CONTROLLER_RUNNERS_H
#include <stdint.h>
#include <models/controller.h>
void
runner_test();
#endif /* CONTROLLER_RUNNERS_H */

View file

View file

@ -1,16 +0,0 @@
#ifndef CONTROLLER_WIRING_DEBUG_H
#define CONTROLLER_WIRING_DEBUG_H
#include <logger.h>
#ifdef WIRING_PI_DEBUG
#define LOG_WIRING_PI LOGGER_DEBUG
#define wiringPiSetup() LOG_WIRING_PI("wiringPi wiringPiSetup()\n")
#define wiringPiSetupSys() LOG_WIRING_PI("wiringPi wiringPiSetupSys()\n")
#define pinMode(x,y) LOG_WIRING_PI("wiringPi pinMode(%d, %d)\n", x, y)
#define digitalWrite(x,y) LOG_WIRING_PI("wiringPi digitalWrite(%d, %d)\n", x, y)
#define piFaceSetup(x) LOG_WIRING_PI("wiringPi piFaceSetup(%d)\n", x)
#endif
#endif /* CONTROLLER_WIRING_DEBUG_H */

100
main.go Normal file
View file

@ -0,0 +1,100 @@
package main
/*
#cgo CFLAGS: -g -Wall
#cgo LDFLAGS: -lwiringPi -lwiringPiDev
#include <wiringPi.h>
*/
import "C"
import (
"net/http"
"strings"
"github.com/go-playground/validator/v10"
"github.com/spf13/viper"
"emgauwa.app/emgauwa-core/models"
"emgauwa.app/emgauwa-core/utils"
)
type (
EmgauwaValidator struct {
validator *validator.Validate
}
)
func (cv *EmgauwaValidator) Validate(i interface{}) error {
return cv.validator.Struct(i)
}
func SkipperSwagger(c echo.Context) bool {
if strings.Contains(c.Request().URL.Path, "swagger") {
return true
}
return false
}
func init() {
utils.ConfigInit()
utils.LoggerInit()
models.DatabaseInit()
}
// @title Emgauwa API
// @version 1.0
// @description This is a sample server server.
// @termsOfService http://swagger.io/terms/
// @contact.name Tobias Reisinger
// @contact.url https://serguzim.me
// @contact.email tobias@msrg.cc
func main() {
C.wiringPiSetup()
e := echo.New()
v := validator.New()
v.RegisterValidation("hhmmformat", utils.ValidatorsHHMMFormat)
e.Validator = &EmgauwaValidator{validator: v}
e.Pre(middleware.RemoveTrailingSlash())
e.Use(middleware.CORS())
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Skipper: SkipperSwagger,
}))
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${time_rfc3339} [${method}] ${uri} > ${status} (${latency_human})\n",
}))
e.Use(middleware.Recover())
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
Skipper: SkipperSwagger,
Root: viper.GetString("content-dir"),
HTML5: true,
}))
e.GET("api/swagger/*", echoSwagger.WrapHandler)
e.GET("api/swagger", func(c echo.Context) error {
return c.Redirect(http.StatusMovedPermanently, "/api/swagger/index.html")
})
r_api_v1 := e.Group("api/v1")
{
r_api_v1.GET("/schedules", api_v1.Schedules_GET)
r_api_v1.POST("/schedules", api_v1.Schedules_POST)
r_api_v1.GET("/schedules/:schedule_uid", api_v1.Schedules_UID_GET)
r_api_v1.PUT("/schedules/:schedule_uid", api_v1.Schedules_UID_PUT)
r_api_v1.DELETE("/schedules/:schedule_uid", api_v1.Schedules_UID_DELETE)
r_api_v1.GET("/schedules/tag/:tag", api_v1.Schedules_Tag_TAG_GET)
r_api_v1.POST("/schedules/list", api_v1.Schedules_List_POST)
r_api_v1.GET("/controllers", api_v1.Controllers_GET)
r_api_v1.GET("/tags", api_v1.Tags_GET)
r_api_v1.GET("/ws/relays", api_v1.WS_Relays_GET)
}
e.Start(viper.GetString("bind.http"))
}

View file

@ -1,49 +0,0 @@
create table controllers
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
uid BLOB
NOT NULL
UNIQUE,
name VARCHAR(128),
command_port INTEGER
);
create table relays
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
number INTEGER
NOT NULL,
name VARCHAR(128)
);
create table schedules
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
uid BLOB
NOT NULL
UNIQUE,
name VARCHAR(128),
periods BLOB
);
create table junction_relay_schedule
(
weekday SMALLINT
NOT NULL,
relay_id INTEGER
REFERENCES relays (id)
ON DELETE CASCADE,
schedule_id INTEGER
DEFAULT 1
REFERENCES schedules (id)
ON DELETE SET DEFAULT
);
INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00');
INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000009F05');

View file

@ -1,191 +0,0 @@
#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;
}

View file

@ -1,35 +0,0 @@
#include <stdio.h>
#include <connections.h>
#include <models/controller.h>
#include <config.h>
#include <handlers.h>
struct mg_connection *global_connection_mqtt;
struct mg_connection*
connection_discovery_bind(struct mg_mgr *mgr)
{
char address[32];
sprintf(address, "udp://0.0.0.0:%u", global_config.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;
}

View file

@ -1,264 +0,0 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <logger.h>
#include <database.h>
#include <sql/migration_0.h>
sqlite3 *global_database;
static int in_transaction;
void
database_init()
{
int rc = sqlite3_open(global_config.database, &global_database);
if(rc)
{
LOGGER_CRIT("can't open database: %s\n", sqlite3_errmsg(global_database));
exit(1);
}
database_migrate();
sqlite3_exec(global_database, "PRAGMA foreign_keys = ON", 0, 0, 0);
in_transaction = 0;
}
void
database_free()
{
sqlite3_close(global_database);
}
void
database_migrate()
{
uint16_t version_num = 0;
int s, rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "PRAGMA user_version;", -1, &stmt, NULL);
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
version_num = sqlite3_column_int(stmt, 0);
}
else
{
version_num = 0;
}
uint16_t new_version_num = version_num;
char* err_msg;
sqlite3_finalize(stmt);
switch(version_num)
{
case 0:
LOGGER_INFO("migrating LEVEL 0\n");
rc = sqlite3_exec(global_database, (const char *)sql_migration_0_sql, NULL, NULL, &err_msg);
if(rc)
{
LOGGER_CRIT("couldn't migrate LEVEL 0 (%s)\n", err_msg);
exit(1);
}
new_version_num = 1;
default:
break;
}
char pragma_query[32];
sprintf(pragma_query, "PRAGMA user_version=%d;", new_version_num);
sqlite3_exec(global_database, pragma_query, 0, 0, 0);
LOGGER_DEBUG("storing new user_version %d\n", new_version_num);
return;
}
int
database_transaction_begin()
{
if(!in_transaction)
{
LOGGER_DEBUG("beginning transaction\n");
sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
in_transaction = 1;
return 1;
}
return 0;
}
void
database_transaction_commit()
{
LOGGER_DEBUG("commiting transaction\n");
sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
in_transaction = 0;
}
void
database_transaction_rollback()
{
LOGGER_DEBUG("rolling back transaction\n");
sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
in_transaction = 0;
}
int
database_helper_get_id(sqlite3_stmt *stmt)
{
int result = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
result = sqlite3_column_int(stmt, 0);
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return 0;
}
}
}
sqlite3_finalize(stmt);
return result;
}
int*
database_helper_get_ids(sqlite3_stmt *stmt)
{
int *result = malloc(sizeof(int));
int new_id;
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
new_id = sqlite3_column_int(stmt, 0);
if(new_id != 0) // found row for other target (relay <> schedule)
{
row++;
result = (int*)realloc(result, sizeof(int) * (row + 1));
result[row - 1] = new_id;
}
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
result[row] = 0;
return result;
}
char*
database_helper_get_string(sqlite3_stmt *stmt)
{
char *result = NULL;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
const char *found_string = (const char *)sqlite3_column_text(stmt, 0);
result = (char*)malloc(sizeof(char) * (strlen(found_string) + 1));
strcpy(result, found_string);
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
return result;
}
char**
database_helper_get_strings(sqlite3_stmt *stmt)
{
char **result = malloc(sizeof(char*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
const char *new_string = (const char *)sqlite3_column_text(stmt, 0);
int new_string_len = strlen(new_string);
row++;
result = (char**)realloc(result, sizeof(char*) * (row + 1));
result[row - 1] = malloc(sizeof(char) * (new_string_len + 1));
strcpy(result[row - 1], new_string);
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
result[row] = NULL;
return result;
}

View file

@ -1,11 +0,0 @@
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <drivers.h>
void
driver_gpio_set(int pin, int value)
{
digitalWrite(pin, value);
}

View file

@ -1,11 +0,0 @@
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <drivers.h>
void
driver_piface_set(int pin, int value)
{
digitalWrite(PIFACE_GPIO_BASE + pin, value);
}

View file

@ -1,251 +0,0 @@
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <uuid/uuid.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <logger.h>
#include <database.h>
#include <handlers.h>
#include <helpers.h>
#include <mpack.h>
#include <models/controller.h>
#include <models/relay.h>
#include <models/schedule.h>
#include <models/junction_relay_schedule.h>
typedef enum
{
COMMAND_CODE_CONTROLLER_ID_GET = 0,
COMMAND_CODE_CONTROLLER_TIME_GET = 1,
COMMAND_CODE_CONTROLLER_NAME_SET = 2,
COMMAND_CODE_CONTROLLER_NAME_GET = 3,
COMMAND_CODE_RELAY_SCHEDULES_SET = 100,
COMMAND_CODE_RELAY_SCHEDULES_GET = 101,
COMMAND_CODE_RELAY_NAME_SET = 102,
COMMAND_CODE_RELAY_NAME_GET = 103,
COMMAND_CODE_RELAY_PULSE = 200,
COMMAND_CODE_SCHEDULE_UPDATE = 300
} command_code_t;
typedef enum
{
COMMAND_MAPPING_CODE = 0,
COMMAND_MAPPING_NAME = 1,
COMMAND_MAPPING_RELAY_NUM = 2,
COMMAND_MAPPING_SCHEDULES_ARRAY = 3,
COMMAND_MAPPING_SCHEDULE_ID = 4,
COMMAND_MAPPING_PERIODS_COUNT = 5,
COMMAND_MAPPING_PERIODS_BLOB = 6,
COMMAND_MAPPING_PULSE_DURATION = 7,
} control_mapping_t;
static void
handler_command_relay_pulse(mpack_node_t map)
{
uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
if(relay_num > global_config.relay_count)
{
LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config.relay_count);
return;
}
relay_t *target_relay = global_controller->relays[relay_num];
(void)target_relay;
int duration = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_PULSE_DURATION));
if(duration == 0)
{
duration = global_config.relay_configs[relay_num].pulse_duration;
}
target_relay->pulse_timer = duration;
LOGGER_DEBUG("pulsing relay %d for %ds\n", relay_num, duration);
}
static void
handler_command_controller_name_set(mpack_node_t map)
{
char name_buffer[MAX_NAME_LENGTH + 1];
mpack_node_copy_cstr(mpack_node_map_uint(map, COMMAND_MAPPING_NAME), name_buffer, MAX_NAME_LENGTH + 1);
controller_set_name(global_controller, name_buffer);
LOGGER_DEBUG("setting new name %s for controller\n", name_buffer);
controller_save();
}
static void
handler_command_relay_name_set(mpack_node_t map)
{
uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
const char *relay_name = mpack_node_str(mpack_node_map_uint(map, COMMAND_MAPPING_NAME));
if(relay_num > global_config.relay_count)
{
LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config.relay_count);
return;
}
relay_set_name(global_controller->relays[relay_num], relay_name);
LOGGER_DEBUG("setting new name %s for relay %d\n", relay_name, relay_num);
relay_save(global_controller->relays[relay_num]);
}
static void
handler_command_schedule_update(mpack_node_t map)
{
uuid_t schedule_uid;
memcpy(schedule_uid, mpack_node_data(mpack_node_map_uint(map, COMMAND_MAPPING_SCHEDULE_ID)), sizeof(uuid_t));
uint16_t periods_count = mpack_node_u16(mpack_node_map_uint(map, COMMAND_MAPPING_PERIODS_COUNT));
uint16_t *periods = (uint16_t*)mpack_node_bin_data(mpack_node_map_uint(map, COMMAND_MAPPING_PERIODS_BLOB));
schedule_t *schedule = schedule_get_by_uid(schedule_uid);
schedule_t *new_schedule = schedule_create(schedule_uid, periods_count, periods);
if(schedule)
{
new_schedule->id = schedule->id;
schedule_free(schedule);
}
schedule_save(new_schedule);
int *relay_ids = junction_relay_schedule_get_relay_ids_with_schedule(new_schedule->id);
for(int i = 0; i < global_config.relay_count; ++i)
{
for(int j = 0; relay_ids[j] != 0; ++j)
{
if(global_controller->relays[i]->id == relay_ids[j])
{
relay_reload_schedules(global_controller->relays[i]);
}
}
}
free(relay_ids);
schedule_free(new_schedule);
}
static void
handler_command_relay_schedules_set(mpack_node_t map)
{
uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
if(relay_num > global_config.relay_count)
{
LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config.relay_count);
return;
}
LOGGER_DEBUG("setting schedules for relay %d\n", relay_num);
relay_t *target_relay = global_controller->relays[relay_num];
database_transaction_begin();
junction_relay_schedule_remove_for_relay(target_relay->id);
mpack_node_t schedules_array = mpack_node_map_uint(map, COMMAND_MAPPING_SCHEDULES_ARRAY);
for(int i = 0; i < 7; ++i)
{
mpack_node_t schedule_map = mpack_node_array_at(schedules_array, i);
uuid_t schedule_uid;
memcpy(schedule_uid, mpack_node_data(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_SCHEDULE_ID)), sizeof(uuid_t));
uint16_t periods_count = mpack_node_u16(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_PERIODS_COUNT));
uint16_t *periods = (uint16_t*)mpack_node_bin_data(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_PERIODS_BLOB));
schedule_t *schedule = schedule_get_by_uid(schedule_uid);
schedule_t *new_schedule = schedule_create(schedule_uid, periods_count, periods);
if(schedule)
{
new_schedule->id = schedule->id;
schedule_free(schedule);
}
schedule_save(new_schedule);
junction_relay_schedule_insert(i, target_relay->id, new_schedule->id);
}
relay_reload_schedules(target_relay);
database_transaction_commit();
}
void
handler_command(struct mg_connection *c, int ev, void *ev_data)
{
(void)ev_data;
if(ev != MG_EV_RECV)
{
return;
}
uint32_t payload_length = *((uint32_t*)c->recv_mbuf.buf);
if(c->recv_mbuf.len < payload_length + sizeof(payload_length))
{
return;
}
char *payload = c->recv_mbuf.buf + sizeof(payload_length);
mpack_tree_t tree;
mpack_tree_init_data(&tree, payload, payload_length);
mpack_tree_parse(&tree);
mpack_node_t root = mpack_tree_root(&tree);
uint16_t command_code = mpack_node_u16(mpack_node_map_uint(root, COMMAND_MAPPING_CODE));
LOGGER_DEBUG("received command %d\n", command_code);
switch(command_code)
{
case COMMAND_CODE_CONTROLLER_ID_GET:
break;
case COMMAND_CODE_CONTROLLER_TIME_GET:
break;
case COMMAND_CODE_CONTROLLER_NAME_SET:
handler_command_controller_name_set(root);
break;
case COMMAND_CODE_CONTROLLER_NAME_GET:
break;
case COMMAND_CODE_RELAY_SCHEDULES_SET:
handler_command_relay_schedules_set(root);
break;
case COMMAND_CODE_RELAY_SCHEDULES_GET:
break;
case COMMAND_CODE_RELAY_NAME_SET:
handler_command_relay_name_set(root);
break;
case COMMAND_CODE_RELAY_NAME_GET:
break;
case COMMAND_CODE_RELAY_PULSE:
handler_command_relay_pulse(root);
break;
case COMMAND_CODE_SCHEDULE_UPDATE:
handler_command_schedule_update(root);
break;
default:
LOGGER_ERR("received invalid command\n");
}
if(mpack_tree_destroy(&tree) != mpack_ok)
{
LOGGER_WARNING("error when destroying mpack tree\n");
}
LOGGER_DEBUG("done with command %d - closing connection\n", command_code);
c->flags |= MG_F_SEND_AND_CLOSE;
}

View file

@ -1,95 +0,0 @@
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <uuid/uuid.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <logger.h>
#include <mongoose.h>
#include <handlers.h>
#include <helpers.h>
#include <mpack.h>
#include <enums.h>
void
handler_discovery(struct mg_connection *c, int ev, void *ev_data)
{
(void)ev_data;
if(ev == MG_EV_RECV)
{
uint16_t discovery_answer_port;
char ip_buf[32];
mg_conn_addr_to_str(c, ip_buf, sizeof(ip_buf), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_REMOTE);
if(c->recv_mbuf.len != sizeof(discovery_answer_port))
{
LOGGER_ERR("received invalid discovery from %s\n", ip_buf);
return;
}
discovery_answer_port = *((uint16_t*)c->recv_mbuf.buf);
LOGGER_INFO("received discovery from %s:%d\n", ip_buf, discovery_answer_port);
if(discovery_answer_port == 0)
{
LOGGER_ERR("invalid port received\n");
return;
}
char* payload;
size_t payload_size;
mpack_writer_t writer;
mpack_writer_init_growable(&writer, &payload, &payload_size);
mpack_start_map(&writer, 4);
mpack_write_uint(&writer, DISCOVERY_MAPPING_ID);
mpack_write_bin(&writer, (char*)global_controller->uid, sizeof(uuid_t));
mpack_write_uint(&writer, DISCOVERY_MAPPING_COMMAND_PORT);
mpack_write_u16(&writer, global_controller->command_port);
mpack_write_uint(&writer, DISCOVERY_MAPPING_RELAY_COUNT);
mpack_write_u8(&writer, global_config.relay_count);
mpack_write_uint(&writer, DISCOVERY_MAPPING_NAME);
mpack_write_cstr(&writer, global_controller->name);
mpack_finish_map(&writer);
// finish writing
if(mpack_writer_destroy(&writer) != mpack_ok)
{
LOGGER_ERR("error writing discovery answer payload\n");
return;
}
size_t bytes_transferred;
int fd_answer = helper_connect_tcp_server(ip_buf, discovery_answer_port);
if(fd_answer == -1)
{
LOGGER_ERR("error during connecting\n");
free(payload);
return;
}
if((bytes_transferred = send(fd_answer, &payload_size, sizeof(payload_size), 0)) <= 0)
{
LOGGER_ERR("error during sending\n");
free(payload);
close(fd_answer);
return;
}
if((bytes_transferred = send(fd_answer, payload, payload_size, 0)) <= 0)
{
LOGGER_ERR("error during sending\n");
free(payload);
close(fd_answer);
return;
}
free(payload);
}
}

View file

@ -1,92 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <uuid/uuid.h>
#include <logger.h>
#include <models/controller.h>
#include <handlers.h>
#include <drivers.h>
#include <enums.h>
#include <helpers.h>
#include <wiringPi.h>
#include <wiring_debug.h>
void
handler_loop(struct mg_connection *c_mqtt)
{
char topic_buf[100];
char payload_buf[2];
char controller_uid[UUID_STR_LEN];
uuid_unparse(global_controller->uid, controller_uid);
struct tm time_last, time_now;
time_t timestamp;
timestamp = time(NULL) - 1;
localtime_r(&timestamp, &time_last);
timestamp = time(NULL);
localtime_r(&timestamp, &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);
}
}
}

View file

@ -1,43 +0,0 @@
#include <logger.h>
#include <handlers.h>
#include <connections.h>
#include <models/controller.h>
void
handler_mqtt(struct mg_connection *nc, int ev, void *p) {
struct mg_mqtt_message *msg = (struct mg_mqtt_message *) p;
(void) nc;
switch (ev)
{
case MG_EV_CONNECT:
{
struct mg_send_mqtt_handshake_opts opts;
memset(&opts, 0, sizeof(opts));
// TODO add password
mg_set_protocol_mqtt(nc);
mg_send_mqtt_handshake_opt(nc, global_controller->name, opts);
break;
}
case MG_EV_MQTT_CONNACK:
if(msg->connack_ret_code != MG_EV_MQTT_CONNACK_ACCEPTED)
{
LOGGER_INFO("Got MQTT connection error: %d\n", msg->connack_ret_code);
break;
}
if(!global_connection_mqtt)
{
LOGGER_DEBUG("connected to MQTT server\n");
global_connection_mqtt = nc;
}
break;
case MG_EV_CLOSE:
if(global_connection_mqtt)
{
LOGGER_DEBUG("disconnected from MQTT server\n");
}
global_connection_mqtt = NULL;
break;
}
}

View file

@ -1,52 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <logger.h>
#include <helpers.h>
int
helper_bind_tcp_server(char* addr, uint16_t port, int max_client_backlog)
{
char port_str[6];
sprintf(port_str, "%d", port);
struct addrinfo hints, *res;
int fd;
int status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((status = getaddrinfo(addr, port_str, &hints, &res)) != 0)
{
LOGGER_ERR("getaddrinfo: %s\n", gai_strerror(status));
return -1;
}
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if ((status = bind(fd, res->ai_addr, res->ai_addrlen)) == -1)
{
LOGGER_ERR("error binding socket: %s\n", strerror(errno));
freeaddrinfo(res);
return -1;
}
if ((status = listen(fd, max_client_backlog)) == -1)
{
LOGGER_ERR("error setting up listener: %s\n", strerror(errno));
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return fd;
}

View file

@ -1,39 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <logger.h>
#include <helpers.h>
int
helper_connect_tcp_server(char* host, uint16_t port)
{
char port_str[6];
sprintf(port_str, "%d", port);
int s, status;
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; //set IP Protocol flag (IPv4 or IPv6 - we don't care)
hints.ai_socktype = SOCK_STREAM; //set socket flag
if ((status = getaddrinfo(host, port_str, &hints, &res)) != 0) { //getaddrinfo() will evaluate the given address, using the hints-flags and port, and return an IP address and other server infos
LOGGER_ERR("getaddrinfo: %s\n", gai_strerror(status));
return -1;
}
//res got filled out by getaddrinfo() for us
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
LOGGER_ERR("connect() failed\n");
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return s;
}

View file

@ -1,81 +0,0 @@
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <config.h>
#include <logger.h>
static uid_t
get_uid_for_user(char *user)
{
if(user == NULL || user[0] == '\0')
{
return getuid();
}
struct passwd *pwd = calloc(1, sizeof(struct passwd));
size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
char *buffer = malloc(buffer_len);
getpwnam_r(user, pwd, buffer, buffer_len, &pwd);
if(pwd == NULL)
{
LOGGER_CRIT("couldn't find user to drop privileges\n");
exit(1);
}
uid_t result = pwd->pw_uid;
free(buffer);
free(pwd);
return result;
}
static gid_t
get_gid_for_group(char *group)
{
if(group == NULL || group[0] == '\0')
{
return getgid();
}
struct group *grp = calloc(1, sizeof(struct group));
size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
char *buffer = malloc(buffer_len);
getgrnam_r(group, grp, buffer, buffer_len, &grp);
if(grp == NULL)
{
LOGGER_CRIT("couldn't find group to drop privileges\n");
exit(1);
}
gid_t result = grp->gr_gid;
free(buffer);
free(grp);
return result;
}
int
helper_drop_privileges()
{
uid_t uid = get_uid_for_user(global_config.user);
gid_t gid = get_gid_for_group(global_config.group);
LOGGER_DEBUG("drop privileges to %lu:%lu\n", uid, gid);
if (setgid(gid) == -1)
{
LOGGER_CRIT("failed to drop group privileges\n");
exit(1);
}
if (setuid(uid) == -1)
{
LOGGER_CRIT("failed to drop user privileges\n");
exit(1);
}
return 0;
}

View file

@ -1,24 +0,0 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <helpers.h>
#include <logger.h>
uint16_t
helper_get_port(int sock)
{
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
if (getsockname(sock, (struct sockaddr *)&sin, &len) == -1)
{
LOGGER_ERR("could not get socket name for port: %s\n", strerror(errno));
return 0;
}
else
{
return ntohs(sin.sin_port);
}
}

View file

@ -1,11 +0,0 @@
#include <time.h>
#include <helpers.h>
int
helper_get_weekday(const struct tm *time_struct)
{
int wday_sun_sat = time_struct->tm_wday;
int wday_mon_sun = (wday_sun_sat + 6) % 7;
return wday_mon_sun;
}

View file

@ -1,57 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <logger.h>
#include <helpers.h>
int
helper_open_discovery_socket(uint16_t discovery_port)
{
struct addrinfo hints, *res;
int fd, status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; // use ipv4
hints.ai_socktype = SOCK_DGRAM; //set socket flag
hints.ai_flags = AI_PASSIVE; // get my IP
char discovery_port_str[6];
sprintf(discovery_port_str, "%u", discovery_port);
//get connection info for our computer
if ((status = getaddrinfo(NULL, discovery_port_str, &hints, &res)) != 0)
{
LOGGER_CRIT("getaddrinfo: %s\n", gai_strerror(status));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
//creating socket
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
int yes = 1;
// lose the pesky "Address already in use" error message
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
{
LOGGER_CRIT("setsockopt: %s\n", strerror(errno));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
if (bind(fd, res->ai_addr, res->ai_addrlen) == -1)
{
LOGGER_CRIT("bind: %s\n", strerror(errno));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
freeaddrinfo(res);
LOGGER_INFO("opened discovery socket on port %u\n", discovery_port);
return fd;
}

View file

@ -1,70 +0,0 @@
#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;
}

View file

@ -1,85 +0,0 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <config.h>
#include <logger.h>
const char *COLOR_DEBUG = COLOR_GREEN;
const char *COLOR_INFO = COLOR_CYAN;
const char *COLOR_NOTICE = COLOR_CYAN;
const char *COLOR_WARNING = COLOR_YELLOW;
const char *COLOR_ERR = COLOR_RED;
const char *COLOR_CRIT = COLOR_MAGENTA;
const char *COLOR_EMERG = COLOR_MAGENTA;
void
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...)
{
if(global_config.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);
}

View file

@ -1,208 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <lmdb.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;
}

View file

@ -1,256 +0,0 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <sqlite3.h>
#include <models/controller.h>
#include <logger.h>
#include <config.h>
#include <constants.h>
#include <database.h>
controller_t *global_controller;
static int
db_update_insert(controller_t *controller, sqlite3_stmt *stmt)
{
LOGGER_DEBUG("saving controller '%s' into database (id: %d)\n", controller->name, controller->id);
int rc;
sqlite3_bind_int(stmt, 1, controller->id);
sqlite3_bind_blob(stmt, 2, controller->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, controller->name, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, controller->command_port);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
static controller_t*
controller_db_select_mapper(sqlite3_stmt *stmt)
{
controller_t *new_controller = malloc(sizeof(controller_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i': // id
new_controller->id = sqlite3_column_int(stmt, i);
break;
case 'c': // command_port
new_controller->command_port = sqlite3_column_int(stmt, i);
break;
case 'n': // name
strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'u': // uid
uuid_copy(new_controller->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
new_controller->relays = malloc(sizeof(relay_t) * global_config.relay_count);
uint8_t i;
for(i = 0; i < global_config.relay_count; ++i)
{
new_controller->relays[i] = relay_load(i);
if(!new_controller->relays[i])
{
new_controller->relays[i] = relay_create(i);
relay_save(new_controller->relays[i]);
}
}
return new_controller;
}
static controller_t**
controller_db_select(sqlite3_stmt *stmt)
{
controller_t **all_controllers = malloc(sizeof(controller_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
controller_t *new_controller = controller_db_select_mapper(stmt);
row++;
all_controllers = (controller_t**)realloc(all_controllers, sizeof(controller_t*) * (row + 1));
all_controllers[row - 1] = new_controller;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_controllers[row] = NULL;
return all_controllers;
}
int
controller_save()
{
int opened_transaction = database_transaction_begin();
sqlite3_stmt *stmt;
if(global_controller->id)
{
sqlite3_prepare_v2(global_database, "UPDATE controllers set uid = ?2, name = ?3, command_port = ?4 WHERE id = ?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO controllers(uid, name, command_port) values (?2, ?3, ?4);", -1, &stmt, NULL);
}
int result = db_update_insert(global_controller, stmt);
if(result)
{
if(global_controller->id)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
if(opened_transaction)
{
database_transaction_rollback();
}
}
else
{
if(!global_controller->id)
{
global_controller->id = sqlite3_last_insert_rowid(global_database);
}
if(opened_transaction)
{
database_transaction_commit();
}
}
return result;
}
controller_t*
controller_load()
{
LOGGER_DEBUG("getting controller from database\n");
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM controllers LIMIT 1;", -1, &stmt, NULL);
controller_t **sql_result = controller_db_select(stmt);
controller_t *result = sql_result[0];
free(sql_result);
return result;
}
int
controller_remove(controller_t *controller)
{
sqlite3_stmt *stmt;
if(!controller->id)
{
return 0;
}
sqlite3_prepare_v2(global_database, "DELETE FROM controllers WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, controller->id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
controller_t*
controller_create(void)
{
controller_t *new_controller = malloc(sizeof(*new_controller));
new_controller->id = 0;
uuid_generate(new_controller->uid);
strncpy(new_controller->name, global_config.name, MAX_NAME_LENGTH);
new_controller->name[MAX_NAME_LENGTH] = '\0';
new_controller->command_port = 0;
new_controller->relays = malloc(sizeof(relay_t) * global_config.relay_count);
uint8_t i;
for(i = 0; i < global_config.relay_count; ++i)
{
new_controller->relays[i] = relay_load(i);
if(!new_controller->relays[i])
{
new_controller->relays[i] = relay_create(i);
relay_save(new_controller->relays[i]);
}
}
return new_controller;
}
void
controller_set_name(controller_t *controller, const char *name)
{
strncpy(controller->name, name, MAX_NAME_LENGTH);
controller->name[MAX_NAME_LENGTH] = '\0';
}
void
controller_free(controller_t *controller)
{
for(int i = 0; i < global_config.relay_count; ++i)
{
relay_free(controller->relays[i]);
}
free(controller->relays);
free(controller);
}
void
controller_debug(controller_t *controller)
{
if(controller == NULL)
{
LOGGER_DEBUG("controller is NULL\n");
return;
}
char uuid_str[37];
uuid_unparse(controller->uid, uuid_str);
LOGGER_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)controller);
LOGGER_DEBUG("(2/3) name: %s\n", controller->name);
LOGGER_DEBUG("(3/3) relays @ %p:\n", (void*)controller->relays);
for(int i = 0; i < global_config.relay_count; ++i)
{
relay_debug(controller->relays[i]);
}
}

View file

@ -1,102 +0,0 @@
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <models/junction_relay_schedule.h>
#include <logger.h>
#include <macros.h>
#include <database.h>
int
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id)
{
int rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "INSERT INTO junction_relay_schedule(weekday, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
return 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);
}

View file

@ -1,37 +0,0 @@
#include <stdlib.h>
#include <logger.h>
#include <constants.h>
#include <models/period.h>
int
period_includes_time(period_t period, struct tm *time_struct)
{
uint16_t start = period.start;
uint16_t end = period.end;
time_t timestamp = time_struct->tm_hour * 60;
timestamp += time_struct->tm_min;
// "normal" timespan
if(start < end)
{
if(start <= timestamp && end > timestamp)
{
return 1;
}
return 0;
}
// timespan goes through 00:00
if(end < start)
{
if(start >= timestamp && end < timestamp)
{
return 1;
}
return 0;
}
return 0;
}

View file

@ -1,293 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <logger.h>
#include <helpers.h>
#include <database.h>
#include <models/relay.h>
#include <models/junction_relay_schedule.h>
static int
db_update_insert(relay_t *relay, sqlite3_stmt *stmt)
{
LOGGER_DEBUG("saving relay '%s' into database (id: %d)\n", relay->name, relay->id);
int rc;
sqlite3_bind_int(stmt, 1, relay->id);
sqlite3_bind_int(stmt, 2, relay->number);
sqlite3_bind_text(stmt, 3, relay->name, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
static relay_t*
relay_db_select_mapper(sqlite3_stmt *stmt)
{
relay_t *new_relay = malloc(sizeof(relay_t));
new_relay->is_on = 0;
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i':
new_relay->id = sqlite3_column_int(stmt, i);
break;
case 'n':
switch(name[1])
{
case 'a': // name
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'u': // number
new_relay->number = sqlite3_column_int(stmt, i);
break;
default:
break;
}
break;
default: // ignore columns not implemented
break;
}
}
memset(new_relay->schedules, 0, sizeof(schedule_t*) * 7);
relay_reload_schedules(new_relay);
new_relay->is_on = -1;
new_relay->pulse_timer = 0;
new_relay->sent_to_broker = 0;
return new_relay;
}
static relay_t**
relay_db_select(sqlite3_stmt *stmt)
{
relay_t **all_relays = malloc(sizeof(relay_t*));
int row = 0;
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);
}

View file

@ -1,322 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <logger.h>
#include <database.h>
#include <models/schedule.h>
#include <models/junction_relay_schedule.h>
static int
db_update_insert(schedule_t *schedule, sqlite3_stmt *stmt)
{
char uuid_str[UUID_STR_LEN];
uuid_unparse(schedule->uid, uuid_str);
LOGGER_DEBUG("saving schedule '%s' into database (id: %d)\n", uuid_str, schedule->id);
int rc;
uint16_t *periods_blob = schedule_periods_to_blob(schedule);
int blob_size = (int)sizeof(uint16_t) * ((periods_blob[0] * 2) + 1);
sqlite3_bind_int(stmt, 1, schedule->id);
sqlite3_bind_blob(stmt, 2, schedule->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, periods_blob, blob_size, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
free(periods_blob);
return rc != SQLITE_DONE;
}
static schedule_t*
schedule_db_select_mapper(sqlite3_stmt *stmt)
{
const uint16_t *periods_blob;
schedule_t *new_schedule = malloc(sizeof(schedule_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i': // id
new_schedule->id = sqlite3_column_int(stmt, i);
break;
case 'p': // periods
periods_blob = sqlite3_column_blob(stmt, i);
new_schedule->periods_count = periods_blob[0];
new_schedule->periods = malloc(sizeof(period_t) * periods_blob[0]);
for(int i = 0; i < periods_blob[0]; ++i)
{
new_schedule->periods[i].start = periods_blob[(i * 2) + 1];
new_schedule->periods[i].end = periods_blob[(i * 2) + 2];
}
break;
case 'u': // uid
uuid_copy(new_schedule->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
return new_schedule;
}
static schedule_t**
schedule_db_select(sqlite3_stmt *stmt)
{
schedule_t **all_schedules = malloc(sizeof(schedule_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
schedule_t *new_schedule = schedule_db_select_mapper(stmt);
row++;
all_schedules = (schedule_t**)realloc(all_schedules, sizeof(schedule_t*) * (row + 1));
all_schedules[row - 1] = new_schedule;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_schedules[row] = NULL;
return all_schedules;
}
int
schedule_save(schedule_t *schedule)
{
int opened_transaction = database_transaction_begin();
sqlite3_stmt *stmt;
if(schedule->id)
{
sqlite3_prepare_v2(global_database, "UPDATE schedules SET uid = ?2, periods = ?3 WHERE id=?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO schedules(uid, periods) values (?2, ?3);", -1, &stmt, NULL);
}
int result = db_update_insert(schedule, stmt);
if(result)
{
if(schedule->id)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
if(opened_transaction)
{
database_transaction_rollback();
}
}
else
{
if(!schedule->id)
{
schedule->id = sqlite3_last_insert_rowid(global_database);
}
if(opened_transaction)
{
database_transaction_commit();
}
}
return result;
}
schedule_t**
schedule_get_relay_weekdays(int relay_id)
{
LOGGER_DEBUG("getting schedules [relay_id=%d] from database\n", relay_id);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT schedules.* FROM schedules INNER JOIN junction_relay_schedule ON schedules.id == junction_relay_schedule.schedule_id WHERE junction_relay_schedule.relay_id = ?1 ORDER BY junction_relay_schedule.weekday ASC", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return schedule_db_select(stmt);
}
schedule_t*
schedule_get_by_uid(uuid_t uid)
{
char uuid_str[UUID_STR_LEN];
schedule_uid_unparse(uid, uuid_str);
LOGGER_DEBUG("getting schedule [uid=%s] from database\n", uuid_str);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM schedules WHERE uid = ?1;", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
schedule_t **sql_result = schedule_db_select(stmt);
schedule_t *result = sql_result[0];
free(sql_result);
return result;
}
schedule_t*
schedule_create(uuid_t uid, uint16_t length, uint16_t *periods_blob)
{
schedule_t *new_schedule = malloc(sizeof(schedule_t));
new_schedule->id = 0;
memmove(new_schedule->uid, uid, sizeof(uuid_t));
new_schedule->periods_count = length;
new_schedule->periods = NULL;
if(length)
{
new_schedule->periods = malloc(sizeof(period_t) * length);
for(uint16_t i = 0; i < length; ++i)
{
new_schedule->periods[i].start = periods_blob[0 + (i * 2)];
new_schedule->periods[i].end = periods_blob[1 + (i * 2)];
}
}
return new_schedule;
}
uint16_t*
schedule_periods_to_blob(schedule_t *schedule)
{
uint16_t *periods_blob = malloc(sizeof(uint16_t) * ((2 * schedule->periods_count) + 1));
periods_blob[0] = schedule->periods_count;
for(uint16_t i = 0; i < schedule->periods_count; ++i)
{
periods_blob[1 + (i * 2)] = schedule->periods[i].start;
periods_blob[2 + (i * 2)] = schedule->periods[i].end;
}
return periods_blob;
}
int
schedule_uid_parse(const char *uid_str, uuid_t result)
{
if(strcmp("off", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "off", 3);
return 0;
}
if(strcmp("on", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "on", 2);
return 0;
}
if(uuid_parse(uid_str, result))
{
return 1;
}
return 0;
}
void
schedule_uid_unparse(const uuid_t uid, char *result)
{
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strcpy(result, "off");
return;
}
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strcpy(result, "on");
return;
}
uuid_unparse(uid, result);
}
void
schedule_free(schedule_t *schedule)
{
free(schedule->periods);
free(schedule);
}
void
schedule_free_list(schedule_t **schedules)
{
for(int i = 0; schedules[i] != NULL; ++i)
{
schedule_free(schedules[i]);
}
free(schedules);
}
void
schedule_debug(schedule_t *schedule)
{
if(schedule == NULL)
{
LOGGER_DEBUG("schedule is NULL\n");
return;
}
char uuid_str[UUID_STR_LEN];
schedule_uid_unparse(schedule->uid, uuid_str);
LOGGER_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)schedule);
LOGGER_DEBUG("(2/3) id: %3d; period count: %3d\n", schedule->id, schedule->periods_count);
// one block: "HH:MM-HH:MM, " --> size: 13 (14 with '\0')
char *periods_debug_str = malloc(sizeof(char) * ((schedule->periods_count * 13) + 1));
periods_debug_str[0] = '\0';
for(uint16_t i = 0; i < schedule->periods_count; ++i)
{
sprintf(
periods_debug_str + (13 * i),
"%02d:%02d-%02d:%02d, ",
schedule->periods[i].start / 60,
schedule->periods[i].start % 60,
schedule->periods[i].end / 60,
schedule->periods[i].end % 60
);
}
LOGGER_DEBUG("(3/3) periods: %s\n", periods_debug_str);
free(periods_debug_str);
}

View file

@ -1,35 +0,0 @@
#include <unistd.h>
#include <runners.h>
#include <logger.h>
#include <drivers.h>
#include <drivers.h>
void
runner_test()
{
// from x down to 0 to turn all relays off in the end
for(uint_fast8_t i = 0; i < global_config.relay_count; ++i)
{
for(int test_run = 2; test_run >= 0; --test_run)
{
int is_active = test_run % 2;
if(global_config.relay_configs[i].inverted)
{
is_active = !is_active;
}
switch(global_config.relay_configs[i].driver)
{
case RELAY_DRIVER_GPIO:
driver_gpio_set(global_config.relay_configs[i].pin, is_active);
break;
case RELAY_DRIVER_PIFACE:
driver_piface_set(global_config.relay_configs[i].pin, is_active);
break;
default:
LOGGER_WARNING("relay %d is not using a driver\n", i);
}
sleep(1);
}
}
}

384
vendor/argparse.c vendored
View file

@ -1,384 +0,0 @@
/**
* Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
* All rights reserved.
*
* Use of this source code is governed by a MIT-style license that can be found
* in the LICENSE file.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "argparse.h"
#define OPT_UNSET 1
#define OPT_LONG (1 << 1)
static const char *
prefix_skip(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
static int
prefix_cmp(const char *str, const char *prefix)
{
for (;; str++, prefix++)
if (!*prefix) {
return 0;
} else if (*str != *prefix) {
return (unsigned char)*prefix - (unsigned char)*str;
}
}
static void
argparse_error(struct argparse *self, const struct argparse_option *opt,
const char *reason, int flags)
{
(void)self;
if (flags & OPT_LONG) {
fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
} else {
fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
}
exit(1);
}
static int
argparse_getvalue(struct argparse *self, const struct argparse_option *opt,
int flags)
{
const char *s = NULL;
if (!opt->value)
goto skipped;
switch (opt->type) {
case ARGPARSE_OPT_BOOLEAN:
if (flags & OPT_UNSET) {
*(int *)opt->value = *(int *)opt->value - 1;
} else {
*(int *)opt->value = *(int *)opt->value + 1;
}
if (*(int *)opt->value < 0) {
*(int *)opt->value = 0;
}
break;
case ARGPARSE_OPT_BIT:
if (flags & OPT_UNSET) {
*(int *)opt->value &= ~opt->data;
} else {
*(int *)opt->value |= opt->data;
}
break;
case ARGPARSE_OPT_STRING:
if (self->optvalue) {
*(const char **)opt->value = self->optvalue;
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(const char **)opt->value = *++self->argv;
} else {
argparse_error(self, opt, "requires a value", flags);
}
break;
case ARGPARSE_OPT_INTEGER:
errno = 0;
if (self->optvalue) {
*(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
} else {
argparse_error(self, opt, "requires a value", flags);
}
if (errno)
argparse_error(self, opt, strerror(errno), flags);
if (s[0] != '\0')
argparse_error(self, opt, "expects an integer value", flags);
break;
case ARGPARSE_OPT_FLOAT:
errno = 0;
if (self->optvalue) {
*(float *)opt->value = strtof(self->optvalue, (char **)&s);
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(float *)opt->value = strtof(*++self->argv, (char **)&s);
} else {
argparse_error(self, opt, "requires a value", flags);
}
if (errno)
argparse_error(self, opt, strerror(errno), flags);
if (s[0] != '\0')
argparse_error(self, opt, "expects a numerical value", flags);
break;
default:
assert(0);
}
skipped:
if (opt->callback) {
return opt->callback(self, opt);
}
return 0;
}
static void
argparse_options_check(const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
switch (options->type) {
case ARGPARSE_OPT_END:
case ARGPARSE_OPT_BOOLEAN:
case ARGPARSE_OPT_BIT:
case ARGPARSE_OPT_INTEGER:
case ARGPARSE_OPT_FLOAT:
case ARGPARSE_OPT_STRING:
case ARGPARSE_OPT_GROUP:
continue;
default:
fprintf(stderr, "wrong option type: %d", options->type);
break;
}
}
}
static int
argparse_short_opt(struct argparse *self, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
if (options->short_name == *self->optvalue) {
self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
return argparse_getvalue(self, options, 0);
}
}
return -2;
}
static int
argparse_long_opt(struct argparse *self, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
const char *rest;
int opt_flags = 0;
if (!options->long_name)
continue;
rest = prefix_skip(self->argv[0] + 2, options->long_name);
if (!rest) {
// negation disabled?
if (options->flags & OPT_NONEG) {
continue;
}
// only OPT_BOOLEAN/OPT_BIT supports negation
if (options->type != ARGPARSE_OPT_BOOLEAN && options->type !=
ARGPARSE_OPT_BIT) {
continue;
}
if (prefix_cmp(self->argv[0] + 2, "no-")) {
continue;
}
rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
if (!rest)
continue;
opt_flags |= OPT_UNSET;
}
if (*rest) {
if (*rest != '=')
continue;
self->optvalue = rest + 1;
}
return argparse_getvalue(self, options, opt_flags | OPT_LONG);
}
return -2;
}
int
argparse_init(struct argparse *self, struct argparse_option *options,
const char *const *usages, int flags)
{
memset(self, 0, sizeof(*self));
self->options = options;
self->usages = usages;
self->flags = flags;
self->description = NULL;
self->epilog = NULL;
return 0;
}
void
argparse_describe(struct argparse *self, const char *description,
const char *epilog)
{
self->description = description;
self->epilog = epilog;
}
int
argparse_parse(struct argparse *self, int argc, const char **argv)
{
self->argc = argc - 1;
self->argv = argv + 1;
self->out = argv;
argparse_options_check(self->options);
for (; self->argc; self->argc--, self->argv++) {
const char *arg = self->argv[0];
if (arg[0] != '-' || !arg[1]) {
if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
goto end;
}
// if it's not option or is a single char '-', copy verbatim
self->out[self->cpidx++] = self->argv[0];
continue;
}
// short option
if (arg[1] != '-') {
self->optvalue = arg + 1;
switch (argparse_short_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
while (self->optvalue) {
switch (argparse_short_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
}
continue;
}
// if '--' presents
if (!arg[2]) {
self->argc--;
self->argv++;
break;
}
// long option
switch (argparse_long_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
continue;
unknown:
fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
argparse_usage(self);
exit(1);
}
end:
memmove(self->out + self->cpidx, self->argv,
self->argc * sizeof(*self->out));
self->out[self->cpidx + self->argc] = NULL;
return self->cpidx + self->argc;
}
void
argparse_usage(struct argparse *self)
{
if (self->usages) {
fprintf(stdout, "Usage: %s\n", *self->usages++);
while (*self->usages && **self->usages)
fprintf(stdout, " or: %s\n", *self->usages++);
} else {
fprintf(stdout, "Usage:\n");
}
// print description
if (self->description)
fprintf(stdout, "%s\n", self->description);
fputc('\n', stdout);
const struct argparse_option *options;
// figure out best width
size_t usage_opts_width = 0;
size_t len;
options = self->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
len = 0;
if ((options)->short_name) {
len += 2;
}
if ((options)->short_name && (options)->long_name) {
len += 2; // separator ", "
}
if ((options)->long_name) {
len += strlen((options)->long_name) + 2;
}
if (options->type == ARGPARSE_OPT_INTEGER) {
len += strlen("=<int>");
}
if (options->type == ARGPARSE_OPT_FLOAT) {
len += strlen("=<flt>");
} else if (options->type == ARGPARSE_OPT_STRING) {
len += strlen("=<str>");
}
len = (len + 3) - ((len + 3) & 3);
if (usage_opts_width < len) {
usage_opts_width = len;
}
}
usage_opts_width += 4; // 4 spaces prefix
options = self->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
size_t pos = 0;
int pad = 0;
if (options->type == ARGPARSE_OPT_GROUP) {
fputc('\n', stdout);
fprintf(stdout, "%s", options->help);
fputc('\n', stdout);
continue;
}
pos = fprintf(stdout, " ");
if (options->short_name) {
pos += fprintf(stdout, "-%c", options->short_name);
}
if (options->long_name && options->short_name) {
pos += fprintf(stdout, ", ");
}
if (options->long_name) {
pos += fprintf(stdout, "--%s", options->long_name);
}
if (options->type == ARGPARSE_OPT_INTEGER) {
pos += fprintf(stdout, "=<int>");
} else if (options->type == ARGPARSE_OPT_FLOAT) {
pos += fprintf(stdout, "=<flt>");
} else if (options->type == ARGPARSE_OPT_STRING) {
pos += fprintf(stdout, "=<str>");
}
if (pos <= usage_opts_width) {
pad = usage_opts_width - pos;
} else {
fputc('\n', stdout);
pad = usage_opts_width;
}
fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
}
// print epilog
if (self->epilog)
fprintf(stdout, "%s\n", self->epilog);
}
int
argparse_help_cb(struct argparse *self, const struct argparse_option *option)
{
(void)option;
argparse_usage(self);
exit(0);
}

130
vendor/argparse.h vendored
View file

@ -1,130 +0,0 @@
/**
* Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
* All rights reserved.
*
* Use of this source code is governed by a MIT-style license that can be found
* in the LICENSE file.
*/
#ifndef ARGPARSE_H
#define ARGPARSE_H
/* For c++ compatibility */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct argparse;
struct argparse_option;
typedef int argparse_callback (struct argparse *self,
const struct argparse_option *option);
enum argparse_flag {
ARGPARSE_STOP_AT_NON_OPTION = 1,
};
enum argparse_option_type {
/* special */
ARGPARSE_OPT_END,
ARGPARSE_OPT_GROUP,
/* options with no arguments */
ARGPARSE_OPT_BOOLEAN,
ARGPARSE_OPT_BIT,
/* options with arguments (optional or required) */
ARGPARSE_OPT_INTEGER,
ARGPARSE_OPT_FLOAT,
ARGPARSE_OPT_STRING,
};
enum argparse_option_flags {
OPT_NONEG = 1, /* disable negation */
};
/**
* argparse option
*
* `type`:
* holds the type of the option, you must have an ARGPARSE_OPT_END last in your
* array.
*
* `short_name`:
* the character to use as a short option name, '\0' if none.
*
* `long_name`:
* the long option name, without the leading dash, NULL if none.
*
* `value`:
* stores pointer to the value to be filled.
*
* `help`:
* the short help message associated to what the option does.
* Must never be NULL (except for ARGPARSE_OPT_END).
*
* `callback`:
* function is called when corresponding argument is parsed.
*
* `data`:
* associated data. Callbacks can use it like they want.
*
* `flags`:
* option flags.
*/
struct argparse_option {
enum argparse_option_type type;
const char short_name;
const char *long_name;
void *value;
const char *help;
argparse_callback *callback;
intptr_t data;
int flags;
};
/**
* argpparse
*/
struct argparse {
// user supplied
const struct argparse_option *options;
const char *const *usages;
int flags;
const char *description; // a description after usage
const char *epilog; // a description at the end
// internal context
int argc;
const char **argv;
const char **out;
int cpidx;
const char *optvalue; // current option value
};
// built-in callbacks
int argparse_help_cb(struct argparse *self,
const struct argparse_option *option);
// built-in option macros
#define OPT_END() { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 }
#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ }
#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ }
#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ }
#define OPT_FLOAT(...) { ARGPARSE_OPT_FLOAT, __VA_ARGS__ }
#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ }
#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 }
#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \
"show this help message and exit", \
argparse_help_cb, 0, OPT_NONEG)
int argparse_init(struct argparse *self, struct argparse_option *options,
const char *const *usages, int flags);
void argparse_describe(struct argparse *self, const char *description,
const char *epilog);
int argparse_parse(struct argparse *self, int argc, const char **argv);
void argparse_usage(struct argparse *self);
#ifdef __cplusplus
}
#endif
#endif

5016
vendor/confini.c vendored

File diff suppressed because it is too large Load diff

547
vendor/confini.h vendored
View file

@ -1,547 +0,0 @@
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
/**
@file confini.h
@brief libconfini header
@author Stefano Gioffr&eacute;
@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

File diff suppressed because it is too large Load diff

6285
vendor/mongoose.h vendored

File diff suppressed because it is too large Load diff

6440
vendor/mpack.c vendored

File diff suppressed because it is too large Load diff

7151
vendor/mpack.h vendored

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
#define EMGAUWA_CONTROLLER_VERSION "@CMAKE_PROJECT_VERSION@"