Cleaned up repository
- only moving files around
This commit is contained in:
6
esp-firmware/.gitignore
vendored
Normal file
6
esp-firmware/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
venv
|
||||
10
esp-firmware/.vscode/extensions.json
vendored
Normal file
10
esp-firmware/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
57
esp-firmware/.vscode/settings.json
vendored
Normal file
57
esp-firmware/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.tcc": "cpp",
|
||||
"deque": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"vector": "cpp",
|
||||
"system_error": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cmath": "cpp",
|
||||
"array": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"string_view": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"fstream": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"memory": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"numeric": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"utility": "cpp",
|
||||
"typeinfo": "cpp"
|
||||
},
|
||||
"vsmqtt.brokerProfiles": [
|
||||
{
|
||||
"name": "homeassistant",
|
||||
"host": "homeassistant",
|
||||
"port": 1883,
|
||||
"username": "musicmouse",
|
||||
"clientId": "vsmqtt_client",
|
||||
"password": "KNLEFLZF94yA6Zhj141",
|
||||
}
|
||||
]
|
||||
}
|
||||
323
esp-firmware/bell_curve_approx.ipynb
Normal file
323
esp-firmware/bell_curve_approx.ipynb
Normal file
File diff suppressed because one or more lines are too long
109
esp-firmware/lib/ledtl/Esp32DriverRGBW.cpp
Normal file
109
esp-firmware/lib/ledtl/Esp32DriverRGBW.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#ifndef PLATFORM_NATIVE
|
||||
|
||||
#include "drivers/Esp32DriverRGBW.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/rmt.h>
|
||||
|
||||
// Timing constants
|
||||
static constexpr uint16_t DIVIDER = 4;
|
||||
static constexpr double RMT_DURATION_NS = 12.5; // Minimum time of a single RMT duration based on clock ns
|
||||
static constexpr uint32_t T0H = 300;
|
||||
static constexpr uint32_t T0L = 900;
|
||||
static constexpr uint32_t T1H = 600;
|
||||
static constexpr uint32_t T1L = 600;
|
||||
static constexpr uint32_t TRS = 80000;
|
||||
static constexpr rmt_item32_t bit0Data = {uint32_t(T0H / (RMT_DURATION_NS * DIVIDER)), 1, uint32_t(T0L / (RMT_DURATION_NS * DIVIDER)), 0};
|
||||
static constexpr rmt_item32_t bit1Data = {uint32_t(T1H / (RMT_DURATION_NS * DIVIDER)), 1, uint32_t(T1L / (RMT_DURATION_NS * DIVIDER)), 0};
|
||||
|
||||
// Function registered at the RMT driver that converts regular uint8_t values containing r,g,b,w values
|
||||
// to rmt_item32_t for each bit. (8 bit -> 32 * 8 bit)
|
||||
static void IRAM_ATTR uint8ToRmtAdaptor(const void *src, rmt_item32_t *dest, size_t srcSize,
|
||||
size_t wantedNum, size_t *translatedSize, size_t *itemNum)
|
||||
{
|
||||
if (src == NULL || dest == NULL)
|
||||
{
|
||||
*translatedSize = 0;
|
||||
*itemNum = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t size = 0;
|
||||
size_t num = 0;
|
||||
uint8_t *psrc = (uint8_t *)src;
|
||||
rmt_item32_t *pdest = dest;
|
||||
while (size < srcSize && num < wantedNum)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
// MSB first
|
||||
const bool isBitSet = *psrc & (1 << (7 - i));
|
||||
pdest->val = isBitSet ? bit1Data.val : bit0Data.val;
|
||||
num++;
|
||||
pdest++;
|
||||
}
|
||||
size++;
|
||||
psrc++;
|
||||
}
|
||||
*translatedSize = size;
|
||||
*itemNum = num;
|
||||
}
|
||||
|
||||
void Esp32DriverRGBW::begin(int gpio, int rmtChannel)
|
||||
{
|
||||
rmtChannel_ = rmtChannel;
|
||||
transmitting_ = false;
|
||||
|
||||
rmt_config_t rmt_tx;
|
||||
rmt_tx.channel = static_cast<rmt_channel_t>(rmtChannel);
|
||||
rmt_tx.gpio_num = static_cast<gpio_num_t>(gpio);
|
||||
rmt_tx.rmt_mode = RMT_MODE_TX;
|
||||
rmt_tx.mem_block_num = 1;
|
||||
rmt_tx.clk_div = DIVIDER;
|
||||
rmt_tx.tx_config.loop_en = false;
|
||||
rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||
rmt_tx.tx_config.carrier_en = false;
|
||||
rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
|
||||
rmt_tx.tx_config.idle_output_en = true;
|
||||
|
||||
ESP_ERROR_CHECK(rmt_config(&rmt_tx));
|
||||
ESP_ERROR_CHECK(rmt_driver_install(rmt_tx.channel, 0, 0));
|
||||
|
||||
rmt_translator_init((rmt_channel_t)rmtChannel, uint8ToRmtAdaptor);
|
||||
}
|
||||
|
||||
void Esp32DriverRGBW::end()
|
||||
{
|
||||
ESP_ERROR_CHECK(rmt_driver_uninstall((rmt_channel_t)rmtChannel_));
|
||||
}
|
||||
|
||||
void Esp32DriverRGBW::writeSync(const uint32_t *rgbwData, int numLeds)
|
||||
{
|
||||
waitForTransmissionToFinish();
|
||||
auto data = reinterpret_cast<const uint8_t *>(rgbwData);
|
||||
ESP_ERROR_CHECK(rmt_write_sample((rmt_channel_t)rmtChannel_, data, numLeds * 4, true));
|
||||
}
|
||||
|
||||
void Esp32DriverRGBW::writeAsync(const uint32_t *rgbwData, int numLeds)
|
||||
{
|
||||
waitForTransmissionToFinish();
|
||||
auto data = reinterpret_cast<const uint8_t *>(rgbwData);
|
||||
ESP_ERROR_CHECK(rmt_write_sample((rmt_channel_t)rmtChannel_, data, numLeds * 4, false));
|
||||
transmitting_ = true;
|
||||
}
|
||||
|
||||
bool Esp32DriverRGBW::waitForTransmissionToFinish(int waitMs)
|
||||
{
|
||||
if (!transmitting_)
|
||||
return true;
|
||||
auto ret = rmt_wait_tx_done((rmt_channel_t)rmtChannel_, waitMs / portTICK_PERIOD_MS);
|
||||
if (ret == ESP_OK)
|
||||
{
|
||||
transmitting_ = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
109
esp-firmware/lib/ledtl/containers/LedStripRGBW.h
Normal file
109
esp-firmware/lib/ledtl/containers/LedStripRGBW.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/// a static array of red-green-blue-white values together with free functions to set them
|
||||
#pragma once
|
||||
|
||||
#include "helpers/ColorRGBW.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
template <int TNumLeds>
|
||||
class LedStripRGBW
|
||||
{
|
||||
public:
|
||||
static constexpr int NUM_LEDS = TNumLeds;
|
||||
|
||||
static constexpr int normalizeIdx(int idx)
|
||||
{
|
||||
return (idx < 0) ? (idx + NUM_LEDS) : (idx >= NUM_LEDS ? idx - NUM_LEDS : idx);
|
||||
}
|
||||
|
||||
void set(int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
|
||||
{
|
||||
// green: 0
|
||||
// red: 8
|
||||
// blue: 16
|
||||
// white: 24
|
||||
idx = normalizeIdx(idx);
|
||||
data_[idx] = (g << 0) | (r << 8) | (b << 16) | (w << 24);
|
||||
}
|
||||
|
||||
void getRGBW(int idx, uint8_t &r, uint8_t &g, uint8_t &b, uint8_t &w)
|
||||
{
|
||||
idx = normalizeIdx(idx);
|
||||
g = (data_[idx] >> 0) & 0xff;
|
||||
r = (data_[idx] >> 8) & 0xff;
|
||||
b = (data_[idx] >> 16) & 0xff;
|
||||
w = (data_[idx] >> 24) & 0xff;
|
||||
}
|
||||
|
||||
const uint32_t *rawData() const { return data_; }
|
||||
constexpr static int numLeds() { return TNumLeds; }
|
||||
|
||||
private:
|
||||
uint32_t data_[TNumLeds];
|
||||
};
|
||||
|
||||
template <typename TLedStrip>
|
||||
constexpr int numLeds()
|
||||
{
|
||||
return TLedStrip::NUM_LEDS;
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
constexpr int numLeds(const LedStripRGBW<TNumLeds> &)
|
||||
{
|
||||
return TNumLeds;
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
void setLedRGB(LedStripRGBW<TNumLeds> &s, int beginIdx, int endIdx, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
for (int i = beginIdx; i < endIdx; ++i)
|
||||
s.set(i, r, g, b, 0);
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
void setLedRGB(LedStripRGBW<TNumLeds> &s, int idx, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
s.set(idx, r, g, b, 0);
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
void setLedRGBW(LedStripRGBW<TNumLeds> &s, int beginIdx, int endIdx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
|
||||
{
|
||||
for (int i = beginIdx; i < endIdx; ++i)
|
||||
s.set(i, r, g, b, w);
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
void setLedRGBW(LedStripRGBW<TNumLeds> &s, int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
|
||||
{
|
||||
s.set(idx, r, g, b, w);
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
void setLedRGBW(LedStripRGBW<TNumLeds> &s, int idx, const ColorRGBW &c)
|
||||
{
|
||||
s.set(idx, c.r, c.g, c.b, c.w);
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
void setLedRGBW(LedStripRGBW<TNumLeds> &s, int beginIdx, int endIdx, const ColorRGBW &c)
|
||||
{
|
||||
for (int i = beginIdx; i < endIdx; ++i)
|
||||
s.set(i, c.r, c.g, c.b, c.w);
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
void clear(LedStripRGBW<TNumLeds> &s)
|
||||
{
|
||||
for (int i = 0; i < TNumLeds; ++i)
|
||||
s.set(i, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
template <int TNumLeds>
|
||||
ColorRGBW getLedRGBW(LedStripRGBW<TNumLeds> &s, int idx)
|
||||
{
|
||||
ColorRGBW res;
|
||||
s.getRGBW(idx, res.r, res.g, res.b, res.w);
|
||||
return res;
|
||||
}
|
||||
23
esp-firmware/lib/ledtl/drivers/Esp32DriverRGBW.h
Normal file
23
esp-firmware/lib/ledtl/drivers/Esp32DriverRGBW.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef PLATFORM_NATIVE
|
||||
|
||||
#include "containers/LedStripRGBW.h"
|
||||
|
||||
class Esp32DriverRGBW
|
||||
{
|
||||
public:
|
||||
void begin(int gpio, int rmtChannel);
|
||||
void end();
|
||||
|
||||
void writeSync(const uint32_t *rgbwData, int numLeds);
|
||||
|
||||
void writeAsync(const uint32_t *rgbwData, int numLeds);
|
||||
bool waitForTransmissionToFinish(int waitMs = 1000);
|
||||
|
||||
private:
|
||||
int rmtChannel_;
|
||||
bool transmitting_;
|
||||
};
|
||||
|
||||
#endif
|
||||
153
esp-firmware/lib/ledtl/effects/AlexaSwipe.h
Normal file
153
esp-firmware/lib/ledtl/effects/AlexaSwipe.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include "effects/Common.h"
|
||||
#include "helpers/ColorRGBW.h"
|
||||
#include "helpers/ColorHSV.h"
|
||||
#include "helpers/ColorConversions.h"
|
||||
#include "helpers/BellCurve.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EffectAlexaSwipeConfig
|
||||
{
|
||||
float primaryColorWidth; // in degrees
|
||||
float transitionWidth;
|
||||
float swipeSpeed; // in degrees per second
|
||||
float bellCurveWidthInLeds;
|
||||
float startPosition;
|
||||
bool forward;
|
||||
ColorRGBW primaryColor;
|
||||
ColorRGBW secondaryColor;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename TLedStrip>
|
||||
class EffectAlexaSwipe
|
||||
{
|
||||
public:
|
||||
static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
|
||||
static constexpr int DELAY_MS = 10;
|
||||
|
||||
using ConfigType = EffectAlexaSwipeConfig;
|
||||
|
||||
EffectAlexaSwipe(const EffectAlexaSwipeConfig &cfg, TLedStrip &ledStrip)
|
||||
: ledStrip_(ledStrip),
|
||||
currentPosition_(0),
|
||||
transitionWidth_(cfg.transitionWidth / 360.0f * NUM_LEDS),
|
||||
invTransitionWidth_(1.0f / transitionWidth_),
|
||||
primaryColorWidth_(cfg.primaryColorWidth / 360.0f * NUM_LEDS + cfg.bellCurveWidthInLeds),
|
||||
bellCurveWidth_(cfg.bellCurveWidthInLeds),
|
||||
invBellCurveWidth_(1.0f / cfg.bellCurveWidthInLeds),
|
||||
speed_(cfg.swipeSpeed / 360 / 1000 * NUM_LEDS * DELAY_MS),
|
||||
startPosition_(cfg.startPosition / 360.0f * NUM_LEDS),
|
||||
primaryColor_(rgb2hsv(cfg.primaryColor)),
|
||||
secondaryColor_(rgb2hsv(cfg.secondaryColor)),
|
||||
finished_(false)
|
||||
{
|
||||
if (cfg.forward)
|
||||
{
|
||||
currentPosition_ = 0;
|
||||
direction_ = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentPosition_ = float(NUM_LEDS) / 2.0f + bellCurveWidth_ / 2;
|
||||
direction_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool finished() const { return finished_; }
|
||||
|
||||
int operator()()
|
||||
{
|
||||
clear(ledStrip_);
|
||||
|
||||
const auto width = std::min(int(currentPosition_ + 1), int(NUM_LEDS / 2));
|
||||
|
||||
setLedRGBW(ledStrip_, startPosition_, getColor(currentPosition_));
|
||||
for (int i = 1; i < width; ++i)
|
||||
{
|
||||
const float x = currentPosition_ - float(i);
|
||||
if (x > 0.0f)
|
||||
{
|
||||
const int led1 = startPosition_ + i;
|
||||
const int led2 = startPosition_ - i;
|
||||
const ColorRGBW color = getColor(x);
|
||||
setLedRGBW(ledStrip_, led1, color);
|
||||
setLedRGBW(ledStrip_, led2, color);
|
||||
}
|
||||
}
|
||||
currentPosition_ += direction_ * speed_;
|
||||
const auto maxPosition = float(NUM_LEDS) / 2.0f + bellCurveWidth_ / 2;
|
||||
const auto minPosition = 0.0f;
|
||||
currentPosition_ = std::min(currentPosition_, maxPosition);
|
||||
currentPosition_ = std::max(currentPosition_, minPosition);
|
||||
if (currentPosition_ <= minPosition || currentPosition_ >= maxPosition)
|
||||
finished_ = true;
|
||||
|
||||
return DELAY_MS;
|
||||
}
|
||||
|
||||
private:
|
||||
void getParams(float x, float &interpFac, float &brightness)
|
||||
{
|
||||
brightness = stepFunction(x, bellCurveWidth_, invBellCurveWidth_);
|
||||
if (x < primaryColorWidth_)
|
||||
interpFac = 0.0f;
|
||||
else if (x > primaryColorWidth_ + transitionWidth_)
|
||||
interpFac = 1.0f;
|
||||
else
|
||||
interpFac = (x - primaryColorWidth_) * invTransitionWidth_;
|
||||
}
|
||||
|
||||
// x is positive distance from running front
|
||||
ColorRGBW getColor(float x)
|
||||
{
|
||||
float interpFac;
|
||||
float brightness;
|
||||
getParams(x, interpFac, brightness);
|
||||
ColorHSV result{
|
||||
interpFac * secondaryColor_.h + (1.0f - interpFac) * primaryColor_.h,
|
||||
interpFac * secondaryColor_.s + (1.0f - interpFac) * primaryColor_.s,
|
||||
interpFac * secondaryColor_.v + (1.0f - interpFac) * primaryColor_.v};
|
||||
result.v *= brightness;
|
||||
return hsv2rgb(result);
|
||||
}
|
||||
|
||||
TLedStrip &ledStrip_;
|
||||
|
||||
// in number of leds
|
||||
float currentPosition_;
|
||||
float transitionWidth_;
|
||||
float invTransitionWidth_;
|
||||
float primaryColorWidth_;
|
||||
float bellCurveWidth_;
|
||||
float invBellCurveWidth_;
|
||||
float speed_;
|
||||
|
||||
int startPosition_;
|
||||
|
||||
const ColorHSV primaryColor_;
|
||||
const ColorHSV secondaryColor_;
|
||||
float direction_;
|
||||
|
||||
bool finished_;
|
||||
};
|
||||
|
||||
// Traits
|
||||
template <>
|
||||
struct EffectIdToConfig<EffectId::ALEXA_SWIPE>
|
||||
{
|
||||
using type = EffectAlexaSwipeConfig;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct EffectConfigToId<EffectAlexaSwipeConfig>
|
||||
{
|
||||
static constexpr auto id = EffectId::ALEXA_SWIPE;
|
||||
};
|
||||
|
||||
template <typename TLedStrip>
|
||||
struct EffectIdToClass<EffectId::ALEXA_SWIPE, TLedStrip>
|
||||
{
|
||||
using type = EffectAlexaSwipe<TLedStrip>;
|
||||
};
|
||||
90
esp-firmware/lib/ledtl/effects/Circular.h
Normal file
90
esp-firmware/lib/ledtl/effects/Circular.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include "effects/Common.h"
|
||||
#include "helpers/ColorRGBW.h"
|
||||
#include "helpers/BellCurve.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EffectCircularConfig
|
||||
{
|
||||
float speed; // in degrees per second
|
||||
float width; // width in degrees
|
||||
ColorRGBW color;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename TLedStrip>
|
||||
class EffectCircular
|
||||
{
|
||||
public:
|
||||
static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
|
||||
static constexpr int DELAY_MS = 10;
|
||||
using ConfigType = EffectCircularConfig;
|
||||
|
||||
EffectCircular(const EffectCircularConfig &cfg, TLedStrip &ledStrip)
|
||||
: config_(cfg),
|
||||
ledStrip_(ledStrip),
|
||||
currentPosition_(0),
|
||||
widthInLeds_((numLeds(ledStrip) * cfg.width / 360)),
|
||||
invWidth_(1.0f / widthInLeds_)
|
||||
{
|
||||
}
|
||||
|
||||
int operator()()
|
||||
{
|
||||
int startLed = int(currentPosition_);
|
||||
float distDown = currentPosition_ - float(startLed);
|
||||
float distUp = 1.f - distDown;
|
||||
|
||||
clear(ledStrip_);
|
||||
// center
|
||||
setLedRGBW(ledStrip_, startLed,
|
||||
config_.color * bellCurveApproximation(distDown, invWidth_));
|
||||
|
||||
// down
|
||||
for (int i = 1; i < widthInLeds_ / 2 + 1; ++i)
|
||||
{
|
||||
setLedRGBW(ledStrip_, startLed - i,
|
||||
config_.color * bellCurveApproximation(distDown + i, invWidth_));
|
||||
}
|
||||
|
||||
// up
|
||||
for (int i = 1; i < widthInLeds_ / 2 + 1; ++i)
|
||||
{
|
||||
setLedRGBW(ledStrip_, startLed + i,
|
||||
config_.color * bellCurveApproximation(distUp + i - 1, invWidth_));
|
||||
}
|
||||
|
||||
currentPosition_ += config_.speed / 1000 / 360 * NUM_LEDS * DELAY_MS;
|
||||
if (currentPosition_ > NUM_LEDS)
|
||||
currentPosition_ -= NUM_LEDS;
|
||||
|
||||
return DELAY_MS;
|
||||
}
|
||||
|
||||
private:
|
||||
EffectCircularConfig config_;
|
||||
TLedStrip &ledStrip_;
|
||||
float currentPosition_; // between 0 and num leds
|
||||
int widthInLeds_;
|
||||
float invWidth_;
|
||||
};
|
||||
|
||||
// Traits
|
||||
template <>
|
||||
struct EffectIdToConfig<EffectId::CIRCULAR>
|
||||
{
|
||||
using type = EffectCircularConfig;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct EffectConfigToId<EffectCircularConfig>
|
||||
{
|
||||
static constexpr auto id = EffectId::CIRCULAR;
|
||||
};
|
||||
|
||||
template <typename TLedStrip>
|
||||
struct EffectIdToClass<EffectId::CIRCULAR, TLedStrip>
|
||||
{
|
||||
using type = EffectCircular<TLedStrip>;
|
||||
};
|
||||
27
esp-firmware/lib/ledtl/effects/Common.h
Normal file
27
esp-firmware/lib/ledtl/effects/Common.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
enum class EffectId
|
||||
{
|
||||
STATIC,
|
||||
CIRCULAR,
|
||||
ALEXA_SWIPE,
|
||||
RANDOM_TWO_COLOR_INTERPOLATION,
|
||||
SWIPE_AND_CHANGE, // combination of ALEXA_SWIPE and RANDOM_TWO_COLOR_INTERPOLATION
|
||||
REVERSE_SWIPE,
|
||||
STATIC_DETAILED,
|
||||
};
|
||||
|
||||
template <EffectId id>
|
||||
struct EffectIdToConfig
|
||||
{
|
||||
};
|
||||
|
||||
template <typename EffectConfig>
|
||||
struct EffectConfigToId
|
||||
{
|
||||
};
|
||||
|
||||
template <EffectId id, typename TLedStrip>
|
||||
struct EffectIdToClass
|
||||
{
|
||||
};
|
||||
155
esp-firmware/lib/ledtl/effects/RandomTwoColorInterpolation.h
Normal file
155
esp-firmware/lib/ledtl/effects/RandomTwoColorInterpolation.h
Normal file
@@ -0,0 +1,155 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "effects/Common.h"
|
||||
#include "helpers/ColorRGBW.h"
|
||||
#include "helpers/ColorHSV.h"
|
||||
#include "helpers/ColorConversions.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EffectRandomTwoColorInterpolationConfig
|
||||
{
|
||||
int32_t cycleDurationMs;
|
||||
bool startWithExisting;
|
||||
int32_t numSegments;
|
||||
|
||||
bool hue1Random;
|
||||
bool hue2Random;
|
||||
|
||||
ColorHSV color1;
|
||||
ColorHSV color2;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename TLedStrip>
|
||||
class EffectRandomTwoColorInterpolation
|
||||
{
|
||||
public:
|
||||
static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
|
||||
static constexpr int DELAY_MS = 10;
|
||||
|
||||
EffectRandomTwoColorInterpolation(const EffectRandomTwoColorInterpolationConfig &cfg, TLedStrip &ledStrip)
|
||||
: ledStrip_(ledStrip),
|
||||
config_(cfg),
|
||||
progress_(0.0f)
|
||||
{
|
||||
currentColors_ = arr1;
|
||||
nextColors_ = arr2;
|
||||
|
||||
if (config_.startWithExisting)
|
||||
for (int i = 0; i < NUM_LEDS; ++i)
|
||||
currentColors_[i] = rgb2hsv(getLedRGBW(ledStrip_, i));
|
||||
else
|
||||
randomizeColors(currentColors_);
|
||||
|
||||
randomizeColors(nextColors_);
|
||||
}
|
||||
|
||||
void begin()
|
||||
{
|
||||
if (config_.startWithExisting)
|
||||
for (int i = 0; i < NUM_LEDS; ++i)
|
||||
currentColors_[i] = rgb2hsv(getLedRGBW(ledStrip_, i));
|
||||
}
|
||||
|
||||
int operator()()
|
||||
{
|
||||
for (int i = 0; i < NUM_LEDS; ++i)
|
||||
setLedRGBW(ledStrip_, i, hsv2rgb(interpolate(currentColors_[i], nextColors_[i], progress_)));
|
||||
|
||||
progress_ += (float(DELAY_MS) / config_.cycleDurationMs);
|
||||
if (progress_ > 1)
|
||||
{
|
||||
progress_ = 0;
|
||||
std::swap(currentColors_, nextColors_);
|
||||
randomizeColors(nextColors_);
|
||||
}
|
||||
return DELAY_MS;
|
||||
}
|
||||
|
||||
private:
|
||||
float randomFloat()
|
||||
{
|
||||
return float(esp_random()) / float(UINT32_MAX);
|
||||
}
|
||||
|
||||
ColorHSV randomColor()
|
||||
{
|
||||
ColorHSV color1 = config_.color1;
|
||||
ColorHSV color2 = config_.color2;
|
||||
|
||||
if (config_.hue1Random)
|
||||
color1.h = randomFloat() * 360;
|
||||
if (config_.hue2Random)
|
||||
color2.h = randomFloat() * 360;
|
||||
|
||||
float f = randomFloat();
|
||||
return interpolate(color1, color2, f);
|
||||
}
|
||||
|
||||
void randomizeColors(ColorHSV *arr)
|
||||
{
|
||||
int segmentLength = NUM_LEDS / config_.numSegments;
|
||||
int lastSegmentLength = NUM_LEDS - (segmentLength * (config_.numSegments - 1));
|
||||
|
||||
ColorHSV firstColor = randomColor();
|
||||
|
||||
ColorHSV currentColor = firstColor;
|
||||
ColorHSV nextColor = randomColor();
|
||||
|
||||
int position = random(0, NUM_LEDS);
|
||||
const auto incrPosition = [&position]()
|
||||
{
|
||||
position = (position == NUM_LEDS - 1) ? 0 : position + 1;
|
||||
};
|
||||
|
||||
for (int segmentIdx = 0; segmentIdx < config_.numSegments - 1; ++segmentIdx)
|
||||
{
|
||||
for (int i = 0; i < segmentLength; ++i)
|
||||
{
|
||||
float f = float(i) / float(segmentLength);
|
||||
arr[position] = interpolate(currentColor, nextColor, f);
|
||||
incrPosition();
|
||||
}
|
||||
currentColor = nextColor;
|
||||
nextColor = randomColor();
|
||||
}
|
||||
// last segment
|
||||
for (int i = 0; i < lastSegmentLength; ++i)
|
||||
{
|
||||
float f = float(i) / float(lastSegmentLength);
|
||||
arr[position] = interpolate(currentColor, firstColor, f);
|
||||
incrPosition();
|
||||
}
|
||||
}
|
||||
|
||||
TLedStrip &ledStrip_;
|
||||
EffectRandomTwoColorInterpolationConfig config_;
|
||||
|
||||
ColorHSV arr1[NUM_LEDS];
|
||||
ColorHSV arr2[NUM_LEDS];
|
||||
|
||||
ColorHSV *currentColors_;
|
||||
ColorHSV *nextColors_;
|
||||
|
||||
float progress_;
|
||||
};
|
||||
|
||||
// Traits
|
||||
template <>
|
||||
struct EffectIdToConfig<EffectId::RANDOM_TWO_COLOR_INTERPOLATION>
|
||||
{
|
||||
using type = EffectRandomTwoColorInterpolationConfig;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct EffectConfigToId<EffectRandomTwoColorInterpolationConfig>
|
||||
{
|
||||
static constexpr auto id = EffectId::RANDOM_TWO_COLOR_INTERPOLATION;
|
||||
};
|
||||
|
||||
template <typename TLedStrip>
|
||||
struct EffectIdToClass<EffectId::RANDOM_TWO_COLOR_INTERPOLATION, TLedStrip>
|
||||
{
|
||||
using type = EffectRandomTwoColorInterpolation<TLedStrip>;
|
||||
};
|
||||
112
esp-firmware/lib/ledtl/effects/ReverseSwipe.h
Normal file
112
esp-firmware/lib/ledtl/effects/ReverseSwipe.h
Normal file
@@ -0,0 +1,112 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "effects/Common.h"
|
||||
#include "helpers/BellCurve.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EffectReverseSwipeConfig
|
||||
{
|
||||
float swipeSpeed; // in degrees per second
|
||||
float bellCurveWidthInLeds;
|
||||
float startPosition;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename TLedStrip>
|
||||
class EffectReverseSwipe
|
||||
{
|
||||
|
||||
public:
|
||||
static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
|
||||
static constexpr int DELAY_MS = 10;
|
||||
|
||||
using ConfigType = EffectReverseSwipeConfig;
|
||||
|
||||
EffectReverseSwipe(const EffectReverseSwipeConfig &cfg, TLedStrip &ledStrip)
|
||||
: ledStrip_(ledStrip),
|
||||
currentPosition_(float(NUM_LEDS) / 2 + cfg.bellCurveWidthInLeds),
|
||||
bellCurveWidth_(cfg.bellCurveWidthInLeds),
|
||||
invBellCurveWidth_(1.0f / cfg.bellCurveWidthInLeds),
|
||||
speed_(cfg.swipeSpeed / 360 / 1000 * NUM_LEDS * DELAY_MS),
|
||||
startPosition_(cfg.startPosition / 360.0f * NUM_LEDS),
|
||||
finished_(false)
|
||||
{
|
||||
for (int i = 0; i < NUM_LEDS; ++i)
|
||||
state_[i] = getLedRGBW(ledStrip_, i);
|
||||
}
|
||||
|
||||
bool finished() const { return finished_; }
|
||||
|
||||
int operator()()
|
||||
{
|
||||
if (finished_)
|
||||
return 60000;
|
||||
|
||||
const auto width = std::min(int(currentPosition_ + 1), int(NUM_LEDS / 2) + 1);
|
||||
|
||||
{
|
||||
float brightness = stepFunction(currentPosition_, bellCurveWidth_, invBellCurveWidth_);
|
||||
ColorRGBW &prevC = state_[ledStrip_.normalizeIdx(startPosition_)];
|
||||
setLedRGBW(ledStrip_, startPosition_, prevC * brightness);
|
||||
}
|
||||
|
||||
for (int i = 1; i < width; ++i)
|
||||
{
|
||||
const float x = currentPosition_ - float(i);
|
||||
if (x > 0.0f)
|
||||
{
|
||||
const int led1 = startPosition_ + i;
|
||||
const int led2 = startPosition_ - i;
|
||||
float brightness = stepFunction(x, bellCurveWidth_, invBellCurveWidth_);
|
||||
ColorRGBW &prevC1 = state_[ledStrip_.normalizeIdx(led1)];
|
||||
ColorRGBW &prevC2 = state_[ledStrip_.normalizeIdx(led2)];
|
||||
|
||||
setLedRGBW(ledStrip_, led1, prevC1 * brightness);
|
||||
setLedRGBW(ledStrip_, led2, prevC2 * brightness);
|
||||
}
|
||||
}
|
||||
|
||||
currentPosition_ -= speed_;
|
||||
if (currentPosition_ < 0)
|
||||
{
|
||||
finished_ = true;
|
||||
clear(ledStrip_);
|
||||
}
|
||||
|
||||
return DELAY_MS;
|
||||
}
|
||||
|
||||
private:
|
||||
TLedStrip &ledStrip_;
|
||||
float currentPosition_;
|
||||
|
||||
float bellCurveWidth_;
|
||||
float invBellCurveWidth_;
|
||||
float speed_;
|
||||
|
||||
int startPosition_;
|
||||
|
||||
bool finished_;
|
||||
|
||||
ColorRGBW state_[NUM_LEDS];
|
||||
};
|
||||
|
||||
// Traits
|
||||
template <>
|
||||
struct EffectIdToConfig<EffectId::REVERSE_SWIPE>
|
||||
{
|
||||
using type = EffectReverseSwipeConfig;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct EffectConfigToId<EffectReverseSwipeConfig>
|
||||
{
|
||||
static constexpr auto id = EffectId::REVERSE_SWIPE;
|
||||
};
|
||||
|
||||
template <typename TLedStrip>
|
||||
struct EffectIdToClass<EffectId::REVERSE_SWIPE, TLedStrip>
|
||||
{
|
||||
using type = EffectReverseSwipe<TLedStrip>;
|
||||
};
|
||||
63
esp-firmware/lib/ledtl/effects/Static.h
Normal file
63
esp-firmware/lib/ledtl/effects/Static.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "effects/Common.h"
|
||||
#include "helpers/ColorRGBW.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EffectStaticConfig
|
||||
{
|
||||
EffectStaticConfig(const ColorRGBW &c = ColorRGBW{0, 0, 0, 0}, uint16_t beg = 0, uint16_t en = 0)
|
||||
: color(c), begin(beg), end(en) {}
|
||||
|
||||
ColorRGBW color;
|
||||
uint16_t begin = 0;
|
||||
uint16_t end = 0;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename TLedStrip>
|
||||
class EffectStatic
|
||||
{
|
||||
public:
|
||||
static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
|
||||
using ConfigType = EffectStaticConfig;
|
||||
|
||||
EffectStatic(const EffectStaticConfig &cfg, TLedStrip &ledStrip)
|
||||
: config_(cfg),
|
||||
ledStrip_(ledStrip)
|
||||
{
|
||||
}
|
||||
|
||||
int operator()()
|
||||
{
|
||||
if (config_.begin == config_.end)
|
||||
setLedRGBW(ledStrip_, 0, NUM_LEDS, config_.color);
|
||||
else
|
||||
setLedRGBW(ledStrip_, config_.begin, config_.end, config_.color);
|
||||
|
||||
return 10000; // nothing changing, return some large time to sleep
|
||||
}
|
||||
|
||||
private:
|
||||
EffectStaticConfig config_;
|
||||
TLedStrip &ledStrip_;
|
||||
};
|
||||
|
||||
// Traits
|
||||
template <>
|
||||
struct EffectIdToConfig<EffectId::STATIC>
|
||||
{
|
||||
using type = EffectStaticConfig;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct EffectConfigToId<EffectStaticConfig>
|
||||
{
|
||||
static constexpr auto id = EffectId::STATIC;
|
||||
};
|
||||
|
||||
template <typename TLedStrip>
|
||||
struct EffectIdToClass<EffectId::STATIC, TLedStrip>
|
||||
{
|
||||
using type = EffectStatic<TLedStrip>;
|
||||
};
|
||||
107
esp-firmware/lib/ledtl/effects/StaticDetailed.h
Normal file
107
esp-firmware/lib/ledtl/effects/StaticDetailed.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include "effects/Common.h"
|
||||
#include "helpers/ColorRGBW.h"
|
||||
#include "helpers/ColorConversions.h"
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EffectStaticDetailedConfig
|
||||
{
|
||||
EffectStaticDetailedConfig(const ColorRGBW &c = ColorRGBW{0, 0, 0, 0}, uint16_t beg = 0, uint16_t en = 0)
|
||||
: color(c), begin(beg), end(en) {}
|
||||
|
||||
ColorRGBW color;
|
||||
uint16_t increment = 1;
|
||||
float begin = 0.0f;
|
||||
float end = 0.0f;
|
||||
float transition_time_in_ms = 0.0f;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename TLedStrip>
|
||||
class EffectStaticDetailed
|
||||
{
|
||||
public:
|
||||
static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
|
||||
static constexpr int DELAY_MS = 10;
|
||||
using ConfigType = EffectStaticDetailedConfig;
|
||||
|
||||
EffectStaticDetailed(const EffectStaticDetailedConfig &cfg, TLedStrip &ledStrip)
|
||||
: config_(cfg),
|
||||
ledStrip_(ledStrip)
|
||||
{
|
||||
for (int i = 0; i < NUM_LEDS; ++i)
|
||||
state_[i] = getLedRGBW(ledStrip_, i);
|
||||
|
||||
beginIdx_ = constrain(static_cast<int>(cfg.begin * NUM_LEDS + 0.5f), 0, NUM_LEDS - 1);
|
||||
endIdx_ = constrain(static_cast<int>(cfg.end * NUM_LEDS + 0.5f), 0, NUM_LEDS - 1);
|
||||
while (endIdx_ < beginIdx_)
|
||||
endIdx_ += NUM_LEDS;
|
||||
}
|
||||
|
||||
bool finished() const { return finished_; }
|
||||
|
||||
int operator()()
|
||||
{
|
||||
if (finished_)
|
||||
return 1000000;
|
||||
|
||||
const float progress = config_.transition_time_in_ms > 0.0f ? static_cast<float>(DELAY_MS * calls_) / config_.transition_time_in_ms : 1.f;
|
||||
|
||||
// Finished case
|
||||
if (config_.transition_time_in_ms <= 0.0f || progress >= 1.0)
|
||||
{
|
||||
finished_ = true;
|
||||
clear(ledStrip_);
|
||||
for (int i = beginIdx_; i < endIdx_; i += config_.increment)
|
||||
setLedRGBW(ledStrip_, i % NUM_LEDS, config_.color);
|
||||
return 10000000;
|
||||
}
|
||||
|
||||
// In-progress case
|
||||
clear(ledStrip_);
|
||||
for (int i = beginIdx_; i < endIdx_; i += config_.increment)
|
||||
{
|
||||
const auto idx = i % NUM_LEDS;
|
||||
ColorRGBW newColor = ColorRGBW::interpolate(state_[idx], config_.color, progress);
|
||||
setLedRGBW(ledStrip_, idx, newColor);
|
||||
}
|
||||
|
||||
++calls_;
|
||||
return DELAY_MS;
|
||||
}
|
||||
|
||||
private:
|
||||
static int int_interpolate(int prev, int next, float progress)
|
||||
{
|
||||
return static_cast<float>(prev) * (1 - progress) +
|
||||
static_cast<float>(next) * progress;
|
||||
}
|
||||
EffectStaticDetailedConfig config_;
|
||||
TLedStrip &ledStrip_;
|
||||
ColorRGBW state_[NUM_LEDS];
|
||||
int beginIdx_;
|
||||
int endIdx_;
|
||||
int calls_ = 0;
|
||||
bool finished_ = false;
|
||||
};
|
||||
|
||||
// Traits
|
||||
template <>
|
||||
struct EffectIdToConfig<EffectId::STATIC_DETAILED>
|
||||
{
|
||||
using type = EffectStaticDetailedConfig;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct EffectConfigToId<EffectStaticDetailedConfig>
|
||||
{
|
||||
static constexpr auto id = EffectId::STATIC_DETAILED;
|
||||
};
|
||||
|
||||
template <typename TLedStrip>
|
||||
struct EffectIdToClass<EffectId::STATIC_DETAILED, TLedStrip>
|
||||
{
|
||||
using type = EffectStaticDetailed<TLedStrip>;
|
||||
};
|
||||
64
esp-firmware/lib/ledtl/effects/SwipeAndChange.h
Normal file
64
esp-firmware/lib/ledtl/effects/SwipeAndChange.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "effects/Common.h"
|
||||
#include "effects/AlexaSwipe.h"
|
||||
#include "effects/RandomTwoColorInterpolation.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EffectSwipeAndChangeConfig
|
||||
{
|
||||
EffectAlexaSwipeConfig swipeCfg;
|
||||
EffectRandomTwoColorInterpolationConfig changeCfg;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename TLedStrip>
|
||||
class EffectSwipeAndChange
|
||||
{
|
||||
public:
|
||||
EffectSwipeAndChange(const EffectSwipeAndChangeConfig &cfg, TLedStrip &ledStrip)
|
||||
: effect1_(cfg.swipeCfg, ledStrip),
|
||||
effect2_(cfg.changeCfg, ledStrip),
|
||||
effectRunning_(0)
|
||||
{
|
||||
}
|
||||
|
||||
int operator()()
|
||||
{
|
||||
if (!effect1_.finished())
|
||||
{
|
||||
return effect1_();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (effectRunning_ == 0)
|
||||
effect2_.begin();
|
||||
effectRunning_ = 1;
|
||||
return effect2_();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EffectAlexaSwipe<TLedStrip> effect1_;
|
||||
EffectRandomTwoColorInterpolation<TLedStrip> effect2_;
|
||||
int effectRunning_;
|
||||
};
|
||||
|
||||
// Traits
|
||||
template <>
|
||||
struct EffectIdToConfig<EffectId::SWIPE_AND_CHANGE>
|
||||
{
|
||||
using type = EffectSwipeAndChangeConfig;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct EffectConfigToId<EffectSwipeAndChangeConfig>
|
||||
{
|
||||
static constexpr auto id = EffectId::SWIPE_AND_CHANGE;
|
||||
};
|
||||
|
||||
template <typename TLedStrip>
|
||||
struct EffectIdToClass<EffectId::SWIPE_AND_CHANGE, TLedStrip>
|
||||
{
|
||||
using type = EffectSwipeAndChange<TLedStrip>;
|
||||
};
|
||||
6
esp-firmware/lib/ledtl/effects/swipe.py
Normal file
6
esp-firmware/lib/ledtl/effects/swipe.py
Normal file
File diff suppressed because one or more lines are too long
35
esp-firmware/lib/ledtl/helpers/BellCurve.h
Normal file
35
esp-firmware/lib/ledtl/helpers/BellCurve.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
static inline float bellCurveApproximation(float x, float inverseWidth)
|
||||
{
|
||||
if (x < 0)
|
||||
x = -x;
|
||||
|
||||
const auto nx = x * inverseWidth * 4;
|
||||
if (nx > 2)
|
||||
return 0.0f;
|
||||
|
||||
const auto x2 = nx * nx;
|
||||
const auto x3 = x2 * nx;
|
||||
const auto res = 1.0f + 0.27606958941084f * x3 - 0.80213917882168f * x2;
|
||||
|
||||
return res < 0.0f ? 0.0f : res;
|
||||
}
|
||||
|
||||
// Function start at 0 (x=0) and goes smoothly up to 1 and arrives 1 at x=width
|
||||
// inverse width has to be 1 / width (should be cached outside)
|
||||
static inline float stepFunction(float x, float width, float inverseWidth)
|
||||
{
|
||||
if (x < 0.0f)
|
||||
return 0.0f;
|
||||
if (x >= width)
|
||||
return 1.0f;
|
||||
|
||||
auto bellInvWidth = inverseWidth * 0.5f;
|
||||
auto nx = (-x + width) * bellInvWidth * 4;
|
||||
auto x2 = nx * nx;
|
||||
auto x3 = x2 * nx;
|
||||
return 1 + 0.2760695894 * x3 - 0.8021391 * x2;
|
||||
}
|
||||
116
esp-firmware/lib/ledtl/helpers/ColorConversions.h
Normal file
116
esp-firmware/lib/ledtl/helpers/ColorConversions.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#include "helpers/ColorHSV.h"
|
||||
#include "helpers/ColorRGBW.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
// https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both
|
||||
inline ColorHSV rgb2hsv(const ColorRGBW &in)
|
||||
{
|
||||
ColorHSV out;
|
||||
|
||||
const float r = (float)(in.r) / 255.0f;
|
||||
const float g = (float)(in.g) / 255.0f;
|
||||
const float b = (float)(in.b) / 255.0f;
|
||||
|
||||
const float min = std::min(r, std::min(g, b));
|
||||
const float max = std::max(r, std::max(g, b));
|
||||
|
||||
out.v = max; // v
|
||||
const float delta = max - min;
|
||||
if (delta < 0.00001f)
|
||||
{
|
||||
out.s = 0;
|
||||
out.h = 0; // undefined, maybe nan?
|
||||
return out;
|
||||
}
|
||||
if (max > 0.0f)
|
||||
{ // NOTE: if Max is == 0, this divide would cause a crash
|
||||
out.s = (delta / max); // s
|
||||
}
|
||||
else
|
||||
{
|
||||
// if max is 0, then r = g = b = 0
|
||||
// s = 0, h is undefined
|
||||
out.s = 0.0f;
|
||||
out.h = 0.0f; //NAN; // its now undefined
|
||||
return out;
|
||||
}
|
||||
if (r >= max) // > is bogus, just keeps compiler happy
|
||||
out.h = (g - b) / delta; // between yellow & magenta
|
||||
else if (g >= max)
|
||||
out.h = 2.0f + (b - r) / delta; // between cyan & yellow
|
||||
else
|
||||
out.h = 4.0f + (r - g) / delta; // between magenta & cyan
|
||||
|
||||
out.h *= 60.0f; // degrees
|
||||
|
||||
if (out.h < 0.0f)
|
||||
out.h += 360.0f;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
ColorRGBW hsv2rgb(const ColorHSV &in)
|
||||
{
|
||||
int i;
|
||||
ColorRGBW out;
|
||||
out.w = 0;
|
||||
|
||||
if (in.s <= 0.0f)
|
||||
{ // < is bogus, just shuts up warnings
|
||||
out.r = (uint8_t)(in.v * 255.0f);
|
||||
out.g = (uint8_t)(in.v * 255.0f);
|
||||
out.b = (uint8_t)(in.v * 255.0f);
|
||||
return out;
|
||||
}
|
||||
float hh = in.h;
|
||||
if (hh >= 360.0f)
|
||||
hh = 0.0f;
|
||||
hh /= 60.0f;
|
||||
i = (long)hh;
|
||||
auto ff = hh - i;
|
||||
float p = in.v * (1.0f - in.s);
|
||||
float q = in.v * (1.0f - (in.s * ff));
|
||||
float t = in.v * (1.0f - (in.s * (1.0f - ff)));
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
out.r = (uint8_t)(in.v * 255.0f);
|
||||
out.g = (uint8_t)(t * 255.0f);
|
||||
out.b = (uint8_t)(p * 255.0f);
|
||||
break;
|
||||
case 1:
|
||||
out.r = (uint8_t)(q * 255.0f);
|
||||
out.g = (uint8_t)(in.v * 255.0f);
|
||||
out.b = (uint8_t)(p * 255.0f);
|
||||
break;
|
||||
case 2:
|
||||
out.r = (uint8_t)(p * 255.0f);
|
||||
out.g = (uint8_t)(in.v * 255.0f);
|
||||
out.b = (uint8_t)(t * 255.0f);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
out.r = (uint8_t)(p * 255.0f);
|
||||
out.g = (uint8_t)(q * 255.0f);
|
||||
out.b = (uint8_t)(in.v * 255.0f);
|
||||
break;
|
||||
case 4:
|
||||
out.r = (uint8_t)(t * 255.0f);
|
||||
out.g = (uint8_t)(p * 255.0f);
|
||||
out.b = (uint8_t)(in.v * 255.0f);
|
||||
break;
|
||||
case 5:
|
||||
default:
|
||||
out.r = (uint8_t)(in.v * 255.0f);
|
||||
out.g = (uint8_t)(p * 255.0f);
|
||||
out.b = (uint8_t)(q * 255.0f);
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
46
esp-firmware/lib/ledtl/helpers/ColorHSV.h
Normal file
46
esp-firmware/lib/ledtl/helpers/ColorHSV.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct ColorHSV
|
||||
{
|
||||
float h, s, v;
|
||||
};
|
||||
|
||||
inline ColorHSV operator*(const ColorHSV &c, float scalar)
|
||||
{
|
||||
|
||||
return {scalar * c.h,
|
||||
scalar * c.s,
|
||||
scalar * c.v};
|
||||
}
|
||||
|
||||
inline ColorHSV operator*(float scalar, const ColorHSV &c)
|
||||
{
|
||||
return {scalar * c.h,
|
||||
scalar * c.s,
|
||||
scalar * c.v};
|
||||
}
|
||||
|
||||
inline ColorHSV operator+(const ColorHSV &c1, const ColorHSV &c2)
|
||||
{
|
||||
return {
|
||||
c1.h + c2.h,
|
||||
c1.s + c2.s,
|
||||
c1.v + c2.v};
|
||||
}
|
||||
|
||||
inline ColorHSV interpolate(const ColorHSV &c1, const ColorHSV &c2, float f)
|
||||
{
|
||||
return ColorHSV{
|
||||
(1.0f - f) * c1.h + f * c2.h,
|
||||
(1.0f - f) * c1.s + f * c2.s,
|
||||
(1.0f - f) * c1.v + f * c2.v};
|
||||
}
|
||||
|
||||
#ifndef PLATFORM_NATIVE
|
||||
inline void print(const char *prefix, const ColorHSV &c)
|
||||
{
|
||||
Serial.printf("%s HSV(%f, %f, %f)\n", prefix, c.h, c.s, c.v);
|
||||
}
|
||||
#endif
|
||||
26
esp-firmware/lib/ledtl/helpers/ColorRGBW.h
Normal file
26
esp-firmware/lib/ledtl/helpers/ColorRGBW.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct ColorRGBW
|
||||
{
|
||||
uint8_t r, g, b, w;
|
||||
|
||||
ColorRGBW operator*(float s) const
|
||||
{
|
||||
return {uint8_t(s * r),
|
||||
uint8_t(s * g),
|
||||
uint8_t(s * b),
|
||||
uint8_t(s * w)};
|
||||
}
|
||||
|
||||
static inline ColorRGBW interpolate(const ColorRGBW &c1, const ColorRGBW &c2, float f)
|
||||
{
|
||||
return ColorRGBW{
|
||||
static_cast<uint8_t>((1.0f - f) * static_cast<float>(c1.r) + f * static_cast<float>(c2.r)),
|
||||
static_cast<uint8_t>((1.0f - f) * static_cast<float>(c1.g) + f * static_cast<float>(c2.g)),
|
||||
static_cast<uint8_t>((1.0f - f) * static_cast<float>(c1.b) + f * static_cast<float>(c2.b)),
|
||||
static_cast<uint8_t>((1.0f - f) * static_cast<float>(c1.w) + f * static_cast<float>(c2.w)),
|
||||
};
|
||||
}
|
||||
};
|
||||
561
esp-firmware/lib/nfc/rc522.c
Normal file
561
esp-firmware/lib/nfc/rc522.c
Normal file
@@ -0,0 +1,561 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "soc/gpio_struct.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "rc522.h"
|
||||
|
||||
static const char *TAG = "ESP-RC522";
|
||||
|
||||
struct rc522
|
||||
{
|
||||
bool running;
|
||||
rc522_config_t *config;
|
||||
spi_device_handle_t spi;
|
||||
TaskHandle_t task_handle;
|
||||
bool scan_started;
|
||||
bool tag_was_present_last_time;
|
||||
};
|
||||
|
||||
typedef struct rc522 *rc522_handle_t;
|
||||
|
||||
static rc522_handle_t hndl = NULL;
|
||||
|
||||
#define rc522_fw_version() rc522_read(0x37)
|
||||
|
||||
bool rc522_is_inited()
|
||||
{
|
||||
return hndl != NULL;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_spi_init()
|
||||
{
|
||||
if (!hndl || !hndl->config)
|
||||
{
|
||||
ESP_LOGE(TAG, "Fail to init SPI. Invalid handle");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (hndl->spi)
|
||||
{
|
||||
ESP_LOGW(TAG, "SPI already initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.miso_io_num = hndl->config->miso_io,
|
||||
.mosi_io_num = hndl->config->mosi_io,
|
||||
.sclk_io_num = hndl->config->sck_io,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1};
|
||||
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.clock_speed_hz = 5000000,
|
||||
.mode = 0,
|
||||
.spics_io_num = hndl->config->sda_io,
|
||||
.queue_size = 7,
|
||||
.flags = SPI_DEVICE_HALFDUPLEX};
|
||||
|
||||
esp_err_t err = spi_bus_initialize(hndl->config->spi_host_id, &buscfg, 0);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
err = spi_bus_add_device(hndl->config->spi_host_id, &devcfg, &hndl->spi);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
spi_bus_free(hndl->config->spi_host_id);
|
||||
hndl->spi = NULL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_write_n(uint8_t addr, uint8_t n, uint8_t *data)
|
||||
{
|
||||
uint8_t *buffer = (uint8_t *)malloc(n + 1);
|
||||
buffer[0] = (addr << 1) & 0x7E;
|
||||
|
||||
for (uint8_t i = 1; i <= n; i++)
|
||||
{
|
||||
buffer[i] = data[i - 1];
|
||||
}
|
||||
|
||||
spi_transaction_t t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
t.length = 8 * (n + 1);
|
||||
t.tx_buffer = buffer;
|
||||
|
||||
esp_err_t ret = spi_device_transmit(hndl->spi, &t);
|
||||
|
||||
free(buffer);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_write(uint8_t addr, uint8_t val)
|
||||
{
|
||||
return rc522_write_n(addr, 1, &val);
|
||||
}
|
||||
|
||||
static uint8_t *rc522_read_n(uint8_t addr, uint8_t n)
|
||||
{
|
||||
if (n <= 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spi_transaction_t t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
uint8_t *buffer = (uint8_t *)malloc(n);
|
||||
|
||||
t.flags = SPI_TRANS_USE_TXDATA;
|
||||
t.length = 8;
|
||||
t.tx_data[0] = ((addr << 1) & 0x7E) | 0x80;
|
||||
t.rxlength = 8 * n;
|
||||
t.rx_buffer = buffer;
|
||||
|
||||
esp_err_t ret = spi_device_transmit(hndl->spi, &t);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static uint8_t rc522_read(uint8_t addr)
|
||||
{
|
||||
uint8_t *buffer = rc522_read_n(addr, 1);
|
||||
uint8_t res = buffer[0];
|
||||
free(buffer);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_set_bitmask(uint8_t addr, uint8_t mask)
|
||||
{
|
||||
return rc522_write(addr, rc522_read(addr) | mask);
|
||||
}
|
||||
|
||||
static esp_err_t rc522_clear_bitmask(uint8_t addr, uint8_t mask)
|
||||
{
|
||||
return rc522_write(addr, rc522_read(addr) & ~mask);
|
||||
}
|
||||
|
||||
static esp_err_t rc522_antenna_on()
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
if (~(rc522_read(0x14) & 0x03))
|
||||
{
|
||||
ret = rc522_set_bitmask(0x14, 0x03);
|
||||
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return rc522_write(0x26, 0x60); // 43dB gain
|
||||
}
|
||||
|
||||
static void rc522_task(void *arg);
|
||||
|
||||
esp_err_t rc522_init(rc522_config_t *config)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (hndl)
|
||||
{
|
||||
ESP_LOGW(TAG, "Already initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!(hndl = calloc(1, sizeof(struct rc522))))
|
||||
{
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (!(hndl->config = calloc(1, sizeof(rc522_config_t))))
|
||||
{
|
||||
rc522_destroy();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// copy config considering defaults
|
||||
hndl->config->callback = config->callback;
|
||||
hndl->config->miso_io = config->miso_io == 0 ? RC522_DEFAULT_MISO : config->miso_io;
|
||||
hndl->config->mosi_io = config->mosi_io == 0 ? RC522_DEFAULT_MOSI : config->mosi_io;
|
||||
hndl->config->sck_io = config->sck_io == 0 ? RC522_DEFAULT_SCK : config->sck_io;
|
||||
hndl->config->sda_io = config->sda_io == 0 ? RC522_DEFAULT_SDA : config->sda_io;
|
||||
hndl->config->spi_host_id = config->spi_host_id == 0 ? RC522_DEFAULT_SPI_HOST : config->spi_host_id;
|
||||
hndl->config->scan_interval_ms = config->scan_interval_ms < 50 ? RC522_DEFAULT_SCAN_INTERVAL_MS : config->scan_interval_ms;
|
||||
hndl->config->task_stack_size = config->task_stack_size == 0 ? RC522_DEFAULT_TACK_STACK_SIZE : config->task_stack_size;
|
||||
hndl->config->task_priority = config->task_priority == 0 ? RC522_DEFAULT_TACK_STACK_PRIORITY : config->task_priority;
|
||||
|
||||
esp_err_t err = rc522_spi_init();
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
rc522_destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
// ---------- RW test ------------
|
||||
const uint8_t test_addr = 0x24, test_val = 0x25;
|
||||
for (uint8_t i = test_val; i < test_val + 2; i++)
|
||||
{
|
||||
if ((err = rc522_write(test_addr, i)) != ESP_OK || rc522_read(test_addr) != i)
|
||||
{
|
||||
ESP_LOGE(TAG, "RW test fail");
|
||||
rc522_destroy();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
// ------- End of RW test --------
|
||||
|
||||
rc522_write(0x01, 0x0F);
|
||||
rc522_write(0x2A, 0x8D);
|
||||
rc522_write(0x2B, 0x3E);
|
||||
rc522_write(0x2D, 0x1E);
|
||||
rc522_write(0x2C, 0x00);
|
||||
rc522_write(0x15, 0x40);
|
||||
rc522_write(0x11, 0x3D);
|
||||
|
||||
rc522_antenna_on();
|
||||
|
||||
hndl->running = true;
|
||||
if (xTaskCreate(rc522_task, "rc522_task", hndl->config->task_stack_size, NULL, hndl->config->task_priority, &hndl->task_handle) != pdTRUE)
|
||||
{
|
||||
ESP_LOGE(TAG, "Fail to create rc522 task");
|
||||
rc522_destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Fail to create timer");
|
||||
rc522_destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initialized (firmware: 0x%x)", rc522_fw_version());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint64_t rc522_sn_to_u64(uint8_t *sn)
|
||||
{
|
||||
if (!sn)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t result = 0;
|
||||
for (int i = 4; i >= 0; i--)
|
||||
{
|
||||
result |= ((uint64_t)sn[i] << (i * 8));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Returns pointer to dynamically allocated array of two element */
|
||||
static uint8_t *rc522_calculate_crc(uint8_t *data, uint8_t n)
|
||||
{
|
||||
rc522_clear_bitmask(0x05, 0x04);
|
||||
rc522_set_bitmask(0x0A, 0x80);
|
||||
|
||||
rc522_write_n(0x09, n, data);
|
||||
|
||||
rc522_write(0x01, 0x03);
|
||||
|
||||
uint8_t i = 255;
|
||||
uint8_t nn = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
nn = rc522_read(0x05);
|
||||
i--;
|
||||
|
||||
if (!(i != 0 && !(nn & 0x04)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *res = (uint8_t *)malloc(2);
|
||||
|
||||
res[0] = rc522_read(0x22);
|
||||
res[1] = rc522_read(0x21);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static uint8_t *rc522_card_write(uint8_t cmd, uint8_t *data, uint8_t n, uint8_t *res_n)
|
||||
{
|
||||
uint8_t *result = NULL;
|
||||
uint8_t irq = 0x00;
|
||||
uint8_t irq_wait = 0x00;
|
||||
uint8_t last_bits = 0;
|
||||
uint8_t nn = 0;
|
||||
|
||||
if (cmd == 0x0E)
|
||||
{
|
||||
irq = 0x12;
|
||||
irq_wait = 0x10;
|
||||
}
|
||||
else if (cmd == 0x0C)
|
||||
{
|
||||
irq = 0x77;
|
||||
irq_wait = 0x30;
|
||||
}
|
||||
|
||||
rc522_write(0x02, irq | 0x80);
|
||||
rc522_clear_bitmask(0x04, 0x80);
|
||||
rc522_set_bitmask(0x0A, 0x80);
|
||||
rc522_write(0x01, 0x00);
|
||||
|
||||
rc522_write_n(0x09, n, data);
|
||||
|
||||
rc522_write(0x01, cmd);
|
||||
|
||||
if (cmd == 0x0C)
|
||||
{
|
||||
rc522_set_bitmask(0x0D, 0x80);
|
||||
}
|
||||
|
||||
uint16_t i = 1000;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
nn = rc522_read(0x04);
|
||||
i--;
|
||||
|
||||
if (!(i != 0 && (((nn & 0x01) == 0) && ((nn & irq_wait) == 0))))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc522_clear_bitmask(0x0D, 0x80);
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
if ((rc522_read(0x06) & 0x1B) == 0x00)
|
||||
{
|
||||
if (cmd == 0x0C)
|
||||
{
|
||||
nn = rc522_read(0x0A);
|
||||
last_bits = rc522_read(0x0C) & 0x07;
|
||||
|
||||
if (last_bits != 0)
|
||||
{
|
||||
*res_n = (nn - 1) + last_bits;
|
||||
}
|
||||
else
|
||||
{
|
||||
*res_n = nn;
|
||||
}
|
||||
|
||||
result = (uint8_t *)malloc(*res_n);
|
||||
|
||||
for (i = 0; i < *res_n; i++)
|
||||
{
|
||||
result[i] = rc522_read(0x09);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t *rc522_request(uint8_t *res_n)
|
||||
{
|
||||
uint8_t *result = NULL;
|
||||
rc522_write(0x0D, 0x07);
|
||||
|
||||
uint8_t req_mode = 0x26;
|
||||
result = rc522_card_write(0x0C, &req_mode, 1, res_n);
|
||||
|
||||
if (*res_n * 8 != 0x10)
|
||||
{
|
||||
free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t *rc522_anticoll()
|
||||
{
|
||||
uint8_t res_n;
|
||||
|
||||
rc522_write(0x0D, 0x00);
|
||||
uint8_t *result = rc522_card_write(0x0C, (uint8_t[]){0x93, 0x20}, 2, &res_n);
|
||||
|
||||
if (result && res_n != 5)
|
||||
{ // all cards/tags serial numbers is 5 bytes long (?)
|
||||
free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t *rc522_get_tag()
|
||||
{
|
||||
uint8_t *result = NULL;
|
||||
uint8_t *res_data = NULL;
|
||||
uint8_t res_data_n;
|
||||
|
||||
res_data = rc522_request(&res_data_n);
|
||||
|
||||
if (res_data != NULL)
|
||||
{
|
||||
free(res_data);
|
||||
|
||||
result = rc522_anticoll();
|
||||
|
||||
if (result != NULL)
|
||||
{
|
||||
uint8_t buf[] = {0x50, 0x00, 0x00, 0x00};
|
||||
uint8_t *crc = rc522_calculate_crc(buf, 2);
|
||||
|
||||
buf[2] = crc[0];
|
||||
buf[3] = crc[1];
|
||||
|
||||
free(crc);
|
||||
|
||||
res_data = rc522_card_write(0x0C, buf, 4, &res_data_n);
|
||||
free(res_data);
|
||||
|
||||
rc522_clear_bitmask(0x08, 0x08);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t rc522_start(rc522_start_args_t start_args)
|
||||
{
|
||||
esp_err_t err = rc522_init(&start_args);
|
||||
return err != ESP_OK ? err : rc522_start2();
|
||||
}
|
||||
|
||||
esp_err_t rc522_start2()
|
||||
{
|
||||
if (!hndl)
|
||||
{
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
hndl->scan_started = true;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rc522_pause()
|
||||
{
|
||||
if (!hndl)
|
||||
{
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!hndl->scan_started)
|
||||
{
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
hndl->scan_started = false;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void rc522_destroy()
|
||||
{
|
||||
if (!hndl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
rc522_pause(); // stop timer
|
||||
hndl->running = false; // task will delete itself
|
||||
|
||||
if (hndl->spi)
|
||||
{
|
||||
spi_bus_remove_device(hndl->spi);
|
||||
spi_bus_free(hndl->config->spi_host_id);
|
||||
hndl->spi = NULL;
|
||||
}
|
||||
|
||||
free(hndl->config);
|
||||
hndl->config = NULL;
|
||||
|
||||
free(hndl);
|
||||
hndl = NULL;
|
||||
}
|
||||
|
||||
bool last_time_no_tag = false;
|
||||
|
||||
static void rc522_task(void *arg)
|
||||
{
|
||||
while (hndl->running)
|
||||
{
|
||||
if (!hndl->scan_started)
|
||||
{
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t *serial_no = rc522_get_tag();
|
||||
|
||||
if (serial_no && !hndl->tag_was_present_last_time)
|
||||
{
|
||||
last_time_no_tag = false;
|
||||
rc522_tag_callback_t cb = hndl->config->callback;
|
||||
if (cb)
|
||||
{
|
||||
cb(serial_no);
|
||||
}
|
||||
}
|
||||
|
||||
if (serial_no == NULL && !last_time_no_tag)
|
||||
{
|
||||
hndl->config->callback(NULL);
|
||||
last_time_no_tag = true;
|
||||
}
|
||||
|
||||
if ((hndl->tag_was_present_last_time = (serial_no != NULL)))
|
||||
{
|
||||
free(serial_no);
|
||||
serial_no = NULL;
|
||||
}
|
||||
|
||||
int delay_interval_ms = hndl->config->scan_interval_ms;
|
||||
|
||||
if (hndl->tag_was_present_last_time)
|
||||
{
|
||||
delay_interval_ms *= 2; // extra scan-bursting prevention
|
||||
}
|
||||
|
||||
vTaskDelay(delay_interval_ms / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
92
esp-firmware/lib/nfc/rc522.h
Normal file
92
esp-firmware/lib/nfc/rc522.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
#define RC522_DEFAULT_MISO (25)
|
||||
#define RC522_DEFAULT_MOSI (23)
|
||||
#define RC522_DEFAULT_SCK (19)
|
||||
#define RC522_DEFAULT_SDA (22)
|
||||
#define RC522_DEFAULT_SPI_HOST (VSPI_HOST)
|
||||
#define RC522_DEFAULT_SCAN_INTERVAL_MS (125)
|
||||
#define RC522_DEFAULT_TACK_STACK_SIZE (4 * 1024)
|
||||
#define RC522_DEFAULT_TACK_STACK_PRIORITY (4)
|
||||
|
||||
typedef void(*rc522_tag_callback_t)(uint8_t*);
|
||||
|
||||
typedef struct {
|
||||
int miso_io; /*<! MFRC522 MISO gpio (Default: 25) */
|
||||
int mosi_io; /*<! MFRC522 MOSI gpio (Default: 23) */
|
||||
int sck_io; /*<! MFRC522 SCK gpio (Default: 19) */
|
||||
int sda_io; /*<! MFRC522 SDA gpio (Default: 22) */
|
||||
spi_host_device_t spi_host_id; /*<! Default VSPI_HOST (SPI3) */
|
||||
rc522_tag_callback_t callback; /*<! Scanned tags handler */
|
||||
uint16_t scan_interval_ms; /*<! How fast will ESP32 scan for nearby tags, in miliseconds. Default: 125ms */
|
||||
size_t task_stack_size; /*<! Stack size of rc522 task (Default: 4 * 1024) */
|
||||
uint8_t task_priority; /*<! Priority of rc522 task (Default: 4) */
|
||||
} rc522_config_t;
|
||||
|
||||
typedef rc522_config_t rc522_start_args_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize RC522 module.
|
||||
* To start scanning tags - call rc522_resume or rc522_start2 function.
|
||||
* @param config Configuration
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rc522_init(rc522_config_t* config);
|
||||
|
||||
/**
|
||||
* @brief Convert serial number (array of 5 bytes) to uint64_t number
|
||||
* @param sn Serial number
|
||||
* @return Serial number in number representation. If fail, 0 will be retured
|
||||
*/
|
||||
uint64_t rc522_sn_to_u64(uint8_t* sn);
|
||||
|
||||
/**
|
||||
* @brief Check if RC522 is inited
|
||||
* @return true if RC522 is inited
|
||||
*/
|
||||
bool rc522_is_inited();
|
||||
|
||||
/**
|
||||
* @brief This function will call rc522_init function and immediately start to scan tags by calling rc522_resume function.
|
||||
* NOTE: This function will be refactored in future to just start scanning without
|
||||
* initialization (same as rc522_resume). For initialization rc522_init will be required to call before this function.
|
||||
* @param start_args Configuration
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rc522_start(rc522_start_args_t start_args);
|
||||
|
||||
/**
|
||||
* @brief Start to scan tags. If already started, ESP_OK will just be returned.
|
||||
* NOTE: This function is implemented because in time of implementation rc522_start function is intented for
|
||||
* initialization and scanning in once. In future, when rc522_start gonna be refactored to just start to scan tags
|
||||
* without initialization, this function will be just alias of rc522_start.
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rc522_start2();
|
||||
|
||||
/**
|
||||
* @brief Start to scan tags. If already started, ESP_OK will just be returned.
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
#define rc522_resume() rc522_start2()
|
||||
|
||||
/**
|
||||
* @brief Pause scan tags. If already paused, ESP_OK will just be returned.
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rc522_pause();
|
||||
|
||||
/**
|
||||
* @brief Destroy RC522 and free all resources
|
||||
*/
|
||||
void rc522_destroy();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
345
esp-firmware/lib/rotary_encoder/rotary_encoder.c
Normal file
345
esp-firmware/lib/rotary_encoder/rotary_encoder.c
Normal file
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Copyright (c) 2019 David Antliff
|
||||
* Copyright 2011 Ben Buxton
|
||||
*
|
||||
* This file is part of the esp32-rotary-encoder component.
|
||||
*
|
||||
* esp32-rotary-encoder is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* esp32-rotary-encoder is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with esp32-rotary-encoder. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file rotary_encoder.c
|
||||
* @brief Driver implementation for the ESP32-compatible Incremental Rotary Encoder component.
|
||||
*
|
||||
* Based on https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
|
||||
* Original header follows:
|
||||
*
|
||||
* Rotary encoder handler for arduino. v1.1
|
||||
*
|
||||
* Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
|
||||
* Contact: bb@cactii.net
|
||||
*
|
||||
* A typical mechanical rotary encoder emits a two bit gray code
|
||||
* on 3 output pins. Every step in the output (often accompanied
|
||||
* by a physical 'click') generates a specific sequence of output
|
||||
* codes on the pins.
|
||||
*
|
||||
* There are 3 pins used for the rotary encoding - one common and
|
||||
* two 'bit' pins.
|
||||
*
|
||||
* The following is the typical sequence of code on the output when
|
||||
* moving from one step to the next:
|
||||
*
|
||||
* Position Bit1 Bit2
|
||||
* ----------------------
|
||||
* Step1 0 0
|
||||
* 1/4 1 0
|
||||
* 1/2 1 1
|
||||
* 3/4 0 1
|
||||
* Step2 0 0
|
||||
*
|
||||
* From this table, we can see that when moving from one 'click' to
|
||||
* the next, there are 4 changes in the output code.
|
||||
*
|
||||
* - From an initial 0 - 0, Bit1 goes high, Bit0 stays low.
|
||||
* - Then both bits are high, halfway through the step.
|
||||
* - Then Bit1 goes low, but Bit2 stays high.
|
||||
* - Finally at the end of the step, both bits return to 0.
|
||||
*
|
||||
* Detecting the direction is easy - the table simply goes in the other
|
||||
* direction (read up instead of down).
|
||||
*
|
||||
* To decode this, we use a simple state machine. Every time the output
|
||||
* code changes, it follows state, until finally a full steps worth of
|
||||
* code is received (in the correct order). At the final 0-0, it returns
|
||||
* a value indicating a step in one direction or the other.
|
||||
*
|
||||
* It's also possible to use 'half-step' mode. This just emits an event
|
||||
* at both the 0-0 and 1-1 positions. This might be useful for some
|
||||
* encoders where you want to detect all positions.
|
||||
*
|
||||
* If an invalid state happens (for example we go from '0-1' straight
|
||||
* to '1-0'), the state machine resets to the start until 0-0 and the
|
||||
* next valid codes occur.
|
||||
*
|
||||
* The biggest advantage of using a state machine over other algorithms
|
||||
* is that this has inherent debounce built in. Other algorithms emit spurious
|
||||
* output with switch bounce, but this one will simply flip between
|
||||
* sub-states until the bounce settles, then continue along the state
|
||||
* machine.
|
||||
* A side effect of debounce is that fast rotations can cause steps to
|
||||
* be skipped. By not requiring debounce, fast rotations can be accurately
|
||||
* measured.
|
||||
* Another advantage is the ability to properly handle bad state, such
|
||||
* as due to EMI, etc.
|
||||
* It is also a lot simpler than others - a static state table and less
|
||||
* than 10 lines of logic.
|
||||
*/
|
||||
|
||||
#include "rotary_encoder.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#define TAG "rotary_encoder"
|
||||
|
||||
//#define ROTARY_ENCODER_DEBUG
|
||||
|
||||
// Use a single-item queue so that the last value can be easily overwritten by the interrupt handler
|
||||
#define EVENT_QUEUE_LENGTH 1
|
||||
|
||||
#define TABLE_ROWS 7
|
||||
|
||||
#define DIR_NONE 0x0 // No complete step yet.
|
||||
#define DIR_CW 0x10 // Clockwise step.
|
||||
#define DIR_CCW 0x20 // Anti-clockwise step.
|
||||
|
||||
// Create the half-step state table (emits a code at 00 and 11)
|
||||
#define R_START 0x0
|
||||
#define H_CCW_BEGIN 0x1
|
||||
#define H_CW_BEGIN 0x2
|
||||
#define H_START_M 0x3
|
||||
#define H_CW_BEGIN_M 0x4
|
||||
#define H_CCW_BEGIN_M 0x5
|
||||
|
||||
static const uint8_t _ttable_half[TABLE_ROWS][TABLE_COLS] = {
|
||||
// 00 01 10 11 // BA
|
||||
{H_START_M, H_CW_BEGIN, H_CCW_BEGIN, R_START}, // R_START (00)
|
||||
{H_START_M | DIR_CCW, R_START, H_CCW_BEGIN, R_START}, // H_CCW_BEGIN
|
||||
{H_START_M | DIR_CW, H_CW_BEGIN, R_START, R_START}, // H_CW_BEGIN
|
||||
{H_START_M, H_CCW_BEGIN_M, H_CW_BEGIN_M, R_START}, // H_START_M (11)
|
||||
{H_START_M, H_START_M, H_CW_BEGIN_M, R_START | DIR_CW}, // H_CW_BEGIN_M
|
||||
{H_START_M, H_CCW_BEGIN_M, H_START_M, R_START | DIR_CCW}, // H_CCW_BEGIN_M
|
||||
};
|
||||
|
||||
// Create the full-step state table (emits a code at 00 only)
|
||||
# define F_CW_FINAL 0x1
|
||||
# define F_CW_BEGIN 0x2
|
||||
# define F_CW_NEXT 0x3
|
||||
# define F_CCW_BEGIN 0x4
|
||||
# define F_CCW_FINAL 0x5
|
||||
# define F_CCW_NEXT 0x6
|
||||
|
||||
static const uint8_t _ttable_full[TABLE_ROWS][TABLE_COLS] = {
|
||||
// 00 01 10 11 // BA
|
||||
{R_START, F_CW_BEGIN, F_CCW_BEGIN, R_START}, // R_START
|
||||
{F_CW_NEXT, R_START, F_CW_FINAL, R_START | DIR_CW}, // F_CW_FINAL
|
||||
{F_CW_NEXT, F_CW_BEGIN, R_START, R_START}, // F_CW_BEGIN
|
||||
{F_CW_NEXT, F_CW_BEGIN, F_CW_FINAL, R_START}, // F_CW_NEXT
|
||||
{F_CCW_NEXT, R_START, F_CCW_BEGIN, R_START}, // F_CCW_BEGIN
|
||||
{F_CCW_NEXT, F_CCW_FINAL, R_START, R_START | DIR_CCW}, // F_CCW_FINAL
|
||||
{F_CCW_NEXT, F_CCW_FINAL, F_CCW_BEGIN, R_START}, // F_CCW_NEXT
|
||||
};
|
||||
|
||||
static uint8_t _process(rotary_encoder_info_t * info)
|
||||
{
|
||||
uint8_t event = 0;
|
||||
if (info != NULL)
|
||||
{
|
||||
// Get state of input pins.
|
||||
uint8_t pin_state = (gpio_get_level(info->pin_b) << 1) | gpio_get_level(info->pin_a);
|
||||
|
||||
// Determine new state from the pins and state table.
|
||||
#ifdef ROTARY_ENCODER_DEBUG
|
||||
uint8_t old_state = info->table_state;
|
||||
#endif
|
||||
info->table_state = info->table[info->table_state & 0xf][pin_state];
|
||||
|
||||
// Return emit bits, i.e. the generated event.
|
||||
event = info->table_state & 0x30;
|
||||
#ifdef ROTARY_ENCODER_DEBUG
|
||||
ESP_EARLY_LOGD(TAG, "BA %d%d, state 0x%02x, new state 0x%02x, event 0x%02x",
|
||||
pin_state >> 1, pin_state & 1, old_state, info->table_state, event);
|
||||
#endif
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
static void _isr_rotenc(void * args)
|
||||
{
|
||||
rotary_encoder_info_t * info = (rotary_encoder_info_t *)args;
|
||||
uint8_t event = _process(info);
|
||||
bool send_event = false;
|
||||
|
||||
switch (event)
|
||||
{
|
||||
case DIR_CW:
|
||||
++info->state.position;
|
||||
info->state.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE;
|
||||
send_event = true;
|
||||
break;
|
||||
case DIR_CCW:
|
||||
--info->state.position;
|
||||
info->state.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE;
|
||||
send_event = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (send_event && info->queue)
|
||||
{
|
||||
rotary_encoder_event_t queue_event =
|
||||
{
|
||||
.state =
|
||||
{
|
||||
.position = info->state.position,
|
||||
.direction = info->state.direction,
|
||||
},
|
||||
};
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
|
||||
if (task_woken)
|
||||
{
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (info)
|
||||
{
|
||||
info->pin_a = pin_a;
|
||||
info->pin_b = pin_b;
|
||||
info->table = &_ttable_full[0]; //enable_half_step ? &_ttable_half[0] : &_ttable_full[0];
|
||||
info->table_state = R_START;
|
||||
info->state.position = 0;
|
||||
info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
|
||||
|
||||
// configure GPIOs
|
||||
gpio_pad_select_gpio(info->pin_a);
|
||||
gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY);
|
||||
gpio_set_direction(info->pin_a, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE);
|
||||
|
||||
gpio_pad_select_gpio(info->pin_b);
|
||||
gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY);
|
||||
gpio_set_direction(info->pin_b, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE);
|
||||
|
||||
// install interrupt handlers
|
||||
gpio_isr_handler_add(info->pin_a, _isr_rotenc, info);
|
||||
gpio_isr_handler_add(info->pin_b, _isr_rotenc, info);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "info is NULL");
|
||||
err = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (info)
|
||||
{
|
||||
info->table = enable ? &_ttable_half[0] : &_ttable_full[0];
|
||||
info->table_state = R_START;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "info is NULL");
|
||||
err = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (info)
|
||||
{
|
||||
gpio_num_t temp = info->pin_a;
|
||||
info->pin_a = info->pin_b;
|
||||
info->pin_b = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "info is NULL");
|
||||
err = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (info)
|
||||
{
|
||||
gpio_isr_handler_remove(info->pin_a);
|
||||
gpio_isr_handler_remove(info->pin_b);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "info is NULL");
|
||||
err = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
QueueHandle_t rotary_encoder_create_queue(void)
|
||||
{
|
||||
return xQueueCreate(EVENT_QUEUE_LENGTH, sizeof(rotary_encoder_event_t));
|
||||
}
|
||||
|
||||
esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (info)
|
||||
{
|
||||
info->queue = queue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "info is NULL");
|
||||
err = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (info && state)
|
||||
{
|
||||
// make a snapshot of the state
|
||||
state->position = info->state.position;
|
||||
state->direction = info->state.direction;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "info and/or state is NULL");
|
||||
err = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (info)
|
||||
{
|
||||
info->state.position = 0;
|
||||
info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "info is NULL");
|
||||
err = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
172
esp-firmware/lib/rotary_encoder/rotary_encoder.h
Normal file
172
esp-firmware/lib/rotary_encoder/rotary_encoder.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) 2019 David Antliff
|
||||
* Copyright 2011 Ben Buxton
|
||||
*
|
||||
* This file is part of the esp32-rotary-encoder component.
|
||||
*
|
||||
* esp32-rotary-encoder is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* esp32-rotary-encoder is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with esp32-rotary-encoder. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file rotary_encoder.h
|
||||
* @brief Interface definitions for the ESP32-compatible Incremental Rotary Encoder component.
|
||||
*
|
||||
* This component provides a means to interface with a typical rotary encoder such as the EC11 or LPD3806.
|
||||
* These encoders produce a quadrature signal on two outputs, which can be used to track the position and
|
||||
* direction as movement occurs.
|
||||
*
|
||||
* This component provides functions to initialise the GPIOs and install appropriate interrupt handlers to
|
||||
* track a single device's position. An event queue is used to provide a way for a user task to obtain
|
||||
* position information from the component as it is generated.
|
||||
*
|
||||
* Note that the queue is of length 1, and old values will be overwritten. Using a longer queue is
|
||||
* possible with some minor modifications however newer values are lost if the queue overruns. A circular
|
||||
* buffer where old values are lost would be better (maybe StreamBuffer in FreeRTOS 10.0.0?).
|
||||
*/
|
||||
|
||||
#ifndef ROTARY_ENCODER_H
|
||||
#define ROTARY_ENCODER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_err.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef int32_t rotary_encoder_position_t;
|
||||
|
||||
/**
|
||||
* @brief Enum representing the direction of rotation.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
ROTARY_ENCODER_DIRECTION_NOT_SET = 0, ///< Direction not yet known (stationary since reset)
|
||||
ROTARY_ENCODER_DIRECTION_CLOCKWISE,
|
||||
ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE,
|
||||
} rotary_encoder_direction_t;
|
||||
|
||||
// Used internally
|
||||
///@cond INTERNAL
|
||||
#define TABLE_COLS 4
|
||||
typedef uint8_t table_row_t[TABLE_COLS];
|
||||
///@endcond
|
||||
|
||||
/**
|
||||
* @brief Struct represents the current state of the device in terms of incremental position and direction of last movement
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
rotary_encoder_position_t position; ///< Numerical position since reset. This value increments on clockwise rotation, and decrements on counter-clockewise rotation. Counts full or half steps depending on mode. Set to zero on reset.
|
||||
rotary_encoder_direction_t direction; ///< Direction of last movement. Set to NOT_SET on reset.
|
||||
} rotary_encoder_state_t;
|
||||
|
||||
/**
|
||||
* @brief Struct carries all the information needed by this driver to manage the rotary encoder device.
|
||||
* The fields of this structure should not be accessed directly.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
gpio_num_t pin_a; ///< GPIO for Signal A from the rotary encoder device
|
||||
gpio_num_t pin_b; ///< GPIO for Signal B from the rotary encoder device
|
||||
QueueHandle_t queue; ///< Handle for event queue, created by ::rotary_encoder_create_queue
|
||||
const table_row_t * table; ///< Pointer to active state transition table
|
||||
uint8_t table_state; ///< Internal state
|
||||
volatile rotary_encoder_state_t state; ///< Device state
|
||||
} rotary_encoder_info_t;
|
||||
|
||||
/**
|
||||
* @brief Struct represents a queued event, used to communicate current position to a waiting task
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
rotary_encoder_state_t state; ///< The device state corresponding to this event
|
||||
} rotary_encoder_event_t;
|
||||
|
||||
/**
|
||||
* @brief Initialise the rotary encoder device with the specified GPIO pins and full step increments.
|
||||
* This function will set up the GPIOs as needed,
|
||||
* Note: this function assumes that gpio_install_isr_service(0) has already been called.
|
||||
* @param[in, out] info Pointer to allocated rotary encoder info structure.
|
||||
* @param[in] pin_a GPIO number for rotary encoder output A.
|
||||
* @param[in] pin_b GPIO number for rotary encoder output B.
|
||||
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||
*/
|
||||
esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b);
|
||||
|
||||
/**
|
||||
* @brief Enable half-stepping mode. This generates twice as many counted steps per rotation.
|
||||
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||
* @param[in] enable If true, count half steps. If false, only count full steps.
|
||||
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||
*/
|
||||
esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable);
|
||||
|
||||
/**
|
||||
* @brief Reverse (flip) the sense of the direction.
|
||||
* Use this if clockwise/counterclockwise are not what you expect.
|
||||
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||
*/
|
||||
esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info);
|
||||
|
||||
/**
|
||||
* @brief Remove the interrupt handlers installed by ::rotary_encoder_init.
|
||||
* Note: GPIOs will be left in the state they were configured by ::rotary_encoder_init.
|
||||
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||
*/
|
||||
esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info);
|
||||
|
||||
/**
|
||||
* @brief Create a queue handle suitable for use as an event queue.
|
||||
* @return A handle to a new queue suitable for use as an event queue.
|
||||
*/
|
||||
QueueHandle_t rotary_encoder_create_queue(void);
|
||||
|
||||
/**
|
||||
* @brief Set the driver to use the specified queue as an event queue.
|
||||
* It is recommended that a queue constructed by ::rotary_encoder_create_queue is used.
|
||||
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||
* @param[in] queue Handle to queue suitable for use as an event queue. See ::rotary_encoder_create_queue.
|
||||
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||
*/
|
||||
esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue);
|
||||
|
||||
/**
|
||||
* @brief Get the current position of the rotary encoder.
|
||||
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||
* @param[in, out] state Pointer to an allocated rotary_encoder_state_t struct that will
|
||||
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||
*/
|
||||
esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state);
|
||||
|
||||
/**
|
||||
* @brief Reset the current position of the rotary encoder to zero.
|
||||
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||
*/
|
||||
esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ROTARY_ENCODER_H
|
||||
13
esp-firmware/musicmouse.service
Normal file
13
esp-firmware/musicmouse.service
Normal file
@@ -0,0 +1,13 @@
|
||||
# Put this into /etc/systemd/system/musicmouse.service
|
||||
[Unit]
|
||||
Description=Music Mouse RFID Music Player
|
||||
After=multi-user.target
|
||||
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
ExecStart=/opt/musicmouse/venv/bin/python /opt/musicmouse/espmusicmouse/host_driver/main.py /media/musicmouse/
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
34
esp-firmware/platformio.ini
Normal file
34
esp-firmware/platformio.ini
Normal file
@@ -0,0 +1,34 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
data_dir = data
|
||||
default_envs = esp32
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = esp-wrover-kit
|
||||
board_upload.flash_size = "4MB"
|
||||
build_flags = -DPLATFORM_ESP32
|
||||
framework = arduino
|
||||
monitor_port = /dev/ttyUSB0
|
||||
upload_port = /dev/ttyUSB0
|
||||
monitor_speed = 115200
|
||||
src_filter = +<*> -<host_test.cpp>
|
||||
lib_deps =
|
||||
miguelbalboa/MFRC522
|
||||
bxparks/AceButton@^1.9.1
|
||||
|
||||
[env:native]
|
||||
platform = native
|
||||
src_filter = +<*> -<main.cpp>
|
||||
build_flags = -Ilib/ledtl -g -DPLATFORM_NATIVE
|
||||
lib_deps =
|
||||
bxparks/AceButton@^1.9.1
|
||||
249
esp-firmware/src/Messages.h
Normal file
249
esp-firmware/src/Messages.h
Normal file
@@ -0,0 +1,249 @@
|
||||
#include "effects/Circular.h"
|
||||
#include "effects/Static.h"
|
||||
#include "effects/AlexaSwipe.h"
|
||||
#include "effects/ReverseSwipe.h"
|
||||
#include "effects/RandomTwoColorInterpolation.h"
|
||||
#include "effects/SwipeAndChange.h"
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <cstdint>
|
||||
|
||||
constexpr uint32_t MAGIC_TOKEN_HOST_TO_FW = 0x1d6379e3;
|
||||
constexpr uint32_t MAGIC_TOKEN_FW_TO_HOST = 0x10c65631;
|
||||
|
||||
template <typename T>
|
||||
struct ClassToMessageType
|
||||
{
|
||||
};
|
||||
|
||||
enum class MessageFwToHost : uint8_t
|
||||
{
|
||||
RFID_TOKEN_READ = 0,
|
||||
ROTARY_ENCODER = 1,
|
||||
TOUCH_BUTTON_PRESS = 2,
|
||||
TOUCH_BUTTON_RELEASE = 3,
|
||||
BUTTON_EVENT = 4,
|
||||
};
|
||||
|
||||
enum class TouchButton : uint8_t
|
||||
{
|
||||
LEFT_FOOT = 0,
|
||||
RIGHT_FOOT = 1,
|
||||
LEFT_EAR = 2,
|
||||
RIGHT_EAR = 3
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct MsgRfidTokenRead
|
||||
{
|
||||
uint8_t tagId[5];
|
||||
};
|
||||
|
||||
struct MsgRotaryEncoder
|
||||
{
|
||||
int32_t position;
|
||||
int32_t increment;
|
||||
uint8_t direction;
|
||||
};
|
||||
|
||||
struct MsgTouchButtonPress
|
||||
{
|
||||
TouchButton button;
|
||||
};
|
||||
|
||||
struct MsgTouchButtonRelease
|
||||
{
|
||||
TouchButton button;
|
||||
};
|
||||
|
||||
struct MsgButtonEvent
|
||||
{
|
||||
uint8_t buttonNr;
|
||||
uint8_t eventType;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<MsgRfidTokenRead>
|
||||
{
|
||||
static constexpr auto msgType = MessageFwToHost::RFID_TOKEN_READ;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<MsgRotaryEncoder>
|
||||
{
|
||||
static constexpr auto msgType = MessageFwToHost::ROTARY_ENCODER;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<MsgTouchButtonPress>
|
||||
{
|
||||
static constexpr auto msgType = MessageFwToHost::TOUCH_BUTTON_PRESS;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<MsgTouchButtonRelease>
|
||||
{
|
||||
static constexpr auto msgType = MessageFwToHost::TOUCH_BUTTON_RELEASE;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<MsgButtonEvent>
|
||||
{
|
||||
static constexpr auto msgType = MessageFwToHost::BUTTON_EVENT;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
enum class MessageHostToFw : uint8_t
|
||||
{
|
||||
LED_WHEEL_EFFECT_STATIC = 0,
|
||||
LED_WHEEL_EFFECT_ALEXA_SWIPE = 1,
|
||||
LED_WHEEL_EFFECT_CIRCULAR = 2,
|
||||
LED_WHEEL_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION = 3,
|
||||
LED_WHEEL_EFFECT_SWIPE_AND_CHANGE = 4,
|
||||
LED_WHEEL_EFFECT_REVERSE_SWIPE = 5,
|
||||
|
||||
MOUSE_LED_EFFECT_STATIC = 6,
|
||||
MOUSE_LED_EFFECT_CIRCULAR = 7,
|
||||
MOUSE_LED_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION = 8,
|
||||
MOUSE_LED_EFFECT_SWIPE_AND_CHANGE = 9,
|
||||
MOUSE_LED_EFFECT_REVERSE_SWIPE = 10,
|
||||
|
||||
SHELF_LED_EFFECT_STATIC = 15,
|
||||
SHELF_LED_EFFECT_CIRCULAR = 16,
|
||||
SHELF_LED_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION = 17,
|
||||
SHELF_LED_EFFECT_SWIPE_AND_CHANGE = 18,
|
||||
SHELF_LED_EFFECT_REVERSE_SWIPE = 19,
|
||||
SHELF_LED_EFFECT_STATIC_DETAILED = 20,
|
||||
|
||||
PREV_BUTTON_LED = 21,
|
||||
NEXT_BUTTON_LED = 22,
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<EffectStaticConfig>
|
||||
{
|
||||
static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_STATIC;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<EffectAlexaSwipeConfig>
|
||||
{
|
||||
static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_ALEXA_SWIPE;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<EffectCircularConfig>
|
||||
{
|
||||
static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_CIRCULAR;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<EffectRandomTwoColorInterpolationConfig>
|
||||
{
|
||||
static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ClassToMessageType<EffectSwipeAndChangeConfig>
|
||||
{
|
||||
static constexpr auto msgType = MessageHostToFw::LED_WHEEL_EFFECT_SWIPE_AND_CHANGE;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
template <typename TMessage>
|
||||
void sendMessageToHost(const TMessage &msg)
|
||||
{
|
||||
Serial.write((uint8_t *)&MAGIC_TOKEN_FW_TO_HOST, sizeof(MAGIC_TOKEN_FW_TO_HOST));
|
||||
MessageFwToHost msgType = ClassToMessageType<TMessage>::msgType;
|
||||
Serial.write((uint8_t *)&msgType, sizeof(msgType));
|
||||
|
||||
uint16_t msgSize = sizeof(msg);
|
||||
Serial.write((uint8_t *)&msgSize, sizeof(msgSize));
|
||||
Serial.write((uint8_t *)&msg, sizeof(msg));
|
||||
}
|
||||
|
||||
template <typename TEffectConfig, typename TLedTask>
|
||||
inline bool handleLedEffect(TLedTask *ledTask, MessageHostToFw msgType, MessageHostToFw incomingMsgType, uint8_t *msgBuffer)
|
||||
{
|
||||
if (msgType == incomingMsgType)
|
||||
{
|
||||
auto cfg = reinterpret_cast<TEffectConfig *>(msgBuffer);
|
||||
ledTask->startEffect(*cfg);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename LedTask1, typename LedTask2, typename LedTaskShelf>
|
||||
inline void handleIncomingMessagesFromHost(LedTask1 *ledTaskCircle, LedTask2 *ledTaskMouse, LedTaskShelf *ledTaskShelf, uint8_t ledChannelLeft, uint8_t ledChannelRight)
|
||||
{
|
||||
if (Serial.available() < sizeof(MAGIC_TOKEN_FW_TO_HOST) + sizeof(MessageHostToFw) + sizeof(uint16_t))
|
||||
return;
|
||||
|
||||
uint32_t token;
|
||||
Serial.readBytes((uint8_t *)(&token), sizeof(token));
|
||||
if (token != MAGIC_TOKEN_HOST_TO_FW)
|
||||
{
|
||||
Serial.println("Received invalid message");
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHostToFw msgType;
|
||||
Serial.readBytes((uint8_t *)(&msgType), sizeof(msgType));
|
||||
|
||||
uint16_t msgSize;
|
||||
Serial.readBytes((uint8_t *)(&msgSize), sizeof(msgSize));
|
||||
|
||||
static constexpr int maxIncomingBufferSize = 1024;
|
||||
static uint8_t msgBuffer[maxIncomingBufferSize];
|
||||
|
||||
if (msgSize < maxIncomingBufferSize)
|
||||
{
|
||||
Serial.readBytes(msgBuffer, msgSize);
|
||||
|
||||
// clang-format off
|
||||
// LED Circle
|
||||
if(handleLedEffect<EffectStaticConfig >(ledTaskCircle, MessageHostToFw::LED_WHEEL_EFFECT_STATIC, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectAlexaSwipeConfig >(ledTaskCircle, MessageHostToFw::LED_WHEEL_EFFECT_ALEXA_SWIPE, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectCircularConfig >(ledTaskCircle, MessageHostToFw::LED_WHEEL_EFFECT_CIRCULAR, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectRandomTwoColorInterpolationConfig>(ledTaskCircle, MessageHostToFw::LED_WHEEL_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectSwipeAndChangeConfig >(ledTaskCircle, MessageHostToFw::LED_WHEEL_EFFECT_SWIPE_AND_CHANGE, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectReverseSwipeConfig >(ledTaskCircle, MessageHostToFw::LED_WHEEL_EFFECT_REVERSE_SWIPE, msgType, msgBuffer)) {}
|
||||
|
||||
// Mouse LEDs
|
||||
else if(handleLedEffect<EffectStaticConfig >(ledTaskMouse, MessageHostToFw::MOUSE_LED_EFFECT_STATIC, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectCircularConfig >(ledTaskMouse, MessageHostToFw::MOUSE_LED_EFFECT_CIRCULAR, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectRandomTwoColorInterpolationConfig>(ledTaskMouse, MessageHostToFw::MOUSE_LED_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectSwipeAndChangeConfig >(ledTaskMouse, MessageHostToFw::MOUSE_LED_EFFECT_SWIPE_AND_CHANGE, msgType, msgBuffer)) {}
|
||||
else if(handleLedEffect<EffectReverseSwipeConfig >(ledTaskMouse, MessageHostToFw::MOUSE_LED_EFFECT_REVERSE_SWIPE, msgType, msgBuffer)) {}
|
||||
|
||||
// Shelf LEDs
|
||||
else if (handleLedEffect<EffectStaticConfig >(ledTaskShelf, MessageHostToFw::SHELF_LED_EFFECT_STATIC, msgType, msgBuffer)) {}
|
||||
else if (handleLedEffect<EffectCircularConfig >(ledTaskShelf, MessageHostToFw::SHELF_LED_EFFECT_CIRCULAR, msgType, msgBuffer)) {}
|
||||
else if (handleLedEffect<EffectRandomTwoColorInterpolationConfig>(ledTaskShelf, MessageHostToFw::SHELF_LED_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION, msgType, msgBuffer)) {}
|
||||
else if (handleLedEffect<EffectSwipeAndChangeConfig >(ledTaskShelf, MessageHostToFw::SHELF_LED_EFFECT_SWIPE_AND_CHANGE, msgType, msgBuffer)) {}
|
||||
else if (handleLedEffect<EffectReverseSwipeConfig >(ledTaskShelf, MessageHostToFw::SHELF_LED_EFFECT_REVERSE_SWIPE, msgType, msgBuffer)) {}
|
||||
else if (handleLedEffect<EffectStaticDetailedConfig >(ledTaskShelf, MessageHostToFw::SHELF_LED_EFFECT_STATIC_DETAILED, msgType, msgBuffer)) {}
|
||||
// clang-format on
|
||||
|
||||
else if (msgType == MessageHostToFw::PREV_BUTTON_LED)
|
||||
{
|
||||
float *val = reinterpret_cast<float *>(msgBuffer);
|
||||
ledcWrite(ledChannelLeft, uint32_t(255 * (*val)));
|
||||
}
|
||||
else if (msgType == MessageHostToFw::NEXT_BUTTON_LED)
|
||||
{
|
||||
float *val = reinterpret_cast<float *>(msgBuffer);
|
||||
ledcWrite(ledChannelRight, uint32_t(255 * (*val)));
|
||||
}
|
||||
else
|
||||
Serial.println("Unknown message type");
|
||||
}
|
||||
else
|
||||
Serial.printf("Incoming message too large (or invalid) %d\n", msgSize);
|
||||
}
|
||||
129
esp-firmware/src/TaskLed.h
Normal file
129
esp-firmware/src/TaskLed.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include "effects/Circular.h"
|
||||
#include "effects/Common.h"
|
||||
#include "drivers/Esp32DriverRGBW.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include <functional>
|
||||
#include <cstring>
|
||||
|
||||
static constexpr int MAX_EFFECT_CONFIG_SIZE = 128;
|
||||
static constexpr int MAX_EFFECT_CLASS_SIZE = 8 * 1024;
|
||||
|
||||
template <typename TLedStrip>
|
||||
class LedTask
|
||||
{
|
||||
public:
|
||||
void begin(TLedStrip &strip, Esp32DriverRGBW &driver);
|
||||
|
||||
template <typename TEffectConfig>
|
||||
void startEffect(const TEffectConfig &cfg);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
friend void _led_task_func(void *);
|
||||
|
||||
QueueHandle_t queue_ = nullptr;
|
||||
TLedStrip *ledStrip_ = nullptr;
|
||||
Esp32DriverRGBW *driver_ = nullptr;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
template <EffectId staticEffectId, typename TLedStrip>
|
||||
bool dispatchEffectId(EffectId dynamicEffectId, std::function<int()> &effectFunction, TLedStrip &ledStrip,
|
||||
unsigned char *msgBuffer, unsigned char *effectStorage)
|
||||
{
|
||||
if (staticEffectId == dynamicEffectId)
|
||||
{
|
||||
typename EffectIdToConfig<staticEffectId>::type cfg;
|
||||
memcpy(&cfg, msgBuffer + sizeof(EffectId), sizeof(decltype(cfg)));
|
||||
using TEffect = typename EffectIdToClass<staticEffectId, TLedStrip>::type;
|
||||
static_assert(sizeof(TEffect) < MAX_EFFECT_CLASS_SIZE, "Effect to big for effectStorage, increase MAX_EFFECT_CLASS_SIZE");
|
||||
TEffect *effect = new (effectStorage) TEffect(cfg, ledStrip);
|
||||
effectFunction = [effect]()
|
||||
{ return (*effect)(); };
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename TLedStrip>
|
||||
void _led_task_func(void *params)
|
||||
{
|
||||
LedTask<TLedStrip> *task = reinterpret_cast<LedTask<TLedStrip> *>(params);
|
||||
unsigned char msgBuffer[MAX_EFFECT_CONFIG_SIZE];
|
||||
unsigned char effectStorage[MAX_EFFECT_CLASS_SIZE];
|
||||
|
||||
std::function<int()> effectFunction = []() -> int
|
||||
{ return 100000; };
|
||||
|
||||
int timeoutMsForEffect = 100000; // huge timeout since there is no effect in the beginning
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (xQueueReceive(task->queue_, msgBuffer, (TickType_t)(timeoutMsForEffect / portTICK_PERIOD_MS)) == pdTRUE)
|
||||
{
|
||||
// Read and parse effect from queue
|
||||
EffectId id;
|
||||
memcpy(&id, msgBuffer, sizeof(EffectId));
|
||||
|
||||
TLedStrip &ledStrip = *(task->ledStrip_);
|
||||
// clang-format off
|
||||
if (dispatchEffectId<EffectId::CIRCULAR >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {}
|
||||
else if (dispatchEffectId<EffectId::STATIC >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {}
|
||||
else if (dispatchEffectId<EffectId::STATIC_DETAILED >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {}
|
||||
else if (dispatchEffectId<EffectId::ALEXA_SWIPE >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {}
|
||||
else if (dispatchEffectId<EffectId::RANDOM_TWO_COLOR_INTERPOLATION>(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {}
|
||||
else if (dispatchEffectId<EffectId::SWIPE_AND_CHANGE >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {}
|
||||
else if (dispatchEffectId<EffectId::REVERSE_SWIPE >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {}
|
||||
// clang-format on
|
||||
|
||||
timeoutMsForEffect = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
timeoutMsForEffect = effectFunction();
|
||||
task->driver_->writeSync(task->ledStrip_->rawData(), task->ledStrip_->numLeds());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TLedStrip>
|
||||
void LedTask<TLedStrip>::begin(TLedStrip &strip, Esp32DriverRGBW &driver)
|
||||
{
|
||||
queue_ = xQueueCreate(4, MAX_EFFECT_CONFIG_SIZE);
|
||||
if (!queue_)
|
||||
Serial.println("Failed to create LED effect queue");
|
||||
|
||||
ledStrip_ = &strip;
|
||||
driver_ = &driver;
|
||||
|
||||
xTaskCreate(_led_task_func<TLedStrip>, "led task", MAX_EFFECT_CLASS_SIZE + MAX_EFFECT_CONFIG_SIZE + 2048 * 2,
|
||||
(void *)(this), 1, nullptr);
|
||||
}
|
||||
|
||||
template <typename TLedStrip>
|
||||
template <typename TEffectConfig>
|
||||
void LedTask<TLedStrip>::startEffect(const TEffectConfig &cfg)
|
||||
{
|
||||
static constexpr auto msgSize = sizeof(TEffectConfig) + sizeof(EffectId);
|
||||
static_assert(msgSize < MAX_EFFECT_CONFIG_SIZE,
|
||||
"Effect config too large, increase MAX_EFFECT_CONFIG_SIZE");
|
||||
|
||||
unsigned char buffer[MAX_EFFECT_CONFIG_SIZE];
|
||||
if (queue_ == nullptr)
|
||||
{
|
||||
Serial.println("Trying to start effect before queue was set up!");
|
||||
return;
|
||||
}
|
||||
EffectId id = EffectConfigToId<TEffectConfig>::id;
|
||||
memcpy(buffer, &id, sizeof(EffectId));
|
||||
memcpy(buffer + sizeof(EffectId), &cfg, sizeof(TEffectConfig));
|
||||
xQueueSend(queue_, (void *)buffer, (TickType_t)10);
|
||||
}
|
||||
120
esp-firmware/src/host_test.cpp
Normal file
120
esp-firmware/src/host_test.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
|
||||
#include "containers/LedStripRGBW.h"
|
||||
|
||||
#include "effects/AlexaSwipe.h"
|
||||
#include "effects/ReverseSwipe.h"
|
||||
#include "helpers/ColorConversions.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
template <typename T>
|
||||
std::ostream &operator<<(std::ostream &os, const std::vector<T> &vec)
|
||||
{
|
||||
os << "[";
|
||||
for (const auto &e : vec)
|
||||
{
|
||||
if (std::is_same<uint8_t, T>::value)
|
||||
os << int(e) << ",";
|
||||
else
|
||||
os << e << ",";
|
||||
}
|
||||
os << "]";
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::ostream &operator<<(std::ostream &os, const std::vector<std::vector<T>> &vec)
|
||||
{
|
||||
os << "[";
|
||||
for (const auto &e : vec)
|
||||
os << e << ",";
|
||||
os << "]";
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename TEffect, int NLeds>
|
||||
void effectToFile(const std::string &filename, TEffect &effect, LedStripRGBW<NLeds> &strip, int calls = 100)
|
||||
{
|
||||
std::vector<std::vector<uint8_t>> vr(calls);
|
||||
std::vector<std::vector<uint8_t>> vg(calls);
|
||||
std::vector<std::vector<uint8_t>> vb(calls);
|
||||
|
||||
std::vector<std::vector<float>> vh(calls);
|
||||
std::vector<std::vector<float>> vs(calls);
|
||||
std::vector<std::vector<float>> vv(calls);
|
||||
|
||||
for (int time = 0; time < calls; ++time)
|
||||
{
|
||||
effect();
|
||||
vr[time].resize(NLeds);
|
||||
vg[time].resize(NLeds);
|
||||
vb[time].resize(NLeds);
|
||||
|
||||
vh[time].resize(NLeds);
|
||||
vs[time].resize(NLeds);
|
||||
vv[time].resize(NLeds);
|
||||
|
||||
for (int i = 0; i < NLeds; ++i)
|
||||
{
|
||||
uint8_t r, g, b, w;
|
||||
strip.getRGBW(i, r, g, b, w);
|
||||
vr[time][i] = r;
|
||||
vg[time][i] = g;
|
||||
vb[time][i] = b;
|
||||
|
||||
auto hsv = rgb2hsv(ColorRGBW{r, g, b, w});
|
||||
vh[time][i] = hsv.h;
|
||||
vs[time][i] = hsv.s;
|
||||
vv[time][i] = hsv.v;
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream fs(filename.c_str());
|
||||
fs << "r = " << vr << "\n";
|
||||
fs << "g = " << vg << "\n";
|
||||
fs << "b = " << vb << "\n";
|
||||
fs << "h = " << vh << "\n";
|
||||
fs << "s = " << vs << "\n";
|
||||
fs << "v = " << vv << "\n";
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
{
|
||||
auto cfg = EffectAlexaSwipeConfig{20.f, 20.f, 90.f, 5.f, 180, true, ColorRGBW{255, 0, 0, 0}, ColorRGBW{255, 0, 0, 0}};
|
||||
LedStripRGBW<51> strip;
|
||||
|
||||
EffectAlexaSwipe<decltype(strip)> effect(cfg, strip);
|
||||
effectToFile("swipe.py", effect, strip, 200);
|
||||
}
|
||||
|
||||
{
|
||||
auto cfg = EffectReverseSwipeConfig{360.f, 5.f, 180};
|
||||
LedStripRGBW<51> strip;
|
||||
for (int i = 0; i < strip.numLeds(); ++i)
|
||||
setLedRGBW(strip, i, ColorRGBW{255, 255, 255, 0});
|
||||
|
||||
EffectReverseSwipe<decltype(strip)> effect(cfg, strip);
|
||||
effectToFile("reverse_swipe.py", effect, strip, 200);
|
||||
}
|
||||
|
||||
/*
|
||||
effect.currentPosition_ = 150;
|
||||
|
||||
const auto numLeds = strip.numLeds() / 2;
|
||||
|
||||
std::vector<float> brightness(numLeds, 0);
|
||||
std::vector<float> interpolation(numLeds, 0);
|
||||
|
||||
for (int i = 0; i < numLeds; ++i)
|
||||
effect.getParams(float(i), interpolation[i], brightness[i]);
|
||||
|
||||
printVec(brightness);
|
||||
printVec(interpolation);
|
||||
|
||||
effect();
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
241
esp-firmware/src/main.cpp
Normal file
241
esp-firmware/src/main.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
#include <Arduino.h>
|
||||
#include "rc522.h"
|
||||
#include "SPI.h"
|
||||
#include <MFRC522.h>
|
||||
#include "rotary_encoder.h"
|
||||
|
||||
#include "containers/LedStripRGBW.h"
|
||||
#include "drivers/Esp32DriverRGBW.h"
|
||||
#include "effects/Circular.h"
|
||||
#include "effects/Static.h"
|
||||
#include "effects/StaticDetailed.h"
|
||||
#include "effects/AlexaSwipe.h"
|
||||
#include "effects/RandomTwoColorInterpolation.h"
|
||||
|
||||
#include "AceButton.h"
|
||||
|
||||
#include "driver/touch_pad.h"
|
||||
|
||||
#include "Messages.h"
|
||||
|
||||
#include "TaskLed.h"
|
||||
|
||||
// -------------------------------------------------- RFID Reader ----------------------------------------
|
||||
MFRC522 rfid;
|
||||
MFRC522::MIFARE_Key key;
|
||||
|
||||
void tagHandler(uint8_t *sn)
|
||||
{
|
||||
if (sn != nullptr)
|
||||
sendMessageToHost(MsgRfidTokenRead{{sn[0], sn[1], sn[2], sn[3], sn[4]}});
|
||||
else
|
||||
sendMessageToHost(MsgRfidTokenRead{{0, 0, 0, 0, 0}});
|
||||
}
|
||||
|
||||
void setupRfidReader()
|
||||
{
|
||||
const rc522_start_args_t start_args = {
|
||||
21, // MISO
|
||||
5, // MOSI
|
||||
18, // SCK
|
||||
19, // SDA
|
||||
VSPI_HOST,
|
||||
&tagHandler,
|
||||
125, // scan_interval_ms
|
||||
8 * 1024, // stacksize
|
||||
4 // task priority
|
||||
};
|
||||
rc522_start(start_args);
|
||||
}
|
||||
|
||||
// -------------------------------------------------- Rotary Enc ----------------------------------------
|
||||
QueueHandle_t eventQueueRotaryEncoder;
|
||||
rotary_encoder_info_t info;
|
||||
|
||||
void setupRotaryEncoder()
|
||||
{
|
||||
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
ESP_ERROR_CHECK(rotary_encoder_init(&info, GPIO_NUM_26, GPIO_NUM_27));
|
||||
ESP_ERROR_CHECK(rotary_encoder_enable_half_steps(&info, false));
|
||||
eventQueueRotaryEncoder = rotary_encoder_create_queue();
|
||||
ESP_ERROR_CHECK(rotary_encoder_set_queue(&info, eventQueueRotaryEncoder));
|
||||
}
|
||||
|
||||
int32_t lastRotaryPosition = 0;
|
||||
bool lastRotaryPositionValid = false;
|
||||
void handleRotaryEncoder()
|
||||
{
|
||||
rotary_encoder_event_t event = {0};
|
||||
if (xQueueReceive(eventQueueRotaryEncoder, &event, 0) == pdTRUE)
|
||||
{
|
||||
int32_t increment = 0;
|
||||
if (lastRotaryPositionValid)
|
||||
increment = lastRotaryPosition - event.state.position;
|
||||
sendMessageToHost(MsgRotaryEncoder{event.state.position, increment, (uint8_t)(event.state.direction)});
|
||||
lastRotaryPositionValid = true;
|
||||
lastRotaryPosition = event.state.position;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------- Buttons ----------------------------------------
|
||||
|
||||
constexpr int BUTTON_RIGHT_PIN = 25;
|
||||
constexpr int BUTTON_LEFT_PIN = 14;
|
||||
constexpr int ROTARY_PRESS_PIN = 13;
|
||||
constexpr int BUTTON_RIGHT_LED_PIN = 33;
|
||||
constexpr int BUTTON_LEFT_LED_PIN = 12;
|
||||
constexpr int PWM_FREQ = 5000;
|
||||
constexpr int PWM_RESOLUTION = 8;
|
||||
|
||||
using ace_button::AceButton;
|
||||
AceButton buttonLeft(BUTTON_LEFT_PIN);
|
||||
AceButton buttonRight(BUTTON_RIGHT_PIN);
|
||||
AceButton buttonRotary(ROTARY_PRESS_PIN);
|
||||
|
||||
void handleButtonEvent(AceButton *button, uint8_t eventType, uint8_t /*buttonState*/)
|
||||
{
|
||||
uint8_t buttonNr = 0;
|
||||
if (button == &buttonLeft)
|
||||
buttonNr = 1;
|
||||
else if (button == &buttonRight)
|
||||
buttonNr = 2;
|
||||
else if (button == &buttonRotary)
|
||||
buttonNr = 3;
|
||||
sendMessageToHost(MsgButtonEvent{buttonNr, eventType});
|
||||
}
|
||||
|
||||
void setupButtons()
|
||||
{
|
||||
pinMode(BUTTON_RIGHT_PIN, INPUT_PULLUP);
|
||||
pinMode(BUTTON_LEFT_PIN, INPUT_PULLUP);
|
||||
pinMode(ROTARY_PRESS_PIN, INPUT_PULLUP);
|
||||
|
||||
pinMode(BUTTON_RIGHT_LED_PIN, OUTPUT);
|
||||
pinMode(BUTTON_LEFT_LED_PIN, OUTPUT);
|
||||
|
||||
buttonLeft.setEventHandler(handleButtonEvent);
|
||||
buttonRight.setEventHandler(handleButtonEvent);
|
||||
buttonRotary.setEventHandler(handleButtonEvent);
|
||||
|
||||
ledcSetup(0, PWM_FREQ, PWM_RESOLUTION);
|
||||
ledcAttachPin(BUTTON_LEFT_LED_PIN, 0);
|
||||
|
||||
ledcSetup(1, PWM_FREQ, PWM_RESOLUTION);
|
||||
ledcAttachPin(BUTTON_RIGHT_LED_PIN, 1);
|
||||
}
|
||||
|
||||
void handleButtons()
|
||||
{
|
||||
buttonLeft.check();
|
||||
buttonRight.check();
|
||||
buttonRotary.check();
|
||||
}
|
||||
|
||||
// -------------------------------------------------- Led circle ------------------------------------------
|
||||
|
||||
LedStripRGBW<51> ledStripCircle;
|
||||
Esp32DriverRGBW ledDriverCircle;
|
||||
LedTask<decltype(ledStripCircle)> ledTaskCircle;
|
||||
|
||||
void setupLedCircle()
|
||||
{
|
||||
ledDriverCircle.begin(22, 0);
|
||||
ledTaskCircle.begin(ledStripCircle, ledDriverCircle);
|
||||
ledTaskCircle.startEffect(EffectStaticConfig(ColorRGBW{0, 0, 0, 0}));
|
||||
}
|
||||
|
||||
// -------------------------------------------------- Mouse Leds -- ----------------------------------------
|
||||
|
||||
LedStripRGBW<12 + 16 + 17> ledStripMouse;
|
||||
Esp32DriverRGBW ledDriverMouse;
|
||||
LedTask<decltype(ledStripMouse)> ledTaskMouse;
|
||||
|
||||
void setupMouseLeds()
|
||||
{
|
||||
ledDriverMouse.begin(16, 1);
|
||||
ledTaskMouse.begin(ledStripMouse, ledDriverMouse);
|
||||
ledTaskMouse.startEffect(EffectStaticConfig{ColorRGBW{0, 0, 0, 0}, 0, 0});
|
||||
}
|
||||
|
||||
// -------------------------------------------------- Shelf Leds -------------------------------------------
|
||||
|
||||
LedStripRGBW<252> ledStripShelf;
|
||||
Esp32DriverRGBW ledDriverShelf;
|
||||
LedTask<decltype(ledStripShelf)> ledTaskShelf;
|
||||
|
||||
void setupShelfLeds()
|
||||
{
|
||||
ledDriverShelf.begin(17, 2);
|
||||
ledTaskShelf.begin(ledStripShelf, ledDriverShelf);
|
||||
ledTaskShelf.startEffect(EffectStaticConfig{ColorRGBW{0, 0, 0, 0}, 0, 0});
|
||||
}
|
||||
|
||||
// -------------------------------------------------- Touch Buttons ----------------------------------------
|
||||
|
||||
constexpr auto TOUCH_PAD_RIGHT_EAR = TOUCH_PAD_NUM0;
|
||||
constexpr auto TOUCH_PAD_LEFT_EAR = TOUCH_PAD_NUM9;
|
||||
constexpr auto TOUCH_PAD_RIGHT_FOOT = TOUCH_PAD_NUM2;
|
||||
constexpr auto TOUCH_PAD_LEFT_FOOT = TOUCH_PAD_NUM3;
|
||||
|
||||
void setupTouchButtons()
|
||||
{
|
||||
touch_pad_init();
|
||||
touch_pad_set_voltage(TOUCH_HVOLT_2V7, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
|
||||
for (auto pad : {TOUCH_PAD_RIGHT_EAR, TOUCH_PAD_LEFT_EAR, TOUCH_PAD_RIGHT_FOOT, TOUCH_PAD_LEFT_FOOT})
|
||||
touch_pad_config(pad, 0);
|
||||
|
||||
touch_pad_filter_start(2);
|
||||
}
|
||||
|
||||
void handleTouchInputs()
|
||||
{
|
||||
static bool previousState[4];
|
||||
|
||||
uint16_t touchLeftEar = 0;
|
||||
uint16_t touchRightEar = 0;
|
||||
uint16_t touchLeftFoot = 0;
|
||||
uint16_t touchRightFoot = 0;
|
||||
|
||||
touch_pad_read(TOUCH_PAD_LEFT_FOOT, &touchLeftFoot);
|
||||
touch_pad_read(TOUCH_PAD_RIGHT_FOOT, &touchRightFoot);
|
||||
touch_pad_read(TOUCH_PAD_LEFT_EAR, &touchLeftEar);
|
||||
touch_pad_read(TOUCH_PAD_RIGHT_EAR, &touchRightEar);
|
||||
//Serial.printf("Feet %d %d, Ears %d, %d\n", touchLeftFoot, touchRightFoot, touchLeftEar, touchRightEar);
|
||||
//delay(100);
|
||||
bool currentState[4];
|
||||
currentState[int(TouchButton::LEFT_FOOT)] = touchLeftFoot < 380;
|
||||
currentState[int(TouchButton::RIGHT_FOOT)] = touchRightFoot < 380;
|
||||
currentState[int(TouchButton::LEFT_EAR)] = touchLeftEar < 430;
|
||||
currentState[int(TouchButton::RIGHT_EAR)] = touchRightEar < 400;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (previousState[i] == false && currentState[i] == true)
|
||||
sendMessageToHost(MsgTouchButtonPress{TouchButton(i)});
|
||||
else if (previousState[i] == true && currentState[i] == false)
|
||||
sendMessageToHost(MsgTouchButtonRelease{TouchButton(i)});
|
||||
previousState[i] = currentState[i];
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
setupRfidReader();
|
||||
setupRotaryEncoder();
|
||||
setupLedCircle();
|
||||
setupButtons();
|
||||
setupTouchButtons();
|
||||
setupMouseLeds();
|
||||
setupShelfLeds();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
handleIncomingMessagesFromHost(&ledTaskCircle, &ledTaskMouse, &ledTaskShelf, 0, 1);
|
||||
handleTouchInputs();
|
||||
handleRotaryEncoder();
|
||||
handleButtons();
|
||||
}
|
||||
64
esp-firmware/src/mfrc_version.h
Normal file
64
esp-firmware/src/mfrc_version.h
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
#include <SPI.h>
|
||||
#include <MFRC522.h>
|
||||
|
||||
#define RST_PIN 5 // Configurable, see typical pin layout above
|
||||
#define SS_PIN 23 // Configurable, see typical pin layout above
|
||||
|
||||
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance
|
||||
|
||||
//*****************************************************************************************//
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
SPI.begin(22, 19, 18, 23); // Init SPI bus
|
||||
mfrc522.PCD_Init(); // Init MFRC522 card
|
||||
}
|
||||
/**
|
||||
* Helper routine to dump a byte array as hex values to Serial.
|
||||
*/
|
||||
void PrintHex(byte *buffer, byte bufferSize) {
|
||||
for (byte i = 0; i < bufferSize; i++) {
|
||||
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
|
||||
Serial.print(buffer[i], HEX);
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************************//
|
||||
|
||||
void loop() {
|
||||
|
||||
// Look for new cards
|
||||
if ( !mfrc522.PICC_IsNewCardPresent()) {
|
||||
return;
|
||||
}
|
||||
if ( !mfrc522.PICC_ReadCardSerial()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//mfrc522.PICC_DumpDetailsToSerial(&(mfrc522.uid)); //dump some details about the card
|
||||
|
||||
Serial.print("NewCard");
|
||||
PrintHex(mfrc522.uid.uidByte, mfrc522.uid.size);
|
||||
Serial.println("");
|
||||
|
||||
// Check if Card was removed
|
||||
bool cardRemoved = false;
|
||||
int counter = 0;
|
||||
bool current, previous;
|
||||
previous = !mfrc522.PICC_IsNewCardPresent();
|
||||
|
||||
while(!cardRemoved){
|
||||
current =!mfrc522.PICC_IsNewCardPresent();
|
||||
|
||||
if (current && previous) counter++;
|
||||
|
||||
previous = current;
|
||||
cardRemoved = (counter>2);
|
||||
delay(50);
|
||||
}
|
||||
|
||||
Serial.println("Card was removed");
|
||||
delay(500); //change value if you want to read cards faster
|
||||
mfrc522.PICC_HaltA();
|
||||
mfrc522.PCD_StopCrypto1();
|
||||
}
|
||||
40
esp-firmware/todo.md
Normal file
40
esp-firmware/todo.md
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
- button hintergrund beleuchtung [ok]
|
||||
|
||||
- playlisten
|
||||
- runterladen
|
||||
|
||||
- befestigung im regal
|
||||
- winkel
|
||||
- mehrfachsteckdose
|
||||
- lan kabel
|
||||
|
||||
- effekt kanal fuer audioeffekte
|
||||
- "boing" etc runterladen
|
||||
|
||||
- Fernbedienung wenn empfaenger da
|
||||
- HA regeln fuer standard
|
||||
|
||||
|
||||
- ansible cleanup
|
||||
- lirc
|
||||
- musicmouse kanal
|
||||
- musicmouse effect kanal
|
||||
|
||||
- home assistant anbindung
|
||||
- events an HA (figur, button press, ...)
|
||||
- mouse & ring leds von HA
|
||||
- HA device control (led fluter, rollos)
|
||||
- regal licht von HA aus
|
||||
|
||||
- Regal LEDs
|
||||
- kabel von musikmaus
|
||||
- Leisten zuschneiden
|
||||
- kabel auf richtige laenge zuschneiden
|
||||
- Kabel loeten
|
||||
- im Arbeitszimmer testen
|
||||
- Bonus: Ecken drucken
|
||||
|
||||
- Effekte Regal LEDs
|
||||
|
||||
- Musik-abhaengige Effekte
|
||||
Reference in New Issue
Block a user