From db64e4f820625b830c88fabd32dafa4013118465 Mon Sep 17 00:00:00 2001
From: Tobias Reisinger <tobias@msrg.cc>
Date: Tue, 14 Apr 2020 00:50:55 +0200
Subject: [PATCH] add: relay/piface support

---
 CMakeLists.txt                  |  22 +++--
 discovery.c                     | 125 ----------------------------
 drivers/gpio.c                  |  11 +++
 drivers/piface.c                |  11 +++
 handlers/command.c              | 113 +++++++++++++++++++++++++
 handlers/discovery.c            |  72 ++++++++++++++++
 helpers/bind_server.c           |  51 ++++++++++++
 helpers/connect_server.c        |  44 +++++-----
 helpers/get_port.c              |  24 ++++++
 helpers/open_discovery_socket.c |  57 +++++++++++++
 include/config.h                |   6 +-
 include/constants.h             |   3 +
 include/discovery.h             |  27 ------
 include/drivers.h               |  14 ++++
 include/enums.h                 |  36 +++++++-
 include/handlers.h              |  24 ++++++
 include/helper.h                |   7 --
 include/helpers.h               |  25 ++++++
 include/logger.h                |  21 +++--
 include/models/controller.h     |  46 +++++++----
 include/models/period.h         |  19 +++++
 include/models/relay.h          |  61 ++++++++++++--
 include/models/schedule.h       |  28 +++++++
 include/wiring_debug.h          |   7 +-
 logger.c                        |  10 +++
 main.c                          | 141 ++++++++++++++++++++++++++------
 models/controller.c             |  60 ++++++++++----
 models/controller_load.c        |  46 ++++++-----
 models/controller_save.c        |  59 +++++++------
 models/period.c                 |  44 ++++++++++
 models/relay.c                  |  81 +++++++++++++++++-
 models/relay_load.c             | 100 ++++++++++++++++++++++
 models/relay_save.c             |  88 ++++++++++++++++++++
 models/schedule.c               |  89 ++++++++++++++++++++
 34 files changed, 1259 insertions(+), 313 deletions(-)
 delete mode 100644 discovery.c
 create mode 100644 drivers/gpio.c
 create mode 100644 drivers/piface.c
 create mode 100644 handlers/command.c
 create mode 100644 handlers/discovery.c
 create mode 100644 helpers/bind_server.c
 create mode 100644 helpers/get_port.c
 create mode 100644 helpers/open_discovery_socket.c
 create mode 100644 include/constants.h
 delete mode 100644 include/discovery.h
 create mode 100644 include/drivers.h
 create mode 100644 include/handlers.h
 delete mode 100644 include/helper.h
 create mode 100644 include/helpers.h
 create mode 100644 include/models/period.h
 create mode 100644 include/models/schedule.h
 create mode 100644 logger.c
 create mode 100644 models/period.c
 create mode 100644 models/relay_load.c
 create mode 100644 models/relay_save.c
 create mode 100644 models/schedule.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b06ff60..6d03031 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,7 +5,7 @@ add_executable(controller main.c)
 
 option(WIRING_PI_DEBUG "Use WiringPi Debugging Tool" OFF)
 
-SET(CMAKE_C_FLAGS "-Wall -Wextra -lwiringPi -luuid -llmdb -g")
+SET(CMAKE_C_FLAGS "-Wall -Wextra -lwiringPi -lwiringPiDev -luuid -llmdb -g")
 
 string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE)
 add_definitions("-DSOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}")
@@ -16,14 +16,26 @@ if(WIRING_PI_DEBUG)
 endif(WIRING_PI_DEBUG)
 
 aux_source_directory(. SRC_DIR)
-aux_source_directory(models MODEL_SRC)
-aux_source_directory(helpers HELPER_SRC)
+aux_source_directory(models MODELS_SRC)
+aux_source_directory(helpers HELPERS_SRC)
+aux_source_directory(handlers HANDLERS_SRC)
+aux_source_directory(drivers DRIVERS_SRC)
 
-target_sources(controller PRIVATE ${SRC_DIR} ${MODEL_SRC} ${HELPER_SRC})
+target_sources(controller PRIVATE ${SRC_DIR} ${MODELS_SRC} ${HELPERS_SRC} ${HANDLERS_SRC} ${DRIVERS_SRC})
 target_include_directories(controller PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
 
 add_custom_target(run
-    COMMAND controller
+    COMMAND ./controller
+    DEPENDS controller
+    WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
+)
+add_custom_target(debug
+    COMMAND valgrind ./controller
+    DEPENDS controller
+    WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
+)
+add_custom_target(debug-full
+    COMMAND valgrind --leak-check=full --show-leak-kinds=all ./controller
     DEPENDS controller
     WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
 )
diff --git a/discovery.c b/discovery.c
deleted file mode 100644
index b1ae4d6..0000000
--- a/discovery.c
+++ /dev/null
@@ -1,125 +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 <discovery.h>
-#include <helper.h>
-#include <binn.h>
-
-enum DISCOVERY_MAPPING
-{
-    DISCOVERY_MAPPING_ID = 0,
-    DISCOVERY_MAPPING_NAME = 1,
-    DISCOVERY_MAPPING_COMMAND_PORT = 2,
-    DISCOVERY_MAPPING_RELAY_COUNT = 3,
-};
-
-int
-discovery_socket_open(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 = malloc(6 * sizeof(char));
-    sprintf(discovery_port_str, "%u", discovery_port);
-
-    //get connection info for our computer
-    if ((status = getaddrinfo(NULL, discovery_port_str, &hints, &res)) != 0)
-    {
-        LOG_FATAL("getaddrinfo: %s", 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)
-    {
-        LOG_FATAL("setsockopt: %s", strerror(errno));
-        freeaddrinfo(res);
-        exit(EXIT_FAILURE);
-    }
-
-    if (bind(fd, res->ai_addr, res->ai_addrlen) == -1)
-    {
-        LOG_FATAL("bind: %s", strerror(errno));
-        freeaddrinfo(res);
-        exit(EXIT_FAILURE);
-    }
-
-    freeaddrinfo(res);
-
-    LOG_DEBUG("opened discovery socket on port %u", discovery_port);
-
-    return fd;
-}
-
-void
-discovery_handle_discover(int fd, controller *cntrlr)
-{
-    ssize_t bytes_transferred;
-    uint16_t discovery_answer_port;
-    struct sockaddr_in si_other;
-    socklen_t slen = sizeof(si_other);
-
-    if((bytes_transferred = recvfrom(fd, &discovery_answer_port, sizeof(discovery_answer_port), 0, (struct sockaddr *) &si_other, &slen)) <= 0)
-    {
-        LOG_ERROR("received invalid discovery from %s", inet_ntoa(si_other.sin_addr));
-        return;
-    }
-    LOG_DEBUG("received discovery from %s for port %d", inet_ntoa(si_other.sin_addr), discovery_answer_port);
-
-    if(discovery_answer_port == 0)
-    {
-        LOG_ERROR("invalid port received");
-        return;
-    }
-
-    binn *map = binn_map();
-
-    binn_map_set_blob(map, DISCOVERY_MAPPING_ID, &cntrlr->id, sizeof(uuid_t));
-    binn_map_set_str(map, DISCOVERY_MAPPING_NAME, cntrlr->name);
-    binn_map_set_uint32(map, DISCOVERY_MAPPING_COMMAND_PORT, cntrlr->command_port);
-    binn_map_set_uint8(map, DISCOVERY_MAPPING_RELAY_COUNT, cntrlr->relay_count);
-
-    void *payload = binn_ptr(map);
-    size_t payload_size = binn_size(map);
-
-    char discover_answer_port_str[6];
-    sprintf(discover_answer_port_str, "%d", discovery_answer_port);
-    int fd_answer = helper_connect_server(inet_ntoa(si_other.sin_addr), discover_answer_port_str);
-
-    LOG_DEBUG("size: %ld (%ld)", payload_size, sizeof(payload_size));
-
-    if((bytes_transferred = send(fd_answer, &payload_size, sizeof(payload_size), 0)) <= 0)
-    {
-        LOG_ERROR("error during sending");
-        binn_free(map);
-        return;
-    }
-    if((bytes_transferred = send(fd_answer, payload, payload_size, 0)) <= 0)
-    {
-        LOG_ERROR("error during sending");
-        binn_free(map);
-        return;
-    }
-
-    close(fd_answer);
-}
diff --git a/drivers/gpio.c b/drivers/gpio.c
new file mode 100644
index 0000000..fdff05e
--- /dev/null
+++ b/drivers/gpio.c
@@ -0,0 +1,11 @@
+#include <wiringPi.h>
+#include <piFace.h>
+#include <wiring_debug.h>
+
+#include <drivers.h>
+
+void
+driver_gpio_set(relay_t *relay, int value)
+{
+    digitalWrite(relay->number, value);
+}
diff --git a/drivers/piface.c b/drivers/piface.c
new file mode 100644
index 0000000..2cea3bb
--- /dev/null
+++ b/drivers/piface.c
@@ -0,0 +1,11 @@
+#include <wiringPi.h>
+#include <piFace.h>
+#include <wiring_debug.h>
+
+#include <drivers.h>
+
+void
+driver_piface_set(relay_t *relay, int value)
+{
+    digitalWrite(DRIVER_PIFACE_GPIO_BASE + relay->number, value);
+}
diff --git a/handlers/command.c b/handlers/command.c
new file mode 100644
index 0000000..3335445
--- /dev/null
+++ b/handlers/command.c
@@ -0,0 +1,113 @@
+#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 <handlers.h>
+#include <helpers.h>
+#include <enums.h>
+#include <binn.h>
+#include <models/schedule.h>
+
+static void
+handler_command_set_schedule(binn *map, controller_t *controller)
+{
+    uint8_t relay_num = binn_map_uint8(map, COMMAND_MAPPING_RELAY_NUM);
+    int uuid_size = sizeof(uuid_t);
+    uuid_t schedule_id;
+    memmove(schedule_id, binn_map_blob(map, COMMAND_MAPPING_SCHEDULE_ID, &uuid_size), uuid_size);
+    uint16_t periods_count = binn_map_uint16(map, COMMAND_MAPPING_PERIODS_COUNT);
+    int periods_size = sizeof(uint16_t) * (periods_count * 2);
+    uint16_t *periods = binn_map_blob(map, COMMAND_MAPPING_PERIODS_BLOB, &periods_size);
+
+    relay_t *target_relay = controller->relays[relay_num];
+
+    if(target_relay->schedule)
+    {
+        schedule_free(target_relay->schedule);
+    }
+    target_relay->schedule = schedule_create(schedule_id, periods_count, periods);
+}
+
+static void
+handler_command_set_relay_name(binn *map, controller_t *controller)
+{
+    uint8_t relay_num = binn_map_uint8(map, COMMAND_MAPPING_RELAY_NUM);
+    char *relay_name = binn_map_str(map, COMMAND_MAPPING_NAME);
+
+    if(relay_num < controller->relay_count)
+    {
+        relay_set_name(controller->relays[relay_num], relay_name);
+    }
+}
+
+void
+handler_command(int fd, controller_t *controller)
+{
+    struct sockaddr_storage their_addr;
+    socklen_t addr_size;
+    int client_fd;
+
+    addr_size = sizeof(their_addr);
+
+    if((client_fd = accept(fd, (struct sockaddr *) &their_addr, &addr_size)) < 0)
+    {
+        LOG_ERROR("could not accept client: %s", strerror(errno));
+        return;
+    }
+
+    size_t payload_length;
+
+    if(recv(client_fd, &payload_length, sizeof(payload_length), 0) <= 0)
+    {
+        LOG_ERROR("unable to receive header: %s", strerror(errno));
+        return;
+    }
+
+    void *payload = malloc((payload_length + 1));
+    ssize_t bytes_transferred;
+
+    if((bytes_transferred = recv(client_fd, payload, payload_length, 0)) <= 0)
+    {
+        LOG_ERROR("unable to receive payload: %s", strerror(errno));
+        return;
+    }
+
+    uint8_t command_code = binn_map_uint8(payload, COMMAND_MAPPING_CODE);
+
+    LOG_INFO("received command %d", command_code);
+
+    switch(command_code)
+    {
+        case COMMAND_CODE_GET_TIME:
+            break;
+        case COMMAND_CODE_GET_ID:
+            break;
+        case COMMAND_CODE_SET_NAME:
+            controller_set_name(controller, binn_map_str(payload, COMMAND_MAPPING_NAME));
+            break;
+        case COMMAND_CODE_GET_NAME:
+            break;
+        case COMMAND_CODE_SET_SCHEDULE:
+            handler_command_set_schedule(payload, controller);
+            break;
+        case COMMAND_CODE_GET_SCHEDULE:
+            break;
+        case COMMAND_CODE_SET_RELAY_NAME:
+            handler_command_set_relay_name(payload, controller);
+            break;
+        case COMMAND_CODE_GET_RELAY_NAME:
+            break;
+    }
+
+    free(payload);
+    close(client_fd);
+}
diff --git a/handlers/discovery.c b/handlers/discovery.c
new file mode 100644
index 0000000..03decfa
--- /dev/null
+++ b/handlers/discovery.c
@@ -0,0 +1,72 @@
+#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 <handlers.h>
+#include <helpers.h>
+#include <binn.h>
+#include <enums.h>
+
+void
+handler_discovery(int fd, controller_t *controller)
+{
+    ssize_t bytes_transferred;
+    uint16_t discovery_answer_port;
+    struct sockaddr_in si_other;
+    socklen_t slen = sizeof(si_other);
+
+    if((bytes_transferred = recvfrom(fd, &discovery_answer_port, sizeof(discovery_answer_port), 0, (struct sockaddr *) &si_other, &slen)) <= 0)
+    {
+        LOG_ERROR("received invalid discovery from %s", inet_ntoa(si_other.sin_addr));
+        return;
+    }
+    LOG_DEBUG("received discovery from %s for port %d", inet_ntoa(si_other.sin_addr), discovery_answer_port);
+
+    if(discovery_answer_port == 0)
+    {
+        LOG_ERROR("invalid port received");
+        return;
+    }
+
+    binn *map = binn_map();
+
+    binn_map_set_blob(map, DISCOVERY_MAPPING_ID, &controller->id, sizeof(uuid_t));
+    binn_map_set_str(map, DISCOVERY_MAPPING_NAME, controller->name);
+    binn_map_set_uint32(map, DISCOVERY_MAPPING_COMMAND_PORT, controller->command_port);
+    binn_map_set_uint8(map, DISCOVERY_MAPPING_RELAY_COUNT, controller->relay_count);
+
+    void *payload = binn_ptr(map);
+    size_t payload_size = binn_size(map);
+
+    int fd_answer = helper_connect_tcp_server(inet_ntoa(si_other.sin_addr), discovery_answer_port);
+    if(fd_answer == -1)
+    {
+        LOG_ERROR("error during connecting");
+        binn_free(map);
+        return;
+    }
+
+    if((bytes_transferred = send(fd_answer, &payload_size, sizeof(payload_size), 0)) <= 0)
+    {
+        LOG_ERROR("error during sending");
+        binn_free(map);
+        return;
+    }
+    if((bytes_transferred = send(fd_answer, payload, payload_size, 0)) <= 0)
+    {
+        LOG_ERROR("error during sending");
+        binn_free(map);
+        return;
+    }
+
+    close(fd_answer);
+}
diff --git a/helpers/bind_server.c b/helpers/bind_server.c
new file mode 100644
index 0000000..3d08ec6
--- /dev/null
+++ b/helpers/bind_server.c
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <sys/socket.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)
+    {
+        LOG_ERROR("getaddrinfo: %s", 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)
+    {
+        LOG_ERROR("error binding socket: %s", strerror(errno));
+        freeaddrinfo(res);
+        return -1;
+    }
+
+    if ((status = listen(fd, max_client_backlog)) == -1)
+    {
+        LOG_ERROR("error setting up listener: %s", strerror(errno));
+        freeaddrinfo(res);
+        return -1;
+    }
+
+    freeaddrinfo(res);
+
+    return fd;
+}
diff --git a/helpers/connect_server.c b/helpers/connect_server.c
index 8cf67ca..615056a 100644
--- a/helpers/connect_server.c
+++ b/helpers/connect_server.c
@@ -5,33 +5,35 @@
 #include <sys/socket.h>
 
 #include <logger.h>
-#include <helper.h>
+#include <helpers.h>
 
 int
-helper_connect_server(char* host, char* port)
+helper_connect_tcp_server(char* host, uint16_t 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
+    char port_str[6];
+    sprintf(port_str, "%d", port);
 
-  if ((status = getaddrinfo(host, port, &hints, &res)) != 0) { //getaddrinfo() will evaluate the given address, using the hints-flags and port, and return an IP address and other server infos
-      fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
-      exit(EXIT_FAILURE);
-  }
+    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
 
-  //res got filled out by getaddrinfo() for us
-  s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
+    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
+        LOG_ERROR("getaddrinfo: %s\n", gai_strerror(status));
+        return -1;
+    }
 
-  if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
-      fprintf(stderr, "Keine Verbindung mit dem Netzwerk möglich.\n");
-      freeaddrinfo(res);
-      exit(EXIT_FAILURE);
-  }
+    //res got filled out by getaddrinfo() for us
+    s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
 
-  freeaddrinfo(res);
+    if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
+        LOG_ERROR("connect() failed");
+        freeaddrinfo(res);
+        return -1;
+    }
 
-  return s;
+    freeaddrinfo(res);
+
+    return s;
 }
-
diff --git a/helpers/get_port.c b/helpers/get_port.c
new file mode 100644
index 0000000..edcd8a4
--- /dev/null
+++ b/helpers/get_port.c
@@ -0,0 +1,24 @@
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+
+#include <helpers.h>
+#include <logger.h>
+
+uint16_t
+helper_get_port(int sock)
+{
+    struct sockaddr_in sin;
+    socklen_t len = sizeof(sin);
+    if (getsockname(sock, (struct sockaddr *)&sin, &len) == -1)
+    {
+        LOG_ERROR("could not get socket name for port: %s", strerror(errno));
+        return 0;
+    }
+    else
+    {
+        return ntohs(sin.sin_port);
+    }
+}
diff --git a/helpers/open_discovery_socket.c b/helpers/open_discovery_socket.c
new file mode 100644
index 0000000..0b0db89
--- /dev/null
+++ b/helpers/open_discovery_socket.c
@@ -0,0 +1,57 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include <logger.h>
+#include <helpers.h>
+
+int
+helper_open_discovery_socket(uint16_t discovery_port)
+{
+    struct addrinfo hints, *res;
+    int fd, status;
+
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = AF_INET; // use ipv4
+    hints.ai_socktype = SOCK_DGRAM; //set socket flag
+    hints.ai_flags = AI_PASSIVE; // get my IP
+
+    char discovery_port_str[6];
+    sprintf(discovery_port_str, "%u", discovery_port);
+
+    //get connection info for our computer
+    if ((status = getaddrinfo(NULL, discovery_port_str, &hints, &res)) != 0)
+    {
+        LOG_FATAL("getaddrinfo: %s", 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)
+    {
+        LOG_FATAL("setsockopt: %s", strerror(errno));
+        freeaddrinfo(res);
+        exit(EXIT_FAILURE);
+    }
+
+    if (bind(fd, res->ai_addr, res->ai_addrlen) == -1)
+    {
+        LOG_FATAL("bind: %s", strerror(errno));
+        freeaddrinfo(res);
+        exit(EXIT_FAILURE);
+    }
+
+    freeaddrinfo(res);
+
+    LOG_INFO("opened discovery socket on port %u", discovery_port);
+
+    return fd;
+}
diff --git a/include/config.h b/include/config.h
index de9c0fb..5f4c031 100644
--- a/include/config.h
+++ b/include/config.h
@@ -4,11 +4,11 @@
 #include <log_levels.h>
 
 /**
- * @brief Limit the maximum length of a controller name
+ * @brief Limit the maximum length of a controller/relay/etc name
  *
- * The NULL terminator is not included. Arrays of length #CONTROLLER_NAME_LENGTH + 1 are required.
+ * The NULL terminator is not included. Arrays of length #MAX_NAME_LENGTH + 1 are required.
  */
-#define CONTROLLER_NAME_LENGTH 128
+#define MAX_NAME_LENGTH 128
 
 /**
  * @brief Maximum number of dbs for the databases for the MDB_env
diff --git a/include/constants.h b/include/constants.h
new file mode 100644
index 0000000..5bfcb5b
--- /dev/null
+++ b/include/constants.h
@@ -0,0 +1,3 @@
+#define SECONDS_PER_DAY 86400 // 60 * 60 * 24
+
+#define SECONDS_PER_MINUTE 60
diff --git a/include/discovery.h b/include/discovery.h
deleted file mode 100644
index 0e50f15..0000000
--- a/include/discovery.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef CONTROLLER_DISCOVERY_H
-#define CONTROLLER_DISCOVERY_H
-
-#include <models/controller.h>
-
-/**
- * @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
-discovery_socket_open(uint16_t discovery_port);
-
-/**
- * @brief Handle the discovery processing
- *
- * @param fd File descriptor to receive initial data from
- * @param cntrlr Controller to use for answering discovery
- */
-void
-discovery_handle_discover(int fd, controller *cntrlr);
-
-#endif /* CONTROLLER_DISCOVERY_H */
diff --git a/include/drivers.h b/include/drivers.h
new file mode 100644
index 0000000..b33c140
--- /dev/null
+++ b/include/drivers.h
@@ -0,0 +1,14 @@
+#ifndef CONTROLLER_DRIVERS_H
+#define CONTROLLER_DRIVERS_H
+
+#include <models/relay.h>
+
+#define DRIVER_PIFACE_GPIO_BASE 200
+
+void
+driver_piface_set(relay_t *relay, int value);
+
+void
+driver_gpio_set(relay_t *relay, int value);
+
+#endif /* CONTROLLER_DRIVERS_H */
diff --git a/include/enums.h b/include/enums.h
index 34d956f..9a03afe 100644
--- a/include/enums.h
+++ b/include/enums.h
@@ -1,8 +1,40 @@
 #ifndef CONTROLLER_ENUMS_H
 #define CONTROLLER_ENUMS_H
 
-enum poll_fgs {
-    POLL_FGS_DISCOVERY
+enum poll_fgs
+{
+    POLL_FGS_DISCOVERY,
+    POLL_FGS_COMMAND
+};
+
+enum discovery_mapping
+{
+    DISCOVERY_MAPPING_ID = 0,
+    DISCOVERY_MAPPING_NAME = 1,
+    DISCOVERY_MAPPING_COMMAND_PORT = 2,
+    DISCOVERY_MAPPING_RELAY_COUNT = 3,
+};
+
+enum control_mapping
+{
+    COMMAND_MAPPING_CODE = 0,
+    COMMAND_MAPPING_NAME = 1,
+    COMMAND_MAPPING_RELAY_NUM = 2,
+    COMMAND_MAPPING_SCHEDULE_ID = 3,
+    COMMAND_MAPPING_PERIODS_COUNT = 4,
+    COMMAND_MAPPING_PERIODS_BLOB = 5,
+};
+
+enum command_code
+{
+    COMMAND_CODE_GET_TIME = 1,
+    COMMAND_CODE_GET_ID = 2,
+    COMMAND_CODE_SET_NAME = 100,
+    COMMAND_CODE_GET_NAME = 101,
+    COMMAND_CODE_SET_SCHEDULE = 102,
+    COMMAND_CODE_GET_SCHEDULE = 103,
+    COMMAND_CODE_SET_RELAY_NAME = 104,
+    COMMAND_CODE_GET_RELAY_NAME = 105,
 };
 
 #endif /* CONTROLLER_ENUMS_H */
diff --git a/include/handlers.h b/include/handlers.h
new file mode 100644
index 0000000..0b52d64
--- /dev/null
+++ b/include/handlers.h
@@ -0,0 +1,24 @@
+#ifndef CONTROLLER_HANDLERS_H
+#define CONTROLLER_HANDLERS_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(int fd, controller_t *controller);
+
+/**
+ * @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(int fd, controller_t *controller);
+
+#endif /* CONTROLLER_HANDLERS_H */
diff --git a/include/helper.h b/include/helper.h
deleted file mode 100644
index aa2a9f3..0000000
--- a/include/helper.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef CONTROLLER_HELPER_H
-#define CONTROLLER_HELPER_H
-
-int
-helper_connect_server(char* host, char* port);
-
-#endif /* CONTROLLER_HELPER_H */
diff --git a/include/helpers.h b/include/helpers.h
new file mode 100644
index 0000000..b1bd8a5
--- /dev/null
+++ b/include/helpers.h
@@ -0,0 +1,25 @@
+#ifndef CONTROLLER_HELPERS_H
+#define CONTROLLER_HELPERS_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);
+
+#endif /* CONTROLLER_HELPERS_H */
diff --git a/include/logger.h b/include/logger.h
index d648fef..2573ec7 100644
--- a/include/logger.h
+++ b/include/logger.h
@@ -2,45 +2,52 @@
 #define CONTROLLER_LOGGER_H
 
 #include <stdio.h>
+#include <time.h>
 
 #include <colors.h>
 #include <config.h>
 #include <macros.h>
 
-#define _LOGGER_MESSAGE(msg) COLOR_NONE " %s:%s:%d: " msg "\n", __FILENAME__, __func__, __LINE__
+#define _LOGGER_TIMESTAMP_SIZE 32 
+char _LOGGER_TIMESTAMP[_LOGGER_TIMESTAMP_SIZE];
+
+char*
+logger_get_timestamp();
+
+#define _LOGGER_MESSAGE(msg) COLOR_NONE " %s %s:%d:%s: " msg "\n", logger_get_timestamp(), __FILENAME__, __LINE__, __func__
 
 #if LOG_LEVEL >= LOG_LEVEL_TRACE
-    #define LOG_TRACE(msg, ...) printf(COLOR_GREEN "[TRACE]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
+    #define LOG_TRACE(msg, ...) fprintf(stdout, COLOR_GREEN "[TRACE]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
 #else
     #define LOG_TRACE(msg, ...)
 #endif
 
 #if LOG_LEVEL >= LOG_LEVEL_DEBUG
-    #define LOG_DEBUG(msg, ...) printf(COLOR_BLUE "[DEBUG]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
+    #define LOG_DEBUG(msg, ...) fprintf(stdout, COLOR_BLUE "[DEBUG]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
 #else
     #define LOG_DEBUG(msg, ...)
 #endif
 
 #if LOG_LEVEL >= LOG_LEVEL_INFO
-    #define LOG_INFO(msg, ...) printf(COLOR_CYAN "[ INFO] " _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
+    #define LOG_INFO(msg, ...) fprintf(stdout, COLOR_CYAN "[ INFO]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
 #else
     #define LOG_INFO(msg, ...)
 #endif
 
 #if LOG_LEVEL >= LOG_LEVEL_WARN
-    #define LOG_WARN(msg, ...) printf(COLOR_YELLOW "[ WARN] " _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
+    #define LOG_WARN(msg, ...) fprintf(stdout, COLOR_YELLOW "[ WARN]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
 #else
     #define LOG_WARN(msg, ...) 
 #endif
 
 #if LOG_LEVEL >= LOG_LEVEL_ERROR
-    #define LOG_ERROR(msg, ...) printf(COLOR_RED "[ERROR]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
+    #define LOG_ERROR(msg, ...) fprintf(stderr, COLOR_RED "[ERROR]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
 #else
     #define LOG_ERROR(msg, ...)
 #endif
 
 #if LOG_LEVEL >= LOG_LEVEL_FATAL
-    #define LOG_FATAL(msg, ...) printf(COLOR_MAGENTA "[FATAL]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
+    #define LOG_FATAL(msg, ...) fprintf(stderr, COLOR_MAGENTA "[FATAL]" _LOGGER_MESSAGE(msg), ##__VA_ARGS__)
 #else
     #define LOG_FATAL(msg, ...)
 #endif
diff --git a/include/models/controller.h b/include/models/controller.h
index 0a581a8..17f3d08 100644
--- a/include/models/controller.h
+++ b/include/models/controller.h
@@ -11,7 +11,7 @@
 /**
  * @brief Information about this controller
  */
-typedef struct controller
+typedef struct
 {
     /**
      * @brief A unique UUID for this controller
@@ -22,7 +22,7 @@ typedef struct controller
      *
      * Includes a \0 terminator.
      */
-    char name[CONTROLLER_NAME_LENGTH + 1];
+    char name[MAX_NAME_LENGTH + 1];
     /**
      * @brief The command port the controller was bound to
      */
@@ -35,22 +35,22 @@ typedef struct controller
      * @brief Amount of relays available to this controller
      */
     uint8_t relay_count;
-    relay **relays;
+    relay_t **relays;
 
-} controller;
+} controller_t;
 
 /**
  * @brief Key to save controller information in database
  */
-typedef enum controller_db_key
+typedef enum
 {
-    KEY_META_ID = 0,
-    KEY_META_NAME = 1,
-    KEY_META_COMMAND_PORT = 2,
-    KEY_META_DISCOVERY_PORT = 3,
-    KEY_META_RELAY_COUNT = 4,
-    KEY_META_RELAYS = 5,
-} controller_db_key;
+    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_RELAY_COUNT = 4,
+    DB_KEY_CONTROLLER_RELAYS = 5,
+} db_key_controller_e;
 
 /**
  * @brief Create a new instance of controller
@@ -59,7 +59,7 @@ typedef enum controller_db_key
  *
  * @return A new instance of #controller
  */
-controller*
+controller_t*
 controller_create(void);
 
 
@@ -72,20 +72,32 @@ controller_create(void);
  *
  * @return A loaded or new instance of controller or NULL on database error
  */
-controller*
+controller_t*
 controller_load(MDB_env *mdb_env);
 
 /**
  * @brief Save a controller to the database
  *
- * @param cntrlr Instance of a controller
+ * @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(controller *cntrlr, MDB_env *mdb_env);
+controller_save(controller_t *controller, MDB_env *mdb_env);
 
+/**
+ * @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, char *name);
+
+void
+controller_free(controller_t *controller);
 
 /**
  * @brief Debug an instance of #controller
@@ -95,6 +107,6 @@ controller_save(controller *cntrlr, MDB_env *mdb_env);
  * @param cntrlr #controller to debug
  */
 void
-controller_debug(controller *cntrlr);
+controller_debug(controller_t *controller);
 
 #endif //CONTROLLER_CONTROLLER_H
diff --git a/include/models/period.h b/include/models/period.h
new file mode 100644
index 0000000..38a7249
--- /dev/null
+++ b/include/models/period.h
@@ -0,0 +1,19 @@
+#ifndef CONTROLLER_PERIOD_H
+#define CONTROLLER_PERIOD_H
+
+#include <stdint.h>
+#include <time.h>
+
+typedef struct
+{
+    uint16_t start;
+    uint16_t end;
+} period_t;
+
+period_t*
+period_create(uint16_t start, uint16_t end);
+
+int
+period_includes_time(period_t *period, uint16_t timestamp);
+
+#endif /* CONTROLLER_PERIOD_H */
diff --git a/include/models/relay.h b/include/models/relay.h
index 2d8b710..8ce1a6f 100644
--- a/include/models/relay.h
+++ b/include/models/relay.h
@@ -2,16 +2,63 @@
 #define CONTROLLER_RELAY_H
 
 #include <stdint.h>
+#include <time.h>
+#include <lmdb.h>
 
 #include <config.h>
+#include <models/schedule.h>
 
-typedef struct relay {
-    uint8_t index;
-    char name[128];
-    uint16_t *schedule;
-} relay;
+typedef struct
+{
+    uint8_t number;
+    char name[MAX_NAME_LENGTH + 1];
+    schedule_t *schedule;
+} relay_t;
 
-relay*
-relay_init(uint8_t index);
+/**
+ * @brief Key to save relay information in database
+ */
+typedef enum
+{
+    DB_KEY_RELAY_NAME = 0,
+    DB_KEY_RELAY_SCHEDULE_ID = 1,
+    DB_KEY_RELAY_SCHEDULE_PERIODS = 2,
+} db_key_relay_e;
+
+relay_t*
+relay_create(uint8_t number);
+
+void
+relay_set_name(relay_t *relay, char *name);
+
+/**
+ * @brief Load a relay for database or create a new one
+ *
+ * @param mdb_env An opened MDB_env to load from
+ *
+ * @return A loaded or new instance of relay
+ */
+relay_t*
+relay_load(MDB_env *mdb_env, uint8_t num);
+
+/**
+ * @brief Save a relay to the database
+ *
+ * @param relay Instance of a relay
+ * @param mdb_env Already created MDB_env
+ *
+ * @return Indicator to show success (0) or failure (!0)
+ */
+int
+relay_save(relay_t *relay, MDB_env *mdb_env);
+
+int
+relay_is_active(relay_t *relay, time_t timestamp_now);
+
+void
+relay_free(relay_t *relay);
+
+void
+relay_debug(relay_t *relay);
 
 #endif //CONTROLLER_RELAY_H
diff --git a/include/models/schedule.h b/include/models/schedule.h
new file mode 100644
index 0000000..15274d4
--- /dev/null
+++ b/include/models/schedule.h
@@ -0,0 +1,28 @@
+#ifndef CONTROLLER_SCHEDULE_H
+#define CONTROLLER_SCHEDULE_H
+
+#include <stdint.h>
+#include <uuid/uuid.h>
+
+#include <models/period.h>
+
+typedef struct
+{
+    uuid_t id;
+    uint16_t length;
+    period_t **periods;
+} schedule_t;
+
+schedule_t*
+schedule_create(uuid_t id, uint16_t length, uint16_t *periods_blob);
+
+uint16_t*
+schedule_periods_to_blob(schedule_t *schedule);
+
+void
+schedule_free(schedule_t *schedule);
+
+void
+schedule_debug(schedule_t *schedule);
+
+#endif /* CONTROLLER_SCHEDULE_H */
diff --git a/include/wiring_debug.h b/include/wiring_debug.h
index ce9f750..290ede0 100644
--- a/include/wiring_debug.h
+++ b/include/wiring_debug.h
@@ -1,10 +1,15 @@
 #ifndef CONTROLLER_WIRING_DEBUG_H
 #define CONTROLLER_WIRING_DEBUG_H
 
+#include <logger.h>
+
 #ifdef WIRING_PI_DEBUG
-    #define wiringPiSetup() LOG_INFO("wiringP wiringPiSetup()")
+    #define wiringPiSetup() LOG_INFO("wiringPi wiringPiSetup()")
+    #define wiringPiSetupSys() LOG_INFO("wiringPi wiringPiSetupSys()")
     #define pinMode(x,y) LOG_INFO("wiringPi pinMode(%d, %d)", x, y)
     #define digitalWrite(x,y) LOG_INFO("wiringPi digitalWrite(%d, %d)", x, y)
+
+    #define piFaceSetup(x) LOG_INFO("wiringPi piFaceSetup(%d)", x)
 #endif
 
 #endif /* CONTROLLER_WIRING_DEBUG_H */
diff --git a/logger.c b/logger.c
new file mode 100644
index 0000000..d7c8136
--- /dev/null
+++ b/logger.c
@@ -0,0 +1,10 @@
+#include <logger.h>
+
+char*
+logger_get_timestamp()
+{
+    time_t rawtime;
+    time(&rawtime);
+    strftime(_LOGGER_TIMESTAMP, _LOGGER_TIMESTAMP_SIZE, "%Y-%m-%d %H:%M:%S", localtime(&rawtime));
+    return _LOGGER_TIMESTAMP;
+}
diff --git a/main.c b/main.c
index 9934e67..7d8e642 100644
--- a/main.c
+++ b/main.c
@@ -1,17 +1,72 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
+#include <time.h>
 #include <lmdb.h>
 #include <unistd.h>
 #include <sys/socket.h>
 #include <poll.h>
+#include <signal.h>
 
 #include <logger.h>
 #include <models/controller.h>
 #include <database.h>
 #include <config.h>
-#include <discovery.h>
+#include <handlers.h>
+#include <drivers.h>
 #include <enums.h>
+#include <helpers.h>
+#include <wiringPi.h>
+#include <piFace.h>
+#include <wiring_debug.h>
+
+static MDB_env *mdb_env;
+static int fd_discovery;
+static int fd_command;
+static controller_t *this_controller;
+
+static void
+terminate(int signum)
+{
+    LOG_INFO("terminating controller (%d)", signum);
+
+    close(fd_discovery);
+    close(fd_command);
+
+    mdb_env_close(mdb_env);
+
+    controller_free(this_controller);
+
+    exit(signum);
+}
+
+static void
+handle_poll(struct pollfd *fds, int fd_count)
+{
+    /* An event on one of the fds has occurred. */
+    for(int i = 0; i < fd_count; i++) {
+        if(fds[i].revents & POLLIN)
+        {
+            /* data may be read on device number i. */
+            LOG_DEBUG("fd %i may read data", fds[i].fd);
+            switch(i)
+            {
+                case POLL_FGS_DISCOVERY:
+                    handler_discovery(fd_discovery, this_controller);
+                    break;
+                case POLL_FGS_COMMAND:
+                    handler_command(fd_command, this_controller);
+                    controller_save(this_controller, mdb_env);
+                    break;
+            }
+        }
+        if(fds[i].revents & POLLHUP)
+        {
+            /* A hangup has occurred on device number i. */
+            LOG_DEBUG("fd %i got closed", fds[i].fd);
+        }
+    }
+}
 
 /**
  * @brief The main function
@@ -27,18 +82,40 @@ main(int argc, char** argv)
     (void)argc;
     (void)argv;
 
-    MDB_env *mdb_env;
+    signal(SIGINT, terminate);
+    signal(SIGABRT, terminate);
+    signal(SIGTERM, terminate);
+
+    if(sizeof(time_t) < 8)
+    {
+        LOG_WARN("this system is not using 8-bit time");
+    }
+
+    /******************** SETUP DATABASE AND THIS CONTROLLER ********************/
+
     database_setup(&mdb_env);
 
-    controller *this_cntrlr = controller_load(mdb_env);
-    controller_save(this_cntrlr, mdb_env);
+    this_controller = controller_load(mdb_env);
 
-    int fd_discovery = discovery_socket_open(this_cntrlr->discovery_port);
+    fd_discovery = helper_open_discovery_socket(this_controller->discovery_port);
+    fd_command = helper_bind_tcp_server("0.0.0.0", this_controller->command_port, 128);
+
+    this_controller->command_port = helper_get_port(fd_command);
+
+    controller_save(this_controller, mdb_env);
+
+    
+    /******************** SETUP WIRINGPI ********************/
+
+    wiringPiSetupSys();
+    piFaceSetup(200);
+
+
+    /******************** SETUP SOCKETS ********************/
 
     struct pollfd fds[2];
     int timeout_msecs = ACCEPT_TIMEOUT_MSECONDS;
     int ret;
-    int i;
     int fd_count = 0;
 
     /* Open STREAMS device. */
@@ -46,6 +123,12 @@ main(int argc, char** argv)
     fds[POLL_FGS_DISCOVERY].events = POLLIN;
     LOG_DEBUG("setup fd_discovery as %i on index %i", fd_discovery, fd_count);
     fd_count++;
+    fds[POLL_FGS_COMMAND].fd = fd_command;
+    fds[POLL_FGS_COMMAND].events = POLLIN;
+    LOG_DEBUG("setup fd_command as %i on index %i", fd_command, fd_count);
+    fd_count++;
+
+    /******************** START MAIN LOOP ********************/
 
     while(1)
     {
@@ -54,27 +137,37 @@ main(int argc, char** argv)
         if(ret == 0)
         {
             LOG_TRACE("idle loop");
+            for(uint_fast8_t i = 0; i < this_controller->relay_count; ++i)
+            {
+                relay_t *relay = this_controller->relays[i];
+                if(relay_is_active(relay, time(NULL)))
+                {
+                    LOG_DEBUG("relay %d is active", i);
+                    if(relay->number >= 2)
+                    {
+                        driver_gpio_set(relay, HIGH);
+                    }
+                    else
+                    {
+                        driver_piface_set(relay, HIGH);
+                    }
+                }
+                else
+                {
+                    if(relay->number >= 2)
+                    {
+                        driver_gpio_set(relay, LOW);
+                    }
+                    else
+                    {
+                        driver_piface_set(relay, LOW);
+                    }
+                }
+            }
         }
         if(ret > 0)
         {
-            /* An event on one of the fds has occurred. */
-            for(i = 0; i < fd_count; i++) {
-                if(fds[i].revents & POLLIN)
-                {
-                    /* Priority data may be read on device number i. */
-                    LOG_DEBUG("fd %i may read data", fds[i].fd);
-                    switch(i)
-                    {
-                        case POLL_FGS_DISCOVERY:
-                            discovery_handle_discover(fd_discovery, this_cntrlr);
-                    }
-                }
-                if(fds[i].revents & POLLHUP)
-                {
-                    /* A hangup has occurred on device number i. */
-                    LOG_DEBUG("fd %i got closed", fds[i].fd);
-                }
-            }
+            handle_poll(fds, fd_count);
         }
     }
 
diff --git a/models/controller.c b/models/controller.c
index 3b19de5..03a96d3 100644
--- a/models/controller.c
+++ b/models/controller.c
@@ -7,38 +7,62 @@
 #include <models/controller.h>
 #include <macros.h>
 
-controller*
+controller_t*
 controller_create(void)
 {
-    controller *result = malloc(sizeof(*result));
-    uuid_generate(result->id);
+    controller_t *new_controller = malloc(sizeof(*new_controller));
+    uuid_generate(new_controller->id);
 
-    strcpy(result->name, "new emgauwa device");
-    result->command_port = 0;
-    result->discovery_port = 4421;
-    result->relay_count = 10;
+    strcpy(new_controller->name, "new emgauwa device");
+    new_controller->command_port = 0;
+    new_controller->discovery_port = 4421;
+    new_controller->relay_count = 10;
 
-    result->relays = malloc(sizeof(*result->relays) * result->relay_count);
+    new_controller->relays = malloc(sizeof(relay_t) * new_controller->relay_count);
     uint8_t i;
-    for(i = 0; i < result->relay_count; i++)
+    for(i = 0; i < new_controller->relay_count; ++i)
     {
-        result->relays[i] = relay_init(i);
+        new_controller->relays[i] = relay_create(i);
     }
 
-    return result;
+    return new_controller;
 }
 
 void
-controller_debug(controller *cntrlr)
+controller_set_name(controller_t *controller, char *name)
 {
-    if(cntrlr == NULL)
+    strncpy(controller->name, name, MAX_NAME_LENGTH);
+    controller->name[MAX_NAME_LENGTH] = '\0';
+}
+
+void
+controller_free(controller_t *controller)
+{
+    for(int i = 0; i < controller->relay_count; ++i)
+    {
+        relay_free(controller->relays[i]);
+    }
+    free(controller->relays);
+    free(controller);
+}
+
+void
+controller_debug(controller_t *controller)
+{
+    if(controller == NULL)
     {
         LOG_DEBUG("controller is NULL");
+        return;
     }
     char uuid_str[37];
-    uuid_unparse(cntrlr->id, uuid_str);
-    LOG_DEBUG("(1/4) %s @ %p", uuid_str, cntrlr);
-    LOG_DEBUG("(2/4) name: %s", cntrlr->name);
-    LOG_DEBUG("(3/4) relays: %3d", cntrlr->relay_count);
-    LOG_DEBUG("(4/4) command_port: %5d discovery_port: %5d", cntrlr->command_port, cntrlr->discovery_port);
+    uuid_unparse(controller->id, uuid_str);
+    LOG_DEBUG("(1/5) %s @ %p", uuid_str, controller);
+    LOG_DEBUG("(2/5) name: %s", controller->name);
+    LOG_DEBUG("(3/5) command_port: %5d discovery_port: %5d", controller->command_port, controller->discovery_port);
+    LOG_DEBUG("(4/5) relay count: %3d", controller->relay_count);
+    LOG_DEBUG("(5/5) relays @ %p:", controller->relays);
+    for(uint8_t i = 0; i < controller->relay_count; ++i)
+    {
+        relay_debug(controller->relays[i]);
+    }
 }
diff --git a/models/controller_load.c b/models/controller_load.c
index 0660a0b..b6f862d 100644
--- a/models/controller_load.c
+++ b/models/controller_load.c
@@ -8,73 +8,79 @@
 #include <macros.h>
 
 static void
-controller_load_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, controller_db_key key_meta, MDB_val *value)
+controller_load_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_controller_e key_controller, MDB_val *value)
 {
     int err;
     MDB_val key;
 
-    key.mv_size = sizeof(controller_db_key);
-    key.mv_data = &key_meta;
+    key.mv_size = sizeof(db_key_controller_e);
+    key.mv_data = &key_controller;
 
     if((err = mdb_get(mdb_txn, mdb_dbi, &key, value)) != 0)
     {
-        fprintf(stderr, "mdb_get error %s\n", mdb_strerror(err));
+        LOG_ERROR("mdb_get error %s", mdb_strerror(err));
         exit(1);
     }
 }
 
-controller*
+controller_t*
 controller_load(MDB_env *mdb_env)
 {
     int err;
     MDB_txn *mdb_txn;
     MDB_dbi mdb_dbi;
     
-    controller *new_controller;
+    controller_t *new_controller;
 
     if((err = mdb_txn_begin(mdb_env, NULL, MDB_RDONLY, &mdb_txn)) != 0)
     {
-        fprintf(stderr, "mdb_txn_begin error %s\n", mdb_strerror(err));
+        LOG_ERROR("mdb_txn_begin error %s", mdb_strerror(err));
         return NULL;
     }
-    if((err = mdb_dbi_open(mdb_txn, "meta", 0, &mdb_dbi)) != 0)
+    if((err = mdb_dbi_open(mdb_txn, "controller", 0, &mdb_dbi)) != 0)
     {
         switch(err)
         {
             case MDB_NOTFOUND:
+                LOG_INFO("no controller found in db. creating new one");
                 mdb_txn_abort(mdb_txn);
-                new_controller =  controller_create();
+                new_controller = controller_create();
                 controller_save(new_controller, mdb_env);
                 return new_controller;
             default:
-                fprintf(stderr, "mdb_txn_begin error %s\n", mdb_strerror(err));
+                LOG_ERROR("mdb_txn_begin error %s", mdb_strerror(err));
                 return NULL;
         }
     }
 
-    new_controller = malloc(sizeof(*new_controller));
+    new_controller = malloc(sizeof(controller_t));
 
     MDB_val value;
 
-    controller_load_single(mdb_txn, mdb_dbi, KEY_META_ID, &value);
+    controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_ID, &value);
     memmove(new_controller->id, (uuid_t*)value.mv_data, sizeof(uuid_t));
 
-    controller_load_single(mdb_txn, mdb_dbi, KEY_META_NAME, &value);
-    strncpy(new_controller->name, (char*)value.mv_data, CONTROLLER_NAME_LENGTH);
-    new_controller->name[CONTROLLER_NAME_LENGTH] = '\0';
+    controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_NAME, &value);
+    strncpy(new_controller->name, (char*)value.mv_data, MAX_NAME_LENGTH);
+    new_controller->name[MAX_NAME_LENGTH] = '\0';
 
-    controller_load_single(mdb_txn, mdb_dbi, KEY_META_COMMAND_PORT, &value);
+    controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_COMMAND_PORT, &value);
     new_controller->command_port = ((uint16_t*)value.mv_data)[0];
 
-    controller_load_single(mdb_txn, mdb_dbi, KEY_META_DISCOVERY_PORT, &value);
+    controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_DISCOVERY_PORT, &value);
     new_controller->discovery_port = ((uint16_t*)value.mv_data)[0];
 
-    controller_load_single(mdb_txn, mdb_dbi, KEY_META_RELAY_COUNT, &value);
+    controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_RELAY_COUNT, &value);
     new_controller->relay_count = ((uint8_t*)value.mv_data)[0];
 
-    controller_debug(new_controller);
-
     mdb_txn_abort(mdb_txn); // transaction is read only
+
+    new_controller->relays = malloc(sizeof(relay_t*) * new_controller->relay_count);
+    for(uint8_t i = 0; i < new_controller->relay_count; i++)
+    {
+        LOG_TRACE("loading relay %d", i);
+        new_controller->relays[i] = relay_load(mdb_env, i);
+    }
     
     return new_controller;
 }
diff --git a/models/controller_save.c b/models/controller_save.c
index b99c8a9..60881d3 100644
--- a/models/controller_save.c
+++ b/models/controller_save.c
@@ -8,17 +8,17 @@
 #include <macros.h>
 
 int
-controller_save_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, controller_db_key key_meta, MDB_val value)
+controller_save_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_controller_e key_controller, MDB_val value)
 {
     int err;
 
     MDB_val key;
-    key.mv_size = sizeof(controller_db_key);
-    key.mv_data = &key_meta;
+    key.mv_size = sizeof(db_key_controller_e);
+    key.mv_data = &key_controller;
 
     if((err = mdb_put(mdb_txn, mdb_dbi, &key, &value, 0)) != 0)
     {
-        fprintf(stderr, "mdb_put error %s\n", mdb_strerror(err));
+        LOG_ERROR("mdb_put error %s", mdb_strerror(err));
         mdb_txn_abort(mdb_txn);
         return 1;
     }
@@ -26,7 +26,7 @@ controller_save_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, controller_db_key key_
 }
 
 int
-controller_save(controller *cntrlr, MDB_env *mdb_env)
+controller_save(controller_t *controller, MDB_env *mdb_env)
 {
     int err;
 
@@ -34,56 +34,65 @@ controller_save(controller *cntrlr, MDB_env *mdb_env)
     MDB_dbi mdb_dbi;
     MDB_val value;
 
-    controller_debug(cntrlr);
-
     if((err = mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn)) != 0)
     {
-        fprintf(stderr, "mdb_txn_begin error %s\n", mdb_strerror(err));
+        LOG_ERROR("mdb_txn_begin error %s", mdb_strerror(err));
         exit(1);
     }
 
-    if((err = mdb_dbi_open(mdb_txn, "meta", MDB_CREATE, &mdb_dbi)) != 0)
+    if((err = mdb_dbi_open(mdb_txn, "controller", MDB_CREATE, &mdb_dbi)) != 0)
     {
-        fprintf(stderr, "mdb_dbi_open error %s\n", mdb_strerror(err));
+        LOG_ERROR("mdb_dbi_open error %s", mdb_strerror(err));
         exit(1);
     }
 
-    value.mv_size = sizeof(cntrlr->id);
-    value.mv_data = cntrlr->id;
-    if(controller_save_single(mdb_txn, mdb_dbi, KEY_META_ID, value))
+    value.mv_size = sizeof(uuid_t);
+    value.mv_data = controller->id;
+    if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_ID, value))
     {
+        LOG_ERROR("failed to save ID");
         return 1;
     }
 
-    value.mv_size = sizeof(char) * (strlen(cntrlr->name) + 1);
-    value.mv_data = cntrlr->name;
-    if(controller_save_single(mdb_txn, mdb_dbi, KEY_META_NAME, value))
+    value.mv_size = sizeof(char) * (strlen(controller->name) + 1);
+    value.mv_data = controller->name;
+    if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_NAME, value))
     {
+        LOG_ERROR("failed to save name");
         return 1;
     }
 
-    value.mv_size = sizeof(cntrlr->command_port);
-    value.mv_data = &cntrlr->command_port;
-    if(controller_save_single(mdb_txn, mdb_dbi, KEY_META_COMMAND_PORT, value))
+    value.mv_size = sizeof(controller->command_port);
+    value.mv_data = &controller->command_port;
+    if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_COMMAND_PORT, value))
     {
+        LOG_ERROR("failed to save command port");
         return 1;
     }
 
-    value.mv_size = sizeof(cntrlr->discovery_port);
-    value.mv_data = &cntrlr->discovery_port;
-    if(controller_save_single(mdb_txn, mdb_dbi, KEY_META_DISCOVERY_PORT, value))
+    value.mv_size = sizeof(controller->discovery_port);
+    value.mv_data = &controller->discovery_port;
+    if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_DISCOVERY_PORT, value))
     {
+        LOG_ERROR("failed to save discovery port");
         return 1;
     }
 
-    value.mv_size = sizeof(cntrlr->relay_count);
-    value.mv_data = &cntrlr->relay_count;
-    if(controller_save_single(mdb_txn, mdb_dbi, KEY_META_RELAY_COUNT, value))
+    value.mv_size = sizeof(controller->relay_count);
+    value.mv_data = &controller->relay_count;
+    if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_RELAY_COUNT, value))
     {
+        LOG_ERROR("failed to save relay count");
         return 1;
     }
 
     mdb_txn_commit(mdb_txn);
 
+    for(uint8_t i = 0; i < controller->relay_count; ++i)
+    {
+        LOG_TRACE("saving relays[%d/%d]", i, controller->relay_count);
+        relay_save(controller->relays[i], mdb_env);
+    }
+
     return 0;
 }
diff --git a/models/period.c b/models/period.c
new file mode 100644
index 0000000..75a4981
--- /dev/null
+++ b/models/period.c
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+
+#include <logger.h>
+#include <constants.h>
+#include <models/period.h>
+
+period_t*
+period_create(uint16_t start, uint16_t end)
+{
+    period_t *new_period = malloc(sizeof(period_t));
+    new_period->start = start;
+    new_period->end = end;
+
+    return new_period;
+}
+
+int
+period_includes_time(period_t *period, uint16_t timestamp)
+{
+    uint16_t start = period->start;
+    uint16_t end = period->end;
+
+    // "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;
+}
diff --git a/models/relay.c b/models/relay.c
index ce0a213..ed9b73b 100644
--- a/models/relay.c
+++ b/models/relay.c
@@ -1,10 +1,83 @@
 #include <stdlib.h>
+#include <string.h>
+#include <uuid/uuid.h>
 
+#include <constants.h>
+#include <logger.h>
 #include <models/relay.h>
 
-relay*
-relay_init(uint8_t index)
+relay_t*
+relay_create(uint8_t number)
 {
-    (void)index;
-    return NULL;
+    relay_t *new_relay = malloc(sizeof(relay_t));
+
+    new_relay->number = number;
+    new_relay->name[0] = '\0';
+
+    uuid_t off_id;
+    memset(off_id, 0, sizeof(uuid_t));
+    memcpy(off_id, "off", 3);
+
+    new_relay->schedule = schedule_create(off_id, 0, NULL);
+
+    return new_relay;
+}
+
+void
+relay_set_name(relay_t *relay, char *name)
+{
+    strncpy(relay->name, name, MAX_NAME_LENGTH);
+    relay->name[MAX_NAME_LENGTH] = '\0';
+}
+
+int
+relay_is_active(relay_t *relay, time_t timestamp_now)
+{
+    if(relay->schedule->length == 0)
+    {
+        return 0;
+    }
+
+    // we don't need days. reduce to hours, minutes and seconds
+    timestamp_now %= SECONDS_PER_DAY;
+    // finally remove seconds
+    timestamp_now /= SECONDS_PER_MINUTE;
+
+    for(uint16_t i = 0; i < relay->schedule->length; ++i)
+    {
+        if(period_includes_time(relay->schedule->periods[i], timestamp_now))
+        {
+            return 1;
+        }
+    }
+    return 0;
+}
+//struct tm time_start, time_now, time_end;
+//localtime_r(&timestamp_start, &time_start);
+//localtime_r(&timestamp_now, &time_now);
+//localtime_r(&timestamp_end, &time_end);
+//LOG_DEBUG("%02d:%02d - %02d:%02d - %02d:%02d", time_start.tm_hour, time_start.tm_min, time_now.tm_hour, time_now.tm_min, time_end.tm_hour, time_end.tm_min);
+
+void
+relay_debug(relay_t *relay)
+{
+    if(relay == NULL)
+    {
+        LOG_DEBUG("relay is NULL");
+        return;
+    }
+    LOG_DEBUG("(1/3) %d @ %p", relay->number, relay);
+    LOG_DEBUG("(2/3) name: %s", relay->name);
+    LOG_DEBUG("(3/3) schedule:");
+    schedule_debug(relay->schedule);
+}
+
+void
+relay_free(relay_t *relay)
+{
+    if(relay->schedule)
+    {
+        schedule_free(relay->schedule);
+    }
+    free(relay);
 }
diff --git a/models/relay_load.c b/models/relay_load.c
new file mode 100644
index 0000000..1d4a76d
--- /dev/null
+++ b/models/relay_load.c
@@ -0,0 +1,100 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <uuid/uuid.h>
+
+#include <models/relay.h>
+#include <macros.h>
+
+static int
+relay_load_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_relay_e key_relay, uint8_t num, MDB_val *value)
+{
+    int err;
+
+    size_t key_size = sizeof(db_key_relay_e) + sizeof(uint8_t); 
+    void *key_data = malloc(key_size);
+    memmove(key_data, &key_relay, sizeof(db_key_relay_e));
+    memmove(key_data + sizeof(db_key_relay_e), &num, sizeof(uint8_t));
+
+    MDB_val key;
+    key.mv_size = key_size;
+    key.mv_data = key_data;
+
+    if((err = mdb_get(mdb_txn, mdb_dbi, &key, value)) != 0)
+    {
+        LOG_ERROR("mdb_get error %s", mdb_strerror(err));
+        mdb_txn_abort(mdb_txn);
+        free(key_data);
+        return 1;
+    }
+    free(key_data);
+    return 0;
+}
+
+relay_t*
+relay_load(MDB_env *mdb_env, uint8_t num)
+{
+    int err;
+    MDB_txn *mdb_txn;
+    MDB_dbi mdb_dbi;
+    
+    relay_t *new_relay;
+
+    if((err = mdb_txn_begin(mdb_env, NULL, MDB_RDONLY, &mdb_txn)) != 0)
+    {
+        LOG_ERROR("mdb_txn_begin error %s", mdb_strerror(err));
+        return relay_create(num);
+    }
+
+    if((err = mdb_dbi_open(mdb_txn, "relays", 0, &mdb_dbi)) != 0)
+    {
+        switch(err)
+        {
+            case MDB_NOTFOUND:
+                LOG_INFO("no relay for num %d found in db. returning new one (no relays db)", num);
+                mdb_txn_abort(mdb_txn);
+                return relay_create(num);
+            default:
+                LOG_ERROR("mdb_txn_begin error %s", mdb_strerror(err));
+                return relay_create(num);
+        }
+    }
+
+    new_relay = malloc(sizeof(relay_t));
+    new_relay->number = num;
+
+    MDB_val value;
+
+    if((err = relay_load_single(mdb_txn, mdb_dbi, DB_KEY_RELAY_NAME, num, &value)) != 0)
+    {
+        LOG_INFO("no relay for num %d found in db. returning new one", num);
+        mdb_txn_abort(mdb_txn); // transaction is read only
+        return relay_create(num);
+    }
+    strncpy(new_relay->name, (char*)value.mv_data, MAX_NAME_LENGTH);
+    new_relay->name[MAX_NAME_LENGTH] = '\0';
+
+    if((err = relay_load_single(mdb_txn, mdb_dbi, DB_KEY_RELAY_SCHEDULE_ID, num, &value)) != 0)
+    {
+        LOG_INFO("no relay for num %d found in db. returning new one", num);
+        mdb_txn_abort(mdb_txn); // transaction is read only
+        return relay_create(num);
+    }
+    uuid_t *schedule_id = (uuid_t*)value.mv_data;
+
+    if((err = relay_load_single(mdb_txn, mdb_dbi, DB_KEY_RELAY_SCHEDULE_PERIODS, num, &value)) != 0)
+    {
+        LOG_INFO("no relay for num %d found in db. returning new one", num);
+        mdb_txn_abort(mdb_txn); // transaction is read only
+        return relay_create(num);
+    }
+    uint16_t schedule_periods_length = ((uint16_t*)value.mv_data)[0];
+    uint16_t *schedule_periods = ((uint16_t*)value.mv_data) + 1;
+
+    new_relay->schedule = schedule_create(*schedule_id, schedule_periods_length, schedule_periods);
+
+    mdb_txn_abort(mdb_txn); // transaction is read only
+    
+    return new_relay;
+}
diff --git a/models/relay_save.c b/models/relay_save.c
new file mode 100644
index 0000000..e932940
--- /dev/null
+++ b/models/relay_save.c
@@ -0,0 +1,88 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <uuid/uuid.h>
+
+#include <models/relay.h>
+#include <macros.h>
+
+static int
+relay_save_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_relay_e key_relay, uint8_t num, MDB_val value)
+{
+    int err;
+
+    size_t key_size = sizeof(db_key_relay_e) + sizeof(uint8_t); 
+    void *key_data = malloc(key_size);
+    memmove(key_data, &key_relay, sizeof(db_key_relay_e));
+    memmove(key_data + sizeof(db_key_relay_e), &num, sizeof(uint8_t));
+
+    MDB_val key;
+    key.mv_size = key_size;
+    key.mv_data = key_data;
+
+    if((err = mdb_put(mdb_txn, mdb_dbi, &key, &value, 0)) != 0)
+    {
+        LOG_ERROR("mdb_put error %s", mdb_strerror(err));
+        mdb_txn_abort(mdb_txn);
+        free(key_data);
+        return 1;
+    }
+    free(key_data);
+    return 0;
+}
+
+int
+relay_save(relay_t *relay, MDB_env *mdb_env)
+{
+    LOG_TRACE("saving relay %d @ %p", relay->number, relay);
+    int err;
+
+    MDB_txn *mdb_txn;
+    MDB_dbi mdb_dbi;
+    MDB_val value;
+
+    if((err = mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn)) != 0)
+    {
+        LOG_ERROR("mdb_txn_begin error %s", mdb_strerror(err));
+        exit(1);
+    }
+
+    if((err = mdb_dbi_open(mdb_txn, "relays", MDB_CREATE, &mdb_dbi)) != 0)
+    {
+        LOG_ERROR("mdb_dbi_open error %s", mdb_strerror(err));
+        exit(1);
+    }
+
+    value.mv_size = sizeof(char) * (strlen(relay->name) + 1);
+    value.mv_data = relay->name;
+    if(relay_save_single(mdb_txn, mdb_dbi, DB_KEY_RELAY_NAME, relay->number, value))
+    {
+        LOG_ERROR("failed to save name");
+        return 1;
+    }
+
+    value.mv_size = sizeof(uuid_t);
+    value.mv_data = relay->schedule->id;
+    if(relay_save_single(mdb_txn, mdb_dbi, DB_KEY_RELAY_SCHEDULE_ID, relay->number, value))
+    {
+        LOG_ERROR("failed to save ID");
+        return 1;
+    }
+
+    // save periods blob
+    uint16_t *periods_blob = schedule_periods_to_blob(relay->schedule);
+    value.mv_size = sizeof(uint16_t) * ((periods_blob[0] * 2) + 1);
+    value.mv_data = periods_blob;
+    if(relay_save_single(mdb_txn, mdb_dbi, DB_KEY_RELAY_SCHEDULE_PERIODS, relay->number, value))
+    {
+        free(periods_blob);
+        LOG_ERROR("failed to save periods");
+        return 1;
+    }
+    free(periods_blob);
+
+    mdb_txn_commit(mdb_txn);
+
+    return 0;
+}
diff --git a/models/schedule.c b/models/schedule.c
new file mode 100644
index 0000000..ee37df6
--- /dev/null
+++ b/models/schedule.c
@@ -0,0 +1,89 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include <logger.h>
+#include <models/schedule.h>
+
+schedule_t*
+schedule_create(uuid_t id, uint16_t length, uint16_t *periods_blob)
+{
+    schedule_t *new_schedule = malloc(sizeof(schedule_t));
+
+    memmove(new_schedule->id, id, sizeof(uuid_t));
+
+    new_schedule->length = length;
+    new_schedule->periods = NULL;
+
+    if(length)
+    {
+        new_schedule->periods = malloc(sizeof(period_t*) * length);
+
+        for(uint16_t i = 0; i < length; ++i)
+        {
+            uint16_t start = periods_blob[0 + (i * 2)];
+            uint16_t   end = periods_blob[1 + (i * 2)];
+            new_schedule->periods[i] = period_create(start, end);
+        }
+    }
+
+    return new_schedule;
+}
+
+uint16_t*
+schedule_periods_to_blob(schedule_t *schedule)
+{
+    uint16_t *periods_blob = malloc(sizeof(uint16_t) * ((2 * schedule->length) + 1));
+    periods_blob[0] = schedule->length;
+
+    for(uint16_t i = 0; i < schedule->length; ++i)
+    {
+
+        periods_blob[1 + (i * 2)] = schedule->periods[i]->start;
+        periods_blob[2 + (i * 2)] = schedule->periods[i]->end;
+    }
+
+    return periods_blob;
+}
+
+void
+schedule_free(schedule_t *schedule)
+{
+    for(uint16_t i = 0; i < schedule->length; ++i)
+    {
+        free(schedule->periods[i]);
+    }
+    free(schedule->periods);
+    free(schedule);
+}
+
+void
+schedule_debug(schedule_t *schedule)
+{
+    if(schedule == NULL)
+    {
+        LOG_DEBUG("schedule is NULL");
+        return;
+    }
+    char uuid_str[37];
+    uuid_unparse(schedule->id, uuid_str);
+    LOG_DEBUG("(1/3) %s @ %p", uuid_str, schedule);
+    LOG_DEBUG("(2/3) period count: %3d", schedule->length);
+
+    // one block: "HH:MM-HH:MM, " --> size: 13 (14 with '\0')
+    char *periods_debug_str = malloc(sizeof(char) * ((schedule->length * 13) + 1));
+    periods_debug_str[0] = '\0';
+
+    for(uint16_t i = 0; i < schedule->length; ++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
+        );
+    }
+
+    LOG_DEBUG("(3/3) periods: %s", periods_debug_str);
+}