Skip to content

Commit

Permalink
Add basic c-node and supporting services
Browse files Browse the repository at this point in the history
  • Loading branch information
abelino committed Aug 30, 2024
1 parent 4825bef commit be91bc0
Show file tree
Hide file tree
Showing 13 changed files with 394 additions and 2 deletions.
19 changes: 18 additions & 1 deletion lib/bacnet/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,22 @@ defmodule Bacnet.Application do
Supervisor.start_link(children, opts)
end

defp children(), do: []
defp children() do
[
{Bacnet.Logger, nil},
{Bacnet.Heartbeat, nil},
{MuonTrap.Daemon, [bacnetd_exec(), bacnetd_args(), []]}
]
end

defp bacnetd_exec do
"#{:code.priv_dir(:bacnet)}/bacnetd"
end

defp bacnetd_args do
[
"--cookie", Node.get_cookie() |> to_string(),
"--nodename", Node.self() |> to_string(),
]
end
end
26 changes: 26 additions & 0 deletions lib/bacnet/heartbeat.ex
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
40 changes: 40 additions & 0 deletions lib/bacnet/logger.ex
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
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ defmodule Bacnet.MixProject do
{:elixir_cmake, "~> 0.8.0"},
{:excoveralls, "~> 0.18", only: :test},
{:ex_doc, "~> 0.34", only: :dev, runtime: false},
{:muontrap, "~> 1.5"},
]
end

Expand Down
30 changes: 30 additions & 0 deletions src/arg.c
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;
}
}
}
13 changes: 13 additions & 0 deletions src/arg.h
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 */
80 changes: 80 additions & 0 deletions src/ei_client.c
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);
}
}
11 changes: 11 additions & 0 deletions src/ei_client.h
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 */
66 changes: 66 additions & 0 deletions src/ei_heartbeat.c
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);
}
6 changes: 6 additions & 0 deletions src/ei_heartbeat.h
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 */
55 changes: 55 additions & 0 deletions src/ei_log.c
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";
}
}
22 changes: 22 additions & 0 deletions src/ei_log.h
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 */
Loading

0 comments on commit be91bc0

Please sign in to comment.