proj: Migrate to core as git-submodule

This commit is contained in:
2025-02-15 21:45:13 +01:00
parent c7e1b4805b
commit a5805aceb2
99 changed files with 68 additions and 8635 deletions
Binary file not shown.
Binary file not shown.
+4 -2
View File
@@ -7,13 +7,15 @@
"includePath": [ "includePath": [
"${config:idf.espIdfPath}/components/**", "${config:idf.espIdfPath}/components/**",
"${config:idf.espIdfPathWin}/components/**", "${config:idf.espIdfPathWin}/components/**",
"${workspaceFolder}/**" "${workspaceFolder}/**",
"${workspaceFolder}/core/**"
], ],
"browse": { "browse": {
"path": [ "path": [
"${config:idf.espIdfPath}/components", "${config:idf.espIdfPath}/components",
"${config:idf.espIdfPathWin}/components", "${config:idf.espIdfPathWin}/components",
"${workspaceFolder}" "${workspaceFolder}",
"${workspaceFolder}/core"
], ],
"limitSymbolsToIncludedHeaders": true "limitSymbolsToIncludedHeaders": true
} }
+1 -1
View File
@@ -263,7 +263,7 @@
"telnet://${config:project.deviceIp}:23", "telnet://${config:project.deviceIp}:23",
"/W=\"Device-Platform\"", "/W=\"Device-Platform\"",
"/T=1", "/T=1",
"/F=\"${workspaceFolder}/.configs/TelnetTeraTerm.INI\"" "/F=\"${workspaceFolder}/core/.configs/TelnetTeraTerm.INI\""
], ],
"problemMatcher": [] "problemMatcher": []
} }
+5
View File
@@ -1,4 +1,9 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS
"core/components"
"core/managed_components"
)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(X_WatchController_FW) project(X_WatchController_FW)
-98
View File
@@ -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
-540
View File
@@ -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
-5
View File
@@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "."
INCLUDE_DIRS "."
REQUIRES sockets
)
-21
View File
@@ -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
}
-4
View File
@@ -1,4 +0,0 @@
# ArtNet
Implementation is taken form `hideakitai` @ [github](https://github.com/hideakitai/ArtNet)
-10
View File
@@ -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
)
-95
View File
@@ -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;
};
-98
View File
@@ -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);
}
}
-4
View File
@@ -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
}
-87
View File
@@ -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
-98
View File
@@ -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
-184
View File
@@ -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 &param = 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 &param, 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
)
-30
View File
@@ -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());
}
}
-155
View File
@@ -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);
}
-88
View File
@@ -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]);
}
-4
View File
@@ -1,4 +0,0 @@
idf_component_register(
SRC_DIRS "src"
INCLUDE_DIRS "include"
)
-202
View File
@@ -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;
-202
View File
@@ -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;
};
-95
View File
@@ -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 &parameter);
///@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
-27
View File
@@ -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);
-71
View File
@@ -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
-58
View File
@@ -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
-117
View File
@@ -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 &parameter)
: _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
-100
View File
@@ -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
)
-15
View File
@@ -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);
}
}
-9
View File
@@ -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
)
-50
View File
@@ -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);
};
-74
View File
@@ -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()};
}
-11
View File
@@ -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;
};
-82
View File
@@ -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 &param)
: 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 &param = 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; });
}
}
};
-34
View File
@@ -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;
}
};
-64
View File
@@ -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));
}
-48
View File
@@ -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));
}
-141
View File
@@ -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]);
}
-42
View File
@@ -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);
}
-5
View File
@@ -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;
};
-41
View File
@@ -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};
}
-316
View File
@@ -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;
}
-167
View File
@@ -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};
}
-149
View File
@@ -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);
-4
View File
@@ -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;
};
-9
View File
@@ -1,9 +0,0 @@
/**
* @file dummy.cpp
* @author Gustice
* @brief Silence toolchain
*
* @copyright Copyright (c) 2024
*/
#include "portBase.hpp"
-5
View File
@@ -1,5 +0,0 @@
idf_component_register(
SRC_DIRS "src"
INCLUDE_DIRS "include"
REQUIRES esp_netif lwip project_bases freeRtosApi mqtt
)
-196
View File
@@ -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;
};
-65
View File
@@ -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);
};
-309
View File
@@ -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;
};
-309
View File
@@ -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;
};
-68
View File
@@ -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;
}
-150
View File
@@ -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);
}
-9
View File
@@ -1,9 +0,0 @@
/**
* @file tcpSockets.cpp
* @author Gustice
* @brief Function frames for handling sockets
*
* @copyright Copyright (c) 2024
*/
#include "tcpSockets.hpp"
-36
View File
@@ -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());
}
}
-4
View File
@@ -1,4 +0,0 @@
idf_component_register(
SRC_DIRS "extern"
INCLUDE_DIRS "include" "extern"
)
File diff suppressed because it is too large Load Diff
-312
View File
@@ -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
-21
View File
@@ -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
View File
@@ -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`
-161
View File
@@ -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);
}
};
-4
View File
@@ -1,4 +0,0 @@
idf_component_register(
SRC_DIRS "src"
INCLUDE_DIRS "include"
)
-36
View File
@@ -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;
};
-8
View File
@@ -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
View File
@@ -10,6 +10,6 @@ dependencies:
source: source:
type: idf type: idf
version: 5.3.0 version: 5.3.0
manifest_hash: 98a89ad95a9bf328a52537968b9e16f59396acd948940746898355de27de317f manifest_hash: 736e193f01d8a1ca4afaed91b473283f46d56db468270aee2f055b9425257e7f
target: esp32c6 target: esp32c6
version: 1.0.0 version: 1.0.0
+56
View File
@@ -1972,6 +1972,62 @@ CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
# end of Wi-Fi Provisioning Manager # 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 # end of Component config
# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set