-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic c-node and supporting services
- Loading branch information
Showing
13 changed files
with
394 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
defmodule Bacnet.Heartbeat do | ||
@moduledoc """ | ||
Heartbeat server accepting periodic messages, keeping the cnode alive. | ||
""" | ||
|
||
use GenServer | ||
|
||
require Logger | ||
|
||
@spec start_link(any) :: GenServer.on_start() | ||
def start_link(_opts) do | ||
GenServer.start_link(__MODULE__, nil, name: __MODULE__) | ||
end | ||
|
||
@impl GenServer | ||
def init(_) do | ||
{:ok, nil} | ||
end | ||
|
||
@impl GenServer | ||
def handle_info(:heartbeat, state) do | ||
Logger.debug("Heartbeat received from bacnetd") | ||
|
||
{:noreply, state} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
defmodule Bacnet.Logger do | ||
@moduledoc """ | ||
Log server for the cnode. | ||
""" | ||
|
||
use GenServer | ||
|
||
require Logger | ||
|
||
@levels [ | ||
:emergency, | ||
:alert, | ||
:critical, | ||
:error, | ||
:warning, | ||
:notice, | ||
:info, | ||
:debug, | ||
] | ||
|
||
@spec start_link(any) :: GenServer.on_start() | ||
def start_link(_opts) do | ||
GenServer.start_link(__MODULE__, nil, name: __MODULE__) | ||
end | ||
|
||
@impl GenServer | ||
def init(_) do | ||
{:ok, nil} | ||
end | ||
|
||
@impl GenServer | ||
def handle_info({level, term}, state) when level in @levels do | ||
Logger.log(level, maybe_inspect(term)) | ||
|
||
{:noreply, state} | ||
end | ||
|
||
defp maybe_inspect(term) when is_binary(term), do: term | ||
defp maybe_inspect(term), do: inspect(term) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
#include <getopt.h> | ||
#include <string.h> | ||
|
||
#include "arg.h" | ||
|
||
static const struct option options[] = { | ||
{"nodename", required_argument, 0, 'n'}, | ||
{"cookie", required_argument, 0, 'c'}, | ||
{0, 0, 0, 0}, | ||
}; | ||
|
||
void args_parse(arg_t *args, int argc, char **argv) | ||
{ | ||
int opt; | ||
int option_index = 0; | ||
|
||
while ((opt = getopt_long(argc, argv, "n:c:", options, &option_index)) | ||
!= -1) { | ||
switch (opt) { | ||
case 'n': | ||
strncpy(args->nodename, optarg, sizeof(args->nodename)); | ||
break; | ||
case 'c': | ||
strncpy(args->cookie, optarg, sizeof(args->cookie)); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#ifndef ARG_H | ||
#define ARG_H | ||
|
||
#include <ei.h> | ||
|
||
typedef struct { | ||
char nodename[MAXNODELEN + 1]; | ||
char cookie[MAXATOMLEN + 1]; | ||
} arg_t; | ||
|
||
void args_parse(arg_t *args, int argc, char **argv); | ||
|
||
#endif /* ARG_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
#include <pthread.h> | ||
#include <stdbool.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#include "ei_client.h" | ||
|
||
#define OTP_COMPAT_VER 25 | ||
#define HEARTBEAT_PROCESS "Elixir.Bacnet.Heartbeat" | ||
#define CNODE_NAME "bacnetd" | ||
|
||
struct ei_client { | ||
bool ready; | ||
pthread_mutex_t lock; | ||
struct ei_cnode_s cnode; | ||
uint32_t creation; | ||
int port; | ||
int fd; | ||
char nodename[MAXNODELEN + 1]; | ||
char cookie[MAXATOMLEN + 1]; | ||
}; | ||
|
||
static struct ei_client client = { 0 }; | ||
static void ei_free(); | ||
|
||
bool ei_client_config(const char *nodename, const char *cookie) | ||
{ | ||
if (client.ready) { | ||
return true; | ||
} | ||
|
||
if (atexit(ei_free) != 0) { | ||
return false; | ||
} | ||
|
||
strncpy(client.nodename, nodename, sizeof(client.nodename)); | ||
strncpy(client.cookie, cookie, sizeof(client.cookie)); | ||
|
||
ei_init(); | ||
ei_set_compat_rel(OTP_COMPAT_VER); | ||
|
||
pthread_mutex_init(&client.lock, NULL); | ||
client.creation = 0; | ||
|
||
if (ei_connect_init(&client.cnode, CNODE_NAME, cookie, client.creation) < 0) { | ||
return false; | ||
} | ||
|
||
client.creation++; | ||
|
||
client.fd = ei_connect(&client.cnode, client.nodename); | ||
if (client.fd < 0) { | ||
return false; | ||
} | ||
|
||
client.ready = true; | ||
|
||
return true; | ||
} | ||
|
||
bool ei_client_send(char *process_name, ei_x_buff *msg) | ||
{ | ||
if (!client.ready) { | ||
return false; | ||
} | ||
|
||
pthread_mutex_lock(&client.lock); | ||
int ret = ei_reg_send(&client.cnode, client.fd, process_name, msg->buff, | ||
msg->index); | ||
pthread_mutex_unlock(&client.lock); | ||
|
||
return ret == 0 ? true : false; | ||
} | ||
|
||
static void ei_free() | ||
{ | ||
if (client.fd != NULL) { | ||
ei_close_connection(client.fd); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#ifndef EI_CLIENT_H | ||
#define EI_CLIENT_H | ||
|
||
#include <ei.h> | ||
|
||
typedef void(msg_handler_cb_t)(erlang_msg *msg, ei_x_buff *data); | ||
|
||
bool ei_client_config(const char *nodename, const char *cookie); | ||
bool ei_client_send(char *process_name, ei_x_buff *msg); | ||
|
||
#endif /* EI_CLIENT_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#include <signal.h> | ||
#include <stdbool.h> | ||
#include <time.h> | ||
|
||
#include "ei_client.h" | ||
#include "ei_log.h" | ||
|
||
#define HEARTBEAT_PROCESS "Elixir.Bacnet.Heartbeat" | ||
|
||
static bool initialized; | ||
static timer_t timer; | ||
static void heartbeat_cb(int sig, siginfo_t *si, void *uc); | ||
|
||
bool ei_heartbeat_start() | ||
{ | ||
if (initialized) { | ||
LOG_DBG("Heartbeat already initialized"); | ||
return true; | ||
} | ||
|
||
struct sigaction sa = { | ||
.sa_flags = SA_SIGINFO, | ||
.sa_sigaction = heartbeat_cb, | ||
}; | ||
|
||
sigemptyset(&sa.sa_mask); | ||
|
||
if (sigaction(SIGRTMIN, &sa, NULL) < 0) { | ||
return false; | ||
} | ||
|
||
struct sigevent event = { | ||
.sigev_notify = SIGEV_SIGNAL, | ||
.sigev_signo = SIGRTMIN, | ||
}; | ||
|
||
if (timer_create(CLOCK_REALTIME, &event, &timer)) { | ||
return false; | ||
} | ||
|
||
struct itimerspec spec = { | ||
.it_interval = {.tv_sec = 4}, | ||
.it_value = {.tv_sec = 4}, | ||
}; | ||
|
||
if (timer_settime(timer, 0, &spec, NULL)) { | ||
return false; | ||
} | ||
|
||
initialized = true; | ||
|
||
return true; | ||
} | ||
|
||
static void heartbeat_cb(int sig, siginfo_t *si, void *uc) | ||
{ | ||
ei_x_buff msg; | ||
ei_x_new_with_version(&msg); | ||
ei_x_encode_atom(&msg, "heartbeat"); | ||
|
||
if (!ei_client_send(HEARTBEAT_PROCESS, &msg)) { | ||
LOG_ERR("Failed to send heartbeat"); | ||
} | ||
|
||
ei_x_free(&msg); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#ifndef EI_HEARTBEAT_H | ||
#define EI_HEARTBEAT_H | ||
|
||
bool ei_heartbeat_start(); | ||
|
||
#endif /* EI_HEARTBEAT_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#include <stdarg.h> | ||
#include <stdbool.h> | ||
|
||
#include "ei_client.h" | ||
#include "ei_log.h" | ||
|
||
#define MAX_LOG_LEN 1024 | ||
|
||
static const char *level_to_str(log_level_t level); | ||
|
||
void ei_log(log_level_t level, const char *format, ...) | ||
{ | ||
va_list args; | ||
va_start(args, format); | ||
|
||
char log_buffer[MAX_LOG_LEN]; | ||
int log_len = snprintf(log_buffer, sizeof(log_buffer), format, args); | ||
|
||
ei_x_buff msg; | ||
ei_x_new_with_version(&msg); | ||
ei_x_encode_tuple_header(&msg, 2); | ||
ei_x_encode_atom(&msg, level_to_str(level)); | ||
ei_x_encode_binary(&msg, log_buffer, log_len); | ||
|
||
if (!ei_client_send("Elixir.Bacnet.Logger", &msg)) { | ||
char new_format[MAX_LOG_LEN]; | ||
snprintf(new_format, sizeof(new_format), "%s\n", format); | ||
vprintf(new_format, args); | ||
} | ||
|
||
ei_x_free(&msg); | ||
va_end(args); | ||
} | ||
|
||
static const char *level_to_str(log_level_t level) | ||
{ | ||
switch (level) { | ||
case EMERGENCY: | ||
return "emergency"; | ||
case ALERT: | ||
return "alert"; | ||
case CRITICAL: | ||
return "critical"; | ||
case ERROR: | ||
return "error"; | ||
case WARNING: | ||
return "warning"; | ||
case NOTICE: | ||
return "notice"; | ||
case INFO: | ||
return "info"; | ||
case DEBUG: | ||
return "debug"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#ifndef EI_LOG_H | ||
#define EI_LOG_H | ||
|
||
#define LOG_ERR(format, ...) ei_log(ERROR, format __VA_ARGS__) | ||
#define LOG_WRN(format, ...) ei_log(WARNING, format __VA_ARGS__) | ||
#define LOG_INF(format, ...) ei_log(INFO, format __VA_ARGS__) | ||
#define LOG_DBG(format, ...) ei_log(DEBUG, format __VA_ARGS__) | ||
|
||
typedef enum { | ||
EMERGENCY, | ||
ALERT, | ||
CRITICAL, | ||
ERROR, | ||
WARNING, | ||
NOTICE, | ||
INFO, | ||
DEBUG, | ||
} log_level_t; | ||
|
||
void ei_log(log_level_t level, const char *format, ...); | ||
|
||
#endif /* EI_LOG_H */ |
Oops, something went wrong.