Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic c-node and supporting services #2

Merged
merged 5 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛔ (required)

The second arg is the log message as a string, right?

Suggested change
defdelegate ei_log(level, term), to: Logger, as: :log
defdelegate ei_log(level, message), to: Logger, as: :log

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It currently is returning a string, but nothing stopping us from this being able to return a term, i.e. structured data.

Copy link
Member

@amclain amclain Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Oh, well that's cool.

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
Loading