Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
38 changes: 34 additions & 4 deletions .github/workflows/TMS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ jobs:
cd OpenDDS
. setenv.sh
cd ../tactical-microgrid-standard
cmake -B build_static
cmake -B build_static -D BUILD_TESTING=TRUE
cmake --build build_static
- name: 'Build TMS application with shared libs (Linux /macOS)'
if: runner.os == 'Linux' || runner.os == 'macOS'
Expand All @@ -235,7 +235,7 @@ jobs:
cd OpenDDS
. setenv.sh
cd ../tactical-microgrid-standard
cmake -DBUILD_SHARED_LIBS=yes -B build_shared
cmake -DBUILD_SHARED_LIBS=yes -B build_shared -D BUILD_TESTING=TRUE
cmake --build build_shared
- name: 'Build TMS application with static libs (Windows)'
if: runner.os == 'Windows'
Expand All @@ -244,7 +244,7 @@ jobs:
cd OpenDDS
call setenv.cmd
cd ..\tactical-microgrid-standard
cmake -B build_static
cmake -B build_static -D BUILD_TESTING=TRUE
cmake --build build_static
- name: 'Build TMS application with shared libs (Windows)'
if: runner.os == 'Windows'
Expand All @@ -253,5 +253,35 @@ jobs:
cd OpenDDS
call setenv.cmd
cd ..\tactical-microgrid-standard
cmake -DBUILD_SHARED_LIBS=yes -B build_shared
cmake -DBUILD_SHARED_LIBS=yes -B build_shared -D BUILD_TESTING=TRUE
cmake --build build_shared
- name: 'Run tests with static libs (Linux / macOS)'
if: runner.os == 'Linux' || runner.os == 'macOS'
run: |-
cd OpenDDS
. setenv.sh
cd ../tactical-microgrid-standard/build_static
ctest --verbose --output-on-failure
- name: 'Run tests with shared libs (Linux /macOS)'
if: runner.os == 'Linux' || runner.os == 'macOS'
run: |-
cd OpenDDS
. setenv.sh
cd ../tactical-microgrid-standard/build_shared
ctest --verbose --output-on-failure
- name: 'Run tests with static libs (Windows)'
if: runner.os == 'Windows'
shell: cmd
run: |-
cd OpenDDS
call setenv.cmd
cd ..\tactical-microgrid-standard\build_static
ctest --verbose --output-on-failure -C Debug
- name: 'Run tests with shared libs (Windows)'
if: runner.os == 'Windows'
shell: cmd
run: |-
cd OpenDDS
call setenv.cmd
cd ..\tactical-microgrid-standard\build_shared
ctest --verbose --output-on-failure -C Debug
5 changes: 3 additions & 2 deletions tactical-microgrid-standard/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,6 @@ if (MSVC)
target_link_options(Distribution PRIVATE "/STACK:2097152")
endif()


add_subdirectory(tests)
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
25 changes: 20 additions & 5 deletions tactical-microgrid-standard/common/ControllerSelector.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include "ControllerSelector.h"

ControllerSelector::ControllerSelector(const tms::Identity& device_id)
: device_id_(device_id)
: ControllerCallbacks(lock_)

Check warning on line 4 in tactical-microgrid-standard/common/ControllerSelector.cpp

View workflow job for this annotation

GitHub Actions / build (master, macos-13)

base class 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>' is uninitialized when used here to access 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>::lock_' [-Wuninitialized]

Check warning on line 4 in tactical-microgrid-standard/common/ControllerSelector.cpp

View workflow job for this annotation

GitHub Actions / build (master, macos-13)

base class 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>' is uninitialized when used here to access 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>::lock_' [-Wuninitialized]

Check warning on line 4 in tactical-microgrid-standard/common/ControllerSelector.cpp

View workflow job for this annotation

GitHub Actions / build (latest-release, macos-14)

base class 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>' is uninitialized when used here to access 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>::lock_' [-Wuninitialized]

Check warning on line 4 in tactical-microgrid-standard/common/ControllerSelector.cpp

View workflow job for this annotation

GitHub Actions / build (latest-release, macos-14)

base class 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>' is uninitialized when used here to access 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>::lock_' [-Wuninitialized]

Check warning on line 4 in tactical-microgrid-standard/common/ControllerSelector.cpp

View workflow job for this annotation

GitHub Actions / build (master, macos-14)

base class 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>' is uninitialized when used here to access 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>::lock_' [-Wuninitialized]

Check warning on line 4 in tactical-microgrid-standard/common/ControllerSelector.cpp

View workflow job for this annotation

GitHub Actions / build (master, macos-14)

base class 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>' is uninitialized when used here to access 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>::lock_' [-Wuninitialized]

Check warning on line 4 in tactical-microgrid-standard/common/ControllerSelector.cpp

View workflow job for this annotation

GitHub Actions / build (latest-release, macos-13)

base class 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>' is uninitialized when used here to access 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>::lock_' [-Wuninitialized]

Check warning on line 4 in tactical-microgrid-standard/common/ControllerSelector.cpp

View workflow job for this annotation

GitHub Actions / build (latest-release, macos-13)

base class 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>' is uninitialized when used here to access 'TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers>::lock_' [-Wuninitialized]
Comment thread
iguessthislldo marked this conversation as resolved.
Outdated
, device_id_(device_id)
{
}

Expand Down Expand Up @@ -42,6 +43,7 @@
if (di.role() == tms::DeviceRole::ROLE_MICROGRID_CONTROLLER &&
!all_controllers_.count(di.deviceId())) {
all_controllers_[di.deviceId()] = TimePoint::min();
prioritized_controllers_.insert(PrioritizedController(di));
}
}

Expand Down Expand Up @@ -72,6 +74,9 @@
const auto& timer_id = timer.id;
ACE_DEBUG((LM_INFO, "(%P|%t) INFO: ControllerSelector::timed_event(MissedHeartbeat): "
"\"%C\". Timer id: %d\n", selected_.c_str(), timer_id));
if (missed_heartbeat_callback_) {
missed_heartbeat_callback_(selected_);
}
schedule_once(LostController{}, lost_active_controller_delay);

// Start a No MC timer if the device has missed heartbeats from all MCs
Expand All @@ -94,6 +99,9 @@
Guard g(lock_);
ACE_DEBUG((LM_INFO, "(%P|%t) INFO: ControllerSelector::timed_event(LostController): "
"\"%C\"\n", selected_.c_str()));
if (lost_controller_callback_) {
lost_controller_callback_(selected_);
}
selected_.clear();

// Select a new controller if possible. If there are no recent controllers
Expand All @@ -105,6 +113,9 @@
{
Guard g(lock_);
ACE_DEBUG((LM_INFO, "(%P|%t) INFO: ControllerSelector::timed_event(NoControllers)\n"));
if (no_controllers_callback_) {
no_controllers_callback_();
}
// TODO: CONFIG_ON_COMMS_LOSS
}

Expand All @@ -115,13 +126,14 @@
const TimePoint now = Clock::now();

// Select an available controller with smallest identity alphabetically
for (auto it = all_controllers_.begin(); it != all_controllers_.end(); ++it) {
const auto last_hb = now - it->second;
for (auto it = prioritized_controllers_.begin(); it != prioritized_controllers_.end(); ++it) {
auto mc_info = all_controllers_.find(it->id);
const auto last_hb = now - mc_info->second;
// TMS spec doesn't specify this. But it should make sure the controller is still available
// i.e., last heartbeat received within 3 seconds.
if (last_hb < heartbeat_deadline) {
select(it->first, std::chrono::duration_cast<Sec>(last_hb));
return true;
select(mc_info->first, std::chrono::duration_cast<Sec>(last_hb));
break;
}
}
return false;
Expand All @@ -131,6 +143,9 @@
{
ACE_DEBUG((LM_INFO, "(%P|%t) INFO: ControllerSelector::select: \"%C\"\n", id.c_str()));
selected_ = id;
if (new_controller_callback_) {
new_controller_callback_(selected_);
}
send_controller_state();
schedule_once(MissedHeartbeat{}, heartbeat_deadline - last_hb);
}
Expand Down
74 changes: 67 additions & 7 deletions tactical-microgrid-standard/common/ControllerSelector.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <common/OpenDDS_TMS_export.h>

#include <map>
#include <tuple>
#include <set>

struct NewController {
tms::Identity id;
Expand All @@ -25,7 +27,46 @@ struct NoControllers {
static const char* name() { return "NoControllers"; }
};

class PowerDevice;
class OpenDDS_TMS_Export ControllerCallbacks {
public:
using IdCallback = std::function<void(const tms::Identity&)>;
using Callback = std::function<void()>;

explicit ControllerCallbacks(Mutex& lock) : cb_lock_(lock) {}

void set_new_controller_callback(IdCallback cb)
{
Guard g(cb_lock_);
new_controller_callback_ = cb;
}

void set_missed_heartbeat_callback(IdCallback cb)
{
Guard g(cb_lock_);
missed_heartbeat_callback_ = cb;
}

void set_lost_controller_callback(IdCallback cb)
{
Guard g(cb_lock_);
lost_controller_callback_ = cb;
}

void set_no_controllers_callback(Callback cb)
{
Guard g(cb_lock_);
no_controllers_callback_ = cb;
}

protected:
IdCallback new_controller_callback_;
IdCallback missed_heartbeat_callback_;
IdCallback lost_controller_callback_;
Callback no_controllers_callback_;

private:
Mutex& cb_lock_;
};

/**
* Logic for determining what controller should be used, based on TMS A.7.5.
Expand Down Expand Up @@ -55,6 +96,7 @@ class PowerDevice;
* S: If there's a selectable controller with a recent heartbeat
*/
class OpenDDS_TMS_Export ControllerSelector :
public ControllerCallbacks,
public TimerHandler<NewController, MissedHeartbeat, LostController, NoControllers> {
public:
explicit ControllerSelector(const tms::Identity& device_id);
Expand All @@ -75,12 +117,6 @@ class OpenDDS_TMS_Export ControllerSelector :
return selected_ == id;
}

ACE_Reactor* get_reactor() const
{
Guard g(lock_);
return reactor_;
}

void set_ActiveMicrogridControllerState_writer(tms::ActiveMicrogridControllerStateDataWriter_var amcs_dw)
{
amcs_dw_ = amcs_dw;
Expand Down Expand Up @@ -109,6 +145,30 @@ class OpenDDS_TMS_Export ControllerSelector :
tms::Identity selected_;
std::map<tms::Identity, TimePoint> all_controllers_;

struct PrioritizedController {
uint16_t priority = 0;
tms::Identity id;

explicit PrioritizedController(const tms::DeviceInfo& di)
: priority(0)
, id(di.deviceId())
Comment thread
iguessthislldo marked this conversation as resolved.
Outdated
{
const auto& cs = di.controlService();
if (cs) {
const auto& mc = cs->mc();
if (mc) {
priority = mc->priorityRanking();
}
}
}

bool operator<(const PrioritizedController& lhs) const
{
return std::tie(priority, id) < std::tie(lhs.priority, lhs.id);
}
};
std::set<PrioritizedController> prioritized_controllers_;

// Device ID to which this controller selector belong.
tms::Identity device_id_;

Expand Down
7 changes: 7 additions & 0 deletions tactical-microgrid-standard/common/TimerHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ class TimerHandler : public ACE_Event_Handler, protected TimerHolder<EventTypes>
}
}

ACE_Reactor* get_reactor() const
{
Guard g(lock_);
return reactor_;
}

template <typename EventType>
typename Timer<EventType>::Ptr get_timer(const std::string& name = "")
{
Expand Down Expand Up @@ -244,6 +250,7 @@ class TimerHandler : public ACE_Event_Handler, protected TimerHolder<EventTypes>
{
Guard g(lock_);
auto key = *reinterpret_cast<const std::string*>(arg);
ACE_DEBUG((LM_DEBUG, "(%P|%t) TimerHandler::handle_timeout: %C\n", key.c_str()));
if (active_timers_.count(key) == 0) {
ACE_ERROR((LM_WARNING, "(%P|%t) WARNING: TimerHandler::handle_timeout: timer key %C does NOT exist\n", key.c_str()));
}
Expand Down
2 changes: 1 addition & 1 deletion tactical-microgrid-standard/controller/Controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ tms::DeviceInfo Controller::populate_device_info() const
tms::MicrogridControllerInfo mc_info;
{
mc_info.features().push_back(tms::MicrogridControllerFeature::MCF_GENERAL);
mc_info.priorityRanking(0);
mc_info.priorityRanking(priority_);
}
csi.mc() = mc_info;
}
Expand Down
7 changes: 6 additions & 1 deletion tactical-microgrid-standard/controller/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

class Controller : public Handshaking {
public:
explicit Controller(const tms::Identity& id) : Handshaking(id) {}
explicit Controller(const tms::Identity& id, uint16_t priority= 0)
Comment thread
iguessthislldo marked this conversation as resolved.
Outdated
: Handshaking(id)
, priority_(priority)
{
}

DDS::ReturnCode_t init(DDS::DomainId_t domain_id, int argc = 0, char* argv[] = nullptr);
int run();
Expand All @@ -30,6 +34,7 @@ class Controller : public Handshaking {
private:
mutable std::mutex mut_;
PowerDevices power_devices_;
uint16_t priority_;

DDS::DomainId_t tms_domain_id_ = OpenDDS::DOMAIN_UNKNOWN;;
};
Expand Down
5 changes: 5 additions & 0 deletions tactical-microgrid-standard/power_devices/PowerDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ class PowerSim_Idl_Export PowerDevice : public Handshaking {
return essl_;
}

ControllerCallbacks& controller_callbacks()
{
return controller_selector_;
}

protected:
virtual int run_i()
{
Expand Down
8 changes: 4 additions & 4 deletions tactical-microgrid-standard/tests/mc-sel/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
cmake_minimum_required(VERSION 3.27)

project(opendds_tms_mc_sel_test CXX)
project(opendds_tms_mc_sel CXX)
enable_testing()

find_package(OpenDDS REQUIRED)
include(opendds_testing)

add_executable(mc-sel-test mc-sel-test.cpp)
target_link_libraries(mc-sel-test PRIVATE PowerSim_Idl)
add_executable(basic-dev basic-dev.cpp)
target_link_libraries(basic-dev PRIVATE PowerSim_Idl)

add_executable(basic-mc
${CMAKE_SOURCE_DIR}/controller/Controller.cpp
${CMAKE_SOURCE_DIR}/controller/ActiveMicrogridControllerStateDataReaderListenerImpl.cpp
basic-mc.cpp)
target_link_libraries(basic-mc PRIVATE Commands_Idl)

opendds_add_test(COMMAND ./mc-sel-test)
opendds_add_test(ARGS remove_dcs_after=0 test_verbose=1)
35 changes: 35 additions & 0 deletions tactical-microgrid-standard/tests/mc-sel/basic-dev.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "common.h"

#include <power_devices/PowerDevice.h>

#include <tests/Utils/DistributedConditionSet.h>

int main(int argc, char* argv[])
{
const std::string name = "dev";
PowerDevice pd(name);
if (pd.init(domain, argc, argv) != DDS::RETCODE_OK) {
return 1;
}

DistributedConditionSet_rch dcs =
OpenDDS::DCPS::make_rch<FileBasedDistributedConditionSet>();

ControllerCallbacks& cbs = pd.controller_callbacks();
cbs.set_new_controller_callback([dcs, name](const tms::Identity& id) {
printf("new_controller\n");
dcs->post(name, "new controller " + id);
});
cbs.set_missed_heartbeat_callback([dcs, name](const tms::Identity& id) {
dcs->post(name, "missed controller " + id);
});
cbs.set_lost_controller_callback([dcs, name](const tms::Identity& id) {
dcs->post(name, "lost controller " + id);
});
cbs.set_no_controllers_callback([dcs, name]() {
dcs->post(name, "no controllers");
});

Exiter exiter(pd);
return pd.run();
}
Loading
Loading