feat: add external component and some test configurations

This commit is contained in:
Philip Stark 2023-05-03 20:55:22 +02:00
parent 4c612fc462
commit 5b0930d616
11 changed files with 511 additions and 6 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# Gitignore settings for ESPHome
# This is an example and may include too much for your use-case.
# You can modify this file to suit your needs.
/.esphome/
/secrets.yaml
/secrets.*.yaml
!/secrets.example.yaml

View file

@ -0,0 +1,53 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import light
from esphome.components import uart
from esphome.components import sensor
from esphome.const import CONF_ID, CONF_HEIGHT, CONF_TIMEOUT, ICON_GAUGE
DEPENDENCIES = ['time']
AUTO_LOAD = ['light']
wordclock_ns = cg.esphome_ns.namespace('wordcl')
Wordclock = wordclock_ns.class_('Wordclock', cg.Component, light.)
Desky = desky_ns.class_('Desky', cg.Component, uart.UARTDevice)
CONF_UP = "up"
CONF_DOWN = "down"
CONF_REQUEST = "request"
CONF_STOPPING_DISTANCE = "stopping_distance"
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(Desky),
cv.Optional(CONF_UP): pins.gpio_output_pin_schema,
cv.Optional(CONF_DOWN): pins.gpio_output_pin_schema,
cv.Optional(CONF_REQUEST): pins.gpio_output_pin_schema,
cv.Optional(CONF_HEIGHT): sensor.sensor_schema(icon=ICON_GAUGE, accuracy_decimals=0),
cv.Optional(CONF_STOPPING_DISTANCE, default=15): cv.positive_int,
cv.Optional(CONF_TIMEOUT): cv.time_period,
}).extend(uart.UART_DEVICE_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
if CONF_UP in config:
pin = await cg.gpio_pin_expression(config[CONF_UP])
cg.add(var.set_up_pin(pin))
if CONF_DOWN in config:
pin = await cg.gpio_pin_expression(config[CONF_DOWN])
cg.add(var.set_down_pin(pin))
if CONF_REQUEST in config:
pin = await cg.gpio_pin_expression(config[CONF_REQUEST])
cg.add(var.set_request_pin(pin))
if CONF_HEIGHT in config:
sens = await sensor.new_sensor(config[CONF_HEIGHT])
cg.add(var.set_height_sensor(sens))
cg.add(var.set_stopping_distance(config[CONF_STOPPING_DISTANCE]))
if CONF_TIMEOUT in config:
cg.add(var.set_timeout(config[CONF_TIMEOUT].total_milliseconds))

116
components/wordcl/desky.cpp Normal file
View file

@ -0,0 +1,116 @@
#include "desky.h"
#include "esphome/core/log.h"
namespace esphome {
namespace desky {
static const char *TAG = "desky";
const char *desky_operation_to_str(DeskyOperation op) {
switch (op) {
case DESKY_OPERATION_IDLE:
return "IDLE";
case DESKY_OPERATION_RAISING:
return "RAISING";
case DESKY_OPERATION_LOWERING:
return "LOWERING";
default:
return "UNKNOWN";
}
}
void Desky::setup() {
if (this->up_pin_ != nullptr)
this->up_pin_->digital_write(false);
if (this->down_pin_ != nullptr)
this->down_pin_->digital_write(false);
if (this->request_pin_ != nullptr) {
this->request_pin_->digital_write(true);
this->request_time_ = millis();
}
}
void Desky::loop() {
static int state = 0;
static uint8_t high_byte;
while (this->available()) {
uint8_t c;
int value;
this->read_byte(&c);
switch (state) {
case 0:
if (c == 1)
state = 1;
break;
case 1:
if (c == 1)
state = 2;
else
state = 0;
break;
case 2:
high_byte = c;
state = 3;
break;
case 3:
value = (high_byte << 8) + c;
this->current_pos_ = value;
if (this->height_sensor_ != nullptr)
this->height_sensor_->publish_state(value);
state = 0;
break;
}
}
if (this->target_pos_ >= 0) {
if (abs(this->target_pos_ - this->current_pos_) < this->stopping_distance_)
this->stop();
if ((this->timeout_ >= 0) && (millis() - this->start_time_ >= this->timeout_))
this->stop();
}
if ((this->request_time_ > 0) && (millis() - this->request_time_ >= 100)) {
this->request_pin_->digital_write(false);
this->request_time_ = 0;
}
}
void Desky::dump_config() {
ESP_LOGCONFIG(TAG, "Desky desk:");
LOG_SENSOR("", "Height", this->height_sensor_);
LOG_PIN("Up pin: ", this->up_pin_);
LOG_PIN("Down pin: ", this->down_pin_);
LOG_PIN("Request pin: ", this->request_pin_);
}
void Desky::move_to(int target_pos) {
if (abs(target_pos - this->current_pos_) < this->stopping_distance_)
return;
if (target_pos > this->current_pos_) {
if (this->up_pin_ == nullptr)
return;
this->up_pin_->digital_write(true);
this->current_operation = DESKY_OPERATION_RAISING;
} else {
if (this->down_pin_ == nullptr)
return;
this->down_pin_->digital_write(true);
this->current_operation = DESKY_OPERATION_LOWERING;
}
this->target_pos_ = target_pos;
if (this->timeout_ >= 0)
this->start_time_ = millis();
}
void Desky::stop() {
this->target_pos_ = -1;
if (this->up_pin_ != nullptr)
this->up_pin_->digital_write(false);
if (this->down_pin_ != nullptr)
this->down_pin_->digital_write(false);
this->current_operation = DESKY_OPERATION_IDLE;
}
} // namespace desky
} // namespace esphome

52
components/wordcl/desky.h Normal file
View file

@ -0,0 +1,52 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace desky {
enum DeskyOperation : uint8_t {
DESKY_OPERATION_IDLE = 0,
DESKY_OPERATION_RAISING,
DESKY_OPERATION_LOWERING,
};
const char *desky_operation_to_str(DeskyOperation op);
class Desky : public Component, public sensor::Sensor, public uart::UARTDevice {
public:
float get_setup_priority() const override { return setup_priority::LATE; }
void setup() override;
void loop() override;
void dump_config() override;
void set_height_sensor(sensor::Sensor *sensor) { this->height_sensor_ = sensor; }
void set_up_pin(GPIOPin *pin) { this->up_pin_ = pin; }
void set_down_pin(GPIOPin *pin) { this->down_pin_ = pin; }
void set_request_pin(GPIOPin *pin) { this->request_pin_ = pin; }
void set_stopping_distance(int distance) { this->stopping_distance_ = distance; }
void set_timeout(int timeout) { this->timeout_ = timeout; }
void move_to(int height);
void stop();
DeskyOperation current_operation{DESKY_OPERATION_IDLE};
protected:
sensor::Sensor *height_sensor_{nullptr};
GPIOPin *up_pin_{nullptr};
GPIOPin *down_pin_{nullptr};
GPIOPin *request_pin_{nullptr};
int stopping_distance_;
int current_pos_{0};
int target_pos_{-1};
int timeout_{-1};
uint64_t start_time_;
uint64_t request_time_{0};
};
} // namespace desky
} // namespace esphome

66
display_test.yaml Normal file
View file

@ -0,0 +1,66 @@
esphome:
name: "display_thing"
esp8266:
board: d1_mini
framework:
version: recommended
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "${devicename}"
password: !secret ap_password
manual_ip:
static_ip: !secret manualip_static_ip
gateway: !secret manualip_gateway
subnet: !secret manualip_subnet
dns1: 1.1.1.1
dns2: 1.0.0.1
api:
ota:
password: "${devicename}"
logger:
# esp8266_store_log_strings_in_flash: false
web_server:
port: 80
light:
- name: NeoPixel Strip 1
id: neopixel_strip_1
platform: neopixelbus
type: GRB
variant: WS2812
pin: GPIO3
num_leds: 200
# default_transition_length: 0.2s
method:
type: esp8266_dma
display:
- platform: addressable_light
id: led_matrix_display
addressable_light_id: neopixel_strip_1
width: 18
height: 11
rotation: 0°
update_interval: 16ms
lambda: |-
// Draw a bulls-eye pattern
Color red = Color(0xFF0000);
Color green = Color(0x00FF00);
Color blue = Color(0x0000FF);
it.rectangle(0, 0, 18, 11, red);
it.rectangle(1, 1, 17, 10, green);
it.rectangle(2, 2, 16, 9, blue);
it.rectangle(3, 3, 15, 8, red);
time:
- platform: sntp
id: current_time
timezone: !secret timezone

67
flake.nix Normal file
View file

@ -0,0 +1,67 @@
{
description = "EspHome Word Clock";
inputs = {
systems.url = "github:nix-systems/x86_64-linux";
# nixpkgs.url = "github:NixOS/nixpkgs/unstable";
nixpkgs.url = "github:NixOS/nixpkgs/22.11";
utils.url = "github:numtide/flake-utils";
utils.inputs.systems.follows = "systems";
};
outputs = { self, nixpkgs, utils, ... }: utils.lib.eachDefaultSystem (
system:
let pkgs = import nixpkgs { inherit system; };
in {
# This block here is used when running `nix develop`
devShells.default = pkgs.mkShell rec {
# Update the name to something that suites your project.
name = "esphome-wordclock";
# build environment dependencies
packages = with pkgs; [
esphome
];
# Setting up the environment variables you need during development.
# Todo figure out why I can't use clang on Asahi but can on Darwin
# Use "clang++" for most systems but OSX Asahi requires g++ for some reason or a runtime error occurs
shellHook = let
# This is for an icon that is used below for the command line input below
icon = "f121";
in ''
export PS1="$(echo -e '\u${icon}') {\[$(tput sgr0)\]\[\033[38;5;228m\]\w\[$(tput sgr0)\]\[\033[38;5;15m\]} (${name}) \\$ \[$(tput sgr0)\]"
# export COMPILER="clang++"
#export COMPILER="g++"
echo "test";
'';
};
# This is used when running `nix build`
# packages.default = pkgs.llvmPackages_14.stdenv.mkDerivation rec {
packages.default = pkgs.esphome.stdenv.mkDerivation rec {
name = "esphome-wordclock";
version = "2.0.0";
src = self;
# buildInputs = [ pkgs.esphome ];
# buildPhase = "COMPILER='clang++' make";
# installPhase = ''
# mkdir -p $out/bin;
# install -t $out/bin worm
# '';
# meta = with inputs.utils.lib; {
# homepage = "https://github.com/icecreammatt/ssu-cs315-worm";
# description = ''
# Terminal CLI Worm Game
# '';
# };
};
# packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
# packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
});
}

7
secrets.example.yaml Normal file
View file

@ -0,0 +1,7 @@
wifi_password: secret
wifi_ssid: example
manualip_static_ip: 192.168.1.177
manualip_gateway: 192.168.1.1
manualip_subnet: 255.255.255.0
timezone: Europe/Zurich
ap_password: "password for internal fallback accesspoint"

View file

@ -72,6 +72,7 @@ class Wordclock : public Component, public CustomAPIDevice {
CRGB leds[WORDCLOCK_NUM_LEDS]; CRGB leds[WORDCLOCK_NUM_LEDS];
int hour = -1; int hour = -1;
int minute = -1; int minute = -1;
int second = -1;
int red = 124; int red = 124;
int green = 124; int green = 124;
int blue = 124; int blue = 124;
@ -83,7 +84,7 @@ class Wordclock : public Component, public CustomAPIDevice {
clear_all_leds(); clear_all_leds();
FastLED.show(); FastLED.show();
// TODO: Set up some kind of initialization sequence. But it should be based on an effect or similarly supporting the // TODO: Set up some kind of initialization sequence. But it should be based on an effect or similarly supporting the
// cooperative multithreading. delay() calls are uncalled for. ^^ // cooperative multithreading. delay() calls are uncalled for.
} }
void clear_all_leds() { void clear_all_leds() {
@ -195,16 +196,21 @@ class Wordclock : public Component, public CustomAPIDevice {
if (fastledlight2.get_state() > 0 ) { if (fastledlight2.get_state() > 0 ) {
brightness = (int) (fastledlight2.get_brightness()*125); brightness = (int) (fastledlight2.get_brightness()*125);
} else { } else {
ESP_LOGD("loop", "fastledlight state off - b: %i rgb %i %i %i", brightness, red, green, blue); delay(100); // ESP_LOGD("loop", "fastledlight state off - b: %i rgb %i %i %i", brightness, red, green, blue); delay(100);
} }
FastLED.setBrightness(brightness); FastLED.setBrightness(brightness);
FastLED.show(); FastLED.show();
//check if valid time. Blink red,green,blue until valid time is present //check if valid time. Blink red,green,blue until valid time is present
if (!time.is_valid() == false) { if (time.is_valid() == false) {
ESP_LOGD("loop", "time is not valid");
// do something to show that the clock isn't dead. Maybe I can instantiate and effect and use that for this. // do something to show that the clock isn't dead. Maybe I can instantiate and effect and use that for this.
} }
else { else {
if (time.second != second) {
second = time.second;
ESP_LOGD("loop", "time is now [%02i:%02i:%02i]", time.hour, time.minute, time.second);
}
if(time.hour != hour || time.minute != minute) { if(time.hour != hour || time.minute != minute) {
hour = time.hour; hour = time.hour;
minute = time.minute; minute = time.minute;

View file

@ -7,10 +7,21 @@ esphome:
includes: includes:
- wordclock.h - wordclock.h
esp32: external_components:
board: wemos_d1_mini32 - source:
type: local
path: components
components: [ wordcl ]
esp8266:
board: d1_mini
framework: framework:
type: arduino version: recommended
# esp32:
# board: ttgo-t7-v13-mini32
# framework:
# type: arduino
substitutions: substitutions:
devicename: wordclock devicename: wordclock
@ -27,6 +38,8 @@ wifi:
static_ip: !secret manualip_static_ip static_ip: !secret manualip_static_ip
gateway: !secret manualip_gateway gateway: !secret manualip_gateway
subnet: !secret manualip_subnet subnet: !secret manualip_subnet
dns1: 1.1.1.1
dns2: 1.0.0.1
api: api:

89
wordclock8266.yaml Normal file
View file

@ -0,0 +1,89 @@
esphome:
name: "wordclock"
esp8266:
board: d1_mini
framework:
version: recommended
external_components:
- source:
type: local
path: components
components: [ wordcl ]
# substitutions:
# devicename: wordclock
# friendly_name: "Wordclock"
# light_friendly_name: "Wordclock Light"
# wifi:
# ssid: !secret wifi_ssid
# password: !secret wifi_password
# ap:
# ssid: "${devicename}"
# password: !secret ap_password
# manual_ip:
# static_ip: !secret manualip_static_ip
# gateway: !secret manualip_gateway
# subnet: !secret manualip_subnet
# dns1: 1.1.1.1
# dns2: 1.0.0.1
# api:
# ota:
# password: "${devicename}"
# logger:
# # esp8266_store_log_strings_in_flash: false
# web_server:
# port: 80
wordclock:
light:
- name: neopixel
id: neopixel_1
platform: neopixelbus
type: GRB
variant: WS2812
pin: GPIO3
num_leds: 30
default_transition_length: 0.2s
# restore_mode: RESTORE_DEFAULT_ON
method:
type: esp8266_dma
on_turn_on:
then:
- light.turn_on:
id: neopixel_1
brightness: 35%
effect: rainbow
effects:
# - random:
# - pulse:
# - strobe:
# - flicker:
- addressable_rainbow:
name: rainbow
# - addressable_color_wipe:
# - addressable_scan:
# - addressable_twinkle:
# - addressable_random_twinkle:
# - addressable_fireworks:
# - addressable_flicker:
# - wled:
# time:
# - platform: sntp
# id: current_time
# timezone: !secret timezone
# - light.turn_on:
# id: light_1
# brightness: 100%
# effect: addressable_rainbow

29
wordclock_new.yaml Normal file
View file

@ -0,0 +1,29 @@
esphome:
name: wordclock-livingroom
esp32:
board: ttgo-t7-v13-mini32
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
password: ""
ota:
password: ""
wifi:
ssid: "wifithing"
password: "lostandfound1"
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Wordclock-Livingroom"
password: "wGfGBPnJcbzE"
captive_portal: