From 1171ef22be2522612f9180c58cd15419f99338e8 Mon Sep 17 00:00:00 2001 From: Tobias Reisinger Date: Wed, 6 May 2020 01:05:36 +0200 Subject: [PATCH] add: models for controller and relay add: first controller endpoints --- endpoints/api_v1_controllers.c | 40 +++ endpoints/api_v1_controllers_discover.c | 259 ++++++++++++++++++++ include/endpoints/api_v1_controllers.h | 12 + include/enums.h | 8 - include/models/controller.h | 13 +- include/models/relay.h | 15 +- include/models/schedule.h | 3 - models/controller.c | 284 ++++++++++++++++++++++ models/relay.c | 308 ++++++++++++++++++++++++ models/schedule.c | 25 ++ router.c | 4 + 11 files changed, 951 insertions(+), 20 deletions(-) create mode 100644 endpoints/api_v1_controllers.c create mode 100644 endpoints/api_v1_controllers_discover.c create mode 100644 include/endpoints/api_v1_controllers.h create mode 100644 models/controller.c create mode 100644 models/relay.c diff --git a/endpoints/api_v1_controllers.c b/endpoints/api_v1_controllers.c new file mode 100644 index 0000000..a750e50 --- /dev/null +++ b/endpoints/api_v1_controllers.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include +#include + +void +api_v1_controllers_GET(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm) +{ + (void)args; + (void)hm; + controller_t** all_controllers = controller_get_all(); + + cJSON *json = cJSON_CreateArray(); + + for(int i = 0; all_controllers[i] != NULL; ++i) + { + cJSON *json_controller = controller_to_json(all_controllers[i]); + + cJSON_AddItemToArray(json, json_controller); + } + + char *json_str = cJSON_Print(json); + if (json_str == NULL) + { + LOG_ERROR("failed to print controllers json\n"); + mg_send_head(c, 500, 2, "Content-Type: application/json\r\n" STANDARD_HEADERS); + mg_printf(c, "[]"); + } + else + { + mg_send_head(c, 200, strlen(json_str), "Content-Type: application/json\r\n" STANDARD_HEADERS); + mg_printf(c, "%s", json_str); + free(json_str); + } + cJSON_Delete(json); + controller_free_list(all_controllers); +} diff --git a/endpoints/api_v1_controllers_discover.c b/endpoints/api_v1_controllers_discover.c new file mode 100644 index 0000000..c64eb32 --- /dev/null +++ b/endpoints/api_v1_controllers_discover.c @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include +#include + +#define DISCOVERY_TIMEOUT_MS 2000 + +typedef enum +{ + DISCOVERY_MAPPING_ID = 0, + DISCOVERY_MAPPING_NAME = 1, + DISCOVERY_MAPPING_COMMAND_PORT = 2, + DISCOVERY_MAPPING_RELAY_COUNT = 3, +} discovery_mapping_t; + +static int +bind_tcp_server(const char *addr, const char *port, int max_client_backlog) +{ + 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, &hints, &res)) != 0) + { + LOG_ERROR("error getting address info: %s\n", gai_strerror(status)); + } + + fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + + if ((status = bind(fd, res->ai_addr, res->ai_addrlen)) == -1) + { + LOG_ERROR("error binding socket: %s\n", status); + freeaddrinfo(res); + return -1; + } + + if ((status = listen(fd, max_client_backlog)) == -1) + { + LOG_ERROR("error setting up listener: %s\n", status); + freeaddrinfo(res); + return -1; + } + + freeaddrinfo(res); + + return fd; +} + +static int +get_server_port(int fd) +{ + if(fd == -1) + { + return -1; + } + struct sockaddr_in sin; + socklen_t addr_len = sizeof(sin); + if(getsockname(fd, (struct sockaddr *)&sin, &addr_len) == 0) + { + return ntohs(sin.sin_port); + } + return -1; +} + +static int +send_udp_broadcast(const char *addr, uint16_t port, void *message, size_t length) +{ + struct sockaddr_in their_addr; + int fd; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + { + LOG_ERROR("error creating socket\n"); + return -1; + } + + int broadcast = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof broadcast) < 0) + { + LOG_ERROR("error setting broadcast\n"); + return -1; + } + + memset(&their_addr, 0, sizeof(their_addr)); + their_addr.sin_family = AF_INET; + their_addr.sin_port = htons(port); + their_addr.sin_addr.s_addr = inet_addr(addr); + + if(sendto(fd, message, length, 0, (struct sockaddr *)&their_addr, sizeof(their_addr)) < 0) + { + LOG_ERROR("error sending broadcast (%d): '%s'\n", errno, strerror(errno)); + return -1; + } + close(fd); + + return 0; +} + +void +api_v1_controllers_discover_POST(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm) +{ + int discover_server_socket = bind_tcp_server("0.0.0.0", "0", 20); + int discover_server_port = get_server_port(discover_server_socket); + + if(discover_server_port == -1) + { + mg_send_head(c, 500, 2, "Content-Type: application/json\r\n" STANDARD_HEADERS); + mg_printf(c, "[]"); + return; + } + + int16_t payload[1]; + payload[0] = discover_server_port; + + if(send_udp_broadcast("255.255.255.255", global_config.discovery_port, payload, sizeof(payload)) < 0) + { + mg_send_head(c, 500, 2, "Content-Type: application/json\r\n" STANDARD_HEADERS); + mg_printf(c, "[]"); + return; + } + + struct sockaddr_storage their_addr; + socklen_t addr_size; + int client_fd, s_ret; + fd_set accept_fds; + struct timeval timeout; + + uint8_t discover_answer_buf[1]; + + controller_t **known_controllers = controller_get_all(); + + while(true) + { + addr_size = sizeof(their_addr); + + FD_ZERO(&accept_fds); + FD_SET(discover_server_socket, &accept_fds); // NOLINT(hicpp-signed-bitwise) + + timeout.tv_sec = DISCOVERY_TIMEOUT_MS / 1000; + timeout.tv_usec = (DISCOVERY_TIMEOUT_MS % 1000) * 1000; + + s_ret = select(discover_server_socket + 1, &accept_fds, NULL, NULL, &timeout); + if(s_ret == 0) + { + break; + } + else + { + if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0) + { + LOG_ERROR("error accepting client %s\n", strerror(errno)); + continue; + } + + size_t payload_length; + + if(recv(client_fd, &payload_length, sizeof(payload_length), 0) <= 0) + { + LOG_ERROR("error receiving header from client\n"); + continue; + } + + char *answer_payload = (char*)malloc((payload_length)); + ssize_t bytes_transferred; + + if((bytes_transferred = recv(client_fd, answer_payload, payload_length, 0)) <= 0) + { + LOG_ERROR("error receiving payload from client\n"); + continue; + } + + struct sockaddr_in addr; + socklen_t client_addr_size = sizeof(struct sockaddr_in); + if(getpeername(client_fd, (struct sockaddr *)&addr, &client_addr_size) != 0) + { + + LOG_ERROR("error receiving payload from client\n"); + continue; + } + + uuid_t discovered_id; + + mpack_tree_t tree; + mpack_tree_init_data(&tree, answer_payload, payload_length); + mpack_tree_parse(&tree); + mpack_node_t root = mpack_tree_root(&tree); + + memcpy(discovered_id, mpack_node_data(mpack_node_map_uint(root, DISCOVERY_MAPPING_ID)), sizeof(uuid_t)); + + uint16_t discovered_command_port = mpack_node_u16(mpack_node_map_uint(root, DISCOVERY_MAPPING_COMMAND_PORT)); + uint8_t discovered_relay_count = mpack_node_u8(mpack_node_map_uint(root, DISCOVERY_MAPPING_RELAY_COUNT)); + const char *discovered_name = mpack_node_str(mpack_node_map_uint(root, DISCOVERY_MAPPING_NAME)); + + bool found_discovered_in_list = 0; + + for(int i = 0; known_controllers[i] != NULL; i++) + { + if(!found_discovered_in_list) + { + if(uuid_compare(known_controllers[i]->uid, discovered_id) == 0) + { + known_controllers[i]->active = 1; + strncpy(known_controllers[i]->name, discovered_name, MAX_NAME_LENGTH); + known_controllers[i]->name[MAX_NAME_LENGTH] = '\0'; + known_controllers[i]->port = discovered_command_port; + known_controllers[i]->relay_count = discovered_relay_count; + + controller_save(known_controllers[i]); + controller_free(known_controllers[i]); + + found_discovered_in_list = 1; + known_controllers[i] = known_controllers[i + 1]; + } + } + else + { + known_controllers[i] = known_controllers[i + 1]; + } + } + + if(!found_discovered_in_list) + { + controller_t *discovered_controller = malloc(sizeof(controller_t)); + discovered_controller->id = 0; + strcpy(discovered_controller->ip, inet_ntoa(addr.sin_addr)); + memcpy(discovered_controller->uid, discovered_id, sizeof(uuid_t)); + strncpy(discovered_controller->name, discovered_name, MAX_NAME_LENGTH); + discovered_controller->name[MAX_NAME_LENGTH] = '\0'; + discovered_controller->relay_count = discovered_relay_count; + discovered_controller->port = discovered_command_port; + discovered_controller->active = 1; + + controller_save(discovered_controller); + } + mpack_tree_destroy(&tree); + free(answer_payload); + + discover_answer_buf[0] = 0; // TODO add discovery return codes + send(client_fd, discover_answer_buf, sizeof(uint8_t), 0); + close(client_fd); + } + } + for(int i = 0; known_controllers[i] != NULL; i++) + { + known_controllers[i]->active = false; + controller_save(known_controllers[i]); + LOG_DEBUG("lost: %s\n", known_controllers[i]->name); + } + controller_free_list(known_controllers); + + api_v1_controllers_GET(c, args, hm); +} diff --git a/include/endpoints/api_v1_controllers.h b/include/endpoints/api_v1_controllers.h new file mode 100644 index 0000000..a862e5b --- /dev/null +++ b/include/endpoints/api_v1_controllers.h @@ -0,0 +1,12 @@ +#ifndef CORE_ENDPOINTS_API_V1_CONTROLLERS_H +#define CORE_ENDPOINTS_API_V1_CONTROLLERS_H + +#include + +void +api_v1_controllers_discover_POST(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm); + +void +api_v1_controllers_GET(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm); + +#endif /* CORE_ENDPOINTS_API_V1_CONTROLLERS_H */ diff --git a/include/enums.h b/include/enums.h index 824c2f3..606c94c 100644 --- a/include/enums.h +++ b/include/enums.h @@ -7,14 +7,6 @@ typedef enum 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 { COMMAND_MAPPING_CODE = 0, diff --git a/include/models/controller.h b/include/models/controller.h index c3146bc..150fdb8 100644 --- a/include/models/controller.h +++ b/include/models/controller.h @@ -4,12 +4,14 @@ #include #include +#include #include #include typedef struct { - uuid_t id; + int id; + uuid_t uid; char name[128]; char ip[17]; int active; @@ -27,11 +29,14 @@ controller_save(controller_t* contoller); int controller_remove(controller_t* contoller); -char* +cJSON* controller_to_json(controller_t* contoller); -controller_t** -controller_get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param); +controller_t* +controller_get_by_id(int id); + +controller_t* +controller_get_by_uid(uuid_t uid); controller_t** controller_get_all(); diff --git a/include/models/relay.h b/include/models/relay.h index 4e4e47d..b69e742 100644 --- a/include/models/relay.h +++ b/include/models/relay.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include #include @@ -11,21 +13,21 @@ typedef struct { int id; - char name[128]; + char name[MAX_NAME_LENGTH + 1]; int number; - uuid_t controller_id; + int controller_id; int active_schedule_id; schedule_t *active_schedule; schedule_t *schedules[7]; } relay_t; -bool +int relay_save(); -bool +int relay_remove(); -char* +cJSON* relay_to_json(); void @@ -46,4 +48,7 @@ relay_valid_num_is_for_controller(uuid_t controller_id, int relay_num); relay_t** relay_get_all(); +relay_t** +relay_get_by_controller_id(int controller_id); + #endif /* CORE_RELAY_H */ diff --git a/include/models/schedule.h b/include/models/schedule.h index 8fd1517..e3860f4 100644 --- a/include/models/schedule.h +++ b/include/models/schedule.h @@ -40,9 +40,6 @@ schedule_free_list(schedule_t **schedules_list); uint16_t* schedule_periods_to_blob(schedule_t *schedule); -schedule_t** -schedule_get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param); - schedule_t* schedule_get_by_id_or_off(int id); diff --git a/models/controller.c b/models/controller.c new file mode 100644 index 0000000..68f105a --- /dev/null +++ b/models/controller.c @@ -0,0 +1,284 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int +db_update_insert(controller_t *controller, sqlite3_stmt *stmt) +{ + 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_text(stmt, 4, controller->ip, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 5, controller->active); + sqlite3_bind_int(stmt, 6, controller->port); + sqlite3_bind_int(stmt, 7, controller->relay_count); + + 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 'a': // active + new_controller->active = (bool)sqlite3_column_int(stmt, i); + break; + case 'i': + switch(name[1]) + { + case 'd': // id + new_controller->id = sqlite3_column_int(stmt, i); + break; + case 'p': // ip + strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), 16); + break; + default: // ignore columns not implemented + break; + } + break; + case 'n': // name + strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127); + break; + case 'p': // port + new_controller->port = sqlite3_column_int(stmt, i); + break; + case 'r': // relay_count + new_controller->relay_count = sqlite3_column_int(stmt, i); + 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 = relay_get_by_controller_id(new_controller->id); + return new_controller; +} + +static controller_t** +controller_db_select(sqlite3_stmt *stmt) +{ + controller_t **all_controllers = malloc(sizeof(controller_t*)); + + int row = 0; + + while(true) + { + 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 + { + LOG_ERROR("error selecting controllers from database: %s\n", sqlite3_errstr(s)); + break; + } + } + } + sqlite3_finalize(stmt); + all_controllers[row] = NULL; + return all_controllers; +} + +int +controller_save(controller_t *controller) +{ + sqlite3_stmt *stmt; + if(controller->id) + { + sqlite3_prepare_v2(global_database, "UPDATE controllers set uid = ?2, name = ?3, ip = ?4, active = ?5, port = ?6, relay_count = ?7 WHERE id = ?1;", -1, &stmt, NULL); + } + else + { + sqlite3_prepare_v2(global_database, "INSERT INTO controllers(uid, name, ip, active, port, relay_count) values (?2, ?3, ?4, ?5, ?6, ?7);", -1, &stmt, NULL); + } + + int result = db_update_insert(controller, stmt); + + if(result) + { + if(controller->id) + { + LOG_ERROR("error inserting data: %s\n", sqlite3_errmsg(global_database)); + } + else + { + LOG_ERROR("error updating data: %s\n", sqlite3_errmsg(global_database)); + } + } + else + { + controller->id = sqlite3_last_insert_rowid(global_database); + } + 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; +} + +void +controller_free(controller_t *controller) +{ + relay_free_list(controller->relays); + free(controller); +} + +void +controller_free_list(controller_t **controllers) +{ + for(int i = 0; controllers[i] != NULL; ++i) + { + controller_free(controllers[i]); + } + free(controllers); +} + +cJSON* +controller_to_json(controller_t *controller) +{ + cJSON *json = cJSON_CreateObject(); + + cJSON *json_name = cJSON_CreateString(controller->name); + if(json_name == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "name", json_name); + + char uuid_str[UUID_STR_LEN]; + uuid_unparse(controller->uid, uuid_str); + cJSON *json_id = cJSON_CreateString(uuid_str); + if(json_name == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "id", json_id); + + cJSON *json_ip = cJSON_CreateString(controller->ip); + if(json_ip == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "ip", json_ip); + + cJSON *json_port = cJSON_CreateNumber(controller->port); + if(json_port == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "port", json_port); + + cJSON *json_relay_count = cJSON_CreateNumber(controller->relay_count); + if(json_relay_count == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "relay_count", json_relay_count); + + cJSON *json_active = cJSON_CreateBool(controller->active); + if(json_active == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "active", json_active); + + //TODO add relays + + return json; +} + +controller_t* +controller_get_by_id(int id) +{ + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(global_database, "SELECT * FROM controllers WHERE id = ?1;", -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, id); + + controller_t **sql_result = controller_db_select(stmt); + + controller_t *result = sql_result[0]; + free(sql_result); + + return result; +} + +controller_t* +controller_get_by_uid(uuid_t uid) +{ + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(global_database, "SELECT * FROM controllers WHERE uid = ?1;", -1, &stmt, NULL); + sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC); + + controller_t **sql_result = controller_db_select(stmt); + + controller_t *result = sql_result[0]; + free(sql_result); + + return result; +} + +controller_t** +controller_get_all() +{ + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(global_database, "SELECT * FROM controllers;", -1, &stmt, NULL); + + return controller_db_select(stmt); + +} diff --git a/models/relay.c b/models/relay.c new file mode 100644 index 0000000..eb6cb37 --- /dev/null +++ b/models/relay.c @@ -0,0 +1,308 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int +db_update_insert(relay_t *relay, sqlite3_stmt *stmt) +{ + int rc; + + junction_relay_schedule_remove_for_relay(relay->id); + for(int i = 0; i < 7; ++i) + { + junction_relay_schedule_insert(i, relay->id, relay->schedules[i]->id); + } + + sqlite3_bind_int(stmt, 1, relay->id); + sqlite3_bind_int(stmt, 2, relay->number); + sqlite3_bind_text(stmt, 3, relay->name, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 4, relay->controller_id); + + 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)); + for(int i = 0; i < sqlite3_column_count(stmt); i++) + { + const char *name = sqlite3_column_name(stmt, i); + switch(name[0]) + { + case 'c': // controller_id + new_relay->controller_id = sqlite3_column_int(stmt, i); + break; + 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; + } + } + for(int i = 0; i < 7; ++i) + { + int schedule_id = junction_relay_schedule_get_schedule_id(i, new_relay->id); + new_relay->schedules[i] = schedule_get_by_id_or_off(schedule_id); + } + + 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 + { + LOG_ERROR("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) +{ + sqlite3_stmt *stmt; + if(relay->id) + { + sqlite3_prepare_v2(global_database, "UPDATE relays set number = ?2, name = ?3, controller_id = ?4 WHERE id = ?1;", -1, &stmt, NULL); + } + else + { + sqlite3_prepare_v2(global_database, "INSERT INTO relays(number, name, controller_id) values (?2, ?3, ?4);", -1, &stmt, NULL); + } + + int result = db_update_insert(relay, stmt); + + if(result) + { + if(relay->id) + { + LOG_ERROR("error inserting data: %s\n", sqlite3_errmsg(global_database)); + } + else + { + LOG_ERROR("error updating data: %s\n", sqlite3_errmsg(global_database)); + } + } + else + { + relay->id = sqlite3_last_insert_rowid(global_database); + } + return result; +} + +int +relay_remove(relay_t *relay) +{ + sqlite3_stmt *stmt; + if(!relay->id) + { + return 0; + } + + sqlite3_prepare_v2(global_database, "DELETE FROM relays WHERE id=?1;", -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, relay->id); + + int rc = sqlite3_step(stmt); + + sqlite3_finalize(stmt); + + return rc != SQLITE_DONE; +} + +void +relay_free(relay_t *relay) +{ + schedule_free(relay->active_schedule); + for(int i = 0; i < 7; ++i) + { + schedule_free(relay->schedules[i]); + } + free(relay); +} + +void +relay_free_list(relay_t **relays) +{ + for(int i = 0; relays[i] != NULL; ++i) + { + relay_free(relays[i]); + } + free(relays); +} + +cJSON* +relay_to_json(relay_t *relay) +{ + cJSON *json = cJSON_CreateObject(); + + cJSON *json_number = cJSON_CreateNumber(relay->number); + if(json_number == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "number", json_number); + + cJSON *json_name = cJSON_CreateString(relay->name); + if(json_name == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "name", json_name); + + controller_t *controller = controller_get_by_id(relay->id); + if(!controller) + { + cJSON_Delete(json); + return NULL; + } + + char uuid_str[UUID_STR_LEN]; + uuid_unparse(controller->uid, uuid_str); + cJSON *json_id = cJSON_CreateString(uuid_str); + if(json_name == NULL) + { + cJSON_Delete(json); + return NULL; + } + cJSON_AddItemToObject(json, "id", json_id); + + cJSON *json_tags = cJSON_CreateArray(); + int *tags_ids = junction_tag_get_tags_for_relay_id(relay->id); + if(tags_ids != NULL) + { + for(int i = 0; tags_ids[i] != 0; ++i) + { + char *tag = tag_get_tag(tags_ids[i]); + if(tag == NULL) + { + continue; + } + + cJSON *json_tag = cJSON_CreateString(tag); + if (json_tag == NULL) + { + LOG_DEBUG("failed to add tag from string '%s'\n", tag); + free(tag); + continue; + } + cJSON_AddItemToArray(json_tags, json_tag); + free(tag); + } + free(tags_ids); + } + cJSON_AddItemToObject(json, "tags", json_tags); + + return json; +} + +relay_t* +relay_get_by_id(int id) +{ + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE id = ?1;", -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, id); + + relay_t **sql_result = relay_db_select(stmt); + + relay_t *result = sql_result[0]; + free(sql_result); + + return result; +} + +relay_t* +relay_get_by_uid(uuid_t uid) +{ + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE uid = ?1;", -1, &stmt, NULL); + sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC); + + relay_t **sql_result = relay_db_select(stmt); + + relay_t *result = sql_result[0]; + free(sql_result); + + return result; +} + +relay_t** +relay_get_all() +{ + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(global_database, "SELECT * FROM relays;", -1, &stmt, NULL); + + return relay_db_select(stmt); +} + +relay_t** +relay_get_by_controller_id(int controller_id) +{ + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE controller_id;", -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, controller_id); + + return relay_db_select(stmt); + +} diff --git a/models/schedule.c b/models/schedule.c index 8cb1049..5d33870 100644 --- a/models/schedule.c +++ b/models/schedule.c @@ -306,6 +306,31 @@ schedule_to_json(schedule_t *schedule) return json; } +schedule_t* +schedule_get_by_id_or_off(int id) +{ + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(global_database, "SELECT * FROM schedules WHERE id = ?1;", -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, id); + + schedule_t **sql_result = schedule_db_select(stmt); + + schedule_t *result = sql_result[0]; + free(sql_result); + + if(result) + { + return result; + } + + uuid_t tmp_uuid; + memset(tmp_uuid, 0, sizeof(uuid_t)); + memcpy(tmp_uuid, "off", 3); + + return schedule_get_by_uid(tmp_uuid); +} + schedule_t* schedule_get_by_id(int id) { diff --git a/router.c b/router.c index f9ab403..996d8d3 100644 --- a/router.c +++ b/router.c @@ -4,6 +4,7 @@ #include #include +#include static const int HTTP_METHOD_GET = (1 << 0); static const int HTTP_METHOD_POST = (1 << 1); @@ -57,6 +58,9 @@ router_init() router_register_endpoint("/api/v1/schedules/{str}/", HTTP_METHOD_GET, api_v1_schedules_STR_GET); router_register_endpoint("/api/v1/schedules/{str}/", HTTP_METHOD_PUT, api_v1_schedules_STR_PUT); router_register_endpoint("/api/v1/schedules/{str}/", HTTP_METHOD_DELETE, api_v1_schedules_STR_DELETE); + + router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_POST, api_v1_controllers_discover_POST); + router_register_endpoint("/api/v1/controllers/", HTTP_METHOD_GET, api_v1_controllers_GET); } void