add Obegränsad component; update README

This commit is contained in:
Philipp
2022-11-18 20:22:19 +01:00
parent dbed3962a6
commit ab1c0a5403
7 changed files with 483 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
# esphome-frekvens-component
# esphome IKEA LED matrix components for Frekvens & Obegränsad
A custom component for esp-home to interface with IKEA's Frekvens Cube.
A custom component for esphome to interface with IKEA's Frekvens Cube and Obegränsad panel.
## Sources
@@ -15,26 +15,90 @@ Currently there is a dependency upon Adafruit GFX library. In your esphome confi
- Declare necessary libraries
- Define frekvenspanel component
Here is a short config to demonstrate the usage to display time on panel:
```yaml
lib_deps:
- Wire # Also required by GFX.
- SPI # Also required by GFX.
- adafruit/Adafruit BusIO # Required by GFX Library.
- adafruit/Adafruit GFX Library # Required for FrekvensPanel.
- me-no-dev/ESPAsyncTCP
```
Here is a short config to demonstrate the usage to display time on Frekvens and Obegränsad:
### Frekvens
```yaml
esphome:
name: frekvens8266-weather
name: frekvens-clock
platform: ESP8266
board: d1_mini
platformio_options:
upload_speed: 115200
lib_deps:
- me-no-dev/ESPAsyncTCP
- adafruit/Adafruit GFX Library # Required for FrekvensPanel.
- Adafruit BusIO # Required by GFX Library.
- Wire # Also required by GFX.
- SPI # Also required by GFX.
- adafruit/Adafruit BusIO # Required by GFX Library.
- adafruit/Adafruit GFX Library # Required for FrekvensPanel.
- me-no-dev/ESPAsyncTCP
external_components:
- source:
type: local
path: <path of directory containing frekvens_panel>
- source: github://phiten/esphome-ikea-led-matrix@master
components: [ frekvens_panel ]
light:
- platform: monochromatic
name: 'Brightness'
output: matrix_brightness
restore_mode: RESTORE_DEFAULT_ON
output:
- platform: esp8266_pwm
# Enables brightness control.
max_power: 0.05
id: matrix_brightness
pin:
number: GPIO14
inverted: True
time:
- platform: sntp
id: ntp_time
timezone: 'Europe/Berlin'
font:
- file: "04B03.ttf"
id: b03
size: 8
display:
- platform: frekvens_panel
rotation: 90
latch_pin: 12
clock_pin: 04
data_pin: 05
lambda: |-
it.strftime(4, 0, id(b03), "%H", id(ntp_time).now());
it.strftime(4, 8, id(b03), "%M", id(ntp_time).now());
```
### Obegränsad
```yaml
esphome:
name: obegraensad-clock
platform: ESP8266
board: d1_mini
platformio_options:
lib_deps:
- Wire # Also required by GFX.
- SPI # Also required by GFX.
- adafruit/Adafruit BusIO # Required by GFX Library.
- adafruit/Adafruit GFX Library # Required for FrekvensPanel.
- me-no-dev/ESPAsyncTCP
external_components:
- source: github://phiten/esphome-ikea-led-matrix@master
components: [ obegraensad_panel ]
light:
- platform: monochromatic
@@ -53,7 +117,7 @@ output:
time:
- platform: sntp
id: ntp_time
timezone: 'Europe/Paris'
timezone: 'Europe/Berlin'
font:
- file: "04B03.ttf"
@@ -61,14 +125,15 @@ font:
size: 8
display:
- platform: frekvens_panel
- platform: obegraensad_panel
rotation: 90
latch_pin: 12
clock_pin: 04
data_pin: 05
lambda: |-
auto time = id(ntp_time).now();
it.printf(0, 0, id(b03), "%d:%d", time.hour, time.minute);
it.strftime(4, 0, id(b03), "%H", id(ntp_time).now());
it.strftime(4, 8, id(b03), "%M", id(ntp_time).now());
```

View File

View File

@@ -0,0 +1,54 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import display
from esphome.const import (
CONF_ID,
CONF_LAMBDA,
CONF_PAGES,
CONF_CONTRAST,
)
DEPENDENCIES = []
CONF_LATCH_PIN = 'latch_pin'
CONF_CLOCK_PIN = 'clock_pin'
CONF_DATA_PIN = 'data_pin'
obegraensadpanel_ns = cg.esphome_ns.namespace("obegraensadpanel")
Panel = obegraensadpanel_ns.class_(
"Panel", cg.PollingComponent, display.DisplayBuffer
)
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Panel),
cv.Required(CONF_LATCH_PIN): cv.int_,
cv.Required(CONF_CLOCK_PIN): cv.int_,
cv.Required(CONF_DATA_PIN): cv.int_,
cv.Optional(CONF_CONTRAST, default=0x7F): cv.int_,
}
)
.extend(cv.polling_component_schema("200ms")),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await display.register_display(var, config)
cg.add(var.set_pins(
config[CONF_LATCH_PIN],
config[CONF_CLOCK_PIN],
config[CONF_DATA_PIN],
))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View File

@@ -0,0 +1,166 @@
// Copied From https://github.com/frumperino/FrekvensPanel
// By /u/frumperino
// goodwires.org
#include "obegraensad-driver.h"
// lookup tables for led-indices
// stores the bottom-most value in each column, split for bottom and top half of the display
const int lookupTop[] = {254, 14, 253, 13, 250, 10, 249, 9, 246, 6, 245, 5, 242, 2, 241, 1};
const int lookupBottom[] = {255, 15, 252, 12, 251, 11, 248, 8, 247, 7, 244, 4, 243, 3, 240, 0};
ObegraensadPanel::ObegraensadPanel(int p_latch, int p_clock, int p_data) : Adafruit_GFX(16, 16)
{
init(p_latch, p_clock, p_data);
}
void ObegraensadPanel::init(int p_latch, int p_clock, int p_data)
{
_pLatch = p_latch;
_pClock = p_clock;
_pData = p_data;
pinMode(_pLatch, OUTPUT);
pinMode(_pClock, OUTPUT);
pinMode(_pData, OUTPUT);
_width = 16;
_height = 16;
buf = (unsigned short*) malloc(16 * sizeof(unsigned short));
}
// Converts Coordinates to LED Indices. Matrix has the schema:
// +---------+---------+---------+---------+---------+
// | +----+ | +----+ | +----+ | +----+ | +----+ |
// | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | |
// | v | | v | | v | | v | | v | |
// | 254 14 |253 13 | <...> |242 2 |241 1 |
// +---------+---------+---------+---------+---------+
// | +----+ | +----+ | +----+ | +----+ | +----+ |
// | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | |
// | v | | v | | v | | v | | v | |
// | 255 15 | 252 12 | <...> | 243 3 | 240 0 |
// +---------+---------+---------+---------+---------+
// along the arrows: increase by 16 per field
unsigned short ObegraensadPanel::coordToIndex(int16_t x, int16_t y)
{
// Lookup Table for base column values
auto lookup = (y > 7) ? lookupBottom : lookupTop;
// Offset from value in bottom-most row of this column
auto columnOffset = (7 - (y % 8)) * 16;
int index;
if (x % 2 == 0)
{
// even column --> led-indices in column go down (seen from bottom-most row)
index = lookup[x] - columnOffset;
} else
{
// odd column --> led-indices in column go up (seen from bottom-most row)
index = lookup[x] + columnOffset;
}
return index;
}
void ObegraensadPanel::clear()
{
for (int i=0; i<16; i++)
{
buf[i] = 0;
}
}
void ObegraensadPanel::scan()
{
unsigned short* p = &buf[0];
for (int i=0;i<16;i++)
{
unsigned short color = *p++;
for (int j=0; j<16; j++)
{
digitalWrite(_pData, color & 0x01);
color >>= 1;
digitalWrite(_pClock, HIGH);
delayMicroseconds(1);
digitalWrite(_pClock, LOW);
}
}
digitalWrite(_pLatch,HIGH);
delayMicroseconds(1);
digitalWrite(_pLatch,LOW);
}
unsigned short ObegraensadPanel::width()
{
return this->_width;
}
unsigned short ObegraensadPanel::height()
{
return this->_height;
}
boolean ObegraensadPanel::getPixel(int16_t x, int16_t y)
{
//TODO: implement
return false;
}
void ObegraensadPanel::drawPixel(int16_t x, int16_t y, uint16_t color)
{
if ((x >= 0) && (y >= 0) && (x < _width) && ( y < _height))
{
// bit index from start of buf
auto index = coordToIndex(x, y);
// transform index into page (unsigned short) + offset into the short
auto pageIndex = index % 16;
auto page = &buf[pageIndex];
auto offset = index / 16;
// Shift Bit to correct position
auto shifted = (1 << offset);
// If color is set add to page
if (color != 0)
{
// OR to add to page
*page |= shifted;
} else
{
// AND with inverse to remove from page
*page &= (0xFFFF ^ shifted);
}
}
}
void ObegraensadPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color)
{
for (int i=0;i<h;i++)
{
drawPixel(x,y+i,color);
}
}
void ObegraensadPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color)
{
for (int i=0;i<w;i++)
{
drawPixel(x+i,y,color);
}
}
void ObegraensadPanel::fillScreen(uint16_t color) {
auto fillColor = (color == 0) ? 0x00 : 0xFFFF;
for (int i=0; i<16; i++)
{
buf[i] = fillColor;
}
}

View File

@@ -0,0 +1,44 @@
// Copied From https://github.com/frumperino/FrekvensPanel
// by /u/frumperino
// goodwires.org
#ifndef __OBEGRAENSADPANEL_H
#define __OBEGRAENSADPANEL_H
#include <Arduino.h>
#include <Adafruit_GFX.h>
class ObegraensadPanel : public Adafruit_GFX
{
private:
unsigned short _pLatch;
unsigned short _pClock;
unsigned short _pData;
unsigned short* buf;
unsigned short _width;
unsigned short _height;
public:
ObegraensadPanel(int p_latch, int p_clock, int p_data);
void init(int p_latch, int p_clock, int p_data);
void clear();
void scan();
boolean getPixel(int16_t x, int16_t y);
unsigned short width();
unsigned short height();
void drawPixel(int16_t x, int16_t y, uint16_t color) override;
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
void fillScreen(uint16_t color) override;
private:
unsigned short coordToIndex(int16_t x, int16_t y);
};
#endif //__OBEGRAENSADPANEL_H

View File

@@ -0,0 +1,74 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "obegraensad-panel.h"
namespace esphome {
namespace obegraensadpanel {
static const char *const TAG = "obegraensadpanel";
void Panel::initialize() {
this->init_internal_(this->get_buffer_length_());
ESP_LOGV(TAG, "initializing Panel");
this->panel = new ObegraensadPanel(this->p_latch, this->p_clock, this->p_data);
}
int Panel::get_width_internal() { return 16; }
int Panel::get_height_internal() { return 16; }
size_t Panel::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal());
}
void HOT Panel::display() {
uint8_t x, y, cell;
this->panel->clear();
for (x = 0; x < this->get_width_internal(); x++) {
for (y = 0; y < this->get_height_internal(); y++) {
cell = this->buffer_[(this->get_width_internal() * y) + x];
this->panel->drawPixel(x, y, cell);
}
}
this->panel->scan();
}
void HOT Panel::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) {
return;
}
uint16_t pos = x + (y) * this->get_width_internal();
uint8_t subpos = y % 8;
if (color.is_on()) {
this->buffer_[pos] |= 1;
} else {
this->buffer_[pos] &= ~1;
}
}
void Panel::dump_config() {
LOG_DISPLAY("", "Panel", this);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_UPDATE_INTERVAL(this);
}
void Panel::update() {
this->do_update_();
this->display();
}
void Panel::fill(Color color) {
uint8_t fill = color.is_on() ? 0xFF : 0x00;
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
}
} // namespace obegraensadpanel
} // namespace esphome

View File

@@ -0,0 +1,65 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/display/display_buffer.h"
#include "obegraensad-driver.h"
namespace esphome {
namespace obegraensadpanel {
class Panel : public PollingComponent,
public display::DisplayBuffer {
public:
int p_latch;
int p_clock;
int p_data;
void set_pins(int latch, int clock, int data) {
this->p_latch = latch;
this->p_clock = clock;
this->p_data = data;
}
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; }
void data(uint8_t value);
void initialize();
void dump_config() override;
void HOT display();
void update() override;
void fill(Color color) override;
void setup() override {
this->initialize();
}
protected:
void draw_absolute_pixel_internal(int x, int y, Color color) override;
void init_reset_();
size_t get_buffer_length_();
void start_command_();
void end_command_();
void start_data_();
void end_data_();
int get_width_internal() override;
int get_height_internal() override;
GPIOPin *reset_pin_;
GPIOPin *dc_pin_;
ObegraensadPanel *panel;
};
} // namespace pcd8544
} // namespace esphome