Skip to content

Commit

Permalink
[WIP] Allow looping uart streamed gcode
Browse files Browse the repository at this point in the history
  • Loading branch information
dymk committed Oct 1, 2024
1 parent e2bd934 commit 9b721a0
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 14 deletions.
61 changes: 61 additions & 0 deletions FluidNC/src/FixedCircularBuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2024 - Dylan Knutson <[email protected]>
// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file.

#pragma once

#include <vector>
#include <optional>

/**
* A fixed-size circular buffer that stores elements of type T.
* Keeps track of how many elements have been pushed onto it, and allows
* for indexing as if it was an infinite sized array. If indexing into
* the buffer would result in an out-of-bounds access, returns std::nullopt.
*
* This is useful for implementing "scrollback" of a buffer of e.g. user
* provided commands, without using an unbounded amount of memory.
*/
template <typename T>
class FixedCircularBuffer {
public:
std::vector<T> storage;
std::size_t head_idx, tail_idx;

public:
FixedCircularBuffer() : FixedCircularBuffer(0) {}
FixedCircularBuffer(size_t size) : storage(size), head_idx(0), tail_idx(0) {}

/**
* Push an element onto the end of the buffer.
*/
void push(T&& elem) {
storage[tail_idx % storage.size()] = std::move(elem);
tail_idx += 1;
if (tail_idx - head_idx > storage.size()) {
head_idx += 1;
}
}

/**
* Get the element at the given index, or std::nullopt if the index is out of bounds.
*/
std::optional<T> at(std::size_t idx) const {
if (idx >= tail_idx) {
return std::nullopt;
}
if (idx < head_idx) {
return std::nullopt;
}
return storage[idx % storage.size()];
}

/**
* Is the buffer empty?
*/
bool is_empty() const { return head_idx == tail_idx; }

/**
* Get the index of the last element pushed onto the buffer.
*/
std::size_t position() const { return tail_idx; }
};
32 changes: 23 additions & 9 deletions FluidNC/src/Job.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,29 @@
#include "Job.h"
#include <map>
#include <stack>
#include "Protocol.h"

std::stack<JobSource*> job;

Channel* Job::leader = nullptr;

bool Job::active() {
return !job.empty();
return !job.empty() || activeChannel;
}

JobSource activeChannelJobSource(nullptr);

JobSource* Job::source() {
return job.empty() ? nullptr : job.top();
if (job.empty()) {
if (activeChannel) {
activeChannelJobSource.set_channel(activeChannel);
return &activeChannelJobSource;
} else {
return nullptr;
}
} else {
return job.top();
}
}

// save() and restore() are use to close/reopen an SD file atop the job stack
Expand All @@ -38,9 +50,11 @@ void Job::nest(Channel* in_channel, Channel* out_channel) {
job.push(source);
}
void Job::pop() {
auto source = job.top();
job.pop();
delete source;
if (!job.empty()) {
auto source = job.top();
job.pop();
delete source;
}
if (!active()) {
leader = nullptr;
}
Expand All @@ -60,14 +74,14 @@ void Job::abort() {
}

bool Job::get_param(const std::string& name, float& value) {
return job.top()->get_param(name, value);
return source()->get_param(name, value);
}
bool Job::set_param(const std::string& name, float value) {
return job.top()->set_param(name, value);
return source()->set_param(name, value);
}
bool Job::param_exists(const std::string& name) {
return job.top()->param_exists(name);
return source()->param_exists(name);
}
Channel* Job::channel() {
return job.top()->channel();
return source()->channel();
}
1 change: 1 addition & 0 deletions FluidNC/src/Job.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class JobSource {
void set_position(size_t pos) { _channel->set_position(pos); }

Channel* channel() { return _channel; }
void set_channel(Channel* channel) { _channel = channel; }

~JobSource() { delete _channel; }
};
Expand Down
2 changes: 2 additions & 0 deletions FluidNC/src/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ extern volatile bool rtCycleStop;

extern volatile bool runLimitLoop;

extern Channel* activeChannel;

// Alarm codes.
enum class ExecAlarm : uint8_t {
None = 0,
Expand Down
21 changes: 18 additions & 3 deletions FluidNC/src/UartChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
#include "Serial.h" // allChannels

UartChannel::UartChannel(int num, bool addCR) : Channel("uart_channel", num, addCR) {
_lineedit = new Lineedit(this, _line, Channel::maxLine - 1);
_active = false;
_lineedit = new Lineedit(this, _line, Channel::maxLine - 1);
_active = false;
_history_buffer = FixedCircularBuffer<char>(512);
_history_buffer_pos = 0;
}

void UartChannel::init() {
Expand Down Expand Up @@ -63,10 +65,13 @@ size_t UartChannel::write(const uint8_t* buffer, size_t length) {
}

int UartChannel::available() {
return _uart->available();
return (_history_buffer_pos < _history_buffer.position()) || _uart->available();
}

int UartChannel::peek() {
if (_history_buffer_pos < _history_buffer.position()) {
return _history_buffer.at(_history_buffer_pos).value();
}
return _uart->peek();
}

Expand All @@ -90,12 +95,22 @@ bool UartChannel::lineComplete(char* line, char c) {
}

int UartChannel::read() {
if (_history_buffer_pos < _history_buffer.position()) {
int c = _history_buffer.at(_history_buffer_pos).value();
_history_buffer_pos += 1;
return c;
}

int c = _uart->read();
if (c == 0x11) {
// 0x11 is XON. If we receive that, it is a request to use software flow control
_uart->setSwFlowControl(true, -1, -1);
return -1;
}
if (c != -1) {
_history_buffer.push((char)c);
_history_buffer_pos += 1;
}
return c;
}

Expand Down
10 changes: 8 additions & 2 deletions FluidNC/src/UartChannel.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
#include "Uart.h"
#include "Channel.h"
#include "lineedit.h"
#include "FixedCircularBuffer.h"

class UartChannel : public Channel, public Configuration::Configurable {
private:
Lineedit* _lineedit;
Uart* _uart;
Lineedit* _lineedit;
Uart* _uart;
FixedCircularBuffer<char> _history_buffer;
std::size_t _history_buffer_pos;

int _uart_num = 0;
int _report_interval_ms = 0;
Expand Down Expand Up @@ -49,6 +52,9 @@ class UartChannel : public Channel, public Configuration::Configurable {
handler.item("uart_num", _uart_num);
handler.item("message_level", _message_level, messageLevels2);
}

size_t position() override { return _history_buffer_pos; }
void set_position(size_t pos) override { _history_buffer_pos = pos; }
};

extern UartChannel Uart0;
Expand Down
59 changes: 59 additions & 0 deletions FluidNC/src/testbuffer.cpp.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "FixedCircularBuffer.h"

#include <iostream>
#include <iterator>

template <typename T>
std::ostream& operator<<(std::ostream& os, std::vector<T> vec) {
os << "{ ";
std::copy(vec.begin(), vec.end(), std::ostream_iterator<T>(os, " "));
os << "}";
return os;
}

template <typename T>
std::ostream& operator<<(std::ostream& os, FixedCircularBuffer<T> buff) {
os << "[head_idx: " << buff.head_idx << "][tail_idx: " << buff.tail_idx << "][storage: " << buff.storage << "]";
return os;
}

template <typename T>
std::ostream& operator<<(std::ostream& os, std::optional<T> opt) {
if (opt.has_value()) {
os << opt.value();
} else {
os << "nullopt";
}
return os;
}

int main() {
FixedCircularBuffer<char> buffer(4);
buffer.push('a');
buffer.push('b');
std::cout << "buffer is " << buffer << std::endl;
std::cout << "buffer.at(0) is " << buffer.at(0) << std::endl;
std::cout << "buffer.at(1) is " << buffer.at(1) << std::endl;
std::cout << "buffer.at(2) is " << buffer.at(2) << std::endl;
std::cout << "buffer.at(3) is " << buffer.at(3) << std::endl;
std::cout << std::endl;

buffer.push('c');
buffer.push('d');
std::cout << "buffer is " << buffer << std::endl;
std::cout << "buffer.at(0) is " << buffer.at(0) << std::endl;
std::cout << "buffer.at(1) is " << buffer.at(1) << std::endl;
std::cout << "buffer.at(2) is " << buffer.at(2) << std::endl;
std::cout << "buffer.at(3) is " << buffer.at(3) << std::endl;
std::cout << std::endl;

buffer.push('e');
std::cout << "buffer is " << buffer << std::endl;
std::cout << "buffer.at(0) is " << buffer.at(0) << std::endl;
std::cout << "buffer.at(1) is " << buffer.at(1) << std::endl;
std::cout << "buffer.at(2) is " << buffer.at(2) << std::endl;
std::cout << "buffer.at(3) is " << buffer.at(3) << std::endl;
std::cout << "buffer.at(4) is " << buffer.at(4) << std::endl;
std::cout << "buffer.at(5) is " << buffer.at(5) << std::endl;
std::cout << std::endl;
}
42 changes: 42 additions & 0 deletions FluidNC/tests/FixedCircularBufferTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2024 - Dylan Knutson <[email protected]>
// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file.

#include "gtest/gtest.h"
#include "src/FixedCircularBuffer.h"

TEST(FixedCircularBuffer, Empty) {
FixedCircularBuffer<int> buffer(0);

ASSERT_TRUE(buffer.is_empty());
ASSERT_EQ(buffer.position(), 0);
ASSERT_EQ(buffer.at(0), std::nullopt);
ASSERT_EQ(buffer.at(1), std::nullopt);
ASSERT_EQ(buffer.at(2), std::nullopt);
}

TEST(FixedCircularBuffer, OneElement) {
FixedCircularBuffer<int> buffer(1);

buffer.push(42);

ASSERT_FALSE(buffer.is_empty());
ASSERT_EQ(buffer.position(), 1);
ASSERT_EQ(buffer.at(0), 42);
ASSERT_EQ(buffer.at(1), std::nullopt);
ASSERT_EQ(buffer.at(2), std::nullopt);
}

TEST(FixedCircularBuffer, FrontElementsPopped) {
FixedCircularBuffer<int> buffer(2);

buffer.push(1);
buffer.push(2);
buffer.push(3);

ASSERT_FALSE(buffer.is_empty());
ASSERT_EQ(buffer.position(), 3);
ASSERT_EQ(buffer.at(0), std::nullopt);
ASSERT_EQ(buffer.at(1), 2);
ASSERT_EQ(buffer.at(2), 3);
ASSERT_EQ(buffer.at(3), std::nullopt);
}

0 comments on commit 9b721a0

Please sign in to comment.