Skip to content

Commit

Permalink
Merge pull request #2 from redwirelabs/setup-cnode-comms
Browse files Browse the repository at this point in the history
Add basic c-node and supporting services
  • Loading branch information
abelino authored Sep 19, 2024
2 parents 4825bef + fcbd082 commit 2bf6d14
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 11 deletions.
17 changes: 16 additions & 1 deletion lib/bacnet.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
defmodule Bacnet do
defmodule BACNet do
@moduledoc """
BACNet client.
"""

require Logger

defmodule Device do
@typedoc "Placeholder"
@type t :: term
end

@spec add_device(device :: Device.t) :: :ok | {:error, term}
def add_device(device) do
GenServer.call({:global, :bacnetd}, {:add_device, device})
end

@doc false
defdelegate ei_log(level, term), to: Logger, as: :log
end
21 changes: 15 additions & 6 deletions lib/bacnet/application.ex
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
defmodule Bacnet.Application do
defmodule BACNet.Application do
@moduledoc false

use Application

@impl true
def start(_type, _args) do
children = children()
opts = [strategy: :one_for_one, name: Bacnet.Supervisor]
bacnetd_exe = "#{:code.priv_dir(:bacnet)}/bacnetd"
bacnetd_args = [
"--cookie", Node.get_cookie |> to_string,
"--nodename", Node.self |> to_string,
]

Supervisor.start_link(children, opts)
end
children = [
{MuonTrap.Daemon, [bacnetd_exe, bacnetd_args, []]}
]

defp children(), do: []
Supervisor.start_link(
children,
strategy: :one_for_one,
name: BACNet.Supervisor
)
end
end
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Bacnet.MixProject do
defmodule BACNet.MixProject do
use Mix.Project

def project do
Expand Down Expand Up @@ -33,7 +33,7 @@ defmodule Bacnet.MixProject do
def application do
[
extra_applications: [:logger],
mod: {Bacnet.Application, []}
mod: {BACNet.Application, []}
]
end

Expand All @@ -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
2 changes: 1 addition & 1 deletion spec/bacnet_spec.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Bacnet.Spec do
defmodule BACNet.Spec do
use ESpec

specify do: expect true |> to(eq true)
Expand Down
31 changes: 31 additions & 0 deletions src/arg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#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 index = 0;

while ((opt = getopt_long(argc, argv, "n:c:", options, &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 */
120 changes: 120 additions & 0 deletions src/ei_client.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "ei_client.h"

#define OTP_COMPAT_VER 24
#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;

erlang_pid *pid = ei_self(&client.cnode);
if (ei_global_register(client.fd, CNODE_NAME, pid) == -1)
return false;

client.ready = true;

return true;
}

bool ei_client_send(char *process_name, ei_x_buff *message)
{
if (!client.ready)
return false;

pthread_mutex_lock(&client.lock);
int ret = ei_reg_send(
&client.cnode,
client.fd,
process_name,
message->buff,
message->index
);
pthread_mutex_unlock(&client.lock);

return ret == 0 ? true : false;
}

bool ei_client_send_to(erlang_pid *pid, ei_x_buff *message)
{
if (!client.ready)
return false;

pthread_mutex_lock(&client.lock);
int ret = ei_send(client.fd, pid, message->buff, message->index);
pthread_mutex_unlock(&client.lock);

return ret == 0 ? true : false;
}

bool ei_client_call(char *module, char *func, ei_x_buff *args, ei_x_buff *result)
{
pthread_mutex_lock(&client.lock);
int ret = ei_rpc(
&client.cnode,
client.fd,
module,
func,
args->buff,
args->index,
result
);
pthread_mutex_unlock(&client.lock);

return ret == -1 ? false : true;
}

bool ei_client_recv(erlang_msg *meta, ei_x_buff *message)
{
pthread_mutex_lock(&client.lock);
int ret = ei_xreceive_msg(client.fd, meta, message);
pthread_mutex_unlock(&client.lock);

return ret == ERL_ERROR ? false : true;
}

static void ei_free()
{
if (client.fd > 0)
ei_close_connection(client.fd);
}
12 changes: 12 additions & 0 deletions src/ei_client.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef EI_CLIENT_H
#define EI_CLIENT_H

#include <ei.h>

bool ei_client_config(const char *nodename, const char *cookie);
bool ei_client_send(char *process_name, ei_x_buff *message);
bool ei_client_send_to(erlang_pid *pid, ei_x_buff *messageg);
bool ei_client_call(char *module, char *func, ei_x_buff *message, ei_x_buff *out);
bool ei_client_recv(erlang_msg *meta, ei_x_buff *message);

#endif /* EI_CLIENT_H */
52 changes: 52 additions & 0 deletions src/ei_log.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <stdarg.h>
#include <stdbool.h>

#include "ei_client.h"
#include "ei_log.h"

#define MAX_LOG_LENGTH 1024

static const char *level_to_str(log_level_t level);

void ei_log(log_level_t level, const char *format, ...)
{
va_list vargs;
va_start(vargs, format);

char log_buffer[MAX_LOG_LENGTH];
int log_length = vsnprintf(log_buffer, sizeof(log_buffer), format, vargs);

ei_x_buff out;
ei_x_new(&out);

ei_x_buff args;
ei_x_new(&args);
ei_x_encode_list_header(&args, 2);
ei_x_encode_atom(&args, level_to_str(level));
ei_x_encode_binary(&args, log_buffer, log_length);
ei_x_encode_empty_list(&args);

if (!ei_client_call("Elixir.BACNet", "ei_log", &args, &out)) {
char new_format[MAX_LOG_LENGTH];
snprintf(new_format, sizeof(new_format), "%s\n", format);
vprintf(new_format, vargs);
}

ei_x_free(&out);
ei_x_free(&args);
va_end(vargs);
}

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";
}
}
26 changes: 26 additions & 0 deletions src/ei_log.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef EI_LOG_H
#define EI_LOG_H

#define LOG_EMERGENCY(format, ...) ei_log(EMERGENCY, format, ##__VA_ARGS__)
#define LOG_ALERT(format, ...) ei_log(ALERT, format, ##__VA_ARGS__)
#define LOG_CRITICAL(format, ...) ei_log(CRITICAL, format, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) ei_log(ERROR, format, ##__VA_ARGS__)
#define LOG_WARNING(format, ...) ei_log(WARNING, format, ##__VA_ARGS__)
#define LOG_NOTICE(format, ...) ei_log(NOTICE, format, ##__VA_ARGS__)
#define LOG_INFO(format, ...) ei_log(INFO, format, ##__VA_ARGS__)
#define LOG_DEBUG(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 2bf6d14

Please sign in to comment.