proj: Migrate to core as git-submodule
This commit is contained in:
Binary file not shown.
Binary file not shown.
Vendored
+4
-2
@@ -7,13 +7,15 @@
|
||||
"includePath": [
|
||||
"${config:idf.espIdfPath}/components/**",
|
||||
"${config:idf.espIdfPathWin}/components/**",
|
||||
"${workspaceFolder}/**"
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/core/**"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${config:idf.espIdfPath}/components",
|
||||
"${config:idf.espIdfPathWin}/components",
|
||||
"${workspaceFolder}"
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/core"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -263,7 +263,7 @@
|
||||
"telnet://${config:project.deviceIp}:23",
|
||||
"/W=\"Device-Platform\"",
|
||||
"/T=1",
|
||||
"/F=\"${workspaceFolder}/.configs/TelnetTeraTerm.INI\""
|
||||
"/F=\"${workspaceFolder}/core/.configs/TelnetTeraTerm.INI\""
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS
|
||||
"core/components"
|
||||
"core/managed_components"
|
||||
)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(X_WatchController_FW)
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef ARDUINO_ARTNET_H
|
||||
#define ARDUINO_ARTNET_H
|
||||
|
||||
// Spec (Art-Net 4) : http://artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf
|
||||
// Packet Summary : https://art-net.org.uk/structure/packet-summary-2/
|
||||
// Packet Definition : https://art-net.org.uk/structure/streaming-packets/artdmx-packet-definition/
|
||||
|
||||
|
||||
namespace arx { // others
|
||||
|
||||
template <class AlwaysVoid, template<class...> class Check, class... T>
|
||||
struct is_detected_impl : std::false_type {};
|
||||
template <template <class...> class Check, class... T>
|
||||
struct is_detected_impl <std::void_t<Check<T...>>, Check, T...> : std::true_type {};
|
||||
template <template <class...> class Check, class... T>
|
||||
using is_detected = is_detected_impl<void, Check, T...>;
|
||||
|
||||
|
||||
template <typename T>
|
||||
struct is_callable {
|
||||
template <typename U, decltype(&U::operator()) = &U::operator()>
|
||||
struct checker {};
|
||||
template <typename U> static std::true_type test(checker<U> *);
|
||||
template <typename> static std::false_type test(...);
|
||||
static constexpr bool value = decltype(test<T>(nullptr))::value;
|
||||
};
|
||||
template <typename R, typename ... Arguments>
|
||||
struct is_callable<R(*)(Arguments ...)> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
template <typename R, typename ... Arguments>
|
||||
struct is_callable<R(*&)(Arguments ...)> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
template <typename R, typename ... Arguments>
|
||||
struct is_callable<R(&)(Arguments ...)> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
template <typename R, typename ... Arguments>
|
||||
struct is_callable<R(Arguments ...)> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
template <typename R, typename ... Arguments>
|
||||
struct is_callable<std::function<R(Arguments ...)>> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
|
||||
namespace detail {
|
||||
template <typename ret, typename ... arguments>
|
||||
struct function_traits {
|
||||
static constexpr size_t arity = sizeof...(arguments);
|
||||
using result_type = ret;
|
||||
using arguments_types_tuple = std::tuple<arguments ...>;
|
||||
// template <size_t index>
|
||||
// using argument_type = type_at<index, arguments ...>;
|
||||
using function_type = std::function<ret(arguments ...)>;
|
||||
template <typename function_t>
|
||||
static constexpr function_type cast(function_t f) {
|
||||
return static_cast<function_type>(f);
|
||||
}
|
||||
};
|
||||
}
|
||||
template <typename T>
|
||||
struct function_traits : public function_traits<decltype(&T::operator())> {};
|
||||
template <typename class_type, typename ret, typename ... arguments>
|
||||
struct function_traits<ret(class_type::*)(arguments ...) const>
|
||||
: detail::function_traits<ret, arguments ...> {};
|
||||
|
||||
template <typename class_type, typename ret, typename ... arguments>
|
||||
struct function_traits<ret(class_type::*)(arguments ...)>
|
||||
: detail::function_traits<ret, arguments ...> {};
|
||||
|
||||
template <typename ret, typename ... arguments>
|
||||
struct function_traits<ret(*)(arguments ...)>
|
||||
: detail::function_traits<ret, arguments ...> {};
|
||||
|
||||
template <typename ret, typename ... arguments>
|
||||
struct function_traits<ret(*&)(arguments ...)>
|
||||
: detail::function_traits<ret, arguments ...> {};
|
||||
|
||||
template <typename ret, typename ... arguments>
|
||||
struct function_traits<ret(arguments ...)>
|
||||
: detail::function_traits<ret, arguments ...> {};
|
||||
|
||||
template <typename ret, typename ... arguments>
|
||||
struct function_traits<std::function<ret(arguments ...)>>
|
||||
: detail::function_traits<ret, arguments ...> {};
|
||||
|
||||
} // namespace arx
|
||||
|
||||
#include "ArtnetCommon.h"
|
||||
using Artnet = arx::artnet::Manager;
|
||||
using ArtnetSender = arx::artnet::Sender;
|
||||
using ArtnetReceiver = arx::artnet::Receiver;
|
||||
|
||||
#endif // ARDUINO_ARTNET_H
|
||||
@@ -1,540 +0,0 @@
|
||||
|
||||
#pragma once
|
||||
#ifndef ARTNET_COMMON_H
|
||||
#define ARTNET_COMMON_H
|
||||
|
||||
#include "udpSockets.hpp"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <Task.hpp>
|
||||
|
||||
namespace arx {
|
||||
namespace artnet {
|
||||
// Packet Summary : https://art-net.org.uk/structure/packet-summary-2/
|
||||
// Packet Definition : https://art-net.org.uk/structure/streaming-packets/artdmx-packet-definition/
|
||||
|
||||
struct Target { // DMX Universe Port-Address
|
||||
uint32_t universe : 4;
|
||||
uint32_t subnet : 4;
|
||||
uint32_t net : 7;
|
||||
};
|
||||
|
||||
enum class OpCode : uint16_t {
|
||||
// Device Discovery
|
||||
Poll = 0x2000,
|
||||
PollReply = 0x2100,
|
||||
// Device Configuration
|
||||
Address = 0x6000,
|
||||
Input = 0x7000,
|
||||
IpProg = 0xF800,
|
||||
IpProgReply = 0xF900,
|
||||
Command = 0x2400,
|
||||
// Streaming Control
|
||||
Dmx = 0x5000,
|
||||
Nzs = 0x5100,
|
||||
Sync = 0x5200,
|
||||
// RDM
|
||||
TodRequest = 0x8000,
|
||||
TodData = 0x8100,
|
||||
TodControl = 0x8200,
|
||||
Rdm = 0x8300,
|
||||
RdmSub = 0x8400,
|
||||
// Time-Keeping
|
||||
TimeCode = 0x9700,
|
||||
TimeSync = 0x9800,
|
||||
// Triggering
|
||||
Trigger = 0x9900,
|
||||
// Diagnostics
|
||||
DiagData = 0x2300,
|
||||
// File Transfer
|
||||
FirmwareMaster = 0xF200,
|
||||
FirmwareReply = 0xF300,
|
||||
Directory = 0x9A00,
|
||||
DirectoryReply = 0x9B00,
|
||||
FileTnMaster = 0xF400,
|
||||
FileFnMaster = 0xF500,
|
||||
FileFnReply = 0xF600,
|
||||
// N/A
|
||||
NA = 0x0000,
|
||||
};
|
||||
|
||||
constexpr uint16_t OPC(OpCode op) {
|
||||
return static_cast<uint16_t>(op);
|
||||
}
|
||||
|
||||
enum class Index : uint16_t {
|
||||
ID = 0,
|
||||
OP_CODE_L = 8,
|
||||
OP_CODE_H = 9,
|
||||
PROTOCOL_VER_H = 10,
|
||||
PROTOCOL_VER_L = 11,
|
||||
SEQUENCE = 12,
|
||||
PHYSICAL = 13,
|
||||
SUBUNI = 14,
|
||||
NET = 15,
|
||||
LENGTH_H = 16,
|
||||
LENGTH_L = 17,
|
||||
DATA = 18
|
||||
};
|
||||
|
||||
constexpr uint16_t IDX(Index i) {
|
||||
return static_cast<uint16_t>(i);
|
||||
}
|
||||
|
||||
constexpr uint16_t DEFAULT_PORT{0x1936}; // 6454
|
||||
constexpr uint16_t HEADER_SIZE{18};
|
||||
constexpr uint16_t PACKET_SIZE{530};
|
||||
constexpr uint16_t PROTOCOL_VER{0x0014};
|
||||
constexpr uint8_t ID_LENGTH{8};
|
||||
constexpr char ID[ID_LENGTH]{"Art-Net"};
|
||||
constexpr int DEFAULT_FPS_IN_TICKS {40 / portTICK_PERIOD_MS};
|
||||
|
||||
static constexpr uint8_t NUM_PIXELS_PER_UNIV{170};
|
||||
|
||||
using CallbackAllType =
|
||||
std::function<void(const uint32_t universe, const uint8_t *data, const uint16_t size)>;
|
||||
using CallbackType = std::function<void(const uint8_t *data, const uint16_t size)>;
|
||||
|
||||
template <uint16_t SIZE> using Array = std::array<uint8_t, SIZE>;
|
||||
using IntervalMap = std::map<uint32_t, uint32_t>;
|
||||
using CallbackMap = std::map<uint32_t, CallbackType>;
|
||||
using namespace std;
|
||||
|
||||
union ArtPollReply {
|
||||
struct {
|
||||
uint8_t id[8];
|
||||
uint8_t op_code_l;
|
||||
uint8_t op_code_h;
|
||||
uint8_t ip[4];
|
||||
uint8_t port_l;
|
||||
uint8_t port_h;
|
||||
uint8_t ver_h;
|
||||
uint8_t ver_l;
|
||||
uint8_t net_sw;
|
||||
uint8_t sub_sw;
|
||||
uint8_t oem_h;
|
||||
uint8_t oem_l;
|
||||
uint8_t ubea_ver;
|
||||
uint8_t status_1;
|
||||
uint8_t esta_man_l;
|
||||
uint8_t esta_man_h;
|
||||
uint8_t short_name[18];
|
||||
uint8_t long_name[64];
|
||||
uint8_t node_report[64];
|
||||
uint8_t num_ports_h;
|
||||
uint8_t num_ports_l;
|
||||
uint8_t port_types[4];
|
||||
uint8_t good_input[4];
|
||||
uint8_t good_output[4];
|
||||
uint8_t sw_in[4];
|
||||
uint8_t sw_out[4];
|
||||
uint8_t sw_video;
|
||||
uint8_t sw_macro;
|
||||
uint8_t sw_remote;
|
||||
uint8_t spare[3];
|
||||
uint8_t style;
|
||||
uint8_t mac[6];
|
||||
uint8_t bindIp[4];
|
||||
uint8_t bindIndex;
|
||||
uint8_t status_2;
|
||||
uint8_t filler[26];
|
||||
};
|
||||
std::array<uint8_t, 239> b;
|
||||
};
|
||||
|
||||
class Sender {
|
||||
Array<PACKET_SIZE> packet;
|
||||
uint8_t target_net {0};
|
||||
uint8_t target_subnet {0};
|
||||
uint8_t target_universe {0};
|
||||
uint8_t seq {0};
|
||||
uint8_t phy {0};
|
||||
|
||||
IntervalMap intervals;
|
||||
UdpSocket& stream;
|
||||
|
||||
public:
|
||||
Sender(UdpSocket & socket) : stream(socket) {
|
||||
for (size_t i = 0; i < ID_LENGTH; i++) packet[IDX(Index::ID) + i] =
|
||||
static_cast<uint8_t>(ID[i]); packet[IDX(Index::OP_CODE_H)] = (OPC(OpCode::Dmx) >> 8) &
|
||||
0x00FF; packet[IDX(Index::OP_CODE_L)] = (OPC(OpCode::Dmx) >> 0) & 0x00FF;
|
||||
packet[IDX(Index::PROTOCOL_VER_H)] = (PROTOCOL_VER >> 8) & 0x00FF;
|
||||
packet[IDX(Index::PROTOCOL_VER_L)] = (PROTOCOL_VER >> 0) & 0x00FF;
|
||||
}
|
||||
virtual ~Sender() {}
|
||||
|
||||
// streaming packet
|
||||
void streamingData(const uint8_t* const data, const uint16_t size) {
|
||||
memcpy((&packet[IDX(Index::DATA)]), data, size);
|
||||
}
|
||||
void streamingData(const uint16_t ch, const uint8_t data) {
|
||||
packet[IDX(Index::DATA) + ch] = data;
|
||||
}
|
||||
|
||||
void streaming(const std::string& ip, const uint32_t universe) {
|
||||
|
||||
uint32_t now = fos::Timing::getTickCount();
|
||||
if (intervals.find(universe) == intervals.end()) {
|
||||
intervals.insert(make_pair(universe, now));
|
||||
}
|
||||
if (now >= intervals[universe] + DEFAULT_FPS_IN_TICKS) {
|
||||
setUniverse(universe);
|
||||
sendPacket(ip);
|
||||
intervals[universe] = now;
|
||||
}
|
||||
}
|
||||
void streaming(const std::string& ip, const uint8_t net, const uint8_t subnet, const uint8_t
|
||||
universe) {
|
||||
uint32_t u = ((uint32_t)net << 8) | ((uint32_t)subnet << 4) | (uint32_t)universe;
|
||||
streaming(ip, u);
|
||||
}
|
||||
|
||||
// one-line sender
|
||||
void send(const std::string& ip, const uint32_t universe, const uint8_t* const data, const
|
||||
uint16_t size) {
|
||||
setUniverse(universe);
|
||||
streamingData(data, size);
|
||||
sendPacket(ip);
|
||||
}
|
||||
void send(
|
||||
const std::string& ip,
|
||||
const uint8_t net,
|
||||
const uint8_t subnet,
|
||||
const uint8_t universe,
|
||||
const uint8_t* const data,
|
||||
const uint16_t size) {
|
||||
setUniverse(net, subnet, universe);
|
||||
streamingData(data, size);
|
||||
sendPacket(ip);
|
||||
}
|
||||
|
||||
void physical(const uint8_t i) {
|
||||
phy = std::clamp<uint8_t>(i, 0, 3);
|
||||
}
|
||||
|
||||
uint8_t sequence() const {
|
||||
return seq;
|
||||
}
|
||||
|
||||
protected:
|
||||
void sendPacket(const std::string& ip) {
|
||||
packet[IDX(Index::SEQUENCE)] = seq++;
|
||||
packet[IDX(Index::PHYSICAL)] = phy;
|
||||
packet[IDX(Index::NET)] = target_net;
|
||||
packet[IDX(Index::SUBUNI)] = (target_subnet << 4) | target_universe;
|
||||
packet[IDX(Index::LENGTH_H)] = (512 >> 8) & 0xFF;
|
||||
packet[IDX(Index::LENGTH_L)] = (512 >> 0) & 0xFF;
|
||||
|
||||
if (ip == "")
|
||||
{
|
||||
stream.write(packet.data(), packet.size());
|
||||
} else {
|
||||
auto ipAddr = IpAdr(ip, DEFAULT_PORT);
|
||||
stream.write(packet.data(), packet.size(), &ipAddr);
|
||||
}
|
||||
}
|
||||
|
||||
void setUniverse(const uint32_t universe) {
|
||||
target_net = (universe >> 8) & 0x7F;
|
||||
target_subnet = (universe >> 4) & 0x0F;
|
||||
target_universe = universe & 0x0F;
|
||||
}
|
||||
void setUniverse(const uint8_t net, const uint8_t subnet, const uint8_t universe) {
|
||||
target_net = net & 0x7F;
|
||||
target_subnet = subnet & 0x0F;
|
||||
target_universe = universe & 0x0F;
|
||||
}
|
||||
};
|
||||
|
||||
class Receiver {
|
||||
Array<PACKET_SIZE> packet;
|
||||
uint8_t net_switch; // net of universe
|
||||
uint8_t sub_switch; // subnet of universe
|
||||
std::string short_name{"Custom ArtNet"};
|
||||
std::string long_name{"Arduino ArtNet Adaption by gustice"};
|
||||
std::string node_report{""};
|
||||
CallbackMap callbacks;
|
||||
CallbackAllType callback_all;
|
||||
UdpSocket &stream;
|
||||
bool b_verbose{false};
|
||||
|
||||
public:
|
||||
Receiver(UdpSocket &socket, const uint8_t subscribe_net = 0, const uint8_t subscribe_subnet = 0)
|
||||
: stream(socket) {
|
||||
if (subscribe_net > 128) {
|
||||
ESP_LOGE("ARX", "Net must be < 128");
|
||||
} else {
|
||||
net_switch = subscribe_net;
|
||||
}
|
||||
if (subscribe_subnet > 16) {
|
||||
ESP_LOGE("ARX", "Subnet must be < 16");
|
||||
} else {
|
||||
sub_switch = subscribe_subnet;
|
||||
}
|
||||
}
|
||||
virtual ~Receiver() {}
|
||||
|
||||
OpCode parse() {
|
||||
auto data = stream.read();
|
||||
|
||||
if (data.size() <= 0)
|
||||
return OpCode::NA;
|
||||
uint8_t *d = &data[0];
|
||||
|
||||
OpCode op_code = OpCode::NA;
|
||||
if (checkID(d)) {
|
||||
switch (opcode(d)) {
|
||||
case OPC(OpCode::Dmx): {
|
||||
memcpy(packet.data(), d, data.size());
|
||||
if (callback_all)
|
||||
callback_all(universe15bit(), dataPtr(), data.size() - HEADER_SIZE);
|
||||
for (auto &c : callbacks) {
|
||||
if (universe15bit() == c.first)
|
||||
c.second(dataPtr(), data.size() - HEADER_SIZE);
|
||||
}
|
||||
op_code = OpCode::Dmx;
|
||||
break;
|
||||
}
|
||||
case OPC(OpCode::Poll): {
|
||||
pollReply();
|
||||
op_code = OpCode::Poll;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (b_verbose) {
|
||||
ESP_LOGW("ARX", "Unsupported OpCode: %d", opcode(d));
|
||||
}
|
||||
op_code = OpCode::NA;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return op_code;
|
||||
}
|
||||
|
||||
inline std::string id() const {
|
||||
std::string str;
|
||||
for (uint8_t i = 0; i < ID_LENGTH; ++i)
|
||||
str += packet[IDX(Index::ID) + i];
|
||||
return str;
|
||||
}
|
||||
inline uint16_t opcode() const {
|
||||
return (packet[IDX(Index::OP_CODE_H)] << 8) | packet[IDX(Index::OP_CODE_L)];
|
||||
}
|
||||
inline uint16_t opcode(const uint8_t *p) const {
|
||||
return (p[IDX(Index::OP_CODE_H)] << 8) | p[IDX(Index::OP_CODE_L)];
|
||||
}
|
||||
inline uint16_t version() const {
|
||||
return (packet[IDX(Index::PROTOCOL_VER_H)] << 8) | packet[IDX(Index::PROTOCOL_VER_L)];
|
||||
}
|
||||
inline uint8_t sequence() const {
|
||||
return packet[IDX(Index::SEQUENCE)];
|
||||
}
|
||||
inline uint8_t physical() const {
|
||||
return packet[IDX(Index::PHYSICAL)];
|
||||
}
|
||||
uint8_t net() const {
|
||||
return packet[IDX(Index::NET)] & 0x7F;
|
||||
}
|
||||
uint8_t subnet() const {
|
||||
return (packet[IDX(Index::SUBUNI)] >> 4) & 0x0F;
|
||||
}
|
||||
inline uint8_t universe() const {
|
||||
return packet[IDX(Index::SUBUNI)] & 0x0F;
|
||||
}
|
||||
inline uint16_t universe15bit() const {
|
||||
return (packet[IDX(Index::NET)] << 8) | packet[IDX(Index::SUBUNI)];
|
||||
}
|
||||
inline uint16_t length() const {
|
||||
return (packet[IDX(Index::LENGTH_H)] << 8) | packet[IDX(Index::LENGTH_L)];
|
||||
}
|
||||
inline uint16_t size() const {
|
||||
return length();
|
||||
}
|
||||
inline uint8_t *dataPtr() {
|
||||
return &(packet[HEADER_SIZE]);
|
||||
}
|
||||
inline uint8_t dataPtr(const uint16_t i) const {
|
||||
return packet[HEADER_SIZE + i];
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
inline auto subscribe(const uint8_t universe, Fn &&func)
|
||||
-> std::enable_if_t<arx::is_callable<Fn>::value> {
|
||||
if (callbacks.size() >= 4) {
|
||||
if (b_verbose) {
|
||||
ESP_LOGE("ARX", "too many callbacks");
|
||||
}
|
||||
} else {
|
||||
if (universe > 0xF) {
|
||||
if (b_verbose) {
|
||||
ESP_LOGE("ARX", "universe out of bounds");
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
uint32_t u =
|
||||
((uint32_t)net_switch << 8) | ((uint32_t)sub_switch << 4) | (uint32_t)universe;
|
||||
callbacks.insert(make_pair(u, arx::function_traits<Fn>::cast(func)));
|
||||
}
|
||||
}
|
||||
}
|
||||
template <typename Fn> inline auto subscribe(const uint8_t universe, Fn *func) {
|
||||
if (callbacks.size() >= 4) {
|
||||
if (b_verbose) {
|
||||
ESP_LOGE("ARX", "too many callbacks");
|
||||
}
|
||||
} else {
|
||||
if (universe > 0xF) {
|
||||
if (b_verbose) {
|
||||
ESP_LOGE("ARX", "universe out of bounds");
|
||||
}
|
||||
} else {
|
||||
uint32_t u =
|
||||
((uint32_t)net_switch << 8) | ((uint32_t)sub_switch << 4) | (uint32_t)universe;
|
||||
callbacks.insert(make_pair(u, arx::function_traits<Fn>::cast(func)));
|
||||
}
|
||||
}
|
||||
}
|
||||
template <typename Fn>
|
||||
inline auto subscribe15bit(const uint16_t universe, Fn &&func)
|
||||
-> std::enable_if_t<arx::is_callable<Fn>::value> {
|
||||
if (callbacks.size() >= 4) {
|
||||
if (b_verbose) {
|
||||
ESP_LOGE("ARX", "too many callbacks");
|
||||
}
|
||||
} else {
|
||||
callbacks.insert(make_pair(universe, arx::function_traits<Fn>::cast(func)));
|
||||
}
|
||||
}
|
||||
template <typename Fn> inline auto subscribe15bit(const uint16_t universe, Fn *func) {
|
||||
if (callbacks.size() >= 4) {
|
||||
if (b_verbose) {
|
||||
ESP_LOGE("ARX", "too many callbacks");
|
||||
}
|
||||
} else {
|
||||
callbacks.insert(make_pair(universe, arx::function_traits<Fn>::cast(func)));
|
||||
}
|
||||
}
|
||||
template <typename F>
|
||||
inline auto subscribe(F &&func) -> std::enable_if_t<arx::is_callable<F>::value> {
|
||||
callback_all = arx::function_traits<F>::cast(func);
|
||||
}
|
||||
template <typename F> inline auto subscribe(F *func) {
|
||||
callback_all = arx::function_traits<F>::cast(func);
|
||||
}
|
||||
|
||||
inline void unsubscribe(const uint8_t universe) {
|
||||
auto it = callbacks.find(universe);
|
||||
if (it != callbacks.end())
|
||||
callbacks.erase(it);
|
||||
}
|
||||
inline void unsubscribe() {
|
||||
callback_all = nullptr;
|
||||
}
|
||||
|
||||
inline void clear_subscribers() {
|
||||
unsubscribe();
|
||||
callbacks.clear();
|
||||
}
|
||||
|
||||
void nodereport(const std::string nr) {
|
||||
node_report = nr;
|
||||
}
|
||||
|
||||
void verbose(const bool b) {
|
||||
b_verbose = b;
|
||||
}
|
||||
|
||||
protected:
|
||||
private:
|
||||
bool checkID() const {
|
||||
const char *idptr = reinterpret_cast<const char *>(packet.data());
|
||||
return !strcmp(ID, idptr);
|
||||
}
|
||||
bool checkID(const uint8_t *p) const {
|
||||
const char *idptr = reinterpret_cast<const char *>(p);
|
||||
return !strcmp(ID, idptr);
|
||||
}
|
||||
|
||||
void pollReply() {
|
||||
ArtPollReply r;
|
||||
for (size_t i = 0; i < ID_LENGTH; i++)
|
||||
r.id[i] = static_cast<uint8_t>(ID[i]);
|
||||
r.op_code_l = ((uint16_t)OpCode::PollReply >> 0) & 0x00FF;
|
||||
r.op_code_h = ((uint16_t)OpCode::PollReply >> 8) & 0x00FF;
|
||||
IpInfo ipInfo = stream.getIpInfo();
|
||||
Ip my_ip = ipInfo.address;
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
r.ip[i] = my_ip[i];
|
||||
r.port_l = (DEFAULT_PORT >> 0) & 0xFF;
|
||||
r.port_h = (DEFAULT_PORT >> 8) & 0xFF;
|
||||
r.ver_h = (PROTOCOL_VER >> 8) & 0x00FF;
|
||||
r.ver_l = (PROTOCOL_VER >> 0) & 0x00FF;
|
||||
r.oem_h = 0; // https://github.com/tobiasebsen/ArtNode/blob/master/src/Art-NetOemCodes.h
|
||||
r.oem_l = 0xFF; // OemUnknown
|
||||
r.ubea_ver = 0; // UBEA not programmed
|
||||
r.status_1 = 0x00; // Unknown / Normal
|
||||
r.esta_man_l = 0; // No EATA manufacture code
|
||||
r.esta_man_h = 0; // No ESTA manufacture code
|
||||
memset(r.short_name, 0, 18);
|
||||
memset(r.long_name, 0, 64);
|
||||
memset(r.node_report, 0, 64);
|
||||
memcpy(r.short_name, short_name.c_str(), short_name.length());
|
||||
memcpy(r.long_name, long_name.c_str(), long_name.length());
|
||||
memcpy(r.node_report, node_report.c_str(), node_report.length());
|
||||
r.num_ports_h = 0; // Reserved
|
||||
r.num_ports_l = callbacks.size(); // This library implements only 4 port
|
||||
memset(r.port_types, 0, 4);
|
||||
memset(r.sw_in, 0, 4);
|
||||
memset(r.sw_out, 0, 4);
|
||||
memset(r.good_input, 0, 4);
|
||||
memset(r.good_output, 0, 4);
|
||||
r.net_sw = net_switch & 0x7F;
|
||||
r.sub_sw = sub_switch & 0x0F;
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto &pair : callbacks) {
|
||||
// r.port_types[i] = 0xC0; // I/O available by DMX512 // @todo Bug@ liveShowSoftware
|
||||
r.port_types[i] = 0x80; // Output available by DMX512
|
||||
r.good_input[i] = 0x80; // Data received without error
|
||||
r.good_output[i] = 0x80; // Data transmitted without error
|
||||
r.sw_in[i] = i;// dummy: input port is flexible
|
||||
r.sw_out[i] = pair.first & 0x0F;
|
||||
if (++i >= 4)
|
||||
break;
|
||||
}
|
||||
r.sw_video = 0; // Video display shows local data
|
||||
r.sw_macro = 0; // No support for macro key inputs
|
||||
r.sw_remote = 0; // No support for remote trigger inputs
|
||||
memset(r.spare, 0x00, 3);
|
||||
r.style = 0x00; // A DMX to / from Art-Net device
|
||||
std::copy(ipInfo.macAddress.asArray().begin(), ipInfo.macAddress.asArray().end(), std::end(r.mac));
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
r.bindIp[i] = my_ip[i];
|
||||
r.bindIndex = 0;
|
||||
r.status_2 = 0x0C; // features: 15bitAddr | DHCP
|
||||
memset(r.filler, 0x00, 26);
|
||||
Ip bc = ipInfo.getBradcastAddress();
|
||||
IpAdr broadCast(bc.asWord(), DEFAULT_PORT);
|
||||
stream.write(r.b.data(), r.b.size(), &broadCast); // on sending station
|
||||
}
|
||||
};
|
||||
|
||||
class Manager : public Sender, public Receiver {
|
||||
public:
|
||||
Manager(UdpSocket &socket) : Sender(socket), Receiver(socket) {
|
||||
|
||||
}
|
||||
void parse() {
|
||||
this->Receiver::parse();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace artnet
|
||||
} // namespace arx
|
||||
|
||||
#endif // ARTNET_COMMON_H
|
||||
@@ -1,5 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "."
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES sockets
|
||||
)
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Hideaki Tai
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,54 +0,0 @@
|
||||
#include <FastLED.h> // include FastLED *before* Artnet
|
||||
|
||||
// Please include ArtnetEther.h to use Artnet on the platform
|
||||
// which can use both WiFi and Ethernet
|
||||
#include <ArtnetEther.h>
|
||||
// this is also valid for other platforms which can use only Ethernet
|
||||
// #include <Artnet.h>
|
||||
|
||||
// Ethernet stuff
|
||||
const IPAddress ip(192, 168, 0, 201);
|
||||
uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
|
||||
|
||||
ArtnetReceiver artnet;
|
||||
uint8_t universe = 1; // 0 - 15
|
||||
|
||||
// FastLED
|
||||
#define NUM_LEDS 1
|
||||
CRGB leds[NUM_LEDS];
|
||||
const uint8_t PIN_LED_DATA = 3;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(2000);
|
||||
|
||||
FastLED.addLeds<NEOPIXEL, PIN_LED_DATA>(leds, NUM_LEDS);
|
||||
|
||||
Ethernet.begin(mac, ip);
|
||||
artnet.begin();
|
||||
// artnet.subscribe_net(0); // optionally you can change
|
||||
// artnet.subscribe_subnet(0); // optionally you can change
|
||||
|
||||
// if Artnet packet comes to this universe, forward them to fastled directly
|
||||
artnet.forward(universe, leds, NUM_LEDS);
|
||||
|
||||
// this can be achieved manually as follows
|
||||
// if Artnet packet comes to this universe, this function is called
|
||||
// artnet.subscribe(universe, [&](uint8_t* data, uint16_t size)
|
||||
// {
|
||||
// // set led
|
||||
// // artnet data size per packet is 512 max
|
||||
// // so there is max 170 pixel per packet (per universe)
|
||||
// for (size_t pixel = 0; pixel < NUM_LEDS; ++pixel) {
|
||||
// size_t idx = pixel * 3;
|
||||
// leds[pixel].r = data[idx + 0];
|
||||
// leds[pixel].g = data[idx + 1];
|
||||
// leds[pixel].b = data[idx + 2];
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
void loop() {
|
||||
artnet.parse(); // check if artnet packet has come and execute callback
|
||||
FastLED.show();
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Please include ArtnetEther.h to use Artnet on the platform
|
||||
// which can use both WiFi and Ethernet
|
||||
#include <ArtnetEther.h>
|
||||
// this is also valid for other platforms which can use only Ethernet
|
||||
// #include <Artnet.h>
|
||||
|
||||
// Ethernet stuff
|
||||
const IPAddress ip(192, 168, 0, 201);
|
||||
uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
|
||||
|
||||
ArtnetReceiver artnet;
|
||||
uint32_t universe1 = 1; // 0 - 15
|
||||
uint32_t universe2 = 2; // 0 - 15
|
||||
|
||||
void callback(const uint8_t* data, const uint16_t size) {
|
||||
// you can also use pre-defined callbacks
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
Ethernet.begin(mac, ip);
|
||||
artnet.begin();
|
||||
// artnet.subscribe_net(0); // optionally you can change
|
||||
// artnet.subscribe_subnet(0); // optionally you can change
|
||||
|
||||
// if Artnet packet comes to this universe, this function is called
|
||||
artnet.subscribe(universe1, [&](const uint8_t* data, const uint16_t size) {
|
||||
Serial.print("artnet data (universe : ");
|
||||
Serial.print(universe1);
|
||||
Serial.print(", size = ");
|
||||
Serial.print(size);
|
||||
Serial.print(") :");
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
Serial.print(data[i]);
|
||||
Serial.print(",");
|
||||
}
|
||||
Serial.println();
|
||||
});
|
||||
|
||||
// you can also use pre-defined callbacks
|
||||
artnet.subscribe(universe2, callback);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
artnet.parse(); // check if artnet packet has come and execute callback
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
#ifdef __AVR__
|
||||
#warning THIS EXAMPLE MAY USE TOO MUCH MEMORY FOR AVR. WE RECOMMEND TO USE SENDER OR RECEIVER ONLY.
|
||||
#endif
|
||||
|
||||
// Please include ArtnetEther.h to use Artnet on the platform
|
||||
// which can use both WiFi and Ethernet
|
||||
#include <ArtnetEther.h>
|
||||
// this is also valid for other platforms which can use only Ethernet
|
||||
// #include <Artnet.h>
|
||||
|
||||
// Ethernet stuff
|
||||
const IPAddress ip(192, 168, 0, 201);
|
||||
uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
|
||||
|
||||
Artnet artnet;
|
||||
const String target_ip = "192.168.0.200";
|
||||
uint8_t universe = 1; // 0 - 15
|
||||
|
||||
const uint16_t size = 512;
|
||||
uint8_t data[size];
|
||||
uint8_t value = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(2000);
|
||||
|
||||
Ethernet.begin(mac, ip);
|
||||
artnet.begin();
|
||||
// artnet.begin(net, subnet); // optionally you can change
|
||||
|
||||
Serial.println("set subscriber");
|
||||
|
||||
// if Artnet packet comes to this universe, this function is called
|
||||
artnet.subscribe(universe, [](const uint8_t* data, const uint16_t size) {
|
||||
Serial.print("artnet data (universe : ");
|
||||
Serial.print(universe);
|
||||
Serial.print(", size = ");
|
||||
Serial.print(size);
|
||||
Serial.print(") :");
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
Serial.print(data[i]);
|
||||
Serial.print(",");
|
||||
}
|
||||
Serial.println();
|
||||
});
|
||||
|
||||
// if Artnet packet comes, this function is called to every universe
|
||||
artnet.subscribe([&](const uint32_t univ, const uint8_t* data, const uint16_t size) {
|
||||
Serial.print("ArtNet data has come to universe: ");
|
||||
Serial.println(univ);
|
||||
});
|
||||
|
||||
Serial.println("start");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
artnet.parse(); // check if artnet packet has come and execute callback
|
||||
|
||||
value = (millis() / 4) % 256;
|
||||
memset(data, value, size);
|
||||
|
||||
artnet.streaming_data(data, size);
|
||||
artnet.streaming(target_ip, universe); // automatically send set data in 40fps
|
||||
// artnet.streaming(target_ip, net, subnet, univ); // or you can set net, subnet, and universe
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Please include ArtnetEther.h to use Artnet on the platform
|
||||
// which can use both WiFi and Ethernet
|
||||
#include <ArtnetEther.h>
|
||||
// this is also valid for other platforms which can use only Ethernet
|
||||
// #include <Artnet.h>
|
||||
|
||||
// Ethernet stuff
|
||||
const IPAddress ip(192, 168, 0, 201);
|
||||
uint8_t mac[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
|
||||
|
||||
ArtnetSender artnet;
|
||||
const String target_ip = "192.168.0.200";
|
||||
uint32_t universe = 1;
|
||||
|
||||
const uint16_t size = 512;
|
||||
uint8_t data[size];
|
||||
uint8_t value = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(2000);
|
||||
|
||||
Ethernet.begin(mac, ip);
|
||||
artnet.begin();
|
||||
// artnet.begin(net, subnet); // optionally you can change
|
||||
|
||||
Serial.println("start");
|
||||
Serial.println(UDP_TX_PACKET_MAX_SIZE);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
value = (millis() / 4) % 256;
|
||||
memset(data, value, size);
|
||||
|
||||
artnet.streaming_data(data, size);
|
||||
artnet.streaming(target_ip, universe); // automatically send set data in 40fps
|
||||
// artnet.streaming(target_ip, net, subnet, univ); // or you can set net, subnet, and universe
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# ArtNet
|
||||
|
||||
Implementation is taken form `hideakitai` @ [github](https://github.com/hideakitai/ArtNet)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES freeRtosApi
|
||||
project_bases
|
||||
esp_common
|
||||
esp_driver_gpio
|
||||
esp_driver_ledc
|
||||
esp_driver_gptimer
|
||||
)
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* @file gpioPort.hpp
|
||||
* @author Gustice
|
||||
* @brief GPIO-Port classes
|
||||
* @details C++ Wrapper for GPIO port
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "portBase.hpp"
|
||||
|
||||
/**
|
||||
* @brief Digital-Port BaseClass
|
||||
*/
|
||||
class GpioPort : public PortBase {
|
||||
public:
|
||||
enum PullResistor {
|
||||
Floating = 0,
|
||||
PullUp,
|
||||
PullDown,
|
||||
};
|
||||
|
||||
enum Logic {
|
||||
Normal = 0, // Normal logic
|
||||
Inverted, // Inverted logic -> Set low on active state
|
||||
};
|
||||
|
||||
enum Interrupt {
|
||||
PositiveEdge = gpio_int_type_t::GPIO_INTR_POSEDGE,
|
||||
NegativeEdge = gpio_int_type_t::GPIO_INTR_NEGEDGE,
|
||||
AnyEdge = gpio_int_type_t::GPIO_INTR_ANYEDGE
|
||||
};
|
||||
|
||||
GpioPort(gpio_num_t port);
|
||||
~GpioPort();
|
||||
|
||||
uint64_t GetReserved(void) { return _reservedPorts; }
|
||||
|
||||
protected:
|
||||
const gpio_num_t _port;
|
||||
|
||||
private:
|
||||
static uint64_t _reservedPorts;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Input-Port
|
||||
* @details Provides abstract reading functionality
|
||||
*/
|
||||
class InputPort : public GpioPort {
|
||||
public:
|
||||
InputPort(gpio_num_t port, GpioPort::PullResistor resistor = PullResistor::Floating);
|
||||
void setLogic(GpioPort::Logic logic) {
|
||||
_logic = logic;
|
||||
}
|
||||
~InputPort() = default;
|
||||
|
||||
bool read(void);
|
||||
|
||||
private:
|
||||
GpioPort::Logic _logic = GpioPort::Logic::Normal;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Output-Port
|
||||
* @details Provides abstract setting functionality
|
||||
*/
|
||||
class OutputPort : public GpioPort {
|
||||
public:
|
||||
OutputPort(gpio_num_t port, GpioPort::Logic mode = Logic::Normal);
|
||||
~OutputPort(){};
|
||||
|
||||
void write(int active);
|
||||
|
||||
private:
|
||||
const GpioPort::Logic _mode = Normal;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Interrupt-Port
|
||||
*/
|
||||
class TriggeredInput : public InputPort {
|
||||
public:
|
||||
TriggeredInput(gpio_num_t port,
|
||||
GpioPort::PullResistor resistor = GpioPort::PullResistor::Floating,
|
||||
TriggeredInput::Interrupt edge = TriggeredInput::Interrupt::AnyEdge);
|
||||
~TriggeredInput(){};
|
||||
|
||||
void RegisterIsr(gpio_isr_t pIsrHandle);
|
||||
|
||||
private:
|
||||
static bool _isrServiceInitiated;
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* @file gpioPort.cpp
|
||||
* @author Gustice
|
||||
* @brief C++ Wrapper for GPIO port
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "gpioPort.hpp"
|
||||
#include "esp_log.h"
|
||||
|
||||
///
|
||||
/// Gpio - Implementation
|
||||
///
|
||||
uint64_t GpioPort::_reservedPorts = 0;
|
||||
|
||||
GpioPort::GpioPort(gpio_num_t port) : _port(port) {
|
||||
uint64_t p = 1ULL << port;
|
||||
if ((_reservedPorts & p) != 0) {
|
||||
ESP_LOGW("HAL", "GpioPort Port %d is already in use!", port);
|
||||
}
|
||||
|
||||
_reservedPorts |= p;
|
||||
}
|
||||
|
||||
GpioPort::~GpioPort() {
|
||||
uint64_t p = 1ULL << _port;
|
||||
_reservedPorts &= ~p;
|
||||
}
|
||||
|
||||
///
|
||||
/// InputPort - Implementation
|
||||
///
|
||||
InputPort::InputPort(gpio_num_t port, GpioPort::PullResistor resistor) : GpioPort(port) {
|
||||
gpio_config_t config = {
|
||||
.pin_bit_mask = 1ULL << port,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = resistor == PullUp ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = resistor == PullDown ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(gpio_config(&config));
|
||||
}
|
||||
|
||||
bool InputPort::read(void) {
|
||||
auto value = gpio_get_level(_port);
|
||||
return ((value ^ _logic) & 0x01) == 1;
|
||||
}
|
||||
|
||||
///
|
||||
/// TriggeredInput - Implementation
|
||||
///
|
||||
bool TriggeredInput::_isrServiceInitiated = false;
|
||||
|
||||
TriggeredInput::TriggeredInput(gpio_num_t port, GpioPort::PullResistor resistor,
|
||||
TriggeredInput::Interrupt edge)
|
||||
: InputPort(port, resistor) {
|
||||
assert(gpio_set_intr_type(_port, (gpio_int_type_t)edge) == ESP_OK);
|
||||
|
||||
if (!_isrServiceInitiated) {
|
||||
_isrServiceInitiated = true;
|
||||
esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL2);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI("HAL", "ISR-service installed successfully for gpio-port");
|
||||
} else {
|
||||
ESP_LOGE("HAL", "ISR-service failed to install. Code '%s'", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TriggeredInput::RegisterIsr(gpio_isr_t pIsrHandle) {
|
||||
ESP_LOGI("HAL", "Hooking ISR-Function to gpio-port %d", _port);
|
||||
gpio_isr_handler_add(_port, pIsrHandle, nullptr);
|
||||
}
|
||||
|
||||
///
|
||||
/// OutputPort - Implementation
|
||||
///
|
||||
OutputPort::OutputPort(gpio_num_t port, GpioPort::Logic mode) : GpioPort(port), _mode(mode) {
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = 1ULL << port,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io_conf);
|
||||
}
|
||||
|
||||
void OutputPort::write(int active) {
|
||||
if (_mode == GpioPort::Normal) {
|
||||
gpio_set_level(_port, active & 1);
|
||||
} else {
|
||||
active ^= 1;
|
||||
gpio_set_level(_port, active & 1);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
)
|
||||
@@ -1,119 +0,0 @@
|
||||
/**
|
||||
* @file fullDemo.cpp
|
||||
* @author Gustice
|
||||
* @brief Coherent demonstration of FreeRTOS-Abstraction in small environment
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "sdkconfig.h"
|
||||
#include <stdio.h>
|
||||
|
||||
constexpr int StackSize = 1024;
|
||||
constexpr int LED = 2;
|
||||
|
||||
#define TerminalPrint(format, ...) ESP_LOGI("main", format, __VA_ARGS__)
|
||||
|
||||
#include "Mutex.hpp"
|
||||
#include "Queue.hpp"
|
||||
#include "Semaphore.hpp"
|
||||
#include "Task.hpp"
|
||||
#include <string>
|
||||
|
||||
using namespace fos;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
constexpr int WAIT = 1000;
|
||||
|
||||
/// @brief Arbitrary definition for message type (to be used as queue template parameter)
|
||||
struct TxMsg {
|
||||
TxMsg(uint32_t c, std::string m) : cnt(c), message(m){};
|
||||
uint32_t cnt;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
/// @brief Global semaphore for blink tasks
|
||||
Semaphore blinkSem;
|
||||
|
||||
void setupLedPin();
|
||||
void toggleLedPin();
|
||||
|
||||
/// @brief Parameterless task entry for generation of blink signals
|
||||
/// @note uses global semaphore
|
||||
void TaskBlinkProvider() {
|
||||
TerminalPrint("starting blink provider task");
|
||||
while (true) {
|
||||
Task::delayMs(WAIT); // wait
|
||||
blinkSem.give(); // then signal a blink event
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Parameterless task entry for execution of blink signals
|
||||
/// @note uses global semaphore
|
||||
void TaskBlink() {
|
||||
TerminalPrint("starting blink execution task");
|
||||
setupLedPin();
|
||||
while (true) {
|
||||
blinkSem.take(); // wait for blink event signal
|
||||
toggleLedPin(); // execute blink event
|
||||
Task::delayMs(100); // wait
|
||||
toggleLedPin(); // execute blink event
|
||||
TerminalPrint("Blink tick");
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Parametrized task entry for message provider (generates message object)
|
||||
/// @param queue reference to appropriate queue
|
||||
void MessageProvider(Queue<TxMsg> &queue) {
|
||||
uint32_t cnt{};
|
||||
while (true) {
|
||||
auto e = std::make_unique<TxMsg>(cnt++, "tick");
|
||||
queue.enqueue(std::move(e));
|
||||
Task::delayMs(WAIT);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Parametrized task entry for message consumer (sends message over UART)
|
||||
/// @param queue reference to appropriate queue
|
||||
void MessageConsumer(Queue<TxMsg> &queue) {
|
||||
while (true) {
|
||||
auto msg = queue.dequeue(); // Wait with infinit timeout, guaranteed to succeed
|
||||
TerminalPrint("Message: %s %lu", msg->message.c_str(), msg->cnt);
|
||||
}
|
||||
}
|
||||
|
||||
void setupTasks() {
|
||||
/// @note: All definitions as static instances to not be destructed after loosing scope
|
||||
static Queue<TxMsg> txQueue(8);
|
||||
static Task blinkerTask(TaskBlink, "BlinkExec", StackSize);
|
||||
static Task blinkeProviderTask(TaskBlinkProvider, "BlinkFlags", StackSize);
|
||||
|
||||
static TaskT<Queue<TxMsg> &> tickerTask(MessageProvider, txQueue, "Tick", StackSize);
|
||||
static TaskT<Queue<TxMsg> &> senderTask(MessageConsumer, txQueue, "Sender", StackSize);
|
||||
}
|
||||
|
||||
|
||||
extern "C" { // This switch allows the ROS C-implementation to find this main
|
||||
void app_main(void);
|
||||
}
|
||||
|
||||
void app_main() {
|
||||
void setupTasks();
|
||||
|
||||
while (true) {
|
||||
ESP_LOGI("main", "healt tick");
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void setupLedPin() {
|
||||
// no Buildin-led on XIAO C3
|
||||
}
|
||||
void toggleLedPin() {
|
||||
// no Buildin-led on XIAO C3
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* @file mutex.hpp
|
||||
* @author Gustice
|
||||
* @brief Mutex Wrapper
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
namespace fos {
|
||||
|
||||
/**
|
||||
* @brief Mutex
|
||||
* @details FreeRTOS implementation uses already priority inheritance
|
||||
* and are recursively callable
|
||||
*/
|
||||
class Mutex {
|
||||
private:
|
||||
QueueHandle_t _handle;
|
||||
|
||||
public:
|
||||
/// @brief Tick type (redefinition for convenience)
|
||||
using Tick = TickType_t;
|
||||
/// @brief Definition for endless waittime
|
||||
static constexpr Tick MaxDelay = portMAX_DELAY;
|
||||
|
||||
/// @brief Constructor
|
||||
Mutex() {
|
||||
_handle = xSemaphoreCreateMutex();
|
||||
assert(_handle != 0 && "Semaphore create must finish successfully");
|
||||
}
|
||||
|
||||
~Mutex() {
|
||||
vSemaphoreDelete(_handle);
|
||||
};
|
||||
|
||||
/// @brief Relase mutex
|
||||
void release() {
|
||||
auto ret = xSemaphoreGive(_handle);
|
||||
assert(ret == pdPASS && "Underlying semaphore queue must be initialized correcty");
|
||||
}
|
||||
|
||||
/// @brief Take mutex
|
||||
/// @param timeout Timeout to wait for signal (default = wait forever)
|
||||
/// @return true if successfully claimed (not timeout)
|
||||
bool claim(Tick timeout = MaxDelay) {
|
||||
auto ret = xSemaphoreTake(_handle, timeout);
|
||||
return ret == pdPASS;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mutex Guard. Self releasing mutex for atomic operation
|
||||
*/
|
||||
class MutexGuard {
|
||||
private:
|
||||
Mutex &_mutex;
|
||||
bool _claimed;
|
||||
|
||||
public:
|
||||
/// @brief Constructor
|
||||
/// @param mtx Mutex to claim
|
||||
/// @param timeout Timeout to wait for signal (default = wait forever)
|
||||
MutexGuard(Mutex &mtx, Mutex::Tick timeout = Mutex::MaxDelay) : _mutex(mtx) {
|
||||
_claimed = _mutex.claim(timeout);
|
||||
};
|
||||
|
||||
~MutexGuard() {
|
||||
if (!_claimed) {
|
||||
return;
|
||||
}
|
||||
|
||||
_mutex.release();
|
||||
}
|
||||
|
||||
/// @brief Check if mutex was claimed successfully
|
||||
/// @return true if claimed
|
||||
bool isActive() {
|
||||
return _claimed;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fos
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* @file queue.hpp
|
||||
* @author Gustice
|
||||
* @brief Queue Wrapper
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace fos {
|
||||
|
||||
/**
|
||||
* @brief Message Queue
|
||||
*
|
||||
* @tparam T Type for message payload
|
||||
*/
|
||||
template <typename T>
|
||||
class Queue {
|
||||
private:
|
||||
bool _isActive;
|
||||
QueueHandle_t xQueue;
|
||||
|
||||
public:
|
||||
/// @brief Tick type (redefinition for convenience)
|
||||
using Tick = TickType_t;
|
||||
/// @brief Definition for endless waittime
|
||||
static constexpr Tick MaxDelay = portMAX_DELAY;
|
||||
|
||||
/// @brief Constructor
|
||||
/// @param size number of slots in queue
|
||||
Queue(size_t size) {
|
||||
xQueue = xQueueCreate(size, sizeof(T *));
|
||||
assert(xQueue != 0 && "Queue create must finish successfully");
|
||||
_isActive = true;
|
||||
}
|
||||
|
||||
~Queue() {
|
||||
vQueueDelete(xQueue);
|
||||
}
|
||||
|
||||
/// @brief Push new message in queue
|
||||
/// @param message payload
|
||||
/// @return nullptr if successful else give back pointer to queued element if pushing failed
|
||||
std::unique_ptr<T> enqueue(std::unique_ptr<T> message) {
|
||||
if (uxQueueSpacesAvailable(xQueue) == 0) {
|
||||
return message;
|
||||
}
|
||||
T *o = message.release();
|
||||
auto ret = xQueueSend(xQueue, &o, (TickType_t)0);
|
||||
assert(ret == pdPASS && "Underlying semaphore queue must be initialized correcty");
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/// @brief Pop message from queue
|
||||
/// @param timeout timeout to wait for message (default: wait for ever)
|
||||
/// @return pointer to dequeued element
|
||||
std::unique_ptr<T> dequeue(Tick timeout = MaxDelay) {
|
||||
T *pMsg = nullptr;
|
||||
if (!xQueueReceive(xQueue, &pMsg, timeout)) {
|
||||
return {nullptr};
|
||||
}
|
||||
return std::unique_ptr<T>(pMsg);
|
||||
}
|
||||
|
||||
/// @brief Pop latest element from queue and ignore all older elements
|
||||
/// @param timeout timeout to wait for message (default: wait for ever)
|
||||
/// @return pointer to dequeued element
|
||||
std::unique_ptr<T> dequeueLatest(Tick timeout = MaxDelay) {
|
||||
T *pMsg = nullptr;
|
||||
if (!xQueueReceive(xQueue, &pMsg, timeout)) {
|
||||
return {nullptr};
|
||||
}
|
||||
|
||||
while (true == xQueueReceive(xQueue, &pMsg, (TickType_t)0)) {
|
||||
}
|
||||
return std::unique_ptr<T>(pMsg);
|
||||
}
|
||||
|
||||
/// @brief terminate message queue
|
||||
void signalClose() {
|
||||
_isActive = false;
|
||||
}
|
||||
|
||||
/// @brief Check if message queue is active
|
||||
/// @return true if active
|
||||
bool isActive() {
|
||||
return _isActive;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fos
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* @file semaphore.hpp
|
||||
* @author Gustice
|
||||
* @brief Semaphore Wrapper
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
namespace fos {
|
||||
|
||||
/**
|
||||
* @brief Semaphore
|
||||
*/
|
||||
class Semaphore {
|
||||
private:
|
||||
enum Type {
|
||||
Binary, ///< Binary semaphore, can only be true or false, no matter how often given
|
||||
Counting ///< Counting semaphore, keeps track of number of give and takes
|
||||
};
|
||||
|
||||
const Type SemType;
|
||||
QueueHandle_t _handle;
|
||||
|
||||
public:
|
||||
/// @brief Tick type (redefinition for convenience)
|
||||
using Tick = TickType_t;
|
||||
|
||||
/// @brief Definition for endless waittime
|
||||
static constexpr Tick MaxDelay = portMAX_DELAY;
|
||||
|
||||
/// @brief Constructor for binary semaphore
|
||||
Semaphore() : SemType(Semaphore::Binary) {
|
||||
_handle = xSemaphoreCreateBinary();
|
||||
assert(_handle != 0 && "Semaphore create must finish successfully");
|
||||
}
|
||||
|
||||
/// @brief Constructor for conting semaphore
|
||||
/// @param maxCount Counting limit
|
||||
/// @param init Initial count
|
||||
Semaphore(uint32_t maxCount, uint32_t init = 0) : SemType(Semaphore::Counting) {
|
||||
_handle = xSemaphoreCreateCounting(maxCount, init);
|
||||
assert(_handle != 0 && "Semaphore create must finish successfully");
|
||||
}
|
||||
|
||||
~Semaphore() {
|
||||
vSemaphoreDelete(_handle);
|
||||
};
|
||||
|
||||
/// @brief Give signal
|
||||
/// @note Binary semaphore can only be effectively given once
|
||||
void give() {
|
||||
// checking for error would lead to false positives in case of multiple gives ...
|
||||
xSemaphoreGive(_handle);
|
||||
}
|
||||
|
||||
/// @brief Consume signal
|
||||
/// @param timeout Timeout to wait for signal (default = wait forever)
|
||||
/// @return true if successfully consumed (not timeout)
|
||||
bool take(Tick timeout = MaxDelay) {
|
||||
auto ret = xSemaphoreTake(_handle, timeout);
|
||||
return ret == pdPASS;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fos
|
||||
@@ -1,184 +0,0 @@
|
||||
/**
|
||||
* @file task.hpp
|
||||
* @author Gustice
|
||||
* @brief Task Wrapper
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <cassert>
|
||||
#include <string_view>
|
||||
#include <Semaphore.hpp>
|
||||
#define DEFAULT_STACKSIZE 0x1000uL
|
||||
|
||||
namespace fos {
|
||||
/**
|
||||
* @brief Base class for tasks
|
||||
*/
|
||||
class TaskBase {
|
||||
protected:
|
||||
TaskHandle_t _handle;
|
||||
|
||||
static void disposeFrameConditionally() {
|
||||
if constexpr (INCLUDE_vTaskDelete != 0) {
|
||||
vTaskDelete(NULL);
|
||||
// NOTE: This only compiles if symbol INCLUDE_vTaskDelete is defined
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore _finished;
|
||||
|
||||
public:
|
||||
/// @brief Priority type (redefinition for convenience)
|
||||
using Priority = unsigned short;
|
||||
/// @brief Tick type (redefinition for convenience)
|
||||
using Tick = TickType_t;
|
||||
/// @brief Definition for endless waittime
|
||||
static constexpr Tick MaxDelay = portMAX_DELAY;
|
||||
|
||||
/// @brief Maximum delay
|
||||
static constexpr int MaximumDelay = portMAX_DELAY;
|
||||
|
||||
/// @brief Default Stack size
|
||||
static constexpr size_t DefaultStackSize = DEFAULT_STACKSIZE;
|
||||
|
||||
/// @brief Lowest priority for new tasks
|
||||
static constexpr Priority MIN_PRIORITY = tskIDLE_PRIORITY + 1;
|
||||
|
||||
/// @brief Highest priority for new tasks
|
||||
static constexpr Priority MAX_PRIORITY = configMAX_PRIORITIES;
|
||||
|
||||
/// @brief Kill a task
|
||||
/// @note It can lead to undefined behaviour when one task deletes another task on a
|
||||
/// multi-core system. Hence a task should clear itself.
|
||||
void kill() { // NOTE: this leads to undefined behavior if interferes with Destructor
|
||||
vTaskDelete(_handle);
|
||||
}
|
||||
|
||||
/// @brief Join threads
|
||||
/// @param timeout timeout to wait for join else kill
|
||||
/// @return joined successfully
|
||||
bool join(Tick timeout = MaxDelay) {
|
||||
auto success = _finished.take(timeout);
|
||||
if(not success) {
|
||||
kill();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/// @brief Get task state
|
||||
/// @return FreeRTOS Task state
|
||||
eTaskState getState() {
|
||||
return eTaskGetState(_handle);
|
||||
}
|
||||
|
||||
/// @brief Get task handle
|
||||
/// @return FreeRTOS Task handle
|
||||
TaskHandle_t getHandle() {
|
||||
return _handle;
|
||||
}
|
||||
|
||||
/// @brief Delay function
|
||||
/// @param msToDelay Milliseconds to delay
|
||||
/// @note The minimal resolution for the delay is tick based
|
||||
static void delayMs(unsigned int msToDelay) {
|
||||
// pdMS_TO_TICKS(_timeout)
|
||||
vTaskDelay(msToDelay / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
/// @brief Delay function
|
||||
/// @param ticksToDelay Ticks to delay
|
||||
static void delayTicks(unsigned int ticksToDelay) {
|
||||
vTaskDelay(ticksToDelay);
|
||||
}
|
||||
|
||||
virtual ~TaskBase() {
|
||||
kill();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Simple task without task parameter
|
||||
*/
|
||||
class Task : public TaskBase {
|
||||
private:
|
||||
/// @brief Signature for task-implementation-callback
|
||||
using TaskSignature = void (*)();
|
||||
|
||||
static void TaskFrame(void *_this) {
|
||||
auto &task = *reinterpret_cast<Task *>(_this);
|
||||
task._call();
|
||||
// Tasks shall not return, however if this happens dispose task
|
||||
disposeFrameConditionally();
|
||||
task._finished.give();
|
||||
}
|
||||
|
||||
TaskSignature _call;
|
||||
|
||||
public:
|
||||
/// @brief Constructor for task without passed argument
|
||||
/// @param call Function pointer to implementation
|
||||
/// @param name Name of task
|
||||
/// @param stack Stacksize if not default
|
||||
/// @param prio Priority if not default
|
||||
Task(TaskSignature call, const char *name, size_t stack = DefaultStackSize,
|
||||
Priority prio = MIN_PRIORITY)
|
||||
: _call{call} {
|
||||
auto ret = xTaskCreate(TaskFrame, name, stack, this, MIN_PRIORITY, &_handle);
|
||||
assert(pdPASS == ret && "Task create must finish successfully");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Task with task parameter
|
||||
* @tparam T Type of task parameter on call
|
||||
*/
|
||||
template <typename T>
|
||||
class TaskT : public TaskBase {
|
||||
private:
|
||||
/// @brief Signature for task-implementation-callback
|
||||
using TaskSignature = void (*)(T);
|
||||
|
||||
template <typename TS>
|
||||
struct Param {
|
||||
TaskSignature call;
|
||||
const TS &arg;
|
||||
};
|
||||
Param<T> _param;
|
||||
|
||||
static void TaskFrame(void *_this) {
|
||||
auto &task = *reinterpret_cast<TaskT *>(_this);
|
||||
auto ¶m = task._param;
|
||||
param.call(param.arg);
|
||||
// Tasks shall not return, however if this happens dispose task
|
||||
disposeFrameConditionally();
|
||||
task._finished.give();
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief Constructor for task with passed argument
|
||||
/// @param call Function pointer to implementation
|
||||
/// @param param Parameter ot pass task implementation
|
||||
/// @param name Name of task
|
||||
/// @param stack Stacksize if not default
|
||||
/// @param prio Priority if not default
|
||||
TaskT(TaskSignature call, const T ¶m, const char *name, size_t stack = DefaultStackSize,
|
||||
Priority prio = MIN_PRIORITY)
|
||||
: _param{call, param} {
|
||||
auto ret = xTaskCreate(TaskFrame, name, stack, this, MIN_PRIORITY, &_handle);
|
||||
assert(pdPASS == ret && "Task create must finish successfully");
|
||||
}
|
||||
};
|
||||
|
||||
class Timing {
|
||||
public:
|
||||
static uint32_t getTickCount() {
|
||||
return xTaskGetTickCount();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fos
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* @file freeRtosApi.cpp
|
||||
* @author Gustice
|
||||
* @brief FreeRtos wrapper
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
@@ -1,5 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "."
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES cmock freeRtosApi esp_timer
|
||||
)
|
||||
@@ -1,30 +0,0 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "sdkconfig.h"
|
||||
#include <stdio.h>
|
||||
constexpr int StackSize = 1024;
|
||||
|
||||
#include <unity.h>
|
||||
|
||||
class TimeTest {
|
||||
private:
|
||||
bool running{};
|
||||
TickType_t _begin;
|
||||
|
||||
public:
|
||||
TimeTest() {
|
||||
start();
|
||||
}
|
||||
|
||||
void start() {
|
||||
_begin = xTaskGetTickCount();
|
||||
running = true;
|
||||
}
|
||||
|
||||
|
||||
TickType_t getRunTime() {
|
||||
return xTaskGetTickCount() - _begin;
|
||||
}
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
#include "test.hpp"
|
||||
|
||||
#include <Mutex.hpp>
|
||||
#include <Semaphore.hpp>
|
||||
#include <Task.hpp>
|
||||
|
||||
using namespace fos;
|
||||
|
||||
constexpr int SHORT_DELAY = 10;
|
||||
|
||||
TEST_CASE("Test mutexes", "[freeRtos][mutex]") {
|
||||
Mutex eut;
|
||||
// first take fails
|
||||
TEST_ASSERT_TRUE(eut.claim(0));
|
||||
eut.release();
|
||||
|
||||
TEST_ASSERT_TRUE(eut.claim(0));
|
||||
TEST_ASSERT_FALSE(eut.claim(0));
|
||||
|
||||
eut.release();
|
||||
// eut.release(); // not a good idea
|
||||
TEST_ASSERT_TRUE(eut.claim(0));
|
||||
// second take after give fails
|
||||
TEST_ASSERT_FALSE(eut.claim(0));
|
||||
}
|
||||
|
||||
static Semaphore starter1;
|
||||
static void testClaimer(Mutex &m) {
|
||||
m.claim();
|
||||
starter1.give();
|
||||
vTaskDelay(SHORT_DELAY);
|
||||
m.release();
|
||||
vTaskDelay(SHORT_DELAY);
|
||||
m.claim();
|
||||
starter1.give();
|
||||
while (true) {
|
||||
vTaskDelay(SHORT_DELAY);
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE("Test mutex timing", "[freeRtos][mutex]") {
|
||||
static Mutex eut;
|
||||
|
||||
TaskT<Mutex &> claimer(testClaimer, eut, "signal1");
|
||||
starter1.take(100);
|
||||
{ // All ok
|
||||
TimeTest timer;
|
||||
TEST_ASSERT_TRUE(eut.claim(100));
|
||||
TEST_ASSERT_INT_WITHIN(1, SHORT_DELAY, timer.getRunTime());
|
||||
eut.release();
|
||||
}
|
||||
starter1.take(100);
|
||||
{ // Timeout
|
||||
TimeTest timer;
|
||||
TEST_ASSERT_FALSE(eut.claim(20));
|
||||
TEST_ASSERT_INT_WITHIN(1, 20, timer.getRunTime());
|
||||
}
|
||||
|
||||
{ // NoWeit timeout
|
||||
Mutex passiveSem;
|
||||
passiveSem.claim(0);
|
||||
TimeTest timer;
|
||||
TEST_ASSERT_FALSE(passiveSem.claim(0));
|
||||
TEST_ASSERT_INT_WITHIN(1, 0, timer.getRunTime());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test mutex guard", "[freeRtos][mutex]") {
|
||||
Mutex mtx;
|
||||
{
|
||||
MutexGuard eut(mtx, 100);
|
||||
TEST_ASSERT_TRUE(eut.isActive());
|
||||
// already taken
|
||||
TEST_ASSERT_FALSE(mtx.claim(0));
|
||||
}
|
||||
// now free to go
|
||||
TEST_ASSERT_TRUE(mtx.claim(0));
|
||||
{ // Timeout
|
||||
TimeTest timer;
|
||||
MutexGuard eut(mtx, SHORT_DELAY);
|
||||
TEST_ASSERT_FALSE(eut.isActive());
|
||||
TEST_ASSERT_INT_WITHIN(1, SHORT_DELAY, timer.getRunTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
#include "test.hpp"
|
||||
|
||||
#include <Queue.hpp>
|
||||
#include <Task.hpp>
|
||||
#include <string>
|
||||
|
||||
using namespace fos;
|
||||
|
||||
constexpr int SHORT_DELAY = 10;
|
||||
|
||||
struct MessageFrame {
|
||||
MessageFrame(int v, std::string msg) : value(v), message(msg) {}
|
||||
int value;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
TEST_CASE("Test enqueueing then dequeueing", "[freeRtos][queue]") {
|
||||
Queue<MessageFrame> eut(10);
|
||||
|
||||
auto e = std::make_unique<MessageFrame>(1, "one");
|
||||
eut.enqueue(std::move(e));
|
||||
|
||||
auto r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_EQUAL(1, r->value);
|
||||
TEST_ASSERT_TRUE(r->message == "one");
|
||||
}
|
||||
|
||||
TEST_CASE("Test enqueueing then dequeue multiple", "[freeRtos][queue]") {
|
||||
Queue<MessageFrame> eut(10);
|
||||
|
||||
auto e = std::make_unique<MessageFrame>(1, "one");
|
||||
eut.enqueue(std::move(e));
|
||||
e = std::make_unique<MessageFrame>(2, "two");
|
||||
eut.enqueue(std::move(e));
|
||||
e = std::make_unique<MessageFrame>(3, "three");
|
||||
eut.enqueue(std::move(e));
|
||||
|
||||
auto r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_EQUAL(1, r->value);
|
||||
TEST_ASSERT_TRUE(r->message == "one");
|
||||
r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_EQUAL(2, r->value);
|
||||
TEST_ASSERT_TRUE(r->message == "two");
|
||||
r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_EQUAL(3, r->value);
|
||||
TEST_ASSERT_TRUE(r->message == "three");
|
||||
}
|
||||
|
||||
TEST_CASE("Test enqueue cyclically", "[freeRtos][queue]") {
|
||||
constexpr int Size = 10;
|
||||
Queue<MessageFrame> eut(Size);
|
||||
for (size_t i = 0; i < Size * 2; i++) {
|
||||
auto e = std::make_unique<MessageFrame>(i, "Some message");
|
||||
eut.enqueue(std::move(e));
|
||||
|
||||
auto r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_EQUAL(i, r->value);
|
||||
}
|
||||
TEST_ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
TEST_CASE("Test enqueueing till overflow", "[freeRtos][queue]") {
|
||||
Queue<MessageFrame> eut(3);
|
||||
|
||||
auto e = std::make_unique<MessageFrame>(1, "one");
|
||||
auto r = eut.enqueue(std::move(e));
|
||||
TEST_ASSERT_EQUAL(nullptr, r.get());
|
||||
|
||||
e = std::make_unique<MessageFrame>(2, "two");
|
||||
r = eut.enqueue(std::move(e));
|
||||
TEST_ASSERT_EQUAL(nullptr, r.get());
|
||||
|
||||
e = std::make_unique<MessageFrame>(3, "three");
|
||||
r = eut.enqueue(std::move(e));
|
||||
TEST_ASSERT_EQUAL(nullptr, r.get());
|
||||
|
||||
e = std::make_unique<MessageFrame>(4, "four");
|
||||
auto pE = e.get();
|
||||
r = eut.enqueue(std::move(e));
|
||||
TEST_ASSERT_NOT_EQUAL(nullptr, r.get());
|
||||
// also return old element ...
|
||||
TEST_ASSERT_EQUAL(pE, r.get());
|
||||
}
|
||||
|
||||
TEST_CASE("Test enqueueing then dequeue multiple", "[freeRtos][queue]") {
|
||||
Queue<MessageFrame> eut(10);
|
||||
|
||||
auto e = std::make_unique<MessageFrame>(1, "one");
|
||||
eut.enqueue(std::move(e));
|
||||
e = std::make_unique<MessageFrame>(2, "two");
|
||||
eut.enqueue(std::move(e));
|
||||
|
||||
auto r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_EQUAL(1, r->value);
|
||||
r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_EQUAL(2, r->value);
|
||||
r = eut.dequeue(0); // no waiting
|
||||
TEST_ASSERT_EQUAL(nullptr, r.get());
|
||||
}
|
||||
|
||||
static void queue1Gen(Queue<MessageFrame> &q) {
|
||||
vTaskDelay(SHORT_DELAY);
|
||||
auto e = std::make_unique<MessageFrame>(0, "msg");
|
||||
q.enqueue(std::move(e));
|
||||
while (true) {
|
||||
vTaskDelay(SHORT_DELAY);
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE("Test timing of queue", "[freeRtos][queue]") {
|
||||
static Queue<MessageFrame> eut(8);
|
||||
TaskT<Queue<MessageFrame> &> signaler(queue1Gen, eut, "queue1Gen");
|
||||
{ // All ok
|
||||
TimeTest timer;
|
||||
auto r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_INT_WITHIN(1, SHORT_DELAY, timer.getRunTime());
|
||||
}
|
||||
|
||||
{ // Timeout
|
||||
TimeTest timer;
|
||||
auto r = eut.dequeue(20);
|
||||
TEST_ASSERT_NULL(r.get());
|
||||
TEST_ASSERT_INT_WITHIN(1, 20, timer.getRunTime());
|
||||
}
|
||||
}
|
||||
|
||||
static void senderTask(Queue<MessageFrame> &q) {
|
||||
size_t i = 0;
|
||||
while (q.isActive()) {
|
||||
auto e = std::make_unique<MessageFrame>(i++, "Some message");
|
||||
q.enqueue(std::move(e));
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test enqueueing series of message", "[freeRtos][queue]") {
|
||||
constexpr int Size = 10;
|
||||
Queue<MessageFrame> eut(Size);
|
||||
TaskT<Queue<MessageFrame> &> sender(senderTask, eut, "sender");
|
||||
for (size_t i = 0; i < Size * 2; i++) {
|
||||
auto r = eut.dequeue(100);
|
||||
TEST_ASSERT_NOT_NULL(r.get());
|
||||
TEST_ASSERT_EQUAL(i, r->value);
|
||||
}
|
||||
eut.signalClose();
|
||||
TEST_ASSERT_TRUE(true);
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
#include "test.hpp"
|
||||
|
||||
#include <Semaphore.hpp>
|
||||
#include <Task.hpp>
|
||||
|
||||
using namespace fos;
|
||||
|
||||
constexpr int SHORT_DELAY = 10;
|
||||
|
||||
TEST_CASE("Test binary semaphore", "[freeRtos][sem]") {
|
||||
Semaphore eut;
|
||||
// first take fails
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
eut.give();
|
||||
// first take after give fails
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
|
||||
eut.give();
|
||||
eut.give();
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
// second take after give fails
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
}
|
||||
|
||||
TEST_CASE("Test counting semaphore", "[freeRtos][sem]") {
|
||||
Semaphore eut(2, 0);
|
||||
// first take fails
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
eut.give();
|
||||
// first take after give fails
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
|
||||
eut.give();
|
||||
eut.give();
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
// third take after give fails
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
|
||||
eut.give();
|
||||
eut.give();
|
||||
eut.give();
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
// still third take after give fails
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
}
|
||||
|
||||
TEST_CASE("Test preset counting semaphore", "[freeRtos][sem]") {
|
||||
Semaphore eut(2, 2);
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
// third take fails
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
|
||||
eut.give();
|
||||
// first take after give fails
|
||||
TEST_ASSERT_TRUE(eut.take(0));
|
||||
TEST_ASSERT_FALSE(eut.take(0));
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
static void test1Signal(Semaphore &s) {
|
||||
vTaskDelay(SHORT_DELAY);
|
||||
s.give();
|
||||
while (true) {
|
||||
vTaskDelay(SHORT_DELAY);
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE("Test timing binary semaphore", "[freeRtos][sem]") {
|
||||
static Semaphore eut;
|
||||
|
||||
TaskT<Semaphore &> signaler(test1Signal, eut, "signal1");
|
||||
{ // All ok
|
||||
TimeTest timer;
|
||||
TEST_ASSERT_TRUE(eut.take(100));
|
||||
TEST_ASSERT_INT_WITHIN(1, SHORT_DELAY, timer.getRunTime());
|
||||
}
|
||||
|
||||
{ // Timeout
|
||||
TimeTest timer;
|
||||
TEST_ASSERT_FALSE(eut.take(20));
|
||||
TEST_ASSERT_INT_WITHIN(1, 20, timer.getRunTime());
|
||||
}
|
||||
|
||||
{ // NoWait timeout
|
||||
Semaphore passiveSem;
|
||||
TimeTest timer;
|
||||
TEST_ASSERT_FALSE(passiveSem.take(0));
|
||||
TEST_ASSERT_INT_WITHIN(1, 0, timer.getRunTime());
|
||||
}
|
||||
}
|
||||
|
||||
static int syncCount = 0;
|
||||
static void producerTask(Semaphore &s) {
|
||||
while (true) {
|
||||
s.give();
|
||||
vTaskDelay(SHORT_DELAY);
|
||||
};
|
||||
}
|
||||
static void consumerTask(Semaphore &s) {
|
||||
while (true) {
|
||||
s.take();
|
||||
syncCount++;
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE("Test if tasks signaling with semaphore", "[freeRtos][sem]") {
|
||||
static Semaphore eut;
|
||||
|
||||
TaskT<Semaphore &> producer(producerTask, eut, "producer");
|
||||
TaskT<Semaphore &> consumer(consumerTask, eut, "consumer");
|
||||
vTaskDelay(5 * SHORT_DELAY);
|
||||
TEST_ASSERT_INT_WITHIN(1, 5, syncCount);
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "test.hpp"
|
||||
|
||||
#include <Task.hpp>
|
||||
#include <array>
|
||||
|
||||
using namespace fos;
|
||||
|
||||
TEST_CASE("Test Delay", "[freeRtos][task]") {
|
||||
TickType_t start = xTaskGetTickCount();
|
||||
Task::delayMs(5 * portTICK_PERIOD_MS);
|
||||
TickType_t end = xTaskGetTickCount();
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(5, (end - start));
|
||||
TEST_ASSERT_LESS_OR_EQUAL(6, (end - start));
|
||||
}
|
||||
|
||||
static bool taskWasCalled = false;
|
||||
static void callableTask() {
|
||||
taskWasCalled = true;
|
||||
while (true) {
|
||||
vTaskDelay(100);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test creation and deletion", "[freeRtos][task]") {
|
||||
static Task task(callableTask, "task1");
|
||||
Task::delayMs(50);
|
||||
TEST_ASSERT_TRUE(taskWasCalled);
|
||||
task.kill();
|
||||
}
|
||||
|
||||
static void callableTask2() {
|
||||
taskWasCalled = true;
|
||||
while (true) {
|
||||
vTaskDelay(100);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test if task can be killed", "[freeRtos][task]") {
|
||||
static Task task(callableTask2, "task2");
|
||||
auto handle = task.getHandle();
|
||||
configASSERT(handle);
|
||||
TEST_ASSERT_TRUE(eTaskState::eReady == task.getState() ||
|
||||
eTaskState::eBlocked == task.getState());
|
||||
Task::delayMs(50);
|
||||
TEST_ASSERT_TRUE(taskWasCalled);
|
||||
task.kill();
|
||||
TEST_ASSERT_EQUAL(eTaskState::eDeleted, eTaskGetState(handle));
|
||||
}
|
||||
|
||||
static std::array<int, 2> values;
|
||||
|
||||
static void oneOfMultiple(int idx) {
|
||||
static std::array<int, 2> c{1, -1};
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
values.at(idx) += c.at(idx);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
vTaskDelay(100);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test if tasks do not interfere", "[freeRtos][task]") {
|
||||
// Both tasks are run by starting the same static function body
|
||||
int param1 = 0;
|
||||
TaskT<int> task1(oneOfMultiple, param1, "pTask1");
|
||||
int param2 = 1;
|
||||
TaskT<int> task2(oneOfMultiple, param2, "pTask2");
|
||||
|
||||
Task::delayMs(50);
|
||||
|
||||
TEST_ASSERT_EQUAL(10, values[0]);
|
||||
TEST_ASSERT_EQUAL(-10, values[1]);
|
||||
}
|
||||
|
||||
TEST_CASE("Test if tasks can be joined correctly", "[freeRtos][task]") {
|
||||
// Both tasks are run by starting the same static function body
|
||||
int param1 = 0;
|
||||
Task task1(oneOfMultiple, param1, "pTask1");
|
||||
int param2 = 1;
|
||||
TaskT<int> task2(oneOfMultiple, param2, "pTask2");
|
||||
|
||||
Task::delayMs(50);
|
||||
|
||||
TEST_ASSERT_EQUAL(10, values[0]);
|
||||
TEST_ASSERT_EQUAL(-10, values[1]);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
)
|
||||
@@ -1,202 +0,0 @@
|
||||
/**
|
||||
* @file color.hpp
|
||||
* @author Gustice
|
||||
* @brief
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
/// No Waveform definition
|
||||
extern uint8_t const *const noWave;
|
||||
|
||||
/**
|
||||
* @brief Color class
|
||||
*
|
||||
* @details Defines color data and common operations with color objects
|
||||
* \li Supports RGB with overlapping white channel (e.g. SK6812-LED)
|
||||
* \li Supports Operator overloading with '+' and '*' (Combined operation with MixWith)
|
||||
* \li Return formatted data stream that can be directly sent to an LED
|
||||
*/
|
||||
class Color {
|
||||
private:
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
uint8_t white;
|
||||
|
||||
public:
|
||||
struct Rgbw {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
uint8_t white;
|
||||
};
|
||||
|
||||
struct Rgb {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
};
|
||||
|
||||
/// @brief Construct a new Color object
|
||||
/// @details Default color will be Black \ref _ctBlack
|
||||
Color(void) {
|
||||
set(0, 0, 0, 0);
|
||||
};
|
||||
|
||||
/// @brief Copy-Construct a new Color object
|
||||
Color(const Color & c) {
|
||||
red = c.red;
|
||||
green = c.green;
|
||||
blue = c.blue;
|
||||
white = c.white;
|
||||
};
|
||||
|
||||
/// @brief Construct a new Color object according to rgb color-channel-values
|
||||
/// @details This constructor also takes an overlapping white channel
|
||||
/// @param r Red channel
|
||||
/// @param g Green channel
|
||||
/// @param b Blue channel
|
||||
/// @param w White channel (zero by default if not assigned)
|
||||
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { set(r, g, b, w); };
|
||||
|
||||
/// @brief Set the Color object according to rgb color-channel values
|
||||
/// @param r Red channel
|
||||
/// @param g Green channel
|
||||
/// @param b Blue channel
|
||||
/// @param w White channel (zero by default if not assigned)
|
||||
void set(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) {
|
||||
red = r;
|
||||
green = g;
|
||||
blue = b;
|
||||
white = w;
|
||||
}
|
||||
|
||||
/// @brief Returns Color as Color_t structure
|
||||
/// @return Color_t Color
|
||||
template <typename T>
|
||||
T getColor(void) const {
|
||||
if constexpr (std::is_same<T, Rgbw>::value) {
|
||||
return {red, green, blue, white};
|
||||
} else if constexpr (std::is_same<T, Rgb>::value) {
|
||||
return {red, green, blue};
|
||||
} else {
|
||||
throw "wrong type";
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t getRed() { return red; };
|
||||
uint8_t getGreen() { return green; };
|
||||
uint8_t getBlue() { return blue; };
|
||||
uint8_t getWhite() { return white; };
|
||||
|
||||
/// @brief Scaling and overlaying with given color.
|
||||
/// @param c2 Color 2
|
||||
/// @param k scaling factor
|
||||
/// @return Overlayed Color
|
||||
Color mixWith(const Color c2, uint8_t k = 0x7F) {
|
||||
auto t(c2);
|
||||
*this = (*this * (UINT8_MAX - k)) + (t * k);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Writes Color as formatted byte stream to location
|
||||
/// @param color
|
||||
template <typename T>
|
||||
void writeByteStreamTo(uint8_t raw[sizeof(T)]) const {
|
||||
if constexpr (std::is_same<T, Rgbw>::value) {
|
||||
raw[0] = blue;
|
||||
raw[1] = green;
|
||||
raw[2] = red;
|
||||
raw[3] = white;
|
||||
} else if constexpr (std::is_same<T, Rgb>::value) {
|
||||
raw[0] = blue;
|
||||
raw[1] = green;
|
||||
raw[2] = red;
|
||||
} else {
|
||||
throw "wrong type";
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Assign overload
|
||||
Color operator=(const Color &c2) {
|
||||
red = c2.red;
|
||||
green = c2.green;
|
||||
blue = c2.blue;
|
||||
white = c2.white;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Checks if both color values are equal
|
||||
bool operator==(Color const &c2) const {
|
||||
return ((blue == c2.blue) && (green == c2.green) &&
|
||||
(red == c2.red) && (white == c2.white));
|
||||
}
|
||||
|
||||
/// @brief Checks if both color values are not equal
|
||||
bool operator!=(Color const &c2) const {
|
||||
return ((blue != c2.blue) || (green != c2.green) ||
|
||||
(red != c2.red) || (white != c2.white));
|
||||
}
|
||||
|
||||
/// @brief Scale color by scaling each color-channel by given factor
|
||||
/// @param c1 Color
|
||||
/// @param k Scale-factor uint8_t.Max is full-scale
|
||||
/// @return Scaled Color
|
||||
Color operator*(uint8_t k) {
|
||||
red = (uint8_t)((uint16_t)red * k / 0xFF);
|
||||
green = (uint8_t)((uint16_t)green * k / 0xFF);
|
||||
blue = (uint8_t)((uint16_t)blue * k / 0xFF);
|
||||
white = (uint8_t)((uint16_t)white * k / 0xFF);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Overlaying to colors my adding each color channel value
|
||||
/// @note Colors channels should not overlap since there is no automatic ratio correction.
|
||||
/// Use *-Operator first in order to scale each color value a a way that no overflow can occur
|
||||
/// @param c2 Color 2
|
||||
/// @return Overlayed Color
|
||||
Color operator+(Color c2) {
|
||||
red = red + c2.red;
|
||||
green = green + c2.green;
|
||||
blue = blue + c2.blue;
|
||||
white = white + c2.white;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Color) == 4 && "Color Type is to big");
|
||||
|
||||
/// Null color object
|
||||
extern const Color *noColor;
|
||||
/// Used old color agreement (if no color pointer)
|
||||
extern const Color *oldColor;
|
||||
|
||||
/// Black color object constant
|
||||
extern const Color CBlack;
|
||||
/// Red color object constant
|
||||
extern const Color CRed;
|
||||
/// Green color object constant
|
||||
extern const Color CGreen;
|
||||
/// Blue color object constant
|
||||
extern const Color CBlue;
|
||||
/// Magenta color object constant
|
||||
extern const Color CMagenta;
|
||||
/// Cyan color object constant
|
||||
extern const Color CCyan;
|
||||
/// Yellow color object constant
|
||||
extern const Color CYellow;
|
||||
/// White color object constant
|
||||
extern const Color CWhite;
|
||||
/// Warm white color object constant (with higher red channel value)
|
||||
extern const Color CWarmWhite;
|
||||
/// Black color object constant (with higher green channel value)
|
||||
extern const Color CColdWhite;
|
||||
/// Black color object constant (with higher blue channel value)
|
||||
extern const Color CSpookyWhite;
|
||||
@@ -1,202 +0,0 @@
|
||||
/**
|
||||
* @file color.hpp
|
||||
* @author Gustice
|
||||
* @brief
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
/// No Waveform definition
|
||||
extern uint8_t const *const noWave;
|
||||
|
||||
/**
|
||||
* @brief Color class
|
||||
*
|
||||
* @details Defines color data and common operations with color objects
|
||||
* \li Supports RGB with overlapping white channel (e.g. SK6812-LED)
|
||||
* \li Supports Operator overloading with '+' and '*' (Combined operation with MixWith)
|
||||
* \li Return formatted data stream that can be directly sent to an LED
|
||||
*/
|
||||
class Color {
|
||||
private:
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
uint8_t white;
|
||||
|
||||
public:
|
||||
struct Rgbw {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
uint8_t white;
|
||||
};
|
||||
|
||||
struct Rgb {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
};
|
||||
|
||||
/// @brief Construct a new Color object
|
||||
/// @details Default color will be Black \ref _ctBlack
|
||||
Color(void) {
|
||||
set(0, 0, 0, 0);
|
||||
};
|
||||
|
||||
/// @brief Copy-Construct a new Color object
|
||||
Color(const Color & c) {
|
||||
red = c.red;
|
||||
green = c.green;
|
||||
blue = c.blue;
|
||||
white = c.white;
|
||||
};
|
||||
|
||||
/// @brief Construct a new Color object according to rgb color-channel-values
|
||||
/// @details This constructor also takes an overlapping white channel
|
||||
/// @param r Red channel
|
||||
/// @param g Green channel
|
||||
/// @param b Blue channel
|
||||
/// @param w White channel (zero by default if not assigned)
|
||||
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { set(r, g, b, w); };
|
||||
|
||||
/// @brief Set the Color object according to rgb color-channel values
|
||||
/// @param r Red channel
|
||||
/// @param g Green channel
|
||||
/// @param b Blue channel
|
||||
/// @param w White channel (zero by default if not assigned)
|
||||
void set(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) {
|
||||
red = r;
|
||||
green = g;
|
||||
blue = b;
|
||||
white = w;
|
||||
}
|
||||
|
||||
/// @brief Returns Color as Color_t structure
|
||||
/// @return Color_t Color
|
||||
template <typename T>
|
||||
T getColor(void) const {
|
||||
if constexpr (std::is_same<T, Rgbw>::value) {
|
||||
return {red, green, blue, white};
|
||||
} else if constexpr (std::is_same<T, Rgb>::value) {
|
||||
return {red, green, blue};
|
||||
} else {
|
||||
throw "wrong type";
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t getRed() { return red; };
|
||||
uint8_t getGreen() { return green; };
|
||||
uint8_t getBlue() { return blue; };
|
||||
uint8_t getWhite() { return white; };
|
||||
|
||||
/// @brief Scaling and overlaying with given color.
|
||||
/// @param c2 Color 2
|
||||
/// @param k scaling factor
|
||||
/// @return Overlayed Color
|
||||
Color mixWith(const Color c2, uint8_t k = 0x7F) {
|
||||
auto t(c2);
|
||||
*this = (*this * (UINT8_MAX - k)) + (t * k);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Writes Color as formatted byte stream to location
|
||||
/// @param color
|
||||
template <typename T>
|
||||
void writeByteStreamTo(uint8_t raw[sizeof(T)]) const {
|
||||
if constexpr (std::is_same<T, Rgbw>::value) {
|
||||
raw[0] = blue;
|
||||
raw[1] = green;
|
||||
raw[2] = red;
|
||||
raw[3] = white;
|
||||
} else if constexpr (std::is_same<T, Rgb>::value) {
|
||||
raw[0] = blue;
|
||||
raw[1] = green;
|
||||
raw[2] = red;
|
||||
} else {
|
||||
throw "wrong type";
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Assign overload
|
||||
Color operator=(const Color &c2) {
|
||||
red = c2.red;
|
||||
green = c2.green;
|
||||
blue = c2.blue;
|
||||
white = c2.white;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Checks if both color values are equal
|
||||
bool operator==(Color const &c2) const {
|
||||
return ((blue == c2.blue) && (green == c2.green) &&
|
||||
(red == c2.red) && (white == c2.white));
|
||||
}
|
||||
|
||||
/// @brief Checks if both color values are not equal
|
||||
bool operator!=(Color const &c2) const {
|
||||
return ((blue != c2.blue) || (green != c2.green) ||
|
||||
(red != c2.red) || (white != c2.white));
|
||||
}
|
||||
|
||||
/// @brief Scale color by scaling each color-channel by given factor
|
||||
/// @param c1 Color
|
||||
/// @param k Scale-factor uint8_t.Max is full-scale
|
||||
/// @return Scaled Color
|
||||
Color operator*(uint8_t k) {
|
||||
red = (uint8_t)((uint16_t)red * k / 0xFF);
|
||||
green = (uint8_t)((uint16_t)green * k / 0xFF);
|
||||
blue = (uint8_t)((uint16_t)blue * k / 0xFF);
|
||||
white = (uint8_t)((uint16_t)white * k / 0xFF);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Overlaying to colors my adding each color channel value
|
||||
/// @note Colors channels should not overlap since there is no automatic ratio correction.
|
||||
/// Use *-Operator first in order to scale each color value a a way that no overflow can occur
|
||||
/// @param c2 Color 2
|
||||
/// @return Overlayed Color
|
||||
Color operator+(Color c2) {
|
||||
red = red + c2.red;
|
||||
green = green + c2.green;
|
||||
blue = blue + c2.blue;
|
||||
white = white + c2.white;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Color) == 4 && "Color Type is to big");
|
||||
|
||||
/// Null color object
|
||||
extern const Color *noColor;
|
||||
/// Used old color agreement (if no color pointer)
|
||||
extern const Color *oldColor;
|
||||
|
||||
/// Black color object constant
|
||||
extern const Color CBlack;
|
||||
/// Red color object constant
|
||||
extern const Color CRed;
|
||||
/// Green color object constant
|
||||
extern const Color CGreen;
|
||||
/// Blue color object constant
|
||||
extern const Color CBlue;
|
||||
/// Magenta color object constant
|
||||
extern const Color CMagenta;
|
||||
/// Cyan color object constant
|
||||
extern const Color CCyan;
|
||||
/// Yellow color object constant
|
||||
extern const Color CYellow;
|
||||
/// White color object constant
|
||||
extern const Color CWhite;
|
||||
/// Warm white color object constant (with higher red channel value)
|
||||
extern const Color CWarmWhite;
|
||||
/// Black color object constant (with higher green channel value)
|
||||
extern const Color CColdWhite;
|
||||
/// Black color object constant (with higher blue channel value)
|
||||
extern const Color CSpookyWhite;
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* @file ExactIndex.h
|
||||
* @author Gustice
|
||||
* @brief Index-Class with fix-point arithmetic
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Index-Class with fix-point arithmetic for exact stepping though data-points
|
||||
* @details Allows stepping through data-points by quotient step-sizes
|
||||
*/
|
||||
class ExactIndex {
|
||||
public:
|
||||
/// @brief Construct Index-object according to number of data points and steps to be taken
|
||||
/// @param length
|
||||
/// @param steps
|
||||
ExactIndex(unsigned length, uint16_t steps) : Length(std::max(1u, length)), Steps(steps) {
|
||||
_delta = Length << 8;
|
||||
_delta = _delta / steps;
|
||||
_index = unsigned(0u - _delta);
|
||||
}
|
||||
|
||||
/// @brief Get current index-position as floor value to never pass upper bound
|
||||
uint16_t GetIdx(void) { return ((_index) >> 8); };
|
||||
|
||||
/// @brief Move index by quotient step forward
|
||||
ExactIndex &operator++() {
|
||||
_index = _index + _delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Move index by quotient step backward
|
||||
ExactIndex &operator--() {
|
||||
_index = _index - _delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Get current index-position as floor value to never pass upper bound
|
||||
operator int() const { return ((_index) >> 8); }
|
||||
|
||||
/// @brief Get size of data set (number of data points)
|
||||
unsigned GetLength() { return Length; }
|
||||
/// @brief Get number of steps that are to be taken though data set
|
||||
uint16_t GetSteps() { return Steps; }
|
||||
|
||||
private:
|
||||
/// @brief Number of data points
|
||||
unsigned Length;
|
||||
/// @brief Steps to be taken through data-set
|
||||
uint16_t Steps;
|
||||
|
||||
/// @brief Current index in fixed-point representation
|
||||
unsigned _index = 0;
|
||||
|
||||
/// @brief Step-with in fixed-point representation
|
||||
unsigned _delta;
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* @file Macro.h
|
||||
* @author Gustice
|
||||
* @brief Predefined effect macros
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "Color.hpp"
|
||||
#include "Waveform.hpp"
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Effect {
|
||||
|
||||
/// Collection of process values to maintain processing state machine
|
||||
struct StepProcessVars {
|
||||
/// @brief Reference of current color
|
||||
const Color &pCurentColor;
|
||||
/// @brief Number of color entries
|
||||
uint8_t ColorLen;
|
||||
/// @brief Idle value for intensity
|
||||
uint8_t IdleIntensity;
|
||||
/// @brief Full value for intensity
|
||||
uint8_t FullIntensity;
|
||||
/// @brief Dynamic range for effects
|
||||
uint8_t DynamicRange;
|
||||
/// @brief Index in current waveform
|
||||
uint16_t waveIdx;
|
||||
/// @brief Reference to processed waveform
|
||||
const WaveForm_t *const pWave;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Effect implementation for State-Machine
|
||||
* @details State-Machine invokes referred effect-implementation during processing
|
||||
*/
|
||||
typedef Color (*EffectImplementation)(StepProcessVars &pcsParam);
|
||||
|
||||
/**
|
||||
* @brief Definition of Effect-Macro
|
||||
*/
|
||||
struct Macro_t {
|
||||
/// @brief Effect type
|
||||
const EffectImplementation pImplementation;
|
||||
/// @brief Effect duration waveforms will be compressed / stretched to fit the duration
|
||||
const uint8_t Duration;
|
||||
/// @brief Next SubEffect
|
||||
const int8_t Next;
|
||||
/// @brief waveform reference
|
||||
const WaveForm_t *pWave = emptyWave;
|
||||
/// @brief Full-Scale-intensity
|
||||
const uint8_t FsIntensity;
|
||||
/// @brief Num of Repeats
|
||||
const int8_t Repeats;
|
||||
|
||||
Macro_t(uint8_t duration, int8_t next, EffectImplementation state, uint8_t intensity,
|
||||
int8_t repeats = 0)
|
||||
: pImplementation(state), Duration(duration), Next(next), FsIntensity(intensity),
|
||||
Repeats(repeats) {}
|
||||
|
||||
Macro_t(uint8_t duration, int8_t next, WaveForm_t const *wave, EffectImplementation state,
|
||||
uint8_t intensity, int8_t repeats = 0)
|
||||
: pImplementation(state), Duration(duration), Next(next), pWave(wave),
|
||||
FsIntensity(intensity), Repeats(repeats) {}
|
||||
|
||||
private:
|
||||
/// @brief Null-pointer to represent no change in color
|
||||
static constexpr Color *pOldColor = nullptr;
|
||||
|
||||
/// @brief Null-pointer to represent no actual waveform-representation
|
||||
static constexpr WaveForm_t *pNoWave = nullptr;
|
||||
|
||||
/// @brief Default implemented Waveform with no profile
|
||||
static const constexpr WaveForm_t *emptyWave = &EmptyWave;
|
||||
};
|
||||
using Macro = std::vector<Macro_t>;
|
||||
|
||||
/// @brief Dark - Nothing to see here
|
||||
extern const Macro macDark;
|
||||
/// @brief Continuous idle
|
||||
extern const Macro macIdle;
|
||||
/// @brief Continuous flicker
|
||||
extern const Macro macStartFlicker;
|
||||
/// @brief Smooth turn on and then remaining idle
|
||||
extern const Macro macStartIdle;
|
||||
/// @brief Smooth turn on to full color
|
||||
extern const Macro macStartFull;
|
||||
/// @brief Slow pulse starting from idle intensity. Appears like breathing
|
||||
extern const Macro macStdPulse;
|
||||
/// @brief Short pulses starting from idle intensity. Appears nervously.
|
||||
extern const Macro macNervousPulse;
|
||||
|
||||
} // namespace Effect
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* @file EffectProcessor.h
|
||||
* @author Gustice
|
||||
* @brief Effect-Processor-Class for processing effect scenarios and switching actions
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "Color.hpp"
|
||||
#include "Macro.hpp"
|
||||
#include "StateMachine.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Effect {
|
||||
|
||||
/**
|
||||
* @brief EffectProcessor Class
|
||||
* @details Maintains State-Machines, Processes effect-switching, and invokes generation
|
||||
* of raw data stream for LEDs.
|
||||
* \li If effect scenarios are changed a cross-fading is executed
|
||||
* \li Raw-data-stream for each Color object is generated
|
||||
*/
|
||||
class EffectProcessor {
|
||||
public:
|
||||
///@brief Constructor
|
||||
///@param fadeSteps Steps count for cross fade
|
||||
EffectProcessor(uint8_t const fadeSteps = 8);
|
||||
|
||||
/// @brief Set next effect sequence
|
||||
/// @param sequence reference to sequence that has to be processed next
|
||||
/// @param sColor Start color. Use noColor to start with default color
|
||||
/// @param intens Idle intensity for effect. Use NULL to start with default intensity
|
||||
void setEffect(const Macro &sequence, Color const *sColor = noColor, uint8_t delay = 0, uint8_t intens = gu8_idleIntensity);
|
||||
|
||||
/// @brief Execute step of effect processor
|
||||
/// @return Reference to color result
|
||||
Color tick(void);
|
||||
|
||||
private:
|
||||
StateMachine::Parameter _parameter;
|
||||
std::array<StateMachine, 2> _effectMachines;
|
||||
uint8_t _fadeSteps;
|
||||
uint8_t _fadingCnt;
|
||||
StateMachine *_EffPV;
|
||||
StateMachine *_EffPV_old;
|
||||
|
||||
Color crossFadeColors(Color &cNew, Color &cOld, uint8_t k);
|
||||
EffectProcessor();
|
||||
};
|
||||
|
||||
} // namespace Effect
|
||||
@@ -1,118 +0,0 @@
|
||||
/**
|
||||
* @file EffectStateMachine.h
|
||||
* @author Gustice
|
||||
* @brief Effect-State-Machine-Class for processing Effect Macros
|
||||
* @version 0.6
|
||||
* @date 2019-10-01
|
||||
*
|
||||
* @copyright Copyright (c) 2019
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "Color.hpp"
|
||||
#include "Macro.hpp"
|
||||
#include <cstdint>
|
||||
#include "ExactIndex.hpp"
|
||||
|
||||
namespace Effect {
|
||||
|
||||
/**
|
||||
* @brief Effect State Machine Class
|
||||
* @details Processes Effect macro array Macro entry subsequently
|
||||
* Processing is executed in following manner:
|
||||
* \li Index-incrementing each Tick
|
||||
* \li ticks until duration \ref EffMacro is finished
|
||||
* \li each tick the index incremented by templateLength/duration
|
||||
* \li Repeats-processing
|
||||
* \li Each repeat the tick and the index are set back to start
|
||||
* \li Switches to next Macro enty
|
||||
* \li Next entry is given by 'next' index
|
||||
* \li Color changes can be executed optionally
|
||||
*/
|
||||
class StateMachine {
|
||||
public:
|
||||
/// @brief Collection of object configuration at startup
|
||||
struct Parameter {
|
||||
/// @brief Idle intensity as background color
|
||||
uint8_t idleIntens;
|
||||
/// @brief dynamic range as variation around background color
|
||||
uint8_t dynamicRange;
|
||||
/// @brief Fade steps for color changes
|
||||
uint8_t fadeSteps;
|
||||
/// @brief Count of target LEDs
|
||||
uint8_t targetCount;
|
||||
};
|
||||
|
||||
///@brief Construct a new Effect State Machine object
|
||||
///@param templateLength Length of waveforms that are used to display
|
||||
StateMachine(Parameter ¶meter);
|
||||
|
||||
///@brief Sets the effect state machine to process given effect macro
|
||||
///@param sequence reference to sequence
|
||||
///@param startColor Start color. Use noColor to invoke with default color
|
||||
///@param initialDelay Number of steps that are waited before effect is started
|
||||
void setEffect(const Macro &sequence, Color const *startColor = noColor, uint8_t initialDelay = 0);
|
||||
|
||||
/// @brief Sets the effect state machine to process given effect macro
|
||||
/// @param sequence reference to sequence
|
||||
/// @param startColor Start color. Use noColor to invoke with default color
|
||||
/// @param intens Idle intensity for effect. Use nullptr to start with default intensity
|
||||
/// @param delayedStart Number of steps that are waited before effect is started
|
||||
void setEffect(const Macro &sequence, Color const *startColor, const uint8_t *intens,
|
||||
const uint8_t delayedStart);
|
||||
|
||||
/// @brief Execute step of state machine
|
||||
/// @details Each tick the tick variable is decremented. After the limit is reached,
|
||||
/// the current macro line is either repeated or the next macro line is started (with
|
||||
/// an optional color change).\n
|
||||
/// @note Tick must be called regularly. For standard light applications all 40 ms seems to be
|
||||
/// a convenient values.
|
||||
/// @return Color const* Reference to color result
|
||||
virtual Color tick(void);
|
||||
|
||||
/// Black out lights
|
||||
static Color UpdateBlank(StepProcessVars &pcsParam);
|
||||
/// Continuous illumination in idle intensity
|
||||
static Color UpdateIdle(StepProcessVars &pcsParam);
|
||||
/// Keep latest state on hold
|
||||
static Color UpdateFreeze(StepProcessVars &pcsParam);
|
||||
/// Step through a defined waveform
|
||||
static Color UpdateWave(StepProcessVars &pcsParam);
|
||||
/// Step through a defined waveform backwards
|
||||
static Color UpdateRevWave(StepProcessVars &pcsParam);
|
||||
/// Show flicker effect with idle intensity as static part with dynamic flicker-range
|
||||
static Color UpdateFlicker(StepProcessVars &pcsParam);
|
||||
|
||||
protected:
|
||||
/// Collection of process values to maintain processing state machine
|
||||
struct ProcessValues {
|
||||
/// Decrementing tick, counting duration
|
||||
uint8_t tick;
|
||||
/// Decrementing repeats of current Effect-part
|
||||
uint8_t repeats;
|
||||
/// Index to macro element
|
||||
uint8_t macroIdx;
|
||||
/// Current index to waveform
|
||||
uint16_t waveIdx;
|
||||
/// Index step each tick according to duration and template length.
|
||||
uint16_t waveStep;
|
||||
};
|
||||
|
||||
/// @brief Concentrated parameter values of instance
|
||||
Parameter &_params;
|
||||
|
||||
Color const * _curentColor;
|
||||
ProcessValues _prcValues;
|
||||
void SetIndexes(void);
|
||||
|
||||
Macro const *_p_effSeq;
|
||||
Macro_t const *_p_effMac;
|
||||
ExactIndex _idx;
|
||||
|
||||
// Color& (*apF_Effects)(StateMachine * SM);
|
||||
typedef Color const *pEffPrc(StateMachine *);
|
||||
|
||||
};
|
||||
|
||||
} // namespace Effect
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @file WaveForm.h
|
||||
* @author Gustice
|
||||
* @brief C++ encapsulated waveform definition
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Waveforms.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace Effect {
|
||||
|
||||
/**
|
||||
* @brief Waveform definition based on c-definitions
|
||||
*/
|
||||
struct WaveForm_t {
|
||||
/// @brief Reference to waveform
|
||||
const uint8_t *const pWave;
|
||||
/// @brief Length of waveform
|
||||
const unsigned Length;
|
||||
};
|
||||
|
||||
/// @brief Empty waveform with defined pointer to meaningless data to avoid problems
|
||||
extern const WaveForm_t EmptyWave;
|
||||
|
||||
} // namespace Effect
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* @file EffectWaveforms.h
|
||||
* @author Gustice
|
||||
* @brief Predefined Waveforms that can be put together to a complex Waveform
|
||||
* @version 0.6
|
||||
* @date 2019-10-01
|
||||
*
|
||||
* @copyright Copyright (c) 2019
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Effect {
|
||||
|
||||
/// Length of all subsequent Template definitions
|
||||
extern const uint16_t cu16_TemplateLength;
|
||||
/// Idle intensity for Slopes and Pulses
|
||||
extern const uint8_t gu8_idleIntensity;
|
||||
/// Idle intensity for Slopes and Pulses
|
||||
extern const uint8_t gu8_fullIntensity;
|
||||
/// Headroom for Effects on top of idle brightness
|
||||
extern const uint8_t gu8_dynamicRange;
|
||||
/// Standard cross fade steps
|
||||
extern const uint8_t gu8_fadeSteps;
|
||||
|
||||
/// Starts from idle \ref gu8_idleIntensity and represents a gaussian impulse \ref gu8_fullIntensity
|
||||
extern const uint8_t gau8_offsetPulse[];
|
||||
/// Starts at zero and rises to idle intensity \ref gu8_idleIntensity
|
||||
extern const uint8_t gau8_initSlope[];
|
||||
/// Starts from idle \ref gu8_idleIntensity and rises up to full intensity \ref gu8_fullIntensity
|
||||
extern const uint8_t gau8_offsetSlope[];
|
||||
/// Starts at zero and rises to full intensity \ref gu8_fullIntensity
|
||||
extern const uint8_t gau8_fullSlope[];
|
||||
/// Starts at zero and represents a gaussian impulse to full intensity \ref gu8_fullIntensity
|
||||
extern const uint8_t gau8_fullPulse[];
|
||||
|
||||
} // namespace Effect
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @file color.cpp
|
||||
* @author Gustice
|
||||
* @brief Implementation of Color-Class
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "Color.hpp"
|
||||
|
||||
uint8_t const * const noWave = nullptr;
|
||||
|
||||
const Color *noColor = nullptr;
|
||||
const Color *oldColor = nullptr;
|
||||
|
||||
const Color CBlack(0x00, 0x00, 0x00, 0x00);
|
||||
const Color CRed(0xFF, 0x00, 0x00, 0x00);
|
||||
const Color CGreen(0x00, 0xFF, 0x00, 0x00);
|
||||
const Color CBlue(0x00, 0x00, 0xFF, 0x00);
|
||||
const Color CMagenta(0x80, 0x00, 0x80, 0x00);
|
||||
const Color CCyan(0x00, 0x80, 0x80, 0x00);
|
||||
const Color CYellow(0x80, 0x80, 0x00, 0x00);
|
||||
const Color CWhite(0x55, 0x55, 0x55, 0x00);
|
||||
const Color CWarmWhite(0x7F, 0x4F, 0x4F, 0x00);
|
||||
const Color CColdWhite(0x4F, 0x4F, 0x7F, 0x00);
|
||||
const Color CSpookyWhite(0x4F, 0x7F, 0x4F, 0x00);
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* @file EffectMacro.cpp
|
||||
* @author Gustice
|
||||
* @brief Predefined Macros
|
||||
* @version 0.6
|
||||
* @date 2021-03-23
|
||||
*
|
||||
* @copyright Copyright (c) 2021
|
||||
*/
|
||||
|
||||
#include "Macro.hpp"
|
||||
#include "Color.hpp"
|
||||
#include "Waveforms.hpp"
|
||||
#include "StateMachine.hpp"
|
||||
|
||||
namespace Effect {
|
||||
const uint8_t Empty[4] = {0, 0, 0, 0};
|
||||
|
||||
const WaveForm_t EmptyWave{Empty, 4};
|
||||
const WaveForm_t OffsetPulseWave{gau8_offsetPulse, cu16_TemplateLength};
|
||||
const WaveForm_t InitSlopeWave{gau8_initSlope, cu16_TemplateLength};
|
||||
const WaveForm_t OffsetSlopeWave{gau8_offsetSlope, cu16_TemplateLength};
|
||||
const WaveForm_t FullSlopeWave{gau8_fullSlope, cu16_TemplateLength};
|
||||
const WaveForm_t FullPulseWave{gau8_fullPulse, cu16_TemplateLength};
|
||||
|
||||
const Macro macDark = {
|
||||
{32, 0, StateMachine::UpdateBlank, 0, 0}
|
||||
};
|
||||
|
||||
const Macro macIdle = {
|
||||
{32, 1, StateMachine::UpdateIdle, 0xFF},
|
||||
{32, 1, StateMachine::UpdateIdle, 0xFF},
|
||||
};
|
||||
|
||||
const Macro macStartIdle = {
|
||||
{64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF},
|
||||
{32, 1, StateMachine::UpdateIdle, 0xFF},
|
||||
};
|
||||
|
||||
const Macro macStartFlicker = {
|
||||
{64, 1, &InitSlopeWave, StateMachine::UpdateWave, 0xFF},
|
||||
{32, 1, StateMachine::UpdateFlicker, 0xFF},
|
||||
};
|
||||
|
||||
const Macro macStartFull = {
|
||||
{24, 1, &FullSlopeWave, StateMachine::UpdateWave, 0xFF},
|
||||
{32, 1, StateMachine::UpdateIdle, 0xFF},
|
||||
};
|
||||
|
||||
const Macro macStdPulse = {
|
||||
{32, 1, StateMachine::UpdateIdle, 0},
|
||||
{32, 2, &OffsetPulseWave, StateMachine::UpdateWave, 0xFF},
|
||||
{32, 0, StateMachine::UpdateIdle, 0},
|
||||
};
|
||||
|
||||
const Macro macNervousPulse = {
|
||||
{8, 1, StateMachine::UpdateIdle, 0},
|
||||
{16, 2, &OffsetPulseWave, StateMachine::UpdateWave, 0xFF},
|
||||
{8, 0, StateMachine::UpdateIdle, 0},
|
||||
};
|
||||
|
||||
const Macro macStdWipe = {
|
||||
{32, 1, StateMachine::UpdateIdle, 0},
|
||||
|
||||
{32, 2, &OffsetSlopeWave, StateMachine::UpdateWave, 0xFF},
|
||||
{32, 3, StateMachine::UpdateFreeze, 0},
|
||||
{32, 4, &OffsetSlopeWave, StateMachine::UpdateRevWave, 0xFF},
|
||||
{32, 1, StateMachine::UpdateFreeze, 0},
|
||||
};
|
||||
|
||||
} // namespace Effect
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* @file Processor.cpp
|
||||
* @author Gustice
|
||||
* @brief Implementation of Effect-Processor-Class EffectProcessor.h
|
||||
* @version 0.6
|
||||
* @date 2019-10-03
|
||||
*
|
||||
* @copyright Copyright (c) 2019
|
||||
*/
|
||||
|
||||
#include "Processor.hpp"
|
||||
#include "Macro.hpp"
|
||||
#include "Waveforms.hpp"
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace Effect {
|
||||
|
||||
EffectProcessor::EffectProcessor(uint8_t const fadeSteps)
|
||||
: _parameter{0x20, 0x20, 0, 1},
|
||||
_effectMachines{StateMachine{_parameter}, StateMachine{_parameter}} {
|
||||
_EffPV = &_effectMachines[0];
|
||||
_EffPV_old = &_effectMachines[1];
|
||||
_EffPV->setEffect(macDark, noColor);
|
||||
_fadeSteps = fadeSteps;
|
||||
}
|
||||
|
||||
void EffectProcessor::setEffect(const Macro &sequence, Color const *sColor, uint8_t delay, uint8_t intens) {
|
||||
_fadingCnt = _fadeSteps;
|
||||
std::swap(_EffPV, _EffPV_old);
|
||||
_EffPV->setEffect(sequence, sColor, &intens, delay);
|
||||
}
|
||||
|
||||
Color EffectProcessor::tick(void) {
|
||||
uint8_t k;
|
||||
auto cN = (_EffPV->tick());
|
||||
|
||||
if (_fadingCnt > 0) { // always process soft cross dissolve between different macros
|
||||
_fadingCnt--;
|
||||
k = (uint16_t)_fadingCnt * 0xFF / _fadeSteps;
|
||||
|
||||
auto cO = (_EffPV_old->tick());
|
||||
cN = crossFadeColors(cN, cO, k);
|
||||
}
|
||||
|
||||
return cN;
|
||||
}
|
||||
|
||||
Color EffectProcessor::crossFadeColors(Color &cNew, Color &cOld, uint8_t k) {
|
||||
cOld = cOld * k;
|
||||
cNew = cNew * (0xFF - k);
|
||||
cNew = cNew + cOld;
|
||||
|
||||
return cNew;
|
||||
}
|
||||
|
||||
} // namespace Effect
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* @file StateMachine.cpp
|
||||
* @author Gustice
|
||||
* @brief Implementation of Effect-State-Machine-Class EffectStateMachine.h
|
||||
* @version 0.6
|
||||
* @date 2019-10-01
|
||||
*
|
||||
* @copyright Copyright (c) 2019
|
||||
*
|
||||
*/
|
||||
#include "StateMachine.hpp"
|
||||
#include "Waveforms.hpp"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace Effect {
|
||||
|
||||
/// Optional prequel for delayed start.
|
||||
/// \li Duration must be != 0
|
||||
/// \li Next entry will be first entry in given Effect sequence
|
||||
Macro delayPrequel = {
|
||||
{1, 0, StateMachine::UpdateBlank, 0, 0}
|
||||
};
|
||||
|
||||
StateMachine::StateMachine(Parameter ¶meter)
|
||||
: _params(parameter), _idx(/*pseudo values*/ 1, 1) {}
|
||||
|
||||
void StateMachine::setEffect(const Macro &sequence, Color const *startColor, uint8_t initialDelay) {
|
||||
setEffect(sequence, startColor, &_params.idleIntens, initialDelay);
|
||||
}
|
||||
|
||||
void StateMachine::setEffect(const Macro &sequence, Color const *startColor, const uint8_t *intens,
|
||||
const uint8_t delayedStart) {
|
||||
if (startColor != noColor) {
|
||||
_curentColor = startColor;
|
||||
}
|
||||
if (intens != nullptr) {
|
||||
_params.idleIntens = *intens;
|
||||
}
|
||||
|
||||
_p_effSeq = &sequence;
|
||||
if (delayedStart > 0) {
|
||||
_p_effMac = &delayPrequel[0];
|
||||
_prcValues.tick = delayedStart;
|
||||
} else {
|
||||
_p_effMac = &sequence[0];
|
||||
_prcValues.tick = _p_effMac->Duration;
|
||||
}
|
||||
|
||||
_prcValues.tick++;
|
||||
_prcValues.repeats = _p_effMac->Repeats;
|
||||
_idx = ExactIndex(_p_effMac->pWave->Length, _p_effMac->Duration);
|
||||
}
|
||||
|
||||
Color StateMachine::tick(void) {
|
||||
// tick-increment
|
||||
if (--_prcValues.tick == 0) {
|
||||
// repeats-decrement
|
||||
if (_prcValues.repeats-- == 0) {
|
||||
_p_effMac = &(*_p_effSeq)[_p_effMac->Next];
|
||||
_prcValues.repeats = _p_effMac->Repeats;
|
||||
}
|
||||
|
||||
_prcValues.tick = _p_effMac->Duration;
|
||||
_idx = ExactIndex(_p_effMac->pWave->Length, _p_effMac->Duration);
|
||||
}
|
||||
|
||||
++_idx;
|
||||
|
||||
StepProcessVars pv{
|
||||
.pCurentColor = *_curentColor,
|
||||
.ColorLen = _params.targetCount,
|
||||
.IdleIntensity = _params.idleIntens,
|
||||
.FullIntensity = _p_effMac->FsIntensity,
|
||||
.DynamicRange = _params.dynamicRange,
|
||||
.waveIdx = _idx.GetIdx(),
|
||||
.pWave = _p_effMac->pWave,
|
||||
};
|
||||
return _p_effMac->pImplementation(pv);
|
||||
}
|
||||
|
||||
Color StateMachine::UpdateBlank(StepProcessVars &pcsParam) {
|
||||
return CBlack;
|
||||
}
|
||||
|
||||
Color StateMachine::UpdateIdle(StepProcessVars &pcsParam) {
|
||||
auto c = pcsParam.pCurentColor;
|
||||
return c * pcsParam.IdleIntensity;
|
||||
}
|
||||
|
||||
Color StateMachine::UpdateFreeze(StepProcessVars &pcsParam) {
|
||||
return pcsParam.pCurentColor;
|
||||
}
|
||||
|
||||
Color StateMachine::UpdateWave(StepProcessVars &pcsParam) {
|
||||
Color temp = pcsParam.pCurentColor;
|
||||
temp = temp * ((float)(pcsParam.pWave->pWave[pcsParam.waveIdx]) / 0xFF * pcsParam.FullIntensity);
|
||||
return temp;
|
||||
}
|
||||
|
||||
Color StateMachine::UpdateRevWave(StepProcessVars &pcsParam) {
|
||||
unsigned lastIndex = pcsParam.pWave->Length - 1;
|
||||
Color temp = pcsParam.pCurentColor;
|
||||
temp = temp * ((float)(pcsParam.pWave->pWave[lastIndex - pcsParam.waveIdx]) / 0xFF *
|
||||
pcsParam.FullIntensity);
|
||||
return temp;
|
||||
}
|
||||
|
||||
Color StateMachine::UpdateFlicker(StepProcessVars &pcsParam) {
|
||||
uint8_t k = rand();
|
||||
k = pcsParam.IdleIntensity + ((int16_t)k * (pcsParam.DynamicRange) / 0xFF);
|
||||
auto c = pcsParam.pCurentColor;
|
||||
c = c * ((float)(k));
|
||||
return c;
|
||||
}
|
||||
|
||||
} // namespace Effect
|
||||
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* @file Waveforms.cpp
|
||||
* @author Gustice
|
||||
* @brief Implementation of defined Waveforms
|
||||
* @version 0.6
|
||||
* @date 2019-10-01
|
||||
*
|
||||
* @copyright Copyright (c) 2019
|
||||
* @note The defined waveforms are generated with a the WaveformGenerator
|
||||
*/
|
||||
#include "Waveforms.hpp"
|
||||
|
||||
namespace Effect {
|
||||
|
||||
const uint16_t cu16_TemplateLength = 128;
|
||||
const uint8_t gu8_idleIntensity = 0x55;
|
||||
const uint8_t gu8_fullIntensity = 0xFF;
|
||||
const uint8_t gu8_dynamicRange = gu8_fullIntensity - gu8_idleIntensity;
|
||||
const uint8_t gu8_fadeSteps = 10;
|
||||
|
||||
// clang-format off
|
||||
// Offset Pulse: Gaussian pulse from background illumination with full scale intensity
|
||||
const uint8_t gau8_offsetPulse[cu16_TemplateLength * 2] = {
|
||||
0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x58, 0x58, 0x59, 0x59, 0x5A, 0x5B, 0x5C,
|
||||
0x5D, 0x5E, 0x5F, 0x61, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6F, 0x71, 0x74, 0x77, 0x7B, 0x7E,
|
||||
0x81, 0x85, 0x89, 0x8E, 0x92, 0x97, 0x9B, 0xA0, 0xA5, 0xAA, 0xAF, 0xB5, 0xBA, 0xBF, 0xC4, 0xC9,
|
||||
0xCF, 0xD3, 0xD8, 0xDD, 0xE1, 0xE6, 0xEA, 0xED, 0xF1, 0xF4, 0xF7, 0xF9, 0xFB, 0xFD, 0xFD, 0xFE,
|
||||
0xFF, 0xFE, 0xFD, 0xFD, 0xFB, 0xF9, 0xF7, 0xF4, 0xF1, 0xED, 0xEA, 0xE6, 0xE1, 0xDD, 0xD8, 0xD3,
|
||||
0xCF, 0xC9, 0xC4, 0xBF, 0xBA, 0xB5, 0xAF, 0xAA, 0xA5, 0xA0, 0x9B, 0x97, 0x92, 0x8E, 0x89, 0x85,
|
||||
0x81, 0x7E, 0x7B, 0x77, 0x74, 0x71, 0x6F, 0x6C, 0x6A, 0x68, 0x66, 0x64, 0x62, 0x61, 0x5F, 0x5E,
|
||||
0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x59, 0x58, 0x58, 0x57, 0x57, 0x57, 0x56, 0x56, 0x56, 0x55, 0x55,
|
||||
|
||||
0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x58, 0x58, 0x59, 0x59, 0x5A, 0x5B, 0x5C,
|
||||
0x5D, 0x5E, 0x5F, 0x61, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6F, 0x71, 0x74, 0x77, 0x7B, 0x7E,
|
||||
0x81, 0x85, 0x89, 0x8E, 0x92, 0x97, 0x9B, 0xA0, 0xA5, 0xAA, 0xAF, 0xB5, 0xBA, 0xBF, 0xC4, 0xC9,
|
||||
0xCF, 0xD3, 0xD8, 0xDD, 0xE1, 0xE6, 0xEA, 0xED, 0xF1, 0xF4, 0xF7, 0xF9, 0xFB, 0xFD, 0xFD, 0xFE,
|
||||
0xFF, 0xFE, 0xFD, 0xFD, 0xFB, 0xF9, 0xF7, 0xF4, 0xF1, 0xED, 0xEA, 0xE6, 0xE1, 0xDD, 0xD8, 0xD3,
|
||||
0xCF, 0xC9, 0xC4, 0xBF, 0xBA, 0xB5, 0xAF, 0xAA, 0xA5, 0xA0, 0x9B, 0x97, 0x92, 0x8E, 0x89, 0x85,
|
||||
0x81, 0x7E, 0x7B, 0x77, 0x74, 0x71, 0x6F, 0x6C, 0x6A, 0x68, 0x66, 0x64, 0x62, 0x61, 0x5F, 0x5E,
|
||||
0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x59, 0x58, 0x58, 0x57, 0x57, 0x57, 0x56, 0x56, 0x56, 0x55, 0x55,
|
||||
};
|
||||
|
||||
// Full Pulse: Gaussian pulse from dark to full scale intensity
|
||||
const uint8_t gau8_fullPulse[cu16_TemplateLength * 2] = {
|
||||
0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B,
|
||||
0x0D, 0x0E, 0x10, 0x12, 0x14, 0x17, 0x1A, 0x1D, 0x20, 0x23, 0x27, 0x2B, 0x2F, 0x34, 0x39, 0x3E,
|
||||
0x43, 0x49, 0x4F, 0x56, 0x5C, 0x63, 0x6A, 0x71, 0x79, 0x80, 0x88, 0x90, 0x98, 0x9F, 0xA7, 0xAF,
|
||||
0xB7, 0xBE, 0xC5, 0xCC, 0xD3, 0xDA, 0xE0, 0xE5, 0xEA, 0xEF, 0xF3, 0xF6, 0xF9, 0xFC, 0xFD, 0xFE,
|
||||
0xFF, 0xFE, 0xFD, 0xFC, 0xF9, 0xF6, 0xF3, 0xEF, 0xEA, 0xE5, 0xE0, 0xDA, 0xD3, 0xCC, 0xC5, 0xBE,
|
||||
0xB7, 0xAF, 0xA7, 0x9F, 0x98, 0x90, 0x88, 0x80, 0x79, 0x71, 0x6A, 0x63, 0x5C, 0x56, 0x4F, 0x49,
|
||||
0x43, 0x3E, 0x39, 0x34, 0x2F, 0x2B, 0x27, 0x23, 0x20, 0x1D, 0x1A, 0x17, 0x14, 0x12, 0x10, 0x0E,
|
||||
0x0D, 0x0B, 0x0A, 0x08, 0x07, 0x06, 0x05, 0x05, 0x04, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01,
|
||||
|
||||
0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B,
|
||||
0x0D, 0x0E, 0x10, 0x12, 0x14, 0x17, 0x1A, 0x1D, 0x20, 0x23, 0x27, 0x2B, 0x2F, 0x34, 0x39, 0x3E,
|
||||
0x43, 0x49, 0x4F, 0x56, 0x5C, 0x63, 0x6A, 0x71, 0x79, 0x80, 0x88, 0x90, 0x98, 0x9F, 0xA7, 0xAF,
|
||||
0xB7, 0xBE, 0xC5, 0xCC, 0xD3, 0xDA, 0xE0, 0xE5, 0xEA, 0xEF, 0xF3, 0xF6, 0xF9, 0xFC, 0xFD, 0xFE,
|
||||
0xFF, 0xFE, 0xFD, 0xFC, 0xF9, 0xF6, 0xF3, 0xEF, 0xEA, 0xE5, 0xE0, 0xDA, 0xD3, 0xCC, 0xC5, 0xBE,
|
||||
0xB7, 0xAF, 0xA7, 0x9F, 0x98, 0x90, 0x88, 0x80, 0x79, 0x71, 0x6A, 0x63, 0x5C, 0x56, 0x4F, 0x49,
|
||||
0x43, 0x3E, 0x39, 0x34, 0x2F, 0x2B, 0x27, 0x23, 0x20, 0x1D, 0x1A, 0x17, 0x14, 0x12, 0x10, 0x0E,
|
||||
0x0D, 0x0B, 0x0A, 0x08, 0x07, 0x06, 0x05, 0x05, 0x04, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01,
|
||||
};
|
||||
|
||||
// Init Slope: Gaussian slope from dark to background brightness
|
||||
const uint8_t gau8_initSlope[cu16_TemplateLength * 2] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x04,
|
||||
0x04, 0x05, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
|
||||
0x12, 0x13, 0x14, 0x16, 0x17, 0x19, 0x1A, 0x1C, 0x1D, 0x1F, 0x21, 0x22, 0x24, 0x26, 0x27, 0x29,
|
||||
0x2B, 0x2D, 0x2E, 0x30, 0x32, 0x33, 0x35, 0x37, 0x38, 0x3A, 0x3B, 0x3D, 0x3E, 0x40, 0x41, 0x42,
|
||||
0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4C, 0x4D, 0x4E, 0x4E, 0x4F, 0x4F, 0x50,
|
||||
0x50, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, 0x54, 0x54,
|
||||
0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55,
|
||||
};
|
||||
|
||||
// Offset Pulse: Gaussian slope from background illumination to full scale brightness
|
||||
const uint8_t gau8_offsetSlope[cu16_TemplateLength] = {
|
||||
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
|
||||
0x56, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x58, 0x58, 0x59, 0x59, 0x5A, 0x5A, 0x5B, 0x5C, 0x5D,
|
||||
0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x65, 0x66, 0x68, 0x6A, 0x6C, 0x6E, 0x70, 0x72, 0x74, 0x76,
|
||||
0x79, 0x7B, 0x7E, 0x81, 0x84, 0x87, 0x8A, 0x8D, 0x90, 0x93, 0x97, 0x9A, 0x9D, 0xA1, 0xA4, 0xA8,
|
||||
0xAB, 0xAF, 0xB2, 0xB6, 0xB9, 0xBC, 0xC0, 0xC3, 0xC6, 0xC9, 0xCC, 0xCF, 0xD2, 0xD5, 0xD8, 0xDA,
|
||||
0xDD, 0xDF, 0xE1, 0xE3, 0xE5, 0xE7, 0xE9, 0xEB, 0xED, 0xEE, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
|
||||
0xF6, 0xF7, 0xF8, 0xF9, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFC, 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFD,
|
||||
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF,
|
||||
};
|
||||
|
||||
// Full Slope: Gaussian slope from dark to full scale brightness
|
||||
const uint8_t gau8_fullSlope[cu16_TemplateLength] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C,
|
||||
0x0D, 0x0F, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1D, 0x1F, 0x22, 0x25, 0x28, 0x2B, 0x2F, 0x32,
|
||||
0x36, 0x3A, 0x3E, 0x42, 0x46, 0x4B, 0x50, 0x54, 0x59, 0x5E, 0x63, 0x68, 0x6D, 0x72, 0x77, 0x7C,
|
||||
0x82, 0x87, 0x8C, 0x91, 0x96, 0x9B, 0xA0, 0xA5, 0xAA, 0xAE, 0xB3, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
|
||||
0xCC, 0xCF, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE1, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE, 0xEF, 0xF1,
|
||||
0xF2, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF8, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFC, 0xFC, 0xFD, 0xFD,
|
||||
0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF,
|
||||
};
|
||||
|
||||
}// clang-format on
|
||||
@@ -1,5 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "."
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES cmock utils
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @file testUtils.cpp
|
||||
* @author Gustice
|
||||
* @brief Hardware bound unit-tests
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "testable.h"
|
||||
#include "unity.h"
|
||||
#include <limits.h>
|
||||
|
||||
TEST_CASE("No relevant test case", "[utils]") {
|
||||
TEST_ASSERT_EQUAL(0, 0);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
unitTestsFrame()
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* @file TestableUTest.cpp
|
||||
* @author Gustice
|
||||
* @brief
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include "utils.hpp"
|
||||
#include "doctest/doctest.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace SingleActiveTests {
|
||||
|
||||
class Foo : public SingleActive<Foo> {
|
||||
public:
|
||||
int test{};
|
||||
|
||||
Foo() : SingleActive() {}
|
||||
};
|
||||
template <>
|
||||
bool Foo::SingleActive<Foo>::active{};
|
||||
|
||||
class Bar : public SingleActive<Bar> {
|
||||
public:
|
||||
int test{};
|
||||
|
||||
Bar() : SingleActive() {}
|
||||
};
|
||||
template <>
|
||||
bool Bar::SingleActive<Bar>::active{};
|
||||
|
||||
TEST_CASE("Sweet path") {
|
||||
{ // first instances
|
||||
Foo dut;
|
||||
} // destruction
|
||||
|
||||
{ // second instances
|
||||
Foo dut;
|
||||
} // destruction
|
||||
|
||||
{ // third instance
|
||||
Foo dut1;
|
||||
Bar dut2; // First instance of second class
|
||||
} // destruction
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE("Too many instances") {
|
||||
{
|
||||
Foo dut1;
|
||||
Foo *pDut2;
|
||||
CHECK_THROWS_WITH(pDut2 = new Foo, "Instance already active");
|
||||
}
|
||||
CHECK(true);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES freeRtosApi
|
||||
project_bases
|
||||
esp_common
|
||||
esp_driver_gpio
|
||||
esp_driver_i2c
|
||||
)
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* @file i2cPort.hpp
|
||||
* @author Gustice
|
||||
* @brief C++ Wrapper for I2C-interface
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "portBase.hpp"
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief I2C-Master Class
|
||||
* @details More recent implementation of IDF-based I2C master
|
||||
*/
|
||||
class I2cMaster : public PortBase {
|
||||
public:
|
||||
class Slave {
|
||||
public:
|
||||
enum ClockRate {
|
||||
_100k = 100000,
|
||||
_400k = 400000,
|
||||
};
|
||||
|
||||
Slave(I2cMaster &interface, uint8_t addr, ClockRate rate = _100k);
|
||||
~Slave();
|
||||
bool writeTo(const std::vector<uint8_t> &data);
|
||||
std::vector<uint8_t> readFrom(const std::vector<uint8_t> &address, unsigned size);
|
||||
|
||||
protected:
|
||||
const uint8_t Addr;
|
||||
I2cMaster &Interface;
|
||||
i2c_master_dev_handle_t _devHandle;
|
||||
};
|
||||
|
||||
I2cMaster(i2c_port_t port, gpio_num_t sda, gpio_num_t scl);
|
||||
~I2cMaster() = default;
|
||||
|
||||
bool probe(uint8_t addr);
|
||||
|
||||
const i2c_port_t Port;
|
||||
|
||||
private:
|
||||
friend class Slave;
|
||||
i2c_master_bus_handle_t _busHandle;
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* @file pwmExpanderPca9685.hpp
|
||||
* @author Gustice
|
||||
* @brief PWM-Expander PCA9685
|
||||
* @details https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "i2cPort.hpp"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* @brief I2C-Connected LED-Driver with 16 ports
|
||||
* @note This module can also be used to generate Servo-Motor signals
|
||||
*/
|
||||
class PmwExpanderPca9685 {
|
||||
public:
|
||||
static constexpr int PwmChannels = 16;
|
||||
using GrayValues = std::array<uint16_t, PwmChannels>;
|
||||
PmwExpanderPca9685(I2cMaster &port, bool inverted = false, uint8_t address = 0x40); // 0x40 default Address
|
||||
|
||||
void sendImage(GrayValues &grayValues);
|
||||
void reset();
|
||||
void setPwmFreq(float frequency, bool inverted);
|
||||
|
||||
private:
|
||||
I2cMaster::Slave _port;
|
||||
const uint8_t _address;
|
||||
const size_t _count = 16;
|
||||
// 16 LED-Channels with 4 bytes each + 4 byte overhead
|
||||
std::vector<uint8_t> _data;
|
||||
|
||||
bool write(uint8_t reg, uint8_t data);
|
||||
std::optional<uint8_t> read(uint8_t reg);
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* @file i2cPort.cpp
|
||||
* @author Gustice
|
||||
* @brief C++ Wrapper for I2C-interface
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*/
|
||||
|
||||
#include "i2cPort.hpp"
|
||||
#include "esp_log.h"
|
||||
|
||||
const int TxBufDisabled = 0; /*!< I2C master doesn't need buffer */
|
||||
const int RxBufDisabled = 0; /*!< I2C master doesn't need buffer */
|
||||
constexpr int I2C_TOOL_TIMEOUT_VALUE_MS = 50;
|
||||
|
||||
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
|
||||
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
|
||||
|
||||
I2cMaster::I2cMaster(i2c_port_t port, gpio_num_t sda, gpio_num_t scl) : Port(port) {
|
||||
i2c_master_bus_config_t i2c_bus_config{
|
||||
.i2c_port = port,
|
||||
.sda_io_num = sda,
|
||||
.scl_io_num = scl,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
};
|
||||
i2c_bus_config.flags.enable_internal_pullup = 1;
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &_busHandle));
|
||||
}
|
||||
|
||||
bool I2cMaster::probe(uint8_t addr) {
|
||||
esp_err_t ret = i2c_master_probe(_busHandle, addr, I2C_TOOL_TIMEOUT_VALUE_MS);
|
||||
return ret == ESP_OK;
|
||||
}
|
||||
|
||||
I2cMaster::Slave::Slave(I2cMaster &interface, uint8_t addr, ClockRate rate) : Addr(addr), Interface(interface) {
|
||||
i2c_device_config_t i2c_dev_conf{
|
||||
.device_address = addr,
|
||||
.scl_speed_hz = rate,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(interface._busHandle, &i2c_dev_conf, &_devHandle));
|
||||
}
|
||||
|
||||
I2cMaster::Slave::~Slave() {
|
||||
i2c_master_bus_rm_device(_devHandle);
|
||||
}
|
||||
|
||||
bool I2cMaster::Slave::writeTo(const std::vector<uint8_t> &data) {
|
||||
esp_err_t ret = i2c_master_transmit(_devHandle, data.data(), data.size(), I2C_TOOL_TIMEOUT_VALUE_MS);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGW("I2C", "Bus is busy");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
std::vector<uint8_t> I2cMaster::Slave::readFrom(const std::vector<uint8_t> &address, unsigned size) {
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
std::vector<uint8_t> buffer(size);
|
||||
esp_err_t ret = i2c_master_transmit_receive(_devHandle, address.data(), address.size(), buffer.data(), buffer.size(), I2C_TOOL_TIMEOUT_VALUE_MS);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGW("I2C", "Bus is busy");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
constexpr int DisableBuffer = 0; /*!< I2C master doesn't need buffers */
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* @file pwmExpanderPca9685.cpp
|
||||
* @author Gustice
|
||||
* @brief PWM-Expander PCA9685
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "pwmExpanderPca9685.hpp"
|
||||
#include "esp_log.h"
|
||||
#include <cmath>
|
||||
#include <unistd.h> // for usleepCommand
|
||||
|
||||
enum Pca9685Codes {
|
||||
PCA9685_MODE1 = 0x00,
|
||||
PCA9685_MODE2 = 0x01,
|
||||
Led0_ON_L = 0x06,
|
||||
Led0_ON_H = 0x07,
|
||||
Led0_OFF_L = 0x08,
|
||||
Led0_OFF_H = 0x09,
|
||||
// Led1_...Led15
|
||||
AllLedON_L = 0xFA,
|
||||
AllLedON_H = 0xFB,
|
||||
AllLedOFF_L = 0xFC,
|
||||
AllLedOFF_H = 0xFD,
|
||||
PCA9685_PRESCALE = 0xFE,
|
||||
};
|
||||
|
||||
enum Pca9685_Mode1Bits {
|
||||
Mode1_Sleep = 0x10,
|
||||
Mode1_AutoIncrement = 0x20,
|
||||
Mode1_Restart = 0x80,
|
||||
};
|
||||
|
||||
enum Pca9685_Mode2Bits {
|
||||
Mode2_Invert = 0x10,
|
||||
Mode2_OutDriver = 0x04,
|
||||
};
|
||||
|
||||
constexpr int LedPortRegisterWidth = 2 * 2;
|
||||
constexpr int BitResolution = 12;
|
||||
constexpr int PwmMaxValue = 4096 - 1;
|
||||
|
||||
PmwExpanderPca9685::PmwExpanderPca9685(I2cMaster &port, bool inverted, uint8_t address)
|
||||
: _port(port, address, I2cMaster::Slave::_400k), _address(address & 0x7F) {
|
||||
_data.resize(PwmChannels * LedPortRegisterWidth + 1);
|
||||
reset();
|
||||
setPwmFreq(400, inverted);
|
||||
}
|
||||
|
||||
void PmwExpanderPca9685::reset(void) {
|
||||
uint8_t mode = Mode1_Restart | Mode1_AutoIncrement;
|
||||
write(PCA9685_MODE1, mode);
|
||||
usleep(10000); // 10 ms
|
||||
}
|
||||
|
||||
void PmwExpanderPca9685::setPwmFreq(float frequency, bool inverted) {
|
||||
frequency *= 0.9f; // Correct for overshoot in the frequency setting (see issue #11).
|
||||
float prescaleLevel = 25000000;
|
||||
prescaleLevel /= PwmMaxValue;
|
||||
prescaleLevel /= frequency;
|
||||
prescaleLevel -= 1;
|
||||
|
||||
uint8_t prescale = (uint8_t)std::floor(prescaleLevel + 0.5f);
|
||||
uint8_t oldMode = read(PCA9685_MODE1).value_or(0);
|
||||
uint8_t newMode = (oldMode & 0x7F) | Mode1_Sleep; // sleep + Autoincrement
|
||||
write(PCA9685_MODE1, newMode); // go to sleep
|
||||
write(PCA9685_PRESCALE, prescale); // set the prescaler
|
||||
write(PCA9685_MODE1, oldMode);
|
||||
if (inverted) {
|
||||
write(PCA9685_MODE2, Mode2_OutDriver | Mode2_Invert);
|
||||
} else {
|
||||
write(PCA9685_MODE2, Mode2_OutDriver);
|
||||
}
|
||||
|
||||
usleep(5000); // 5 ms
|
||||
// This sets the MODE1 register to turn on auto increment.
|
||||
write(PCA9685_MODE1, oldMode | Mode1_Restart | Mode1_AutoIncrement);
|
||||
}
|
||||
|
||||
void PmwExpanderPca9685::sendImage(GrayValues &grayValues) {
|
||||
// Try to distribute load peaks over full duty cycle
|
||||
// OUT1 ..###______###____###______###____###__..
|
||||
// OUT2 ..__###______###____###______###____###..
|
||||
// OUT3 ..____###______###______###____###_____..
|
||||
// ...
|
||||
int idx = 0;
|
||||
int step = UINT16_MAX / 4 / PwmChannels; // max 90 deg phase shift
|
||||
int curStep = step;
|
||||
_data[idx++] = Led0_ON_L;
|
||||
for (size_t i = 0; i < _count; i++) {
|
||||
curStep = curStep + step;
|
||||
uint16_t dur = (grayValues[i] + curStep) >> 4;
|
||||
uint on = curStep >> 4;
|
||||
// On-Cnt
|
||||
_data[idx++] = (uint8_t)on;
|
||||
_data[idx++] = (uint8_t)(on >> 8);
|
||||
// Off-Cnt
|
||||
_data[idx++] = (uint8_t)dur;
|
||||
_data[idx++] = (uint8_t)(dur >> 8);
|
||||
}
|
||||
_port.writeTo(_data);
|
||||
}
|
||||
|
||||
bool PmwExpanderPca9685::write(uint8_t reg, uint8_t word) {
|
||||
std::vector<uint8_t> data{reg, word};
|
||||
return _port.writeTo(data);
|
||||
}
|
||||
|
||||
std::optional<uint8_t> PmwExpanderPca9685::read(uint8_t reg) {
|
||||
auto value = _port.readFrom({reg}, 1);
|
||||
if (value.size() == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return {value.front()};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES freeRtosApi
|
||||
project_bases
|
||||
board_io
|
||||
esp_common
|
||||
esp_driver_gpio
|
||||
esp_driver_ledc
|
||||
esp_driver_gptimer
|
||||
)
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* @file StepperDrv8833.hpp
|
||||
* @author Gustice
|
||||
* @brief Software-driver for Stepper driver DRV8833
|
||||
* @details https://www.ti.com/lit/ds/symlink/drv8833.pdf?ts=1726872460958&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252Fde-de%252FDRV8833
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "stepperDrv.hpp"
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
class StepperDriverDrv8833 : public StepperDriver {
|
||||
public:
|
||||
struct Pins {
|
||||
ledc_timer_t timer;
|
||||
std::array<ledc_channel_t, 4> channels;
|
||||
std::array<gpio_num_t, 4> pins;
|
||||
};
|
||||
|
||||
StepperDriverDrv8833(Pins pins, int resolution, float fullscale);
|
||||
StepCplx fullStep(int8_t cnt) override;
|
||||
StepCplx step(int8_t cnt) override;
|
||||
StepCplx setPos(uint8_t pos) override;
|
||||
uint8_t getSubPos() {
|
||||
return _position;
|
||||
}
|
||||
|
||||
private:
|
||||
PwmPort _pwmA1;
|
||||
PwmPort _pwmA2;
|
||||
PwmPort _pwmB1;
|
||||
PwmPort _pwmB2;
|
||||
|
||||
std::vector<uint8_t> _outA1;
|
||||
std::vector<uint8_t> _outA2;
|
||||
std::vector<uint8_t> _outB1;
|
||||
std::vector<uint8_t> _outB2;
|
||||
void setOutput(uint8_t pos) override;
|
||||
const uint16_t FullScale;
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* @file gpTimer.hpp
|
||||
* @author Gustice
|
||||
* @brief ESP-hardware-timer class
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
#include "portBase.hpp"
|
||||
|
||||
/**
|
||||
* @brief Base class for tasks
|
||||
*/
|
||||
class HwTimerBase : public PortBase {
|
||||
public:
|
||||
enum Mode {
|
||||
OneShot = 0,
|
||||
Periodic
|
||||
};
|
||||
|
||||
void setup(int thresh, Mode mode);
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
gptimer_handle_t gpTimer{};
|
||||
|
||||
HwTimerBase(gptimer_alarm_cb_t callback);
|
||||
~HwTimerBase();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Simple Timer without callback
|
||||
*/
|
||||
class HwTimer : public HwTimerBase {
|
||||
public:
|
||||
using CallSignature = bool (*)(HwTimer &);
|
||||
|
||||
HwTimer(CallSignature call)
|
||||
: HwTimerBase(CallFrame), _call{call} {
|
||||
}
|
||||
|
||||
private:
|
||||
CallSignature _call;
|
||||
|
||||
static bool IRAM_ATTR CallFrame(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *_this) {
|
||||
auto &tmr = *reinterpret_cast<HwTimer *>(_this);
|
||||
return tmr._call(tmr);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Timer with callback parameter
|
||||
* @tparam T Type of task parameter on call
|
||||
*/
|
||||
template <typename T>
|
||||
class HwTimerT : public HwTimerBase {
|
||||
using CallSignatureT = bool (*)(HwTimerT<T> &, T);
|
||||
|
||||
public:
|
||||
HwTimerT(CallSignatureT call, const T ¶m)
|
||||
: HwTimerBase(CallFrame), _param(call, param) {
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename TS>
|
||||
struct Param {
|
||||
CallSignatureT call;
|
||||
const TS &arg;
|
||||
};
|
||||
static bool IRAM_ATTR CallFrame(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *_this) {
|
||||
auto &tmr = *reinterpret_cast<HwTimerT *>(_this);
|
||||
auto ¶m = tmr._param;
|
||||
return param.call(tmr, param.arg);
|
||||
}
|
||||
Param<T> _param;
|
||||
};
|
||||
@@ -1,333 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "stepperConfig.hpp"
|
||||
#include <Task.hpp>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
class ProfileBuffer {
|
||||
public:
|
||||
static constexpr int SliceSize = 128;
|
||||
static constexpr int BufferSize = 16;
|
||||
using Segment = std::array<uint8_t, SliceSize>;
|
||||
using Buffer = std::array<Segment, BufferSize>;
|
||||
ProfileBuffer() : _semaphore(BufferSize) {}
|
||||
|
||||
void reset() {
|
||||
_idxReader = 0;
|
||||
_idxWriter = 0;
|
||||
_written = 0;
|
||||
_read = 0;
|
||||
_minDiff = BufferSize;
|
||||
_idxProcess = _scratchpad.size() - 1;
|
||||
_writeFinished = false;
|
||||
}
|
||||
|
||||
void append(Segment set, bool isFinal = false) {
|
||||
// _semaphore.give();
|
||||
_writeFinished = isFinal;
|
||||
_scratchpad.at(_idxWriter) = set;
|
||||
_idxWriter++;
|
||||
if (_idxWriter >= _scratchpad.size()) {
|
||||
_idxWriter = 0;
|
||||
}
|
||||
_written++;
|
||||
_minDiff = std::min(_minDiff, _written - _read);
|
||||
}
|
||||
|
||||
const Segment *get() {
|
||||
// _semaphore.take();
|
||||
_idxProcess = _idxReader;
|
||||
_idxReader++;
|
||||
if (_idxReader >= _scratchpad.size()) {
|
||||
_idxReader = 0;
|
||||
}
|
||||
_read++;
|
||||
_minDiff = std::min(_minDiff, _written - _read);
|
||||
return &_scratchpad[_idxProcess];
|
||||
}
|
||||
|
||||
bool reachedFinal() {
|
||||
return _writeFinished && (_read == _written);
|
||||
}
|
||||
|
||||
bool canWrite() {
|
||||
return _idxWriter != _idxProcess;
|
||||
}
|
||||
|
||||
bool canStart() {
|
||||
return (_written > (BufferSize / 2));
|
||||
}
|
||||
|
||||
uint32_t getStats() {
|
||||
return _minDiff;
|
||||
}
|
||||
uint32_t getDiff() {
|
||||
return _written - _read;
|
||||
}
|
||||
uint32_t getRead() {
|
||||
return _read;
|
||||
}
|
||||
|
||||
private:
|
||||
Buffer _scratchpad{};
|
||||
fos::Semaphore _semaphore;
|
||||
uint32_t _idxReader{};
|
||||
uint32_t _idxWriter{};
|
||||
uint32_t _written{};
|
||||
uint32_t _read{};
|
||||
uint32_t _minDiff{BufferSize};
|
||||
uint32_t _idxProcess = _scratchpad.size() - 1;
|
||||
bool _writeFinished{};
|
||||
};
|
||||
|
||||
class ProfileGenerator {
|
||||
public:
|
||||
enum Profile {
|
||||
Smooth = 0,
|
||||
BuildUp,
|
||||
RapidStart,
|
||||
JumpIn,
|
||||
};
|
||||
|
||||
|
||||
ProfileGenerator(StepperConfig &config, ProfileBuffer &buffer, float dT)
|
||||
: _config(config), _transformer(config, buffer), _dT(dT), _aMax(1), _wMax(1) {
|
||||
}
|
||||
|
||||
void setDynamic(float a, float w) {
|
||||
_aMax = a;
|
||||
_wMax = w;
|
||||
}
|
||||
|
||||
void createProfile(Profile profile, float delta) {
|
||||
init();
|
||||
switch (profile) {
|
||||
default:
|
||||
case Profile::Smooth:
|
||||
createSmoothStep(delta);
|
||||
break;
|
||||
case Profile::BuildUp:
|
||||
createBuildUpStep(delta);
|
||||
break;
|
||||
case Profile::RapidStart:
|
||||
createRapidStartSwingInEnd(delta);
|
||||
break;
|
||||
|
||||
case Profile::JumpIn:
|
||||
createJumpInTarget(delta);
|
||||
break;
|
||||
}
|
||||
_transformer.finish();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Point {
|
||||
float p;
|
||||
float w;
|
||||
float t;
|
||||
};
|
||||
class Transformer {
|
||||
public:
|
||||
Transformer(StepperConfig &config, ProfileBuffer &profile)
|
||||
: _config(config), _profile(profile) {
|
||||
add(0);
|
||||
}
|
||||
|
||||
void add(float p) {
|
||||
auto f = p / stp;
|
||||
f = f - std::floor(f);
|
||||
_last = (uint8_t)(f * _config.resolution);
|
||||
_temp[_idx] = _last;
|
||||
_idx++;
|
||||
if (_idx >= _temp.size()) {
|
||||
awaitCanWrite();
|
||||
_profile.append(_temp);
|
||||
ESP_LOGD("Profile", "add slice, canStart %d", _profile.canStart());
|
||||
_idx = 0;
|
||||
}
|
||||
}
|
||||
void finish() {
|
||||
while (_idx < _temp.size()) {
|
||||
_temp[_idx] = _last;
|
||||
_idx++;
|
||||
}
|
||||
awaitCanWrite();
|
||||
_profile.append(_temp, true);
|
||||
ESP_LOGD("Profile", "add final slice");
|
||||
_idx = 0;
|
||||
}
|
||||
|
||||
void awaitCanWrite() {
|
||||
while (!_profile.canWrite()) {
|
||||
fos::Task::delayMs(200);
|
||||
ESP_LOGD("Profile", "waiting... diff=%lu read=%lu canStart %d",
|
||||
_profile.getDiff(), _profile.getRead(), _profile.canStart());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
StepperConfig &_config;
|
||||
ProfileBuffer &_profile;
|
||||
const float stp = _config.stepRad * 4;
|
||||
const float res = stp / _config.resolution;
|
||||
ProfileBuffer::Segment _temp{};
|
||||
uint32_t _idx{};
|
||||
uint8_t _last;
|
||||
};
|
||||
|
||||
StepperConfig &_config;
|
||||
Transformer _transformer;
|
||||
float _dT;
|
||||
float _aMax;
|
||||
float _wMax;
|
||||
|
||||
float _t;
|
||||
// std::vector<float> _pVec;
|
||||
// std::vector<float> _wVec;
|
||||
// std::vector<float> _tVec;
|
||||
float _pBack;
|
||||
float _wBack;
|
||||
float _tBack;
|
||||
|
||||
void createSmoothStep(float delta) {
|
||||
// already solved in simple step
|
||||
smoothSubStep(delta, _aMax, _wMax);
|
||||
}
|
||||
|
||||
void createBuildUpStep(float delta) {
|
||||
// t0..t1: increasing swing
|
||||
buildUpSwing(_aMax / 4, _wMax, 3);
|
||||
auto pnt = getStopDistance(_aMax, _wMax);
|
||||
|
||||
// t1..t2: movement to until almost destination
|
||||
moveUntil(_wMax, delta - pnt.p);
|
||||
// t2..r3: deceleration until destination
|
||||
accelUntil(-_aMax, _wMax, [](Point pnt) { return pnt.w < 0; });
|
||||
}
|
||||
|
||||
void createRapidStartSwingInEnd(float delta) {
|
||||
auto wMax = _wMax;
|
||||
// t0..t1: acceleration to max velocity
|
||||
accelUntil(_aMax * 2, 0, [wMax](Point pnt) { return pnt.w > (wMax * 2); });
|
||||
// t1..r2: deceleration until moving speed
|
||||
accelUntil(-_aMax, _wMax * 2, [wMax](Point pnt) { return pnt.w < wMax; });
|
||||
// t2..t3: movement to until destination (override)
|
||||
moveUntil(_wMax, delta);
|
||||
// t3..t4: deceleration
|
||||
accelUntil(-_aMax, _wMax, [](Point pnt) { return pnt.w < 0; });
|
||||
|
||||
float pCen = delta + (_pBack - delta) / 2;
|
||||
accelUntil(-_aMax, 0, [pCen](Point pnt) { return pnt.p < pCen; });
|
||||
accelUntil(_aMax, _wBack, [](Point pnt) { return pnt.w > 0; });
|
||||
}
|
||||
|
||||
void createJumpInTarget(float delta) {
|
||||
float jumpInRange = std::numbers::pi / 3;
|
||||
float deltaPre = delta - jumpInRange;
|
||||
|
||||
smoothSubStep(deltaPre, _aMax, _wMax);
|
||||
std::vector<float> jumpInSteps{jumpInRange / 2, jumpInRange / 3, jumpInRange / 6};
|
||||
float nextPoint = deltaPre;
|
||||
for (auto &&step : jumpInSteps) {
|
||||
nextPoint = nextPoint + step;
|
||||
smoothSubStep(nextPoint, _aMax * 10, _wMax * 2);
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
_pBack = 0;
|
||||
_wBack = 0;
|
||||
_tBack = 0;
|
||||
_t = 0;
|
||||
}
|
||||
|
||||
void append(Point pnt) {
|
||||
// _pVec.push_back(pnt.p);
|
||||
// _wVec.push_back(pnt.w);
|
||||
// _tVec.push_back(pnt.t);
|
||||
_pBack = pnt.p;
|
||||
_wBack = pnt.w;
|
||||
_transformer.add(pnt.p);
|
||||
}
|
||||
|
||||
void finish() {
|
||||
}
|
||||
|
||||
Point getStopDistance(float a, float w) {
|
||||
float tDec = w / a;
|
||||
float pDec = a * std::pow(tDec, 2) / 2;
|
||||
return {pDec, w, tDec};
|
||||
}
|
||||
|
||||
void accelUntil(float a, float wInit, std::function<bool(Point)> checkCbk) {
|
||||
float t{};
|
||||
auto pX = _pBack;
|
||||
while (true) {
|
||||
float w = wInit + a * t;
|
||||
float p = pX + wInit * t + a * std::pow(t, 2) / 2;
|
||||
if (checkCbk(Point{p, w, t})) {
|
||||
break;
|
||||
}
|
||||
append(Point{p, w, _t + t});
|
||||
t = t + _dT;
|
||||
}
|
||||
_t = _t + t;
|
||||
}
|
||||
|
||||
void moveUntil(float wM, float pEnd) {
|
||||
float t{};
|
||||
auto pX = _pBack;
|
||||
while (true) {
|
||||
float p = pX + wM * t;
|
||||
if (p > pEnd) {
|
||||
break;
|
||||
}
|
||||
append(Point{p, wM, _t + t});
|
||||
t = t + _dT;
|
||||
}
|
||||
_t = _t + t;
|
||||
}
|
||||
|
||||
void buildUpSwing(float a, float vM, int swings) {
|
||||
float t{};
|
||||
auto pX = _pBack;
|
||||
|
||||
float TwMax = vM / a;
|
||||
float tau = 2 * std::numbers::pi;
|
||||
float sTau = swings * tau;
|
||||
|
||||
while (true) {
|
||||
t = t + _dT;
|
||||
float env = a * t;
|
||||
float p = std::sin(sTau * t / TwMax) * env * TwMax / sTau;
|
||||
float w = (p - _pBack) / _dT;
|
||||
if (w > _wMax) {
|
||||
break;
|
||||
}
|
||||
append(Point{p, w, _t + t});
|
||||
}
|
||||
_t = _t + t;
|
||||
}
|
||||
|
||||
void smoothSubStep(float delta, float aM, float wM) {
|
||||
auto pnt = getStopDistance(aM, wM);
|
||||
|
||||
if (pnt.p < delta / 2) {
|
||||
// t0..t1: acceleration to max velocity
|
||||
accelUntil(aM, 0, [wM](Point pnt) { return pnt.w > wM; });
|
||||
// t1..t2: movement to until almost destination
|
||||
moveUntil(wM, delta - pnt.p);
|
||||
// t2..r3: deceleration until destination
|
||||
accelUntil(-aM, wM, [](Point pnt) { return pnt.w < 0; });
|
||||
} else {
|
||||
float pCen = delta + (_pBack - delta) / 2;
|
||||
// t0..t1: acceleration until center is reached
|
||||
accelUntil(-aM, 0, [pCen](Point pnt) { return pnt.p < pCen; });
|
||||
// t2..r3: deceleration until destination
|
||||
accelUntil(-aM, wM, [](Point pnt) { return pnt.w < 0; });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* @file pwmPort.hpp
|
||||
* @author Gustice
|
||||
* @brief C++ Wrapper for PWM-output
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "portBase.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief PWM-Class
|
||||
* @details Provides Write to PWM-Port
|
||||
*/
|
||||
class PwmPort : public PortBase {
|
||||
public:
|
||||
PwmPort(ledc_timer_t timer, ledc_channel_t channel, gpio_num_t output, ledc_timer_bit_t resolution, uint32_t frequency);
|
||||
~PwmPort() = default;
|
||||
|
||||
void write(float duty);
|
||||
void writeRaw(uint16_t duty);
|
||||
|
||||
protected:
|
||||
const uint32_t FullScale;
|
||||
const ledc_timer_t _timer;
|
||||
const ledc_channel_t _channel;
|
||||
const gpio_num_t _port;
|
||||
|
||||
private:
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
|
||||
struct StepperConfig {
|
||||
StepperConfig(float stepDeg, int microsteps)
|
||||
: step(stepDeg), resolution(microsteps), resolutionQuad(resolution / 4) {}
|
||||
|
||||
/// @brief Step-size in degree (e.g. 1.8 deg)
|
||||
float step;
|
||||
/// @brief Step-size in radiant (e.g. 1.8 deg)
|
||||
float stepRad{(float)(std::numbers::pi * step / 180)};
|
||||
/// @brief Number of microsteps for four steps
|
||||
int resolution;
|
||||
/// @brief Number of microsteps for four steps
|
||||
int resolutionQuad;
|
||||
/// @brief Number of Steps for full revolution
|
||||
int steps{std::lround(360.0F / step)};
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @file stepperDrv.hpp
|
||||
* @author Gustice
|
||||
* @brief Abstraction for Stepper driver
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "gpioPort.hpp"
|
||||
#include "pwmPort.hpp"
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
class StepperDriver {
|
||||
public:
|
||||
struct StepCplx {
|
||||
int8_t increment{};
|
||||
uint8_t subPos{};
|
||||
};
|
||||
|
||||
StepperDriver(int resolution)
|
||||
: Resolution(resolution), Quad(Resolution / 4) {}
|
||||
virtual StepCplx fullStep(int8_t cnt) = 0;
|
||||
virtual StepCplx step(int8_t cnt) = 0;
|
||||
virtual StepCplx setPos(uint8_t pos) = 0;
|
||||
uint8_t getSubPos() {
|
||||
return _position;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint8_t Resolution;
|
||||
const uint8_t Quad;
|
||||
int8_t _position{};
|
||||
virtual void setOutput(uint8_t pos) = 0;
|
||||
};
|
||||
@@ -1,305 +0,0 @@
|
||||
/**
|
||||
* @file stepperMotor.hpp
|
||||
* @author Gustice
|
||||
* @brief Stepper Motor device class
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "Semaphore.hpp"
|
||||
#include "gpTimer.hpp"
|
||||
#include "gpioPort.hpp"
|
||||
#include "profileGenerator.hpp"
|
||||
#include "stepperConfig.hpp"
|
||||
#include "stepperDrv.hpp"
|
||||
#include <Task.hpp>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
class StepperMotor {
|
||||
enum Mode {
|
||||
Idle = 0,
|
||||
HomingFromSensor, // if home sensor already active
|
||||
HomingToSensorBegin,
|
||||
HomingToSensorEnd,
|
||||
Homing,
|
||||
Run,
|
||||
CrawlToEnd,
|
||||
StartNew,
|
||||
};
|
||||
|
||||
public:
|
||||
template <int MAX, int PREC>
|
||||
struct Position {
|
||||
uint16_t position{};
|
||||
uint16_t subPos{};
|
||||
Position &operator+(Position &lhs) {
|
||||
auto v1 = getFixPoint();
|
||||
auto v2 = lhs.getFixPoint();
|
||||
auto v = v1 + v2;
|
||||
if (v > (MAX << 16)) {
|
||||
v -= (MAX << 16);
|
||||
}
|
||||
position = getPosition(v);
|
||||
subPos = getSubPos(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Position &operator-(Position &lhs) {
|
||||
auto v1 = getFixPoint();
|
||||
auto v2 = lhs.getFixPoint();
|
||||
auto v = v1 - v2;
|
||||
if (v < (MAX << 16)) {
|
||||
v += (MAX << 16);
|
||||
}
|
||||
position = getPosition(v);
|
||||
subPos = getSubPos(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Position &operator+(StepperDriver::StepCplx &pos) {
|
||||
position += pos.increment;
|
||||
if (position > MAX) {
|
||||
position -= MAX;
|
||||
} else if (position < 0) {
|
||||
position += MAX;
|
||||
} else {
|
||||
// nothing to do here
|
||||
}
|
||||
subPos += pos.subPos;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Position &halv() {
|
||||
auto v1 = getFixPoint();
|
||||
auto v = v1 / 2;
|
||||
position = getPosition(v);
|
||||
subPos = getSubPos(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
float getScaled() {
|
||||
auto scaled = (float)position / MAX;
|
||||
// scaled += (float)subPos / (1 << PREC);
|
||||
return scaled;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t getFixPoint() {
|
||||
int32_t value = (((int32_t)position) << 16) | (subPos << (16 - PREC));
|
||||
return value;
|
||||
}
|
||||
uint16_t getPosition(int32_t value) {
|
||||
auto pos = (value >> 16) & 0xFFFF;
|
||||
return pos;
|
||||
}
|
||||
uint16_t getSubPos(int32_t value) {
|
||||
auto pos = ((value & 0xFFFF) >> (16 - PREC));
|
||||
return pos;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
StepperMotor(StepperConfig &config, StepperDriver &driver, InputPort &initSensor)
|
||||
: _config(config), _driver(driver), _initSensor(initSensor),
|
||||
_timer(TimerCallParam, *this),
|
||||
_buffer{},
|
||||
_generator(_config, _buffer, 0.001F) {
|
||||
_timer.setup(1'000, HwTimerBase::Mode::Periodic);
|
||||
_timer.start();
|
||||
}
|
||||
~StepperMotor() {};
|
||||
|
||||
void findRef() {
|
||||
ESP_LOGI("Stepper", "Homing ...");
|
||||
// if (_initSensor.read()) {
|
||||
// _mode = Mode::HomingFromSensor;
|
||||
// ESP_LOGI("Stepper", "First leave HomePos");
|
||||
// } else {
|
||||
// _mode = Mode::HomingToSensorBegin;
|
||||
// ESP_LOGI("Stepper", "Find start of HomePos");
|
||||
// }
|
||||
// while (_mode != Mode::Idle) {
|
||||
// fos::Task::delayTicks(1);
|
||||
// }
|
||||
|
||||
int fullStep = _config.resolution / 4;
|
||||
_driver.setPos(0);
|
||||
|
||||
if (_initSensor.read()) {
|
||||
ESP_LOGI("Stepper", "Need to move away from home");
|
||||
// MoveAway
|
||||
while (_initSensor.read()) {
|
||||
(void)_driver.step(-fullStep);
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
for (size_t i = 0; i < 8; i++) // Drive another 4 Steps away
|
||||
{
|
||||
(void)_driver.step(-fullStep);
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI("Stepper", "Fast find right side of home sensor");
|
||||
// Find right Endpoint
|
||||
while (not _initSensor.read()) {
|
||||
(void)_driver.step(fullStep);
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
while (_initSensor.read()) { // move away
|
||||
(void)_driver.step(-fullStep);
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
ESP_LOGI("Stepper", "Find right side of home sensor");
|
||||
while (not _initSensor.read()) {
|
||||
(void)_driver.step(1);
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
int pos{};
|
||||
resetPosition();
|
||||
ESP_LOGI("Stepper", "Find end of sensor");
|
||||
while (_initSensor.read()) {
|
||||
(void)_driver.step(1);
|
||||
pos++;
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
for (size_t i = 0; i < 32; i++) // Drive another 4 Steps away
|
||||
{
|
||||
(void)_driver.step(1);
|
||||
pos++;
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
|
||||
ESP_LOGI("Stepper", "Find left side of home sensor");
|
||||
while (not _initSensor.read()) {
|
||||
(void)_driver.step(-1);
|
||||
pos--;
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
|
||||
ESP_LOGI("Stepper", "Move back to mid position %d", pos / 2);
|
||||
for (size_t i = 0; i < pos / 2; i++) {
|
||||
(void)_driver.step(-1);
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
ESP_LOGI("Stepper", "Reset position");
|
||||
resetPosition();
|
||||
}
|
||||
|
||||
void demoFullTurn() {
|
||||
ESP_LOGI("Stepper", "Running %i * %i steps for full turn", _config.resolution, _config.steps);
|
||||
|
||||
ESP_LOGI("Stepper", "Start is %f.2", getScaledPos());
|
||||
for (size_t i = 0; i < _config.resolutionQuad * _config.steps; i++) {
|
||||
auto delta = _driver.step(1);
|
||||
_indexPos = _indexPos + delta;
|
||||
fos::Task::delayTicks(1);
|
||||
}
|
||||
ESP_LOGI("Stepper", "End is %f.2", getScaledPos());
|
||||
}
|
||||
|
||||
void createDemoProfile();
|
||||
void moveBy(float angle);
|
||||
void moveBy(float angle, uint8_t profile, float accel, float speed);
|
||||
|
||||
void newProfile() {
|
||||
_subStep = 0;
|
||||
_mode = StartNew;
|
||||
}
|
||||
|
||||
bool isIdle() {
|
||||
return _mode == Mode::Idle;
|
||||
}
|
||||
|
||||
float getScaledPos() {
|
||||
return _indexPos.getScaled();
|
||||
}
|
||||
|
||||
void resetPosition() {
|
||||
_indexPos = Position<50, 5>();
|
||||
}
|
||||
|
||||
private:
|
||||
StepperConfig &_config;
|
||||
StepperDriver &_driver;
|
||||
InputPort &_initSensor;
|
||||
HwTimerT<StepperMotor &> _timer;
|
||||
Mode _mode{};
|
||||
ProfileBuffer _buffer;
|
||||
ProfileGenerator _generator;
|
||||
int32_t _subStep;
|
||||
const ProfileBuffer::Segment *_pSlice{};
|
||||
|
||||
Position<50, 5> _indexPos{};
|
||||
|
||||
static bool TimerCallParam(HwTimerT<StepperMotor &> &, StepperMotor &stepper) {
|
||||
switch (stepper._mode) {
|
||||
case Mode::HomingFromSensor: {
|
||||
if (stepper._initSensor.read() == false) {
|
||||
stepper._mode = HomingToSensorBegin;
|
||||
break;
|
||||
}
|
||||
(void)stepper._driver.step(1);
|
||||
} break;
|
||||
|
||||
case Mode::HomingToSensorBegin: {
|
||||
if (stepper._initSensor.read()) {
|
||||
stepper._mode = HomingToSensorEnd;
|
||||
stepper._indexPos = {0, stepper._driver.getSubPos()};
|
||||
break;
|
||||
}
|
||||
(void)stepper._driver.step(1);
|
||||
} break;
|
||||
|
||||
case Mode::HomingToSensorEnd: {
|
||||
if (stepper._initSensor.read() == false) {
|
||||
stepper._mode = Homing;
|
||||
stepper._indexPos = stepper._indexPos.halv();
|
||||
break;
|
||||
}
|
||||
auto delta = stepper._driver.step(1);
|
||||
stepper._indexPos = stepper._indexPos + delta;
|
||||
} break;
|
||||
|
||||
case Mode::Homing: {
|
||||
if (stepper._indexPos.position == 0) {
|
||||
stepper._mode = Idle;
|
||||
break;
|
||||
}
|
||||
auto delta = stepper._driver.step(-1);
|
||||
stepper._indexPos = stepper._indexPos + delta;
|
||||
} break;
|
||||
|
||||
case Mode::Run: {
|
||||
auto stp = stepper._pSlice->at(stepper._subStep);
|
||||
stepper._subStep++;
|
||||
if (stepper._subStep >= stepper._pSlice->size()) {
|
||||
stepper._subStep = 0;
|
||||
if (stepper._buffer.reachedFinal()) {
|
||||
stepper._mode = Idle;
|
||||
} else {
|
||||
stepper._pSlice = stepper._buffer.get();
|
||||
}
|
||||
}
|
||||
auto delta = stepper._driver.setPos(stp);
|
||||
stepper._indexPos = stepper._indexPos + delta;
|
||||
} break;
|
||||
|
||||
case Mode::StartNew: {
|
||||
if (stepper._buffer.canStart()) {
|
||||
stepper._mode = Run;
|
||||
stepper._pSlice = stepper._buffer.get();
|
||||
stepper._subStep = 0;
|
||||
}
|
||||
}
|
||||
|
||||
case Mode::Idle:
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* @file gpTimer.cpp
|
||||
* @author Gustice
|
||||
* @brief HW-Timer class implementation
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "gpTimer.hpp"
|
||||
#include "esp_log.h"
|
||||
|
||||
HwTimerBase::HwTimerBase(gptimer_alarm_cb_t callback) {
|
||||
ESP_LOGI("TMR", "Create timer handle");
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick=1us
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gpTimer));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = callback,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gpTimer, &cbs, this));
|
||||
}
|
||||
|
||||
void HwTimerBase::setup(int thresh, Mode mode) {
|
||||
ESP_LOGI("TMR", "Setup timer");
|
||||
ESP_ERROR_CHECK(gptimer_enable(gpTimer));
|
||||
|
||||
gptimer_alarm_config_t cfg{};
|
||||
|
||||
switch (mode) {
|
||||
case Mode::OneShot:
|
||||
cfg.alarm_count = thresh;
|
||||
break;
|
||||
case Mode::Periodic:
|
||||
cfg.reload_count = 0,
|
||||
cfg.alarm_count = thresh;
|
||||
cfg.flags.auto_reload_on_alarm = true;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE("TMR", "Invalid mode %d", mode);
|
||||
break;
|
||||
}
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gpTimer, &cfg));
|
||||
}
|
||||
|
||||
void HwTimerBase::start() {
|
||||
ESP_ERROR_CHECK(gptimer_start(gpTimer));
|
||||
}
|
||||
|
||||
void HwTimerBase::stop() {
|
||||
ESP_LOGI("TMR", "Stop timer");
|
||||
ESP_ERROR_CHECK(gptimer_stop(gpTimer));
|
||||
}
|
||||
|
||||
HwTimerBase::~HwTimerBase() {
|
||||
stop();
|
||||
ESP_LOGI("TMR", "Disable timer");
|
||||
ESP_ERROR_CHECK(gptimer_disable(gpTimer));
|
||||
ESP_LOGI("TMR", "Delete timer");
|
||||
ESP_ERROR_CHECK(gptimer_del_timer(gpTimer));
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* @file pwmPort.cpp
|
||||
* @author Gustice
|
||||
* @brief C++ Wrapper for PWM-output
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "pwmPort.hpp"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
PwmPort::PwmPort(ledc_timer_t timer, ledc_channel_t channel, gpio_num_t output, ledc_timer_bit_t resolution, uint32_t frequency)
|
||||
: FullScale((1 << resolution)), _timer(timer), _channel(channel), _port(output) {
|
||||
ledc_timer_config_t ledc_timer = {
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
.duty_resolution = resolution,
|
||||
.timer_num = timer,
|
||||
.freq_hz = frequency,
|
||||
.clk_cfg = LEDC_AUTO_CLK
|
||||
};
|
||||
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
|
||||
|
||||
// Prepare and then apply the LEDC PWM channel configuration
|
||||
ledc_channel_config_t ledc_channel = {
|
||||
.gpio_num = output,
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
.channel = channel,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.timer_sel = timer,
|
||||
.duty = 0,
|
||||
.hpoint = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
|
||||
}
|
||||
|
||||
void PwmPort::write(float duty) {
|
||||
// duty <= 1.0F;
|
||||
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, _channel, duty * FullScale));
|
||||
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, _channel));
|
||||
}
|
||||
|
||||
void PwmPort::writeRaw(uint16_t duty) {
|
||||
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, _channel, duty));
|
||||
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, _channel));
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/**
|
||||
* @file stepperDrv8833.cpp
|
||||
* @author Gustice
|
||||
* @brief Software-driver for Stepper driver DRV8833
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "stepperDrv8833.hpp"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
|
||||
void populateLookUps(int resolution,
|
||||
std::vector<uint8_t> &a1, std::vector<uint8_t> &a2,
|
||||
std::vector<uint8_t> &b1, std::vector<uint8_t> &b2,
|
||||
uint16_t fullscale)
|
||||
{
|
||||
float step = 2.0F * std::numbers::pi / resolution;
|
||||
const uint16_t Maximum = 1 << LEDC_TIMER_7_BIT;
|
||||
|
||||
for (size_t i = 0; i < resolution; i++) {
|
||||
|
||||
auto cos = std::cos(step * i);
|
||||
if (cos > 0)
|
||||
{
|
||||
// 1 : 0 = Forward
|
||||
// 1 : PWM = Forward PWM, slow decay
|
||||
a1.push_back(Maximum);
|
||||
a2.push_back(Maximum - (cos * fullscale));
|
||||
} else {
|
||||
// 0 : 1 = Reverse
|
||||
// PWM : 1 = Reverse PWM, slow decay
|
||||
a1.push_back(Maximum - (-cos * fullscale));
|
||||
a2.push_back(Maximum);
|
||||
}
|
||||
|
||||
auto sin = std::sin(step * i);
|
||||
if (sin > 0)
|
||||
{
|
||||
// 1 : 0 = Forward
|
||||
// 1 : PWM = Forward PWM, slow decay
|
||||
b1.push_back(Maximum);
|
||||
b2.push_back(Maximum - (sin * fullscale));
|
||||
} else {
|
||||
// 0 : 1 = Reverse
|
||||
// PWM : 1 = Reverse PWM, slow decay
|
||||
b1.push_back(Maximum - ((-sin * fullscale)));
|
||||
b2.push_back(Maximum);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
StepperDriverDrv8833::StepperDriverDrv8833(Pins pins, int resolution, float fullscale)
|
||||
: StepperDriver(resolution),
|
||||
_pwmA1(pins.timer, pins.channels[0], pins.pins[0], LEDC_TIMER_7_BIT, 20000),
|
||||
_pwmA2(pins.timer, pins.channels[1], pins.pins[1], LEDC_TIMER_7_BIT, 20000),
|
||||
_pwmB1(pins.timer, pins.channels[2], pins.pins[2], LEDC_TIMER_7_BIT, 20000),
|
||||
_pwmB2(pins.timer, pins.channels[3], pins.pins[3], LEDC_TIMER_7_BIT, 20000),
|
||||
FullScale((1 << LEDC_TIMER_7_BIT) * std::max(0.0F, std::min(1.0F, fullscale)))
|
||||
{
|
||||
if ((Resolution % 4) != 0) {
|
||||
ESP_LOGE("Stepper", "Resolution must be multiple of 4");
|
||||
}
|
||||
|
||||
populateLookUps(resolution, _outA1, _outA2, _outB1, _outB2, FullScale);
|
||||
|
||||
ESP_LOGD("Stepper", "Output Data:");
|
||||
ESP_LOG_BUFFER_HEXDUMP("Stepper", _outA1.data(), _outA1.size(), ESP_LOG_DEBUG);
|
||||
ESP_LOG_BUFFER_HEXDUMP("Stepper", _outA2.data(), _outA2.size(), ESP_LOG_DEBUG);
|
||||
ESP_LOG_BUFFER_HEXDUMP("Stepper", _outB1.data(), _outB1.size(), ESP_LOG_DEBUG);
|
||||
ESP_LOG_BUFFER_HEXDUMP("Stepper", _outB2.data(), _outB2.size(), ESP_LOG_DEBUG);
|
||||
|
||||
step(0);
|
||||
}
|
||||
|
||||
StepperDriverDrv8833::StepCplx StepperDriverDrv8833::fullStep(int8_t cnt) {
|
||||
if (cnt > 0) {
|
||||
cnt = 1;
|
||||
} else {
|
||||
cnt = -1;
|
||||
}
|
||||
|
||||
int pos = (_position / Quad + cnt) % 4;
|
||||
pos = pos * Quad;
|
||||
return setPos(pos);
|
||||
}
|
||||
|
||||
StepperDriverDrv8833::StepCplx StepperDriverDrv8833::step(int8_t cnt) {
|
||||
if (cnt == 0) {
|
||||
return {0, (uint8_t)_position};
|
||||
}
|
||||
if (cnt > Resolution) {
|
||||
// ESP_LOGE("Stepper", "microstep exceeds four steps");
|
||||
}
|
||||
if (cnt > Quad) {
|
||||
// ESP_LOGW("Stepper", "microstep exceeds quadrature step");
|
||||
}
|
||||
|
||||
int8_t increment{};
|
||||
auto pos = _position + cnt;
|
||||
if (pos < 0) {
|
||||
increment--;
|
||||
pos += Resolution;
|
||||
} else if (pos >= Resolution) {
|
||||
increment++;
|
||||
pos -= Resolution;
|
||||
} else {
|
||||
// in that case ... nothing
|
||||
}
|
||||
setOutput(pos);
|
||||
return {increment, (uint8_t)_position};
|
||||
}
|
||||
|
||||
StepperDriverDrv8833::StepCplx StepperDriverDrv8833::setPos(uint8_t pos) {
|
||||
pos = (pos) % Resolution;
|
||||
|
||||
int8_t increment{};
|
||||
if (pos - _position < 0) {
|
||||
increment--;
|
||||
} else if (pos - _position > Resolution) {
|
||||
increment++;
|
||||
} else {
|
||||
// in that case ... nothing
|
||||
}
|
||||
|
||||
setOutput(pos);
|
||||
return {increment, (uint8_t)_position};
|
||||
}
|
||||
|
||||
void StepperDriverDrv8833::setOutput(uint8_t pos) {
|
||||
if (pos >= Resolution) {
|
||||
// ESP_LOGE("Stepper", "d exceeds micro-stepping size");
|
||||
}
|
||||
_position = pos;
|
||||
_pwmA1.writeRaw(_outA1[pos]);
|
||||
_pwmA2.writeRaw(_outA2[pos]);
|
||||
_pwmB1.writeRaw(_outB1[pos]);
|
||||
_pwmB2.writeRaw(_outB2[pos]);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @brief stepperMotor.cpp
|
||||
* @author Gustice
|
||||
* @brief Stepper Motor device class
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "stepperMotor.hpp"
|
||||
#include "esp_log.h"
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
void StepperMotor::moveBy(float angle) {
|
||||
_buffer.reset();
|
||||
newProfile();
|
||||
_generator.createProfile(ProfileGenerator::Profile::Smooth, angle);
|
||||
// ESP_LOGD("Stepper", "TookTicks for %d data points", profile.steps.size());
|
||||
// ESP_LOG_BUFFER_HEXDUMP("Stepper", profile.steps.data(), profile.steps.size(), ESP_LOG_DEBUG);
|
||||
}
|
||||
|
||||
void StepperMotor::moveBy(float angle, uint8_t idx, float accel, float speed) {
|
||||
// long start = xTaskGetTickCount();
|
||||
ESP_LOGI("Stepper", "Running %.1f with prof=%i a=%.2f w=%.2f",
|
||||
angle * 3 / std::numbers::pi, idx, accel, speed);
|
||||
|
||||
_buffer.reset();
|
||||
newProfile();
|
||||
_generator.setDynamic(accel, speed);
|
||||
_generator.createProfile(static_cast<ProfileGenerator::Profile>(idx), angle);
|
||||
|
||||
while (!isIdle())
|
||||
{
|
||||
fos::Task::delayMs(200);
|
||||
}
|
||||
ESP_LOGI("Profile", "processing finished ...");
|
||||
|
||||
// long ticks = xTaskGetTickCount() - start;
|
||||
// ESP_LOGD("Stepper", "TookTicks %lu ( %lu ms) for %d data points", ticks, ticks * portTICK_PERIOD_MS, profile.steps.size());
|
||||
// ESP_LOG_BUFFER_HEXDUMP("Stepper", profile.steps.data(), profile.steps.size(), ESP_LOG_DEBUG);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES project_bases nvs_flash esp_common
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* @file nvsFunctions.hpp
|
||||
* @author Gustice
|
||||
* @brief NVS-Diagnosis utility
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
class NvsApi {
|
||||
|
||||
public:
|
||||
/// @brief Init and mount file system
|
||||
/// @param mount rooted SubType of partition eg "nvs"
|
||||
static void init(std::string mount = std::string{"nvs"});
|
||||
|
||||
/// @brief Get instance, ensure initialization
|
||||
/// @param mount rooted SubType of partition eg "nvs"
|
||||
/// @return Instance reference
|
||||
static NvsApi &getInstance(std::string mount = std::string{"nvs"});
|
||||
|
||||
void setValue(const std::string &key, const std::string &value);
|
||||
std::string getValue(const std::string &key);
|
||||
|
||||
// Disable move and assignment
|
||||
NvsApi(NvsApi &other) = delete;
|
||||
void operator=(const NvsApi &) = delete;
|
||||
|
||||
protected:
|
||||
NvsApi(std::string mount);
|
||||
~NvsApi();
|
||||
|
||||
private:
|
||||
static NvsApi *_instance;
|
||||
const std::string _mountingPoint;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @file nvsDefines.hpp
|
||||
* @author Gustice
|
||||
* @brief Defines and record types
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "nvs.h"
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
static std::tuple<
|
||||
std::map<nvs_type_t, const char *>,
|
||||
std::map<std::string, nvs_type_t>>
|
||||
CreateNvsDictionaries() {
|
||||
std::array<std::pair<nvs_type_t, const char *>, 11> defines{
|
||||
{{NVS_TYPE_I8, "i8"},
|
||||
{NVS_TYPE_U8, "u8"},
|
||||
{NVS_TYPE_U16, "u16"},
|
||||
{NVS_TYPE_I16, "i16"},
|
||||
{NVS_TYPE_U32, "u32"},
|
||||
{NVS_TYPE_I32, "i32"},
|
||||
{NVS_TYPE_U64, "u64"},
|
||||
{NVS_TYPE_I64, "i64"},
|
||||
{NVS_TYPE_STR, "str"},
|
||||
{NVS_TYPE_BLOB, "blob"},
|
||||
{NVS_TYPE_ANY, "any"}}};
|
||||
|
||||
std::map<nvs_type_t, const char *> k2s;
|
||||
std::map<std::string, nvs_type_t> s2k;
|
||||
|
||||
for (auto &&e : defines) {
|
||||
k2s.emplace(e.first, e.second);
|
||||
s2k.emplace(e.second, e.first);
|
||||
}
|
||||
return {k2s, s2k};
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
/**
|
||||
* @file cmd_nvs.cpp
|
||||
* @author Gustice
|
||||
* @brief NVS-Diagnosis utility (derived from ESP-IDF Console-Example)
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "nvsFunctions.hpp"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "nvs.h"
|
||||
#include "nvsIo.hpp"
|
||||
#include "nvs_flash.h"
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nvsDefines.hpp"
|
||||
|
||||
static void initializeNvs() {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// 1.OTA app partition table has a smaller NVS partition size than the non-OTA
|
||||
// partition table. This size mismatch may cause NVS initialization to fail.
|
||||
// 2.NVS partition contains data in new format and cannot be recognized by this version of code.
|
||||
// If this happens, we erase NVS partition and initialize NVS again.
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
|
||||
NvsApi *NvsApi::_instance = nullptr;
|
||||
|
||||
NvsApi::NvsApi(std::string mount) : _mountingPoint(mount) {
|
||||
initializeNvs();
|
||||
}
|
||||
|
||||
NvsApi::~NvsApi() {
|
||||
}
|
||||
|
||||
NvsApi &NvsApi::getInstance(std::string mount) {
|
||||
if (_instance == nullptr) {
|
||||
init(mount);
|
||||
}
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
void NvsApi::init(std::string mount) {
|
||||
if (_instance != nullptr) {
|
||||
return;
|
||||
}
|
||||
_instance = new NvsApi(mount);
|
||||
}
|
||||
|
||||
const auto [TypeStrMap, StrTypeMap] = CreateNvsDictionaries();
|
||||
|
||||
static char activeNameSpace[16] = "storage";
|
||||
static const char *TAG = "NVS";
|
||||
|
||||
static nvs_type_t confStr2Type(const char *type) {
|
||||
if (auto search = StrTypeMap.find(type); search != StrTypeMap.end()) {
|
||||
return search->second;
|
||||
}
|
||||
ESP_LOGW(TAG, "type '%s' not found", type);
|
||||
return NVS_TYPE_ANY;
|
||||
}
|
||||
|
||||
static const char *confType2Str(nvs_type_t type) {
|
||||
if (auto search = TypeStrMap.find(type); search != TypeStrMap.end()) {
|
||||
return search->second;
|
||||
}
|
||||
ESP_LOGW(TAG, "type %d not found", type);
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
struct SetArgs {
|
||||
struct arg_str *key;
|
||||
struct arg_str *type;
|
||||
struct arg_str *value;
|
||||
struct arg_end *end;
|
||||
};
|
||||
|
||||
struct GetArgs {
|
||||
struct arg_str *key;
|
||||
struct arg_str *type;
|
||||
struct arg_end *end;
|
||||
};
|
||||
|
||||
struct EraseArgs {
|
||||
struct arg_str *key;
|
||||
struct arg_end *end;
|
||||
};
|
||||
|
||||
struct EraseNameSpaceArgs {
|
||||
struct arg_str *nameSpace;
|
||||
struct arg_end *end;
|
||||
};
|
||||
|
||||
struct NameSpaceArgs {
|
||||
struct arg_str *nameSpace;
|
||||
struct arg_end *end;
|
||||
};
|
||||
|
||||
struct ListArgs {
|
||||
struct arg_str *partition;
|
||||
struct arg_str *nameSpace;
|
||||
struct arg_str *type;
|
||||
struct arg_end *end;
|
||||
};
|
||||
|
||||
static esp_err_t setValueInNvs(const char *key, const char *strType, const char *strValue) {
|
||||
nvs_type_t type = confStr2Type(strType);
|
||||
if (type == NVS_TYPE_ANY) {
|
||||
ESP_LOGE(TAG, "Type '%s' is undefined", strType);
|
||||
return ESP_ERR_NVS_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
try {
|
||||
NvsIo nvs(activeNameSpace, NVS_READWRITE);
|
||||
|
||||
esp_err_t err = ESP_FAIL;
|
||||
if (type == NVS_TYPE_I8) {
|
||||
err = nvs.setValueByStr<int8_t>(key, strValue);
|
||||
} else if (type == NVS_TYPE_U8) {
|
||||
err = nvs.setValueByStr<uint8_t>(key, strValue);
|
||||
} else if (type == NVS_TYPE_I16) {
|
||||
err = nvs.setValueByStr<int16_t>(key, strValue);
|
||||
} else if (type == NVS_TYPE_U16) {
|
||||
err = nvs.setValueByStr<uint16_t>(key, strValue);
|
||||
} else if (type == NVS_TYPE_I32) {
|
||||
err = nvs.setValueByStr<int32_t>(key, strValue);
|
||||
} else if (type == NVS_TYPE_U32) {
|
||||
err = nvs.setValueByStr<uint32_t>(key, strValue);
|
||||
} else if (type == NVS_TYPE_I64) {
|
||||
err = nvs.setValueByStr<int64_t>(key, strValue);
|
||||
} else if (type == NVS_TYPE_U64) {
|
||||
err = nvs.setValueByStr<uint64_t>(key, strValue);
|
||||
} else if (type == NVS_TYPE_STR) {
|
||||
err = nvs.setString(key, strValue);
|
||||
} else if (type == NVS_TYPE_BLOB) {
|
||||
err = nvs.setBlob(key, strValue);
|
||||
}
|
||||
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = nvs.commit();
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Value stored under key '%s'", key);
|
||||
}
|
||||
return ESP_OK;
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE(TAG, "%s", e.what());
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
void NvsApi::setValue(const std::string &key, const std::string &value) {
|
||||
if (setValueInNvs(key.c_str(), "str", value.c_str()) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "setValue failed");
|
||||
}
|
||||
}
|
||||
|
||||
std::string NvsApi::getValue(const std::string &key) {
|
||||
try {
|
||||
NvsIo nvs(activeNameSpace, NVS_READONLY);
|
||||
auto opt = nvs.getString(key.c_str());
|
||||
auto value = opt.value();
|
||||
return value;
|
||||
} catch (const std::bad_optional_access &e) {
|
||||
ESP_LOGE(TAG, "Value '%s' not defined", key.c_str());
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE(TAG, "%s", e.what());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static esp_err_t showValueFromNvs(const char *key, const char *strType) {
|
||||
nvs_type_t type = confStr2Type(strType);
|
||||
if (type == NVS_TYPE_ANY) {
|
||||
ESP_LOGE(TAG, "Type '%s' is undefined", strType);
|
||||
return ESP_ERR_NVS_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
try {
|
||||
NvsIo nvs(activeNameSpace, NVS_READONLY);
|
||||
|
||||
if (type == NVS_TYPE_I8) {
|
||||
auto opt = nvs.getInt8(key);
|
||||
printf("%d\n", opt.value());
|
||||
} else if (type == NVS_TYPE_U8) {
|
||||
auto opt = nvs.getUint8(key);
|
||||
printf("%u\n", opt.value());
|
||||
} else if (type == NVS_TYPE_I16) {
|
||||
auto opt = nvs.getInt16(key);
|
||||
printf("%u\n", opt.value());
|
||||
} else if (type == NVS_TYPE_U16) {
|
||||
auto opt = nvs.getUint16(key);
|
||||
printf("%u\n", opt.value());
|
||||
} else if (type == NVS_TYPE_I32) {
|
||||
auto opt = nvs.getInt32(key);
|
||||
printf("%" PRIi32 "\n", opt.value());
|
||||
} else if (type == NVS_TYPE_U32) {
|
||||
auto opt = nvs.getUint32(key);
|
||||
printf("%" PRIu32 "\n", opt.value());
|
||||
} else if (type == NVS_TYPE_I64) {
|
||||
auto opt = nvs.getInt64(key);
|
||||
printf("%lld\n", opt.value());
|
||||
} else if (type == NVS_TYPE_U64) {
|
||||
auto opt = nvs.getUint64(key);
|
||||
printf("%llu\n", opt.value());
|
||||
} else if (type == NVS_TYPE_STR) {
|
||||
auto opt = nvs.getString(key);
|
||||
printf("%s\n", opt.value().c_str());
|
||||
} else if (type == NVS_TYPE_BLOB) {
|
||||
auto opt = nvs.getBlob(key);
|
||||
auto value = opt.value();
|
||||
for (int i = 0; i < value.size(); i++) {
|
||||
printf("%02x", value[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
return ESP_OK;
|
||||
} catch (const std::bad_optional_access &e) {
|
||||
ESP_LOGE(TAG, "%s", "Value not defined");
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE(TAG, "%s", e.what());
|
||||
}
|
||||
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t erase(const char *key) {
|
||||
try {
|
||||
NvsIo nvs(activeNameSpace, NVS_READWRITE);
|
||||
|
||||
esp_err_t err = nvs.erase(key);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = nvs.commit();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Value with key '%s' erased", key);
|
||||
return ESP_OK;
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE(TAG, "%s", e.what());
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t eraseAllIn(const char *name) {
|
||||
try {
|
||||
NvsIo nvs(name, NVS_READWRITE);
|
||||
|
||||
esp_err_t err = nvs.clear();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = nvs.commit();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
ESP_LOGI(TAG, "Namespace '%s' was %s erased", name, (err == ESP_OK) ? "" : "not");
|
||||
|
||||
return ESP_OK;
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE(TAG, "%s", e.what());
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static int list(const char *part, const char *name, const char *strType) {
|
||||
nvs_type_t type = confStr2Type(strType);
|
||||
|
||||
nvs_iterator_t it = NULL;
|
||||
esp_err_t result = nvs_entry_find(part, NULL, type, &it);
|
||||
if (result == ESP_ERR_NVS_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "No such entry was found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "NVS error: %s", esp_err_to_name(result));
|
||||
return 1;
|
||||
}
|
||||
|
||||
do {
|
||||
nvs_entry_info_t info;
|
||||
nvs_entry_info(it, &info);
|
||||
result = nvs_entry_next(&it);
|
||||
|
||||
if ((strlen(name) > 0) && (strcmp(info.namespace_name, name) != 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("namespace '%s', key '%s', type '%s' \n",
|
||||
info.namespace_name, info.key, confType2Str(info.type));
|
||||
} while (result == ESP_OK);
|
||||
|
||||
if (result != ESP_ERR_NVS_NOT_FOUND) { // the last iteration ran into an internal error
|
||||
ESP_LOGE(TAG, "NVS error %s at current iteration, stopping.", esp_err_to_name(result));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/**
|
||||
* @file nvsIo.cpp
|
||||
* @author Gustice
|
||||
* @brief Read-Write access to specific NVS partition
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "nvsIo.hpp"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
static const char *TAG = "NVS";
|
||||
|
||||
static esp_err_t storeBlob(nvs_handle_t nvs, const char *key, const char *str_values) {
|
||||
uint8_t value;
|
||||
size_t str_len = strlen(str_values);
|
||||
size_t blob_len = str_len / 2;
|
||||
|
||||
if (str_len % 2) {
|
||||
ESP_LOGE(TAG, "Blob data must contain even number of characters");
|
||||
return ESP_ERR_NVS_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
char *blob = (char *)malloc(blob_len);
|
||||
if (blob == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
for (int i = 0, j = 0; i < str_len; i++) {
|
||||
char ch = str_values[i];
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
value = ch - '0';
|
||||
} else if (ch >= 'A' && ch <= 'F') {
|
||||
value = ch - 'A' + 10;
|
||||
} else if (ch >= 'a' && ch <= 'f') {
|
||||
value = ch - 'a' + 10;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Blob data contain invalid character");
|
||||
free(blob);
|
||||
return ESP_ERR_NVS_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
if (i & 1) {
|
||||
blob[j++] += value;
|
||||
} else {
|
||||
blob[j] = value << 4;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t err = nvs_set_blob(nvs, key, blob, blob_len);
|
||||
free(blob);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(nvs);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<int8_t>(const char *key, const char *strValue) {
|
||||
int32_t value = strtol(strValue, NULL, 0);
|
||||
if (value < INT8_MIN || value > INT8_MAX || errno == ERANGE) {
|
||||
throw CoreException(ErrMsg_TooLong);
|
||||
}
|
||||
return setInt8(key, (int8_t)value);
|
||||
}
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<uint8_t>(const char *key, const char *strValue) {
|
||||
uint32_t value = strtoul(strValue, NULL, 0);
|
||||
if (value > UINT8_MAX || errno == ERANGE) {
|
||||
throw CoreException(ErrMsg_TooLong);
|
||||
}
|
||||
return setUint8(key, (uint8_t)value);
|
||||
}
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<int16_t>(const char *key, const char *strValue) {
|
||||
int32_t value = strtol(strValue, NULL, 0);
|
||||
if (value < INT16_MIN || value > INT16_MAX || errno == ERANGE) {
|
||||
throw CoreException(ErrMsg_TooLong);
|
||||
}
|
||||
return setInt16(key, (int16_t)value);
|
||||
}
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<uint16_t>(const char *key, const char *strValue) {
|
||||
uint32_t value = strtoul(strValue, NULL, 0);
|
||||
if (value > UINT16_MAX || errno == ERANGE) {
|
||||
throw CoreException(ErrMsg_TooLong);
|
||||
}
|
||||
return setUint16(key, (uint16_t)value);
|
||||
}
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<int32_t>(const char *key, const char *strValue) {
|
||||
int32_t value = strtol(strValue, NULL, 0);
|
||||
if (errno == ERANGE) {
|
||||
throw CoreException(ErrMsg_TooLong);
|
||||
}
|
||||
return setInt32(key, value);
|
||||
}
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<uint32_t>(const char *key, const char *strValue) {
|
||||
uint32_t value = strtoul(strValue, NULL, 0);
|
||||
if (errno == ERANGE) {
|
||||
throw CoreException(ErrMsg_TooLong);
|
||||
}
|
||||
return setUint32(key, value);
|
||||
}
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<int64_t>(const char *key, const char *strValue) {
|
||||
int64_t value = strtoll(strValue, NULL, 0);
|
||||
if (errno == ERANGE) {
|
||||
throw CoreException(ErrMsg_TooLong);
|
||||
}
|
||||
return setInt64(key, value);
|
||||
}
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<uint64_t>(const char *key, const char *strValue) {
|
||||
uint64_t value = strtoull(strValue, NULL, 0);
|
||||
if (errno == ERANGE) {
|
||||
throw CoreException(ErrMsg_TooLong);
|
||||
}
|
||||
return setUint64(key, value);
|
||||
}
|
||||
|
||||
esp_err_t NvsIo::setString(const char *key, const char *strValue) {
|
||||
return nvs_set_str(_nvs, key, strValue);
|
||||
}
|
||||
esp_err_t NvsIo::setBlob(const char *key, const char *strValue) {
|
||||
return storeBlob(_nvs, key, strValue);
|
||||
}
|
||||
|
||||
std::optional<std::string> NvsIo::getString(const char *key) {
|
||||
size_t len;
|
||||
if (nvs_get_str(_nvs, key, NULL, &len) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
auto str = std::make_unique<char[]>(len);
|
||||
if ((nvs_get_str(_nvs, key, str.get(), &len)) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {str.get()};
|
||||
}
|
||||
|
||||
std::optional<std::vector<char>> NvsIo::getBlob(const char *key) {
|
||||
size_t len;
|
||||
if (nvs_get_blob(_nvs, key, NULL, &len) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
auto blob = std::make_unique<char[]>(len);
|
||||
if (nvs_get_blob(_nvs, key, blob.get(), &len) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
std::vector<char> vec;
|
||||
vec.reserve(len);
|
||||
vec.insert(vec.end(), blob.get(), blob.get() + len);
|
||||
return {vec};
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/**
|
||||
* @file nvsIo.hpp
|
||||
* @author Gustice
|
||||
* @brief Read-Write access to specific NVS partition
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "nvs.h"
|
||||
#include "coreExcept.hpp"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class NvsIo {
|
||||
const char *ErrMsg_TooLong = "Value is too long";
|
||||
|
||||
public:
|
||||
NvsIo(const char *nameSpace, nvs_open_mode_t mode) {
|
||||
if (nvs_open(nameSpace, mode, &_nvs) != ESP_OK) {
|
||||
throw CoreException("Unable to open NVS");
|
||||
}
|
||||
}
|
||||
~NvsIo() {
|
||||
nvs_close(_nvs);
|
||||
}
|
||||
esp_err_t commit() {
|
||||
return nvs_commit(_nvs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
esp_err_t setValueByStr(const char *key, const char *strValue);
|
||||
|
||||
esp_err_t setInt8(const char *key, int8_t value) {
|
||||
return nvs_set_i8(_nvs, key, value);
|
||||
}
|
||||
esp_err_t setUint8(const char *key, uint8_t value) {
|
||||
return nvs_set_u8(_nvs, key, value);
|
||||
}
|
||||
esp_err_t setInt16(const char *key, int16_t value) {
|
||||
return nvs_set_i16(_nvs, key, value);
|
||||
}
|
||||
esp_err_t setUint16(const char *key, uint16_t value) {
|
||||
return nvs_set_u16(_nvs, key, value);
|
||||
}
|
||||
esp_err_t setInt32(const char *key, int32_t value) {
|
||||
return nvs_set_i32(_nvs, key, value);
|
||||
}
|
||||
esp_err_t setUint32(const char *key, uint32_t value) {
|
||||
return nvs_set_u32(_nvs, key, value);
|
||||
}
|
||||
esp_err_t setInt64(const char *key, int64_t value) {
|
||||
return nvs_set_i64(_nvs, key, value);
|
||||
}
|
||||
esp_err_t setUint64(const char *key, uint64_t value) {
|
||||
return nvs_set_u64(_nvs, key, value);
|
||||
}
|
||||
esp_err_t setString(const char *key, const char *strValue);
|
||||
esp_err_t setBlob(const char *key, const char *strValue);
|
||||
|
||||
std::optional<int8_t> getInt8(const char *key) {
|
||||
int8_t value;
|
||||
if (nvs_get_i8(_nvs, key, &value) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {value};
|
||||
}
|
||||
std::optional<uint8_t> getUint8(const char *key) {
|
||||
uint8_t value;
|
||||
if (nvs_get_u8(_nvs, key, &value) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {value};
|
||||
}
|
||||
std::optional<int16_t> getInt16(const char *key) {
|
||||
int16_t value;
|
||||
if (nvs_get_i16(_nvs, key, &value) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {value};
|
||||
}
|
||||
std::optional<uint16_t> getUint16(const char *key) {
|
||||
uint16_t value;
|
||||
if (nvs_get_u16(_nvs, key, &value) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {value};
|
||||
}
|
||||
std::optional<int32_t> getInt32(const char *key) {
|
||||
int32_t value;
|
||||
if (nvs_get_i32(_nvs, key, &value) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {value};
|
||||
}
|
||||
std::optional<uint32_t> getUint32(const char *key) {
|
||||
uint32_t value;
|
||||
if (nvs_get_u32(_nvs, key, &value) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {value};
|
||||
}
|
||||
std::optional<int64_t> getInt64(const char *key) {
|
||||
int64_t value;
|
||||
if (nvs_get_i64(_nvs, key, &value) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {value};
|
||||
}
|
||||
std::optional<uint64_t> getUint64(const char *key) {
|
||||
uint64_t value;
|
||||
if (nvs_get_u64(_nvs, key, &value) != ESP_OK) {
|
||||
return {std::nullopt};
|
||||
}
|
||||
return {value};
|
||||
}
|
||||
std::optional<std::string> getString(const char *key);
|
||||
std::optional<std::vector<char>> getBlob(const char *key);
|
||||
|
||||
esp_err_t erase(const char *key) {
|
||||
return nvs_erase_key(_nvs, key);
|
||||
}
|
||||
|
||||
/// @brief Clear all entries in given namespace
|
||||
esp_err_t clear() {
|
||||
return nvs_erase_all(_nvs);
|
||||
}
|
||||
|
||||
private:
|
||||
nvs_handle_t _nvs;
|
||||
};
|
||||
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<int8_t>(const char *key, const char *strValue);
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<uint8_t>(const char *key, const char *strValue);
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<int16_t>(const char *key, const char *strValue);
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<uint16_t>(const char *key, const char *strValue);
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<int32_t>(const char *key, const char *strValue);
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<uint32_t>(const char *key, const char *strValue);
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<int64_t>(const char *key, const char *strValue);
|
||||
template <>
|
||||
esp_err_t NvsIo::setValueByStr<uint64_t>(const char *key, const char *strValue);
|
||||
@@ -1,4 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include" "defines"
|
||||
)
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* @file utils.hpp
|
||||
* @author Gustice
|
||||
* @brief Template
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Base for constructs that allow only one concurrently active instance
|
||||
*
|
||||
* @tparam T
|
||||
* @note needs a `T * T::SingleActive<T>::active{};` definition in source
|
||||
*/
|
||||
template <typename T>
|
||||
class SingleActive {
|
||||
static bool active;
|
||||
|
||||
public:
|
||||
/// @brief Constructor, memorizes if already on instance was successfully build
|
||||
SingleActive() {
|
||||
if (active) {
|
||||
throw "Instance already active";
|
||||
}
|
||||
assert(active == false && "Constructor my only be run if no instance active");
|
||||
active = true;
|
||||
}
|
||||
|
||||
/// @brief Destructor, clears memory of existing instance
|
||||
~SingleActive() {
|
||||
assert(active == true && "Destructor my only be run on active instance");
|
||||
active = false;
|
||||
}
|
||||
|
||||
// Disable move and assignment
|
||||
SingleActive(SingleActive &other) = delete;
|
||||
void operator=(const SingleActive &) = delete;
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* @file coreExcept.hpp
|
||||
* @author Gustice
|
||||
* @brief Template
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
|
||||
/**
|
||||
* @brief Simple exception
|
||||
*/
|
||||
class CoreException : public std::exception {
|
||||
char const *Text;
|
||||
|
||||
public:
|
||||
/// @brief Constructor with simple pointer to text
|
||||
CoreException(char const *text) : Text(text) {
|
||||
}
|
||||
|
||||
/// @brief error message getter
|
||||
char const *what() const throw() override { return Text; }
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @file portBase.hpp
|
||||
* @author Gustice
|
||||
* @brief Base-class for all hardware ports
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class PortBase { // Forbid Copy and Move assignments
|
||||
public:
|
||||
PortBase(/* args */) {};
|
||||
PortBase(const PortBase &other) = delete;
|
||||
PortBase &operator=(const PortBase &other) = delete;
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* @file utils.hpp
|
||||
* @author Gustice
|
||||
* @brief Template
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Common interface for character streams
|
||||
*/
|
||||
class CharStream {
|
||||
public:
|
||||
/// @brief Read method
|
||||
/// @return string
|
||||
virtual std::string read() = 0;
|
||||
|
||||
/// @brief Write character
|
||||
/// @param c character to write
|
||||
virtual void write(char c) = 0;
|
||||
|
||||
/// @brief Write string
|
||||
/// @param str string to write
|
||||
virtual void write(std::string str) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Common interface
|
||||
*/
|
||||
class ByteStream {
|
||||
/// @brief Read method
|
||||
/// @return series of bytes
|
||||
virtual std::vector<uint8_t> read() = 0;
|
||||
|
||||
/// @brief write single byte
|
||||
/// @param b byte to write
|
||||
virtual void write(uint8_t b) = 0;
|
||||
|
||||
/// @brief write series of bytes
|
||||
/// @param data series to write
|
||||
virtual void write(std::vector<uint8_t> data) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Base for constructs that allow only one concurrently active instance
|
||||
*
|
||||
* @tparam T
|
||||
* @note needs a `T * T::SingleActive<T>::active{};` definition in source
|
||||
*/
|
||||
template <typename T>
|
||||
class SingleActive {
|
||||
static bool active;
|
||||
|
||||
public:
|
||||
/// @brief Constructor, memorizes if already on instance was successfully build
|
||||
SingleActive() {
|
||||
if (active) {
|
||||
throw "Instance already active";
|
||||
}
|
||||
assert(active == false && "Constructor my only be run if no instance active");
|
||||
active = true;
|
||||
}
|
||||
|
||||
/// @brief Destructor, clears memory of existing instance
|
||||
~SingleActive() {
|
||||
assert(active == true && "Destructor my only be run on active instance");
|
||||
active = false;
|
||||
}
|
||||
|
||||
// Disable move and assignment
|
||||
SingleActive(SingleActive &other) = delete;
|
||||
void operator=(const SingleActive &) = delete;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @file dummy.cpp
|
||||
* @author Gustice
|
||||
* @brief Silence toolchain
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "portBase.hpp"
|
||||
@@ -1,5 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp_netif lwip project_bases freeRtosApi mqtt
|
||||
)
|
||||
@@ -1,196 +0,0 @@
|
||||
/**
|
||||
* @file etherCommon.hpp
|
||||
* @author Gustice
|
||||
* @brief Helper Functions for Ethernet usage
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/sys.h"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <lwip/netdb.h>
|
||||
#include <string>
|
||||
|
||||
#if defined(CONFIG_LWIP_NETBUF_RECVINFO)
|
||||
constexpr bool NETBUF_RECVINFO = true;
|
||||
#else
|
||||
constexpr bool NETBUF_RECVINFO = false;
|
||||
#endif
|
||||
#if defined(CONFIG_EXAMPLE_IPV4)
|
||||
constexpr bool USE_IPV4 = true;
|
||||
#else
|
||||
constexpr bool USE_IPV4 = false;
|
||||
#endif
|
||||
#if defined(CONFIG_EXAMPLE_IPV6)
|
||||
constexpr bool USE_IPV6 = true;
|
||||
#else
|
||||
constexpr bool USE_IPV6 = false;
|
||||
#endif
|
||||
|
||||
class Ip {
|
||||
public:
|
||||
Ip(uint32_t ipV4) {
|
||||
_address.word = ipV4;
|
||||
}
|
||||
Ip(esp_ip4_addr_t ipV4) {
|
||||
_address.word = ipV4.addr;
|
||||
}
|
||||
|
||||
Ip(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) {
|
||||
_address.bytes[0] = b0;
|
||||
_address.bytes[1] = b1;
|
||||
_address.bytes[2] = b2;
|
||||
_address.bytes[3] = b3;
|
||||
}
|
||||
|
||||
Ip(std::string ipStr) {
|
||||
_address.word = ipaddr_addr(ipStr.c_str());
|
||||
}
|
||||
|
||||
uint8_t operator[](int index) const { return _address.bytes[index]; };
|
||||
uint32_t asWord() const { return _address.word; }
|
||||
std::string asString() const { return inet_ntoa(_address.word); }
|
||||
|
||||
private:
|
||||
union {
|
||||
uint8_t bytes[4]; // IPv4 address
|
||||
uint32_t word;
|
||||
} _address;
|
||||
};
|
||||
|
||||
class Mac {
|
||||
public:
|
||||
Mac(uint8_t val[NETIF_MAX_HWADDR_LEN]) {
|
||||
std::memcpy(bytes.begin(), val, NETIF_MAX_HWADDR_LEN);
|
||||
}
|
||||
|
||||
uint8_t operator[](int index) const {
|
||||
return bytes[index];
|
||||
};
|
||||
|
||||
std::string asString() const {
|
||||
char buffer[64];
|
||||
sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x", bytes[0], bytes[1],
|
||||
bytes[2], bytes[3], bytes[4], bytes[5]);
|
||||
return {buffer};
|
||||
}
|
||||
|
||||
const std::array<uint8_t, NETIF_MAX_HWADDR_LEN> &asArray() const {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, NETIF_MAX_HWADDR_LEN> bytes; // IPv4 address
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IP-set class
|
||||
*/
|
||||
struct IpInfo {
|
||||
/// @brief IP address
|
||||
Ip address{0u};
|
||||
/// @brief IP subnet
|
||||
Ip subnet{0u};
|
||||
/// @brief Gateway IP
|
||||
Ip gateway{0u};
|
||||
/// @brief MAC-Address
|
||||
Mac macAddress;
|
||||
|
||||
/// @brief Get broadcast address from IP and subnet
|
||||
/// @return
|
||||
Ip getBradcastAddress() const {
|
||||
auto sn = address.asWord() | ~subnet.asWord();
|
||||
return Ip(sn);
|
||||
}
|
||||
};
|
||||
|
||||
struct IpAdr {
|
||||
public:
|
||||
IpAdr(std::string ipStr, uint16_t port);
|
||||
IpAdr(uint32_t ip, uint16_t port);
|
||||
IpAdr(uint16_t port);
|
||||
IpAdr(sockaddr_in ipV4data);
|
||||
|
||||
static constexpr int IpProtocol = IPPROTO_IP; // IPPROTO_IPV6 for version 6
|
||||
static constexpr int ProtocolFamily = AF_INET; // AF_INET6 for version 6
|
||||
/// @brief StackFamily e.g. AF_INET or AF_INET6
|
||||
sa_family_t getProtocolFamily() const { return addrStore.ss_family; }
|
||||
|
||||
std::string getIp() {
|
||||
struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore;
|
||||
return inet_ntoa(ipV4->sin_addr.s_addr);
|
||||
}
|
||||
uint16_t getPort() const {
|
||||
struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore;
|
||||
return ntohs(ipV4->sin_port);
|
||||
};
|
||||
struct sockaddr *asSockAddrPtr() const { return (struct sockaddr *)&addrStore; }
|
||||
|
||||
static constexpr int Size = sizeof(sockaddr_storage);
|
||||
private:
|
||||
struct sockaddr_storage addrStore;
|
||||
};
|
||||
|
||||
struct IpAdrV6 {
|
||||
public:
|
||||
IpAdrV6(std::string ipStr, uint16_t port);
|
||||
IpAdrV6(in6_addr ip, uint16_t port);
|
||||
IpAdrV6(uint16_t port);
|
||||
IpAdrV6(sockaddr_in6 ipV6data);
|
||||
|
||||
static constexpr int IpProtocol = IPPROTO_IPV6;
|
||||
static constexpr int ProtocolFamily = AF_INET6;
|
||||
/// @brief StackFamily e.g. AF_INET or AF_INET6
|
||||
sa_family_t getProtocolFamily() const { return addrStore.ss_family; }
|
||||
|
||||
std::string getIp() {
|
||||
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
|
||||
return inet6_ntoa(ipV6->sin6_addr);
|
||||
|
||||
}
|
||||
uint16_t getPort() const {
|
||||
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
|
||||
return ntohs(ipV6->sin6_port);
|
||||
};
|
||||
struct sockaddr *asSockAddrPtr() const { return (struct sockaddr *)&addrStore; }
|
||||
|
||||
static constexpr int Size = sizeof(sockaddr_storage);
|
||||
private:
|
||||
struct sockaddr_storage addrStore;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Simplified abstraction for sockets
|
||||
*/
|
||||
class SocketBase {
|
||||
public:
|
||||
/// @brief Get if stream is active
|
||||
/// @return true if active
|
||||
virtual bool isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
IpInfo getIpInfo() {
|
||||
auto *netIf = esp_netif_get_default_netif();
|
||||
esp_netif_ip_info_t ip;
|
||||
uint8_t mac[NETIF_MAX_HWADDR_LEN];
|
||||
ESP_ERROR_CHECK(esp_netif_get_ip_info(netIf, &ip));
|
||||
ESP_ERROR_CHECK(esp_netif_get_mac(netIf, mac));
|
||||
|
||||
IpInfo ips{
|
||||
.address = Ip{ip.ip},
|
||||
.subnet = Ip(ip.netmask),
|
||||
.gateway = Ip(ip.netmask),
|
||||
.macAddress = Mac(mac)};
|
||||
return ips;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool active;
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* @file mqttClient.hpp
|
||||
* @author Gustice
|
||||
* @brief Wrapper class for TCP/IP based MQTT APIs
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_system.h"
|
||||
#include "mqtt_client.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @brief MqttClient wrapper (Singleton)
|
||||
*/
|
||||
class MqttClient {
|
||||
public:
|
||||
/// @brief Init and mount file system
|
||||
/// @param broker URI to web MQTT broker server
|
||||
static MqttClient &init(std::string broker, std::string prefix);
|
||||
void addSubscription(std::string topic, std::function<void(std::string)> call);
|
||||
void publish(std::string topic, std::string data);
|
||||
void start();
|
||||
bool isRunning() { return _state != State::Init; }
|
||||
|
||||
/// @brief Get instance, ensure initialization
|
||||
/// @param broker URI to web MQTT broker server
|
||||
/// @returnb
|
||||
static MqttClient &getInstance();
|
||||
|
||||
// Disable move and assignment
|
||||
MqttClient(MqttClient &other) = delete;
|
||||
void operator=(const MqttClient &) = delete;
|
||||
|
||||
// protected:
|
||||
MqttClient(std::string broker, std::string pefix);
|
||||
~MqttClient();
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
Init,
|
||||
Started,
|
||||
Disconnected,
|
||||
Connected
|
||||
};
|
||||
struct Subscription {
|
||||
std::string topic;
|
||||
std::function<void(std::string)> callback;
|
||||
bool active{};
|
||||
int id{};
|
||||
};
|
||||
|
||||
std::string _broker;
|
||||
std::string _prefix;
|
||||
State _state{};
|
||||
static MqttClient *_instance;
|
||||
esp_mqtt_client_handle_t _client;
|
||||
std::vector<Subscription> _subscriptions;
|
||||
|
||||
static void eventHandler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
|
||||
};
|
||||
@@ -1,309 +0,0 @@
|
||||
/**
|
||||
* @file tcpSockets.hpp
|
||||
* @author Gustice
|
||||
* @brief Function wrapper for ESP-IDF socket implementation
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "etherCommon.hpp"
|
||||
#include "coreExcept.hpp"
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "simpleStreams.hpp"
|
||||
|
||||
/**
|
||||
* @brief TCP-Socket wrapper for ESP-IDF socket implementation
|
||||
*/
|
||||
class TcpSocket {
|
||||
public:
|
||||
/// @brief Constructor
|
||||
/// @note Socket not active until call of attach method
|
||||
/// @param addr Server/Client address
|
||||
TcpSocket(const IpAdr &addr)
|
||||
: Local(addr) {
|
||||
_listenSockId = socket(addr.ProtocolFamily, SOCK_STREAM, addr.IpProtocol);
|
||||
if (_listenSockId < 0) {
|
||||
ESP_LOGE("TCP", "Unable to create socket: errno %d", errno);
|
||||
throw CoreException("Unable to create socket");
|
||||
}
|
||||
|
||||
if (addr.ProtocolFamily == IPPROTO_IP) {
|
||||
int opt = 1;
|
||||
setsockopt(_listenSockId, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
}
|
||||
|
||||
if constexpr (USE_IPV4 && USE_IPV6) {
|
||||
int opt = 1;
|
||||
// Note that by default IPV6 binds to both protocols, it is must be disabled
|
||||
// if both protocols used at the same time (used in CI)
|
||||
setsockopt(_listenSockId, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
|
||||
}
|
||||
|
||||
ESP_LOGI("TCP", "Socket created");
|
||||
}
|
||||
|
||||
/// @brief Destructor
|
||||
~TcpSocket() {
|
||||
close(_listenSockId);
|
||||
}
|
||||
|
||||
/// @brief Attach to configured socket
|
||||
/// @note Blocks until successful or failed
|
||||
/// @return true if successful
|
||||
bool bindTo() {
|
||||
int err = bind(_listenSockId, Local.asSockAddrPtr(), sizeof(sockaddr_storage));
|
||||
if (err != 0) {
|
||||
ESP_LOGE("TCP", "Socket unable to bind: errno %d", errno);
|
||||
ESP_LOGE("TCP", "IPPROTO: %d", Local.ProtocolFamily);
|
||||
throw CoreException("Socket unable to bind");
|
||||
}
|
||||
ESP_LOGI("TCP", "Socket bound, port %d", Local.getPort());
|
||||
|
||||
err = listen(_listenSockId, 1);
|
||||
if (err != 0) {
|
||||
ESP_LOGE("TCP", "Error occurred during listen: errno %d", errno);
|
||||
throw CoreException("Error occurred during listen");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Get socket id for stream-operations
|
||||
/// @return Id of socket
|
||||
int getSocketId() {
|
||||
return _listenSockId;
|
||||
}
|
||||
|
||||
/// @brief Address of this station (server perspective)
|
||||
const IpAdr &Local;
|
||||
|
||||
private:
|
||||
int _listenSockId;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief TCP session
|
||||
*/
|
||||
class TcpSession : public SocketBase, public CharStream {
|
||||
public:
|
||||
/**
|
||||
* @brief Configuration structure for Session
|
||||
*/
|
||||
struct Config {
|
||||
int keepAlive;
|
||||
int keepIdle;
|
||||
int keepInterval;
|
||||
int keepCount;
|
||||
};
|
||||
|
||||
/// @brief Constructor
|
||||
/// @note Constructor blocks
|
||||
/// @param config Configuration for session
|
||||
/// @param listener reference to socket
|
||||
TcpSession(Config &config, TcpSocket &listener) : _other(IPADDR_ANY, 0) {
|
||||
socklen_t addr_len = _other.Size;
|
||||
_socket = accept(listener.getSocketId(), _other.asSockAddrPtr(), &addr_len);
|
||||
if (_socket < 0) {
|
||||
ESP_LOGE("TCP", "Unable to accept connection: errno %d", errno);
|
||||
active = false;
|
||||
throw CoreException("Unable to accept connection");
|
||||
}
|
||||
|
||||
// Set tcp keepalive option
|
||||
setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &config.keepAlive, sizeof(int));
|
||||
setsockopt(_socket, IPPROTO_TCP, TCP_KEEPIDLE, &config.keepIdle, sizeof(int));
|
||||
setsockopt(_socket, IPPROTO_TCP, TCP_KEEPINTVL, &config.keepInterval, sizeof(int));
|
||||
setsockopt(_socket, IPPROTO_TCP, TCP_KEEPCNT, &config.keepCount, sizeof(int));
|
||||
|
||||
std::string addrStr;
|
||||
if (_other.ProtocolFamily == listener.Local.ProtocolFamily) {
|
||||
if (_other.getProtocolFamily() == PF_INET) {
|
||||
addrStr = _other.getIp();
|
||||
// } else if (_other.addrStore.ss_family == PF_INET6) {
|
||||
// inet6_ntoa_r(((struct sockaddr_in6 *)&_other.addrStore)->sin6_addr, addrStr, sizeof(addrStr) - 1);
|
||||
} else {
|
||||
throw CoreException("Unknown case");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW("TCP", "Wrong IP-version request");
|
||||
active = false;
|
||||
}
|
||||
|
||||
ESP_LOGI("TCP", "Socket accepted ip address: %s", addrStr.c_str());
|
||||
|
||||
active = true;
|
||||
}
|
||||
|
||||
/// @brief Destructor
|
||||
~TcpSession() {
|
||||
if (_socket < 0)
|
||||
return;
|
||||
|
||||
ESP_LOGD("TCP", "Tearing down socket");
|
||||
shutdown(_socket, 0);
|
||||
}
|
||||
|
||||
/// @brief Get IP of remote
|
||||
/// @return
|
||||
IpAdr getSource() {
|
||||
return _other;
|
||||
}
|
||||
|
||||
/// @brief Read string
|
||||
/// @note This call blocks until data is received
|
||||
/// @return read string
|
||||
std::string read() override {
|
||||
auto data = readRaw();
|
||||
if (data.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string ret((const char *)&data.data()[0]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// @brief Write character to socket
|
||||
/// @param b character to write
|
||||
void write(char c) override {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int written = send(_socket, &c, 1, 0);
|
||||
if (written < 0) {
|
||||
ESP_LOGE("TCP", "Error occurred during sending: errno %d", errno);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Write string to specific socket
|
||||
/// @param message payload
|
||||
/// @param dest Destination for message
|
||||
void write(std::string message) override {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int written = send(_socket, message.c_str(), message.size(), 0);
|
||||
if (written < 0) {
|
||||
ESP_LOGE("TCP", "Error occurred during sending: errno %d", errno);
|
||||
}
|
||||
}
|
||||
|
||||
IpAdr getOther() {
|
||||
return _other;
|
||||
}
|
||||
|
||||
protected:
|
||||
int _socket = 0;
|
||||
IpAdr _other;
|
||||
|
||||
std::vector<uint8_t> readRaw() {
|
||||
uint8_t data[128];
|
||||
int len = recv(_socket, data, sizeof(data) - 1, 0);
|
||||
|
||||
if (len < 0) {
|
||||
ESP_LOGE("TCP", "Error occurred during receiving: errno %d", errno);
|
||||
active = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
data[len] = 0;
|
||||
if (len == 0) {
|
||||
ESP_LOGW("TCP", "Connection closed");
|
||||
active = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
ESP_LOGD("TCP", "Received %d bytes: %s", len, data);
|
||||
std::vector<uint8_t> vec(data, data + len + 1);
|
||||
return vec;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr TcpSession::Config DefaultTcpConfig{
|
||||
.keepAlive = 1,
|
||||
.keepIdle = 5,
|
||||
.keepInterval = 5,
|
||||
.keepCount = 3,
|
||||
};
|
||||
|
||||
constexpr uint16_t TelnetPort = 23;
|
||||
|
||||
/**
|
||||
* @brief TCP-Socket for Telnet-Applications
|
||||
*/
|
||||
class TelnetServer {
|
||||
|
||||
/**
|
||||
* @brief Specialization of TCP-Session for Telnet
|
||||
*/
|
||||
class TelnetSession : public TcpSession {
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
TelnetSession(Config &config, TcpSocket &listener) : TcpSession(config, listener) {
|
||||
}
|
||||
|
||||
/// @brief Read string
|
||||
/// @note This call blocks until data is received
|
||||
/// @return read string
|
||||
std::string read() override {
|
||||
auto data = readRaw();
|
||||
if (data.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
data.pop_back(); // 0-termination is not needed here
|
||||
|
||||
int i = 0;
|
||||
while (data[i] == 0xFF) { // while telnet command
|
||||
i += 3; // skip this command
|
||||
}
|
||||
|
||||
if (i == data.size()) { // only configuration
|
||||
send(_socket, data.data(), data.size(), 0);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string ret((const char *)&data[i]);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/// @brief Constructor
|
||||
/// @param addrFamily IPV4 or IPV6
|
||||
TelnetServer(int addrFamily)
|
||||
: Local(addrFamily == AF_INET ? IpAdr(INADDR_ANY, TelnetPort) : IpAdr(TelnetPort)),
|
||||
Config(DefaultTcpConfig) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start server
|
||||
*
|
||||
* @param initCall Callback for shell init
|
||||
* @param cyclicCall Callback for cyclic polling
|
||||
*/
|
||||
void run(std::function<void(TcpSession &)> initCall, std::function<void(TcpSession &)> cyclicCall) {
|
||||
TcpSocket socket(Local);
|
||||
socket.bindTo();
|
||||
while (true) {
|
||||
// Run telnet-session as long socket is active else restart socket
|
||||
ESP_LOGI("Telnet", "Socket waiting for connect");
|
||||
TelnetSession session(Config, socket);
|
||||
|
||||
initCall(session);
|
||||
while (session.isActive()) {
|
||||
cyclicCall(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
IpAdr Local;
|
||||
TcpSession::Config Config;
|
||||
};
|
||||
@@ -1,309 +0,0 @@
|
||||
/**
|
||||
* @file udpSockets.hpp
|
||||
* @author Gustice
|
||||
* @brief UDP-Socket wrapper for ESP-IDF socket implementation
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "Task.hpp"
|
||||
#include "esp_log.h"
|
||||
#include "etherCommon.hpp"
|
||||
#include "coreExcept.hpp"
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "simpleStreams.hpp"
|
||||
|
||||
constexpr bool UDP_WITH_TIMEOUT = false;
|
||||
|
||||
/**
|
||||
* @brief UDP-Socket wrapper for ESP-IDF socket implementation
|
||||
*/
|
||||
class UdpSocket : public ByteStream, public SocketBase {
|
||||
public:
|
||||
/// @brief Constructor for UDP-Socket-Server
|
||||
/// @note Socket not active until call of attach method
|
||||
/// @param addr Server/Client address
|
||||
UdpSocket(const IpAdr &addr)
|
||||
: Local(addr), _other(INADDR_ANY, 0) {
|
||||
_listenSockId = socket(addr.ProtocolFamily, SOCK_DGRAM, addr.IpProtocol);
|
||||
if (_listenSockId < 0) {
|
||||
ESP_LOGE("UDP", "Unable to create socket: errno %d", errno);
|
||||
throw CoreException("Unable to create socket");
|
||||
}
|
||||
ESP_LOGD("UDP", "Socket created");
|
||||
|
||||
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
|
||||
int enable = 1;
|
||||
lwip_setsockopt(_listenSockId, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable));
|
||||
}
|
||||
|
||||
if constexpr (USE_IPV4 && USE_IPV6) {
|
||||
if (addr.ProtocolFamily == AF_INET6) {
|
||||
// Note that by default IPV6 binds to both protocols, it is must be disabled
|
||||
// if both protocols used at the same time (used in CI)
|
||||
int opt = 1;
|
||||
setsockopt(_listenSockId, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
setsockopt(_listenSockId, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (UDP_WITH_TIMEOUT) { // Set timeout if desired
|
||||
struct timeval timeout {
|
||||
.tv_sec = 10,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
setsockopt(_listenSockId, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Destructor
|
||||
~UdpSocket() {
|
||||
active = false;
|
||||
ESP_LOGD("UDP", "Shutting down socket and restarting...");
|
||||
shutdown(_listenSockId, 0);
|
||||
close(_listenSockId);
|
||||
}
|
||||
|
||||
/// @brief Attach to configured socket
|
||||
/// @note Blocks until successful or failed
|
||||
/// @return true if successful
|
||||
bool bindTo() {
|
||||
active = false;
|
||||
int err = bind(_listenSockId, Local.asSockAddrPtr(), sizeof(sockaddr_storage));
|
||||
if (err < 0) {
|
||||
ESP_LOGE("UDP", "Socket unable to bind: errno %d", errno);
|
||||
throw CoreException("Socket unable to bind");
|
||||
}
|
||||
ESP_LOGI("UDP", "Socket bound, port %d", Local.getPort());
|
||||
|
||||
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
|
||||
_iov.iov_base = _rxBuffer;
|
||||
_iov.iov_len = sizeof(_rxBuffer);
|
||||
_msg.msg_control = _cmsg_buf;
|
||||
_msg.msg_controllen = sizeof(_cmsg_buf);
|
||||
_msg.msg_flags = 0;
|
||||
_msg.msg_iov = &_iov;
|
||||
_msg.msg_iovlen = 1;
|
||||
_msg.msg_name = _other.asSockAddrPtr();
|
||||
_msg.msg_namelen = _other.Size;
|
||||
}
|
||||
|
||||
active = true;
|
||||
return active;
|
||||
}
|
||||
|
||||
/// @brief Read from socket
|
||||
/// @note This call blocks until data is received
|
||||
/// @return read bytes
|
||||
std::vector<uint8_t> read() override {
|
||||
char buffer[256];
|
||||
int len = 0;
|
||||
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
|
||||
len = recvmsg(_listenSockId, &_msg, 0);
|
||||
} else {
|
||||
socklen_t socklen = _other.Size;
|
||||
len = recvfrom(_listenSockId, buffer, sizeof(buffer) - 1, 0, _other.asSockAddrPtr(), &socklen);
|
||||
}
|
||||
if (len < 0) {
|
||||
ESP_LOGE("UDP", "recvfrom failed: errno %d", errno);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string addrStr;
|
||||
// Get the sender's ip address as string
|
||||
if (Local.ProtocolFamily == PF_INET) {
|
||||
addrStr = _other.getIp();
|
||||
|
||||
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
|
||||
struct cmsghdr *cmsgtmp;
|
||||
for (cmsgtmp = CMSG_FIRSTHDR(&_msg); cmsgtmp != NULL; cmsgtmp = CMSG_NXTHDR(&_msg, cmsgtmp)) {
|
||||
if (cmsgtmp->cmsg_level == IPPROTO_IP && cmsgtmp->cmsg_type == IP_PKTINFO) {
|
||||
struct in_pktinfo *pktinfo;
|
||||
pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsgtmp);
|
||||
ESP_LOGI("UDP", "dest ip: %s", inet_ntoa(pktinfo->ipi_addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // else if (Local.ProtocolFamily == PF_INET6) {
|
||||
// inet6_ntoa_r(((struct sockaddr_in6 *)&_other.addrStore)->sin6_addr, addrStr, sizeof(addrStr) - 1);
|
||||
// }
|
||||
|
||||
ESP_LOGD("UDP", "Received %d bytes from %s:", len, addrStr.c_str());
|
||||
std::vector<uint8_t> ret(buffer, buffer + len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// @brief Write character to socket
|
||||
/// @param b character to write
|
||||
void write(uint8_t b) override {}
|
||||
|
||||
/// @brief Write bytes to socket
|
||||
/// @param data payload as series of bytes
|
||||
void write(std::vector<uint8_t> data) override {
|
||||
write(&data[0], data.size());
|
||||
}
|
||||
|
||||
/// @brief Write string to specific socket
|
||||
/// @param message payload
|
||||
/// @param dest Destination for message
|
||||
void write(std::string &message, IpAdr *dest = nullptr) {
|
||||
auto data = message.c_str();
|
||||
write((uint8_t *)data, message.size() + 1, dest);
|
||||
}
|
||||
|
||||
/// @brief Write string to specific socket or to last source
|
||||
/// @param data payload
|
||||
/// @param size size of data
|
||||
/// @param addr Destination for message (if nullptr it will be send to last source)
|
||||
void write(uint8_t *data, uint size, IpAdr *dest = nullptr) {
|
||||
int err;
|
||||
if (dest == nullptr) {
|
||||
err = sendto(_listenSockId, data, size, 0, _other.asSockAddrPtr(), sizeof(sockaddr_storage));
|
||||
} else {
|
||||
struct sockaddr_in *ipV4 = (struct sockaddr_in *)dest->asSockAddrPtr();
|
||||
// ESP_LOGI("UDP", "EthIP: %s", inet_ntoa(ipV4->sin_addr.s_addr));
|
||||
// ESP_LOGI("UDP", "PORT: %i", ntohs(ipV4->sin_port));
|
||||
err = sendto(_listenSockId, data, size, 0, dest->asSockAddrPtr(), sizeof(sockaddr_storage));
|
||||
}
|
||||
|
||||
if (err < 0) {
|
||||
ESP_LOGE("UDP", "Error occurred during sending: errno %d", errno);
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Standard destination address
|
||||
const IpAdr &Local;
|
||||
|
||||
/// @brief query if stream is still active
|
||||
/// @return true if active
|
||||
bool isValid() {
|
||||
return _listenSockId >= 0;
|
||||
}
|
||||
|
||||
IpAdr getOther() {
|
||||
return _other;
|
||||
}
|
||||
|
||||
private:
|
||||
int _listenSockId; // os handle for socket
|
||||
IpAdr _other; // Remote Station
|
||||
char _rxBuffer[128];
|
||||
|
||||
// Only used if NETBUF_RECVINFO is active
|
||||
iovec _iov;
|
||||
msghdr _msg;
|
||||
u8_t _cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* @details according to RFC-5424
|
||||
* https://datatracker.ietf.org/doc/html/rfc5424
|
||||
*/
|
||||
class Syslog {
|
||||
public:
|
||||
static constexpr uint16_t SyslogPort = 514;
|
||||
static constexpr std::string_view NilValue = "-";
|
||||
|
||||
enum class Severity {
|
||||
Emergency = 0,
|
||||
Alert = 1,
|
||||
Critical = 2,
|
||||
Error = 3,
|
||||
Warning = 4,
|
||||
Notice = 5,
|
||||
Informational = 6,
|
||||
Debug = 7,
|
||||
};
|
||||
static constexpr uint8_t SeverityMask = 0x000F;
|
||||
|
||||
enum class Facility {
|
||||
kern = 0 << 3,
|
||||
User = 1 << 3,
|
||||
Mail = 2 << 3,
|
||||
Daemon = 3 << 3,
|
||||
Auth = 4 << 3,
|
||||
Syslog = 5 << 3,
|
||||
Lpr = 6 << 3,
|
||||
News = 7 << 3,
|
||||
Uucp = 8 << 3,
|
||||
Cron = 9 << 3,
|
||||
Authpriv = 10 << 3,
|
||||
Ftp = 11 << 3,
|
||||
Ntp = 12 << 3,
|
||||
LogAudit = 13 << 3,
|
||||
LogAlert = 14 << 3,
|
||||
Clock = 15 << 3,
|
||||
Local0 = 16 << 3,
|
||||
Local1 = 17 << 3,
|
||||
Local2 = 18 << 3,
|
||||
Local3 = 19 << 3,
|
||||
Local4 = 20 << 3,
|
||||
Local5 = 21 << 3,
|
||||
Local6 = 22 << 3,
|
||||
Local7 = 23 << 3,
|
||||
};
|
||||
|
||||
static void log(Severity severity, std::string message) {
|
||||
if (_Socket == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[512];
|
||||
// V T H A P M D Msg
|
||||
// alternatively with BOM flag UTF8 \xEF\xBB\xBF
|
||||
auto pos = sprintf(buffer, "<%i>1 - %s %s - - - %s",
|
||||
static_cast<int>(_Facility) | static_cast<int>(severity),
|
||||
_HostName.c_str(),
|
||||
_AppName.c_str(),
|
||||
message.c_str());
|
||||
_Socket->write((uint8_t *)buffer, pos, &_Target);
|
||||
}
|
||||
|
||||
template <typename...Params>
|
||||
static void logv(Severity severity, const char *fmt, Params&&... args) {
|
||||
if (_Socket == nullptr) {
|
||||
return;
|
||||
}
|
||||
// if (severity > _severityThresh) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
char buffer[512];
|
||||
// V T H A P M D Msg
|
||||
// alternatively with BOM flag UTF8 \xEF\xBB\xBF
|
||||
auto pos = sprintf(buffer, "<%i>1 - %s %s - - - ",
|
||||
static_cast<int>(_Facility) | static_cast<int>(severity),
|
||||
_HostName.c_str(),
|
||||
_AppName.c_str());
|
||||
pos += sprintf(&buffer[pos], fmt, std::forward<Params> (args)...);
|
||||
|
||||
_Socket->write((uint8_t *)buffer, pos, &_Target);
|
||||
}
|
||||
|
||||
static void setupTask(IpAdr target) {
|
||||
if (_Socket) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGI("UDP", "Starting syslog session to %s", target.getIp().c_str());
|
||||
_Facility = Facility::Syslog;
|
||||
_HostName = "ESP";
|
||||
_AppName = "App";
|
||||
_Target = IpAdr{target.getIp(), SyslogPort};
|
||||
|
||||
static fos::Task syslogTask(SyslogTask, "SysLog", 4096);
|
||||
}
|
||||
|
||||
private:
|
||||
static void SyslogTask();
|
||||
static Facility _Facility;
|
||||
static std::string _HostName;
|
||||
static std::string _AppName;
|
||||
static UdpSocket *_Socket;
|
||||
static IpAdr _Target;
|
||||
static Severity _severityThresh;
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* @file etherCommon.cpp
|
||||
* @author Gustice
|
||||
* @brief Helper Functions for Ethernet usage
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "etherCommon.hpp"
|
||||
#include <cassert>
|
||||
|
||||
IpAdr::IpAdr(std::string ipStr, uint16_t port)
|
||||
: IpAdr(ipaddr_addr(ipStr.c_str()), port) {
|
||||
}
|
||||
|
||||
IpAdr::IpAdr(uint32_t ip, uint16_t port) {
|
||||
struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore;
|
||||
ipV4->sin_len = sizeof(sockaddr_in);
|
||||
ipV4->sin_addr.s_addr = ip;
|
||||
ipV4->sin_family = AF_INET;
|
||||
ipV4->sin_port = htons(port);
|
||||
}
|
||||
|
||||
IpAdr::IpAdr(uint16_t port) {
|
||||
struct sockaddr_in *ipV4 = (struct sockaddr_in *)&addrStore;
|
||||
ipV4->sin_len = sizeof(sockaddr_in);
|
||||
ipV4->sin_addr.s_addr = INADDR_ANY;
|
||||
ipV4->sin_family = AF_INET;
|
||||
ipV4->sin_port = htons(port);
|
||||
}
|
||||
|
||||
IpAdr::IpAdr(sockaddr_in ipV4data) {
|
||||
struct sockaddr_storage *store = (struct sockaddr_storage *)&ipV4data;
|
||||
assert(ipV4data.sin_family == AF_INET);
|
||||
addrStore = *store;
|
||||
}
|
||||
|
||||
|
||||
IpAdrV6::IpAdrV6(std::string ipStr, uint16_t port) {
|
||||
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
|
||||
ipV6->sin6_len = sizeof(sockaddr_in6);
|
||||
inet6_aton(ipStr.c_str(), &ipV6->sin6_addr);
|
||||
ipV6->sin6_family = AF_INET6;
|
||||
ipV6->sin6_port = htons(port);
|
||||
}
|
||||
|
||||
IpAdrV6::IpAdrV6(in6_addr ip, uint16_t port) {
|
||||
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
|
||||
ipV6->sin6_len = sizeof(sockaddr_in6);
|
||||
memcpy(&ip, &ipV6->sin6_addr, sizeof(in6_addr));
|
||||
ipV6->sin6_family = AF_INET6;
|
||||
ipV6->sin6_port = htons(port);
|
||||
}
|
||||
|
||||
IpAdrV6::IpAdrV6(uint16_t port) {
|
||||
struct sockaddr_in6 *ipV6 = (struct sockaddr_in6 *)&addrStore;
|
||||
ipV6->sin6_len = sizeof(sockaddr_in6);
|
||||
// ip6_addr_set_any()
|
||||
bzero(&ipV6->sin6_addr.un, sizeof(ipV6->sin6_addr.un));
|
||||
ipV6->sin6_family = AF_INET6;
|
||||
ipV6->sin6_port = htons(port);
|
||||
}
|
||||
|
||||
IpAdrV6::IpAdrV6(sockaddr_in6 ipV6data) {
|
||||
struct sockaddr_storage *store = (struct sockaddr_storage *)&ipV6data;
|
||||
assert(ipV6data.sin6_family == AF_INET);
|
||||
addrStore = *store;
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/**
|
||||
* @file mqttClient.cpp
|
||||
* @author Gustice
|
||||
* @brief Wrapper class for TCP/IP based MQTT APIs
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "mqttClient.hpp"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "mqtt_client.h"
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
MqttClient *MqttClient::_instance = nullptr;
|
||||
const char *TAG = "Mqtt";
|
||||
|
||||
static void log_error_if_nonzero(const char *message, int error_code) {
|
||||
if (error_code != 0) {
|
||||
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
|
||||
}
|
||||
}
|
||||
|
||||
void MqttClient::eventHandler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||
ESP_LOGI(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
|
||||
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
|
||||
esp_mqtt_client_handle_t client = event->client;
|
||||
MqttClient &instance = *((MqttClient *)handler_args);
|
||||
|
||||
int msg_id;
|
||||
switch ((esp_mqtt_event_id_t)event_id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
instance._state = State::Connected;
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
|
||||
for (auto &s : instance._subscriptions) {
|
||||
msg_id = esp_mqtt_client_subscribe(client, s.topic.c_str(), 1);
|
||||
ESP_LOGI(TAG, "sent subscribe successful: topic='%s', msg_id=%d", s.topic.c_str(), msg_id);
|
||||
}
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
instance._state = State::Disconnected;
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_UNSUBSCRIBED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_PUBLISHED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_DATA: {
|
||||
bool found = false;
|
||||
std::string topic(event->topic, event->topic_len);
|
||||
for (auto &s : instance._subscriptions) {
|
||||
if (s.topic == topic) {
|
||||
found = true;
|
||||
s.callback(std::string(event->data, event->data_len));
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGW(TAG, "Unrecognized Topic");
|
||||
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
|
||||
printf("DATA=%.*s\r\n", event->data_len, event->data);
|
||||
}
|
||||
} break;
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
|
||||
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
|
||||
log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
|
||||
log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
|
||||
log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
|
||||
ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MqttClient::MqttClient(std::string broker, std::string prefix)
|
||||
: _broker("mqtt://" + broker), _prefix(prefix) {
|
||||
// esp_log_level_set("*", ESP_LOG_INFO);
|
||||
// esp_log_level_set(TAG, ESP_LOG_VERBOSE);
|
||||
// esp_log_level_set("mqtt_client", ESP_LOG_VERBOSE);
|
||||
// esp_log_level_set("transport_base", ESP_LOG_VERBOSE);
|
||||
// esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);
|
||||
// esp_log_level_set("transport", ESP_LOG_VERBOSE);
|
||||
// esp_log_level_set("outbox", ESP_LOG_VERBOSE);
|
||||
|
||||
esp_mqtt_client_config_t mqtt_cfg{};
|
||||
mqtt_cfg.broker.address.uri = _broker.c_str();
|
||||
|
||||
_client = esp_mqtt_client_init(&mqtt_cfg);
|
||||
/* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
|
||||
esp_mqtt_client_register_event(_client, (esp_mqtt_event_id_t)ESP_EVENT_ANY_ID, eventHandler, this);
|
||||
}
|
||||
|
||||
MqttClient::~MqttClient() {
|
||||
for (auto &s : _subscriptions) {
|
||||
auto msg_id = esp_mqtt_client_unsubscribe(_client, s.topic.c_str());
|
||||
ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
|
||||
}
|
||||
esp_mqtt_client_disconnect(_client);
|
||||
}
|
||||
|
||||
MqttClient &MqttClient::getInstance() {
|
||||
if (_instance == nullptr) {
|
||||
throw "No instance created";
|
||||
}
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
MqttClient &MqttClient::init(std::string broker, std::string prefix) {
|
||||
if (_instance != nullptr) {
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
_instance = new MqttClient(broker, prefix);
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
void MqttClient::addSubscription(std::string topic, std::function<void(std::string)> call) {
|
||||
std::string fullTopic = _prefix + "/" + topic;
|
||||
_subscriptions.emplace_back(fullTopic, call, false, 0);
|
||||
// ToDo ensure each topic is only present once
|
||||
}
|
||||
|
||||
void MqttClient::start() {
|
||||
ESP_LOGI(TAG, "Starting MQTT client");
|
||||
_state = State::Started;
|
||||
esp_mqtt_client_start(_client);
|
||||
}
|
||||
|
||||
void MqttClient::publish(std::string topic, std::string data) {
|
||||
if (_state != State::Connected) {
|
||||
ESP_LOGW(TAG, "not connected, cannot publish s=%i", (int)_state);
|
||||
return;
|
||||
}
|
||||
std::string fullTopic = _prefix + "/" + topic;
|
||||
int msg_id = esp_mqtt_client_publish(_client, fullTopic.c_str(), data.c_str(), data.size(), 1, 0);
|
||||
ESP_LOGI(TAG, "sent publish of topic '%s' successful, msg_id=%d", fullTopic.c_str(), msg_id);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @file tcpSockets.cpp
|
||||
* @author Gustice
|
||||
* @brief Function frames for handling sockets
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "tcpSockets.hpp"
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @file udpSockets.cpp
|
||||
* @author Gustice
|
||||
* @brief Function frames for handling sockets
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "udpSockets.hpp"
|
||||
|
||||
UdpSocket * Syslog::_Socket;
|
||||
Syslog::Facility Syslog::_Facility;
|
||||
std::string Syslog::_HostName;
|
||||
std::string Syslog::_AppName;
|
||||
IpAdr Syslog::_Target(INADDR_ANY, 0);
|
||||
Syslog::Severity Syslog::_severityThresh = Syslog::Severity::Informational;
|
||||
|
||||
void Syslog::SyslogTask() {
|
||||
ESP_LOGI("SysLog", "Starting Syslog Task");
|
||||
try {
|
||||
while (true) {
|
||||
IpAdr ip(INADDR_ANY, SyslogPort);
|
||||
UdpSocket socket(ip);
|
||||
_Socket = &socket;
|
||||
socket.bindTo();
|
||||
|
||||
while (socket.isActive()) {
|
||||
(void)socket.read();
|
||||
// ignore all incomming data
|
||||
}
|
||||
}
|
||||
_Socket = nullptr;
|
||||
} catch (const std::exception &e) {
|
||||
ESP_LOGE("SysLog", "%s", e.what());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "extern"
|
||||
INCLUDE_DIRS "include" "extern"
|
||||
)
|
||||
-1156
File diff suppressed because it is too large
Load Diff
-312
@@ -1,312 +0,0 @@
|
||||
#ifndef EMBEDDED_CLI_H
|
||||
#define EMBEDDED_CLI_H
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
extern "C" {
|
||||
#else
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#endif
|
||||
|
||||
// cstdint is available only since C++11, so use C header
|
||||
#include <stdint.h>
|
||||
|
||||
// used for proper alignment of cli buffer
|
||||
#if UINTPTR_MAX == 0xFFFF
|
||||
#define CLI_UINT uint16_t
|
||||
#elif UINTPTR_MAX == 0xFFFFFFFF
|
||||
#define CLI_UINT uint32_t
|
||||
#elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu
|
||||
#define CLI_UINT uint64_t
|
||||
#else
|
||||
#error unsupported pointer size
|
||||
#endif
|
||||
|
||||
#define CLI_UINT_SIZE (sizeof(CLI_UINT))
|
||||
// convert size in bytes to size in terms of CLI_UINTs (rounded up
|
||||
// if bytes is not divisible by size of single CLI_UINT)
|
||||
#define BYTES_TO_CLI_UINTS(bytes) \
|
||||
(((bytes) + CLI_UINT_SIZE - 1)/CLI_UINT_SIZE)
|
||||
|
||||
typedef struct CliCommand CliCommand;
|
||||
typedef struct CliCommandBinding CliCommandBinding;
|
||||
typedef struct EmbeddedCli EmbeddedCli;
|
||||
typedef struct EmbeddedCliConfig EmbeddedCliConfig;
|
||||
|
||||
|
||||
struct CliCommand {
|
||||
/**
|
||||
* Name of the command.
|
||||
* In command "set led 1 1" "set" is name
|
||||
*/
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* String of arguments of the command.
|
||||
* In command "set led 1 1" "led 1 1" is string of arguments
|
||||
* Is ended with double 0x00 char
|
||||
* Use tokenize functions to easily get individual tokens
|
||||
*/
|
||||
char *args;
|
||||
};
|
||||
|
||||
/**
|
||||
* Struct to describe binding of command to function and
|
||||
*/
|
||||
struct CliCommandBinding {
|
||||
/**
|
||||
* Name of command to bind. Should not be NULL.
|
||||
*/
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* Help string that will be displayed when "help <cmd>" is executed.
|
||||
* Can have multiple lines separated with "\r\n"
|
||||
* Can be NULL if no help is provided.
|
||||
*/
|
||||
const char *help;
|
||||
|
||||
/**
|
||||
* Flag to perform tokenization before calling binding function.
|
||||
*/
|
||||
bool tokenizeArgs;
|
||||
|
||||
/**
|
||||
* Pointer to any specific app context that is required for this binding.
|
||||
* It will be provided in binding callback.
|
||||
*/
|
||||
void *context;
|
||||
|
||||
/**
|
||||
* Binding function for when command is received.
|
||||
* If null, default callback (onCommand) will be called.
|
||||
* @param cli - pointer to cli that is calling this binding
|
||||
* @param args - string of args (if tokenizeArgs is false) or tokens otherwise
|
||||
* @param context
|
||||
*/
|
||||
void (*binding)(EmbeddedCli *cli, char *args, void *context);
|
||||
};
|
||||
|
||||
struct EmbeddedCli {
|
||||
/**
|
||||
* Should write char to connection
|
||||
* @param cli - pointer to cli that executed this function
|
||||
* @param c - actual character to write
|
||||
*/
|
||||
void (*writeChar)(EmbeddedCli *cli, char c);
|
||||
|
||||
/**
|
||||
* Called when command is received and command not found in list of
|
||||
* command bindings (or binding function is null).
|
||||
* @param cli - pointer to cli that executed this function
|
||||
* @param command - pointer to received command
|
||||
*/
|
||||
void (*onCommand)(EmbeddedCli *cli, CliCommand *command);
|
||||
|
||||
/**
|
||||
* Can be used for any application context
|
||||
*/
|
||||
void *appContext;
|
||||
|
||||
/**
|
||||
* Pointer to actual implementation, do not use.
|
||||
*/
|
||||
void *_impl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration to create CLI
|
||||
*/
|
||||
struct EmbeddedCliConfig {
|
||||
/**
|
||||
* Invitation string. Is printed at the beginning of each line with user
|
||||
* input
|
||||
*/
|
||||
const char *invitation;
|
||||
|
||||
/**
|
||||
* Size of buffer that is used to store characters until they're processed
|
||||
*/
|
||||
uint16_t rxBufferSize;
|
||||
|
||||
/**
|
||||
* Size of buffer that is used to store current input that is not yet
|
||||
* sended as command (return not pressed yet)
|
||||
*/
|
||||
uint16_t cmdBufferSize;
|
||||
|
||||
/**
|
||||
* Size of buffer that is used to store previously entered commands
|
||||
* Only unique commands are stored in buffer. If buffer is smaller than
|
||||
* entered command (including arguments), command is discarded from history
|
||||
*/
|
||||
uint16_t historyBufferSize;
|
||||
|
||||
/**
|
||||
* Maximum amount of bindings that can be added via addBinding function.
|
||||
* Cli increases takes extra bindings for internal commands:
|
||||
* - help
|
||||
*/
|
||||
uint16_t maxBindingCount;
|
||||
|
||||
/**
|
||||
* Buffer to use for cli and all internal structures. If NULL, memory will
|
||||
* be allocated dynamically. Otherwise this buffer is used and no
|
||||
* allocations are made
|
||||
*/
|
||||
CLI_UINT *cliBuffer;
|
||||
|
||||
/**
|
||||
* Size of buffer for cli and internal structures (in bytes).
|
||||
*/
|
||||
uint16_t cliBufferSize;
|
||||
|
||||
/**
|
||||
* Whether autocompletion should be enabled.
|
||||
* If false, autocompletion is disabled but you still can use 'tab' to
|
||||
* complete current command manually.
|
||||
*/
|
||||
bool enableAutoComplete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns pointer to default configuration for cli creation. It is safe to
|
||||
* modify it and then send to embeddedCliNew().
|
||||
* Returned structure is always the same so do not free and try to use it
|
||||
* immediately.
|
||||
* Default values:
|
||||
* <ul>
|
||||
* <li>rxBufferSize = 64</li>
|
||||
* <li>cmdBufferSize = 64</li>
|
||||
* <li>historyBufferSize = 128</li>
|
||||
* <li>cliBuffer = NULL (use dynamic allocation)</li>
|
||||
* <li>cliBufferSize = 0</li>
|
||||
* <li>maxBindingCount = 8</li>
|
||||
* <li>enableAutoComplete = true</li>
|
||||
* </ul>
|
||||
* @return configuration for cli creation
|
||||
*/
|
||||
EmbeddedCliConfig *embeddedCliDefaultConfig(void);
|
||||
|
||||
/**
|
||||
* Returns how many space in config buffer is required for cli creation
|
||||
* If you provide buffer with less space, embeddedCliNew will return NULL
|
||||
* This amount will always be divisible by CLI_UINT_SIZE so allocated buffer
|
||||
* and internal structures can be properly aligned
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config);
|
||||
|
||||
/**
|
||||
* Create new CLI.
|
||||
* Memory is allocated dynamically if cliBuffer in config is NULL.
|
||||
* After CLI is created, override function pointers to start using it
|
||||
* @param config - config for cli creation
|
||||
* @return pointer to created CLI
|
||||
*/
|
||||
EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config);
|
||||
|
||||
/**
|
||||
* Same as calling embeddedCliNew with default config.
|
||||
* @return
|
||||
*/
|
||||
EmbeddedCli *embeddedCliNewDefault(void);
|
||||
|
||||
/**
|
||||
* Receive character and put it to internal buffer
|
||||
* Actual processing is done inside embeddedCliProcess
|
||||
* You can call this function from something like interrupt service routine,
|
||||
* just make sure that you call it only from single place. Otherwise input
|
||||
* might get corrupted
|
||||
* @param cli
|
||||
* @param c - received char
|
||||
*/
|
||||
void embeddedCliReceiveChar(EmbeddedCli *cli, char c);
|
||||
|
||||
/**
|
||||
* Process rx/tx buffers. Command callbacks are called from here
|
||||
* @param cli
|
||||
*/
|
||||
void embeddedCliProcess(EmbeddedCli *cli);
|
||||
|
||||
/**
|
||||
* Add specified binding to list of bindings. If list is already full, binding
|
||||
* is not added and false is returned
|
||||
* @param cli
|
||||
* @param binding
|
||||
* @return true if binding was added, false otherwise
|
||||
*/
|
||||
bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding);
|
||||
|
||||
/**
|
||||
* Print specified string and account for currently entered but not submitted
|
||||
* command.
|
||||
* Current command is deleted, provided string is printed (with new line) after
|
||||
* that current command is printed again, so user can continue typing it.
|
||||
* @param cli
|
||||
* @param string
|
||||
*/
|
||||
void embeddedCliPrint(EmbeddedCli *cli, const char *string);
|
||||
|
||||
/**
|
||||
* Free allocated for cli memory
|
||||
* @param cli
|
||||
*/
|
||||
void embeddedCliFree(EmbeddedCli *cli);
|
||||
|
||||
/**
|
||||
* Perform tokenization of arguments string. Original string is modified and
|
||||
* should not be used directly (only inside other token functions).
|
||||
* Individual tokens are separated by single 0x00 char, double 0x00 is put at
|
||||
* the end of token list. After calling this function, you can use other
|
||||
* token functions to get individual tokens and token count.
|
||||
*
|
||||
* Important: Call this function only once. Otherwise information will be lost if
|
||||
* more than one token existed
|
||||
* @param args - string to tokenize (must have extra writable char after 0x00)
|
||||
* @return
|
||||
*/
|
||||
void embeddedCliTokenizeArgs(char *args);
|
||||
|
||||
/**
|
||||
* Return specific token from tokenized string
|
||||
* @param tokenizedStr
|
||||
* @param pos (counted from 1)
|
||||
* @return token
|
||||
*/
|
||||
const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos);
|
||||
|
||||
/**
|
||||
* Same as embeddedCliGetToken but works on non-const buffer
|
||||
* @param tokenizedStr
|
||||
* @param pos (counted from 1)
|
||||
* @return token
|
||||
*/
|
||||
char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos);
|
||||
|
||||
/**
|
||||
* Find token in provided tokens string and return its position (counted from 1)
|
||||
* If no such token is found - 0 is returned.
|
||||
* @param tokenizedStr
|
||||
* @param token - token to find
|
||||
* @return position (increased by 1) or zero if no such token found
|
||||
*/
|
||||
uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token);
|
||||
|
||||
/**
|
||||
* Return number of tokens in tokenized string
|
||||
* @param tokenizedStr
|
||||
* @return number of tokens
|
||||
*/
|
||||
uint16_t embeddedCliGetTokenCount(const char *tokenizedStr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif //EMBEDDED_CLI_H
|
||||
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Sviatoslav Kokurin (funbiscuit)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
# Embedded CLI
|
||||
|
||||
Implementation is taken from `funbiscuit` @ [github](https://github.com/funbiscuit/embedded-cli)
|
||||
|
||||
- Only source and header where integrated.
|
||||
- Full project is included in `external/embedded-cli`
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
/**
|
||||
* @file cliWrapper.hpp
|
||||
* @author Gustice
|
||||
* @brief Wrapping Command-Line-Interfaces with custom endpoints
|
||||
* @details Command-Line-Interface is based on the implementation of
|
||||
* https://github.com/funbiscuit/embedded-cli
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "embedded_cli.h"
|
||||
#include <esp_log.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "simpleStreams.hpp"
|
||||
|
||||
/* Module configuration */
|
||||
static constexpr int CLI_RX_BUFFER_SIZE = 64;
|
||||
static constexpr int CLI_CMD_BUFFER_SIZE = 64; // to also accept URLs for Updates
|
||||
static constexpr int CLI_HISTORY_SIZE = 16;
|
||||
static constexpr int CLI_BINDING_COUNT = 8;
|
||||
static constexpr std::string_view CliWelcomeString{
|
||||
"** Smart-DMX-Bridge **\n"
|
||||
" send 'help' for help-page\n"
|
||||
" NOTE: Syslog-messages are sent to client of this session\n"};
|
||||
|
||||
/**
|
||||
* @brief Wrapper for c-implementation of CLI
|
||||
*/
|
||||
class Cli {
|
||||
public:
|
||||
/// @brief Type for callback arguments
|
||||
using CArgs = std::vector<std::string> &;
|
||||
/// @brief Type for bind callback
|
||||
using BindCb = std::function<void(CArgs)>;
|
||||
/// @brief Type for command callback
|
||||
using CommandCb = std::function<void(std::string, CArgs)>;
|
||||
|
||||
/// @brief Parameter structure for command-binding
|
||||
struct Binding {
|
||||
std::string_view name;
|
||||
std::string_view help_output;
|
||||
BindCb callback;
|
||||
};
|
||||
|
||||
/// @brief Constructor
|
||||
/// @param port Reference to serial port
|
||||
/// @param cmd command callback
|
||||
Cli(CharStream &port, CommandCb cmd)
|
||||
: _port(port), _commandCb(cmd) {
|
||||
EmbeddedCliConfig *config = embeddedCliDefaultConfig();
|
||||
// NOTE: Note setting cliBuffer allows Cli to allocate needed buffer in correct size
|
||||
// config->cliBuffer = cliBuffer;
|
||||
// config->cliBufferSize = CLI_BUFFER_SIZE;
|
||||
config->rxBufferSize = CLI_RX_BUFFER_SIZE;
|
||||
config->cmdBufferSize = CLI_CMD_BUFFER_SIZE;
|
||||
config->historyBufferSize = CLI_HISTORY_SIZE;
|
||||
config->maxBindingCount = CLI_BINDING_COUNT;
|
||||
cli = embeddedCliNew(config);
|
||||
|
||||
if (cli == NULL) {
|
||||
ESP_LOGE("CLI", "Cli was not created, requires %d bytes. Check sizes!",
|
||||
embeddedCliRequiredSize(config));
|
||||
return;
|
||||
}
|
||||
cli->onCommand = genericCallback;
|
||||
cli->writeChar = writeToken;
|
||||
// appContext is a user-end variable and is bound to current instance
|
||||
cli->appContext = this;
|
||||
}
|
||||
|
||||
/// @brief Destructor
|
||||
~Cli() {
|
||||
embeddedCliFree(cli);
|
||||
}
|
||||
|
||||
/// @brief Create command binding
|
||||
bool addBinding(Binding bind) {
|
||||
BindCb *cb = new BindCb{bind.callback};
|
||||
|
||||
CliCommandBinding cBinding{.name = bind.name.begin(),
|
||||
.help = bind.help_output.begin(),
|
||||
.tokenizeArgs = true,
|
||||
// NOTE: this hard reference only works because we only provide
|
||||
// an add-function without remove
|
||||
.context = (void *)(cb),
|
||||
.binding = boundCallback};
|
||||
return embeddedCliAddBinding(cli, cBinding);
|
||||
}
|
||||
|
||||
/// @brief Static method to generate c-compatible function-pointer for bound-command-callback
|
||||
/// @details - receives and wraps arguments in string,
|
||||
/// - determines object according to appContext
|
||||
/// - calls appropriate method of appropriate object
|
||||
static void boundCallback(EmbeddedCli *cli, char *args, void *context) {
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
ESP_LOGE("CLI", "No context in callback available");
|
||||
}
|
||||
ESP_LOGD("CLI", "Received command:");
|
||||
|
||||
BindCb &cb = *((BindCb *)context);
|
||||
uint len = embeddedCliGetTokenCount(args);
|
||||
std::vector<std::string> sArgs;
|
||||
while (len-- > 0) {
|
||||
const char *const arg = embeddedCliGetToken(args, 1);
|
||||
ESP_LOGD("CLI", " arg: %s", arg);
|
||||
sArgs.emplace_back(arg);
|
||||
// shift position to end of this element to save time
|
||||
args += sArgs.back().size() + 1; // needs also to be shifted by null-delimiter
|
||||
}
|
||||
cb(sArgs);
|
||||
}
|
||||
|
||||
/// @brief Static method to generate c-compatible function-pointer for command-callback
|
||||
/// @details - receives and wraps arguments in string,
|
||||
/// - determines object according to appContext
|
||||
/// - calls command-callback of appropriate object
|
||||
static void genericCallback(EmbeddedCli *cli, CliCommand *command) {
|
||||
std::string name(command->name);
|
||||
ESP_LOGI("CLI", "Received generic command: %s", name.c_str());
|
||||
|
||||
embeddedCliTokenizeArgs(command->args);
|
||||
std::vector<std::string> sArgs;
|
||||
for (int i = 1; i <= embeddedCliGetTokenCount(command->args); ++i) {
|
||||
sArgs.emplace_back(embeddedCliGetToken(command->args, i));
|
||||
ESP_LOGI("CLI", " arg %d : %s", i, sArgs.back().c_str());
|
||||
}
|
||||
auto &commandCb = static_cast<Cli *>(cli->appContext)->_commandCb;
|
||||
commandCb(name, sArgs);
|
||||
}
|
||||
|
||||
/// @brief Process buffer and trigger commands
|
||||
void process() {
|
||||
auto r = _port.read();
|
||||
for (auto &&c : r) {
|
||||
embeddedCliReceiveChar(cli, c);
|
||||
}
|
||||
embeddedCliProcess(cli);
|
||||
}
|
||||
|
||||
/// @brief publicly exposed write method
|
||||
void write(std::string output) {
|
||||
_port.write(output);
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief Reference to hardware port
|
||||
CharStream &_port;
|
||||
/// @brief Callback for generic command
|
||||
CommandCb _commandCb;
|
||||
EmbeddedCli *cli = nullptr;
|
||||
|
||||
/// @brief Write callback
|
||||
static void writeToken(EmbeddedCli *embeddedCli, char c) {
|
||||
static_cast<Cli *>(embeddedCli->appContext)->_port.write(c);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
)
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @file countDown.hpp
|
||||
* @author Gustice
|
||||
* @brief Classes for typical index manipulation
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class CountDown {
|
||||
public:
|
||||
CountDown(unsigned max) : Max(max) {}
|
||||
|
||||
void Tick() {
|
||||
_cnt++;
|
||||
if (_cnt >= Max) {
|
||||
_cnt = 0;
|
||||
_pFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool GetVolatileFlag() {
|
||||
if (!_pFlag)
|
||||
return false;
|
||||
|
||||
// Reset _pFlag in any case, so it only be consumed once
|
||||
// hence volatile
|
||||
_pFlag = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const unsigned Max;
|
||||
unsigned _cnt;
|
||||
bool _pFlag;
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* @file rotatingIndex.hpp
|
||||
* @author Gustice
|
||||
* @brief Classes for typical index manipulation
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Index with automatic revolution back to start position
|
||||
* @details Useful for Buffer-Pointer, or similar tasks
|
||||
*/
|
||||
class RotatingIndex {
|
||||
public:
|
||||
/// @brief Value at witch the index rotates back to beginning
|
||||
const uint16_t MaxIndex;
|
||||
|
||||
/// @brief Constructor for rotating index
|
||||
/// @brief output index will be set to 0 if incremented value reaches Maximum
|
||||
/// @param max Maximum index for overflow-condition
|
||||
RotatingIndex(uint16_t max) : MaxIndex(max) { _index = 0; }
|
||||
|
||||
/// @brief Get the current index and inkrement index afterwards
|
||||
/// @return uint16_t current index
|
||||
uint16_t getIndexAndIncrement(void) {
|
||||
auto retVal = _index;
|
||||
_index++;
|
||||
if (_index >= MaxIndex) {
|
||||
_index = 0;
|
||||
}
|
||||
return retVal;
|
||||
};
|
||||
|
||||
/// @brief Get the current index without incrementing the index
|
||||
/// @return uint16_t current index
|
||||
uint16_t getIndex(void) { return _index; }
|
||||
|
||||
/// @brief Check if next get will lead to overflow condition
|
||||
bool peekRevolution(void) {
|
||||
if (MaxIndex == 0)
|
||||
return true;
|
||||
|
||||
if (_index >= MaxIndex - 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @brief Reset Index to 0
|
||||
void resetIndex(void) { _index = 0; }
|
||||
|
||||
private:
|
||||
/// Current Index
|
||||
uint16_t _index;
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @file dummy.cpp
|
||||
* @author Gustice
|
||||
* @brief Silence toolchain
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
+1
-1
Submodule core updated: 03852f4beb...3041a17851
+1
-1
@@ -10,6 +10,6 @@ dependencies:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.3.0
|
||||
manifest_hash: 98a89ad95a9bf328a52537968b9e16f59396acd948940746898355de27de317f
|
||||
manifest_hash: 736e193f01d8a1ca4afaed91b473283f46d56db468270aee2f055b9425257e7f
|
||||
target: esp32c6
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1972,6 +1972,62 @@ CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
|
||||
CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
|
||||
# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
|
||||
# end of Wi-Fi Provisioning Manager
|
||||
|
||||
#
|
||||
# mDNS
|
||||
#
|
||||
CONFIG_MDNS_MAX_INTERFACES=3
|
||||
CONFIG_MDNS_MAX_SERVICES=10
|
||||
CONFIG_MDNS_TASK_PRIORITY=1
|
||||
CONFIG_MDNS_ACTION_QUEUE_LEN=16
|
||||
CONFIG_MDNS_TASK_STACK_SIZE=4096
|
||||
# CONFIG_MDNS_TASK_AFFINITY_NO_AFFINITY is not set
|
||||
CONFIG_MDNS_TASK_AFFINITY_CPU0=y
|
||||
CONFIG_MDNS_TASK_AFFINITY=0x0
|
||||
CONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS=2000
|
||||
CONFIG_MDNS_TIMER_PERIOD_MS=100
|
||||
# CONFIG_MDNS_NETWORKING_SOCKET is not set
|
||||
# CONFIG_MDNS_SKIP_SUPPRESSING_OWN_QUERIES is not set
|
||||
# CONFIG_MDNS_ENABLE_DEBUG_PRINTS is not set
|
||||
CONFIG_MDNS_ENABLE_CONSOLE_CLI=y
|
||||
# CONFIG_MDNS_RESPOND_REVERSE_QUERIES is not set
|
||||
CONFIG_MDNS_MULTIPLE_INSTANCE=y
|
||||
|
||||
#
|
||||
# MDNS Predefined interfaces
|
||||
#
|
||||
CONFIG_MDNS_PREDEF_NETIF_STA=y
|
||||
CONFIG_MDNS_PREDEF_NETIF_AP=y
|
||||
CONFIG_MDNS_PREDEF_NETIF_ETH=y
|
||||
# end of MDNS Predefined interfaces
|
||||
# end of mDNS
|
||||
|
||||
#
|
||||
# LittleFS
|
||||
#
|
||||
# CONFIG_LITTLEFS_SDMMC_SUPPORT is not set
|
||||
CONFIG_LITTLEFS_MAX_PARTITIONS=3
|
||||
CONFIG_LITTLEFS_PAGE_SIZE=256
|
||||
CONFIG_LITTLEFS_OBJ_NAME_LEN=64
|
||||
CONFIG_LITTLEFS_READ_SIZE=128
|
||||
CONFIG_LITTLEFS_WRITE_SIZE=128
|
||||
CONFIG_LITTLEFS_LOOKAHEAD_SIZE=128
|
||||
CONFIG_LITTLEFS_CACHE_SIZE=512
|
||||
CONFIG_LITTLEFS_BLOCK_CYCLES=512
|
||||
CONFIG_LITTLEFS_USE_MTIME=y
|
||||
# CONFIG_LITTLEFS_USE_ONLY_HASH is not set
|
||||
# CONFIG_LITTLEFS_HUMAN_READABLE is not set
|
||||
CONFIG_LITTLEFS_MTIME_USE_SECONDS=y
|
||||
# CONFIG_LITTLEFS_MTIME_USE_NONCE is not set
|
||||
# CONFIG_LITTLEFS_SPIFFS_COMPAT is not set
|
||||
# CONFIG_LITTLEFS_FLUSH_FILE_EVERY_WRITE is not set
|
||||
# CONFIG_LITTLEFS_FCNTL_GET_PATH is not set
|
||||
# CONFIG_LITTLEFS_MULTIVERSION is not set
|
||||
# CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE is not set
|
||||
CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT=y
|
||||
# CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL is not set
|
||||
CONFIG_LITTLEFS_ASSERTS=y
|
||||
# end of LittleFS
|
||||
# end of Component config
|
||||
|
||||
# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set
|
||||
|
||||
Reference in New Issue
Block a user