#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <csignal>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>
#include <thread>
#include <vector>
#include <lxmaster/lxmaster.hpp>
static std::atomic<bool> g_interrupted{false};
static void onSignal(int) { g_interrupted = true; }
static void installSignalHandler() {
struct sigaction sa{};
sa.sa_handler = onSignal;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
}
constexpr std::int32_t kAmplitudeCounts = 50000;
constexpr double kFrequencyHz = 1.0;
constexpr int kNumBits = 16;
constexpr double kBitIntervalS = 1.0;
constexpr double kRunSeconds = kNumBits * kBitIntervalS;
constexpr double kTwoPi = 6.283185307179586476925286766559;
static double elapsedS(std::chrono::steady_clock::time_point now,
std::chrono::steady_clock::time_point start) {
return std::chrono::duration<double>(now - start).count();
}
static std::int32_t clampI32(double v) {
return static_cast<std::int32_t>(std::max(
static_cast<double>(std::numeric_limits<std::int32_t>::min()),
std::min(static_cast<double>(std::numeric_limits<std::int32_t>::max()), v)));
}
static std::int32_t positionAt(std::chrono::steady_clock::time_point now,
std::chrono::steady_clock::time_point start, std::int32_t hold) {
const double omega_t = kTwoPi * kFrequencyHz * elapsedS(now, start);
const double amp = static_cast<double>(kAmplitudeCounts);
return clampI32(static_cast<double>(hold) + 0.5 * amp * std::cos(omega_t) - 0.5 * amp);
}
static int activeBitAt(std::chrono::steady_clock::time_point now,
std::chrono::steady_clock::time_point start) {
const int bit = static_cast<int>(elapsedS(now, start) / kBitIntervalS);
return bit < kNumBits ? bit : -1;
}
int main() {
installSignalHandler();
lxmaster::NetworkConfig cfg = lxmaster::NetworkConfig::defaults();
if (cfg.bus.ifname.empty()) {
std::cerr << "No EtherCAT interface: set LXMASTER_RT_IFACE in /etc/profile.d/lxmaster-config.sh "
"(run lxmaster host setup first).\n";
return 1;
}
cfg.eni.eni_path = "/home/user/myenifolder/myenifile.xml";
lxmaster::EcNetwork net(cfg);
std::cout << "sine+shift demo: iface=" << cfg.bus.ifname << " eni=" << cfg.eni.eni_path << "\n";
if (!net.prepare()) {
std::cerr << "EcNetwork::prepare() failed: " << net.lastError() << "\n";
return 1;
}
std::vector<lxmaster::Axis*> axes = net.axes();
if (axes.empty()) {
std::cerr << "ENI produced no motion axes to command.\n";
return 1;
}
for (lxmaster::Axis* ax : axes) {
ax->setDriveMode(lxmaster::DriveOpMode::Csp);
ax->configure();
}
lxmaster::Axis* drive = axes.front();
lxmaster::IoModule* io = nullptr;
for (lxmaster::IoModule* m : net.ioModules()) {
if (m->digitalOutputCount() >= static_cast<std::size_t>(kNumBits)) {
io = m;
break;
}
}
if (io == nullptr) {
std::cerr << "No I/O module with >= " << kNumBits
<< " digital outputs found on the bus.\n";
return 1;
}
io->configure();
if (!net.start()) {
std::cerr << "EcNetwork::start() failed: " << net.lastError() << "\n";
return 1;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
const std::int32_t hold = drive->actualPosition();
std::cout << "Operational.\n"
<< " axis '" << drive->name() << "': center=" << hold
<< " amplitude=" << kAmplitudeCounts
<< " freq=" << kFrequencyHz << " Hz\n"
<< " I/O '" << io->name() << "': " << io->digitalOutputCount()
<< " DO, " << kNumBits << "-bit walk @ "
<< kBitIntervalS << " s\n"
<< " sync=" << (net.syncMode() == lxmaster::SyncMode::DcSync0 ? "DC" : "SM-event")
<< " cycle=" << net.cycleTimeNs() << " ns"
<< " -> run " << kRunSeconds << " s\n";
for (int ch = 0; ch < kNumBits; ++ch) {
io->writeDigital(static_cast<std::size_t>(ch), false);
}
drive->moveTo(hold);
const auto update_period = std::chrono::nanoseconds(net.cycleTimeNs());
std::this_thread::sleep_for(update_period);
const auto start = std::chrono::steady_clock::now();
int active_bit = -1;
while (net.isRunning() && !g_interrupted) {
const auto now = std::chrono::steady_clock::now();
const int bit = activeBitAt(now, start);
if (bit < 0) break;
if (bit != active_bit) {
active_bit = bit;
for (int ch = 0; ch < kNumBits; ++ch) {
io->writeDigital(static_cast<std::size_t>(ch), false);
}
io->writeDigital(static_cast<std::size_t>(bit), true);
std::cout << "Bit " << bit << " ON\n";
}
drive->moveTo(positionAt(now, start, hold));
std::this_thread::sleep_for(update_period);
}
const bool stopped_early = !net.isRunning();
if (!stopped_early) {
for (int ch = 0; ch < kNumBits; ++ch) {
io->writeDigital(static_cast<std::size_t>(ch), false);
}
drive->moveTo(hold);
std::this_thread::sleep_for(update_period);
}
net.stop();
if (stopped_early) {
std::cerr << '\n';
net.reportDeviceStatus(std::cerr);
const std::string reason = net.lastError();
std::cerr << "\nRun ended early (sine/shift incomplete).\n";
std::cerr << " Reason: " << (reason.empty() ? "(none reported)" : reason) << "\n";
if (drive->isFaulted()) {
std::cerr << " Fault: axis '" << drive->name() << "' faulted (statusword=0x" << std::hex
<< drive->statusword() << std::dec << ").\n";
}
return 2;
}
std::cout << "\n";
net.reportDeviceStatus(std::cout);
std::cout << "Done.\n";
return 0;
}