Compare commits

...

10 commits

Author SHA1 Message Date
Michael Hope 9aa40ec69c m5paper: render a basic display with time and sensors 2024-04-17 19:43:06 +02:00
Michael Hope 5efc4d9922 bm8563: fix the timeout scale factor 2024-04-17 19:43:00 +02:00
alorente 2707331ca6
Fix esp-idf framework build 2024-03-10 20:55:59 +01:00
alorente 0e3af1bd47
add external_components 2024-03-10 01:23:40 +01:00
alorente 6405af06fe
Fix GT911 not starting + some improvements 2024-03-09 18:27:20 +01:00
alorente 8c3be33acb
Use standard ADC component 2024-03-08 23:03:32 +01:00
alorente 391f1c85d8
Fix rotation 2024-01-19 16:00:51 +01:00
alorente 74f8c5830f
Add model option for it8951e + some simplifications arround rotation 2024-01-17 18:42:17 +01:00
alorente 72a8413e7b
Fix rotation at startup 2024-01-12 18:09:35 +01:00
alorente 70ee692e20
Small updates and cleanup 2024-01-11 22:58:35 +01:00
21 changed files with 948 additions and 349 deletions

137
.clang-format Normal file
View file

@ -0,0 +1,137 @@
Language: Cpp
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 2000
PointerAlignment: Right
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 2
UseTab: Never

160
.clang-tidy Normal file
View file

@ -0,0 +1,160 @@
---
Checks: >-
*,
-abseil-*,
-altera-*,
-android-*,
-boost-*,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-cert-dcl50-cpp,
-cert-err58-cpp,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
-concurrency-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
-fuchsia-default-arguments-declarations,
-fuchsia-default-arguments-calls,
-google-build-using-namespace,
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
-google-readability-namespace-comments,
-google-readability-todo,
-google-runtime-references,
-hicpp-*,
-llvm-else-after-return,
-llvm-header-guard,
-llvm-include-order,
-llvm-qualified-auto,
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-modernize-avoid-bind,
-modernize-concat-nested-namespaces,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-trailing-return-type,
-modernize-use-nodiscard,
-mpi-*,
-objc-*,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-function-cognitive-complexity,
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-make-member-function-const,
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-runtime-int.TypeSuffix
value: '_t'
- key: llvm-namespace-comment.ShortNamespaceLines
value: '10'
- key: llvm-namespace-comment.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: modernize-make-unique.MakeSmartPtrFunction
value: 'make_unique'
- key: modernize-make-unique.MakeSmartPtrFunctionHeader
value: 'esphome/core/helpers.h'
- key: readability-braces-around-statements.ShortStatementLines
value: 2
- key: readability-identifier-naming.LocalVariableCase
value: 'lower_case'
- key: readability-identifier-naming.ClassCase
value: 'CamelCase'
- key: readability-identifier-naming.StructCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticVariableCase
value: 'lower_case'
- key: readability-identifier-naming.GlobalConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.ParameterCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberSuffix
value: '_'
- key: readability-identifier-naming.PrivateMethodCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMethodSuffix
value: '_'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberSuffix
value: '_'
- key: readability-identifier-naming.FunctionCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodSuffix
value: '_'
- key: readability-identifier-naming.VirtualMethodCase
value: 'lower_case'
- key: readability-identifier-naming.VirtualMethodSuffix
value: ''
- key: readability-qualified-auto.AddConstToQualified
value: 0

2
.gitignore vendored
View file

@ -8,3 +8,5 @@
living_room_screen.yaml living_room_screen.yaml
fonts fonts
__pycache__ __pycache__
mlhx/
v1/

View file

@ -2,9 +2,9 @@ esphome:
name: ${device_id} name: ${device_id}
name_add_mac_suffix: true name_add_mac_suffix: true
on_boot: on_boot:
- priority: 750.0 - priority: 220.0
then: then:
- IT8951E.clear - it8951e.clear
- delay: 100ms - delay: 100ms
- component.update: m5paper_display - component.update: m5paper_display
- priority: -100.0 - priority: -100.0
@ -13,13 +13,16 @@ esphome:
- component.update: m5paper_display - component.update: m5paper_display
esp32: esp32:
board: esp32dev board: m5stack-grey
framework: framework:
type: arduino type: arduino
external_components:
- source: github://Passific/m5paper_esphome
# Enable logging # Enable logging
logger: logger:
level: VERBOSE level: DEBUG
# Enable psram # Enable psram
psram: psram:
@ -83,7 +86,7 @@ globals:
{"mdi-numeric-8-circle-outline", "󰲯"}, {"mdi-numeric-8-circle-outline", "󰲯"},
{"mdi-numeric-9-plus-circle-outline", "󰲳"}, {"mdi-numeric-9-plus-circle-outline", "󰲳"},
} }
font: font:
- file: 'gfonts://Roboto' - file: 'gfonts://Roboto'
id: normal_font id: normal_font
@ -109,7 +112,7 @@ font:
] ]
- file: "fonts/materialdesignicons-webfont.ttf" - file: "fonts/materialdesignicons-webfont.ttf"
id: weather_font id: weather_font
size: 256 size: 256
glyphs: [ glyphs: [
'󰖔', #mdi-weather-night '󰖔', #mdi-weather-night
'󰖐', #mdi-weather-cloudy '󰖐', #mdi-weather-cloudy
@ -137,7 +140,7 @@ font:
'󱊢', #mdi-battery-medium '󱊢', #mdi-battery-medium
'󱊡', #mdi-battery-low '󱊡', #mdi-battery-low
'󱃍', #mdi-battery-alert-variant-outline '󱃍', #mdi-battery-alert-variant-outline
'󱊦', #mdi-battery-charging-high '󱊦', #mdi-battery-charging-high
'󰂑', #mdi-battery-unknown '󰂑', #mdi-battery-unknown
] ]
- file: "fonts/materialdesignicons-webfont.ttf" - file: "fonts/materialdesignicons-webfont.ttf"
@ -162,7 +165,7 @@ font:
'󰲯', #mdi-numeric-8-circle-outline '󰲯', #mdi-numeric-8-circle-outline
'󰲳', #mdi-numeric-9-plus-circle-outline '󰲳', #mdi-numeric-9-plus-circle-outline
] ]
spi: spi:
clk_pin: GPIO14 clk_pin: GPIO14
mosi_pin: GPIO12 mosi_pin: GPIO12
@ -175,8 +178,9 @@ i2c:
display: display:
- platform: it8951e - platform: it8951e
id: m5paper_display id: m5paper_display
display_cs_pin: GPIO15 cs_pin: GPIO15
reset_pin: GPIO23 reset_pin: GPIO23
reset_duration: 100ms
busy_pin: GPIO27 busy_pin: GPIO27
rotation: 0 rotation: 0
reversed: False reversed: False
@ -344,7 +348,7 @@ display:
float battery_level = id(m5paper_battery_level).state; float battery_level = id(m5paper_battery_level).state;
if (NOT_NAN(battery_level)) if (NOT_NAN(battery_level))
{ {
if (battery_level < 10) if (battery_level < 10)
battery_icon = id(material_icons_map)["mdi-battery-alert-variant-outline"]; battery_icon = id(material_icons_map)["mdi-battery-alert-variant-outline"];
else if (battery_level < 40) else if (battery_level < 40)
@ -492,8 +496,9 @@ switch:
time: time:
- platform: homeassistant - platform: homeassistant
id: ha_time id: homeassistant_time
on_time_sync: timezone: Europe/Paris
on_time_sync:
- bm8563.write_time - bm8563.write_time
- platform: bm8563 - platform: bm8563
id: rtc_time id: rtc_time
@ -506,14 +511,17 @@ time:
m5paper: m5paper:
battery_power_pin: GPIO5 battery_power_pin: GPIO5
main_power_pin: GPIO2 main_power_pin: GPIO2
update_interval: 10s
battery_voltage:
name: ${device_name} battery voltage
id: m5paper_battery_voltage
device_class: "voltage"
state_class: "measurement"
sensor: sensor:
- platform: adc
disabled_by_default: true
pin: GPIO35
name: ${device_name} battery voltage
id: m5paper_battery_voltage
update_interval: 10s
attenuation: 11db
filters:
- multiply: 2 #1,27272727
- platform: sht3xd - platform: sht3xd
temperature: temperature:
name: ${device_name} temperature name: ${device_name} temperature
@ -539,20 +547,19 @@ sensor:
update_interval: 20s update_interval: 20s
lambda: |- lambda: |-
constexpr float min_level = 3.52; constexpr float min_level = 3.52;
constexpr float max_level = 4.1; constexpr float max_level = 4.15;
float level = ((id(m5paper_battery_voltage).state - min_level) / (max_level - min_level)) * 100.00; return ((id(m5paper_battery_voltage).state - min_level) / (max_level - min_level)) * 100.00;
if (level < 0) filters:
return 0; - clamp:
if (level > 100) min_value: 0
return 100; max_value: 100
return level;
- platform: homeassistant - platform: homeassistant
name: Outdoor temperature name: Outdoor temperature
id: outdoor_temperature id: outdoor_temperature
entity_id: ${outdoor_temperature} entity_id: ${outdoor_temperature}
- platform: homeassistant - platform: homeassistant
name: Outdoor humidity name: Outdoor humidity
id: outdoor_humidity id: outdoor_humidity
entity_id: ${outdoor_humidity} entity_id: ${outdoor_humidity}
- platform: homeassistant - platform: homeassistant
name: Rainfall last hour name: Rainfall last hour
@ -563,7 +570,7 @@ sensor:
id: outdoor_wind_strength id: outdoor_wind_strength
entity_id: ${outdoor_wind_strength} entity_id: ${outdoor_wind_strength}
- platform: homeassistant - platform: homeassistant
name: Indoor temprature name: Indoor temperature
id: indoor_temperature id: indoor_temperature
entity_id: ${indoor_temperature} entity_id: ${indoor_temperature}
- platform: homeassistant - platform: homeassistant
@ -612,21 +619,21 @@ binary_sensor:
name: ${device_name} right button name: ${device_name} right button
id: right_button id: right_button
icon: mdi:gesture-tap-button icon: mdi:gesture-tap-button
pin: pin:
number: GPIO37 number: GPIO37
inverted: true inverted: true
on_release: on_release:
- component.update: m5paper_display - component.update: m5paper_display
- platform: gpio - platform: gpio
name: ${device_name} BTN/PWR button name: ${device_name} BTN/PWR button
icon: mdi:gesture-tap-button icon: mdi:gesture-tap-button
pin: pin:
number: GPIO38 number: GPIO38
inverted: true inverted: true
- platform: gpio - platform: gpio
name: ${device_name} left button name: ${device_name} left button
icon: mdi:gesture-tap-button icon: mdi:gesture-tap-button
pin: pin:
number: GPIO39 number: GPIO39
inverted: true inverted: true
- platform: homeassistant - platform: homeassistant

11
NOTES.md Normal file
View file

@ -0,0 +1,11 @@
# Power
PS_ON / GPIO2 enables the battery
Wake up on:
- KEY_PUSH / GPIO38 - rocker pushed in
- RTC_ALM

View file

@ -1,6 +1,8 @@
# m5paper_esphome # m5paper_esphome
Based on https://github.com/sebirdman/m5paper_esphome Based on https://github.com/paveldn/m5paper_esphome
Himself based on https://github.com/sebirdman/m5paper_esphome
![Screen example](./img/screen_demo.jpg) ![Screen example](./img/screen_demo.jpg)
@ -8,7 +10,6 @@ Work in progress
All components are functional, but likely have bugs. All components are functional, but likely have bugs.
Please, download font from https://materialdesignicons.com/ and put in font folder Please, download font from https://materialdesignicons.com/ and put in 'fonts' folder
GT911 work based on: https://github.com/TomG736/esphome-GT911
BM8563 work based on: https://github.com/TomG736/esphome-BM8563 BM8563 work based on: https://github.com/TomG736/esphome-BM8563

View file

@ -1,7 +0,0 @@
@echo off
if [%1] == [] goto err
powershell -command "esphome compile %1 2>&1 | ? {$_.ToString().trim().Length -ne \"\" } | tee -filepath buildlog.txt"
goto:eof
:err
echo Please use with esphome configuration as parameter
echo Example: build.cmd haier.yaml

View file

@ -1,7 +0,0 @@
@echo off
if [%1] == [] goto err
wsl --cd "%cd%" /bin/sh -c "esphome compile %1 2>&1 | tee buildlog.txt"
goto:eof
:err
echo Please use with esphome configuration as parameter
echo Example: build.cmd haier.yaml

View file

@ -1,28 +1,31 @@
#include "esphome/core/log.h"
#include "esphome/components/i2c/i2c_bus.h"
#include "bm8563.h" #include "bm8563.h"
#include "esphome/components/i2c/i2c_bus.h"
#include "esphome/core/log.h"
namespace esphome { namespace esphome {
namespace bm8563 { namespace bm8563 {
static const char *TAG = "bm8563.sensor"; static const char* TAG = "bm8563.sensor";
void BM8563::setup(){ void BM8563::setup() {
this->write_byte_16(0,0); this->write_byte_16(0, 0);
this->setupComplete = true; this->setupComplete = true;
} }
void BM8563::update(){ void BM8563::update() {
if(!this->setupComplete){ if (!this->setupComplete) {
return; return;
} }
ESP_LOGI(TAG, "update");
this->read_time(); this->read_time();
} }
void BM8563::dump_config(){ void BM8563::dump_config() {
ESP_LOGCONFIG(TAG, "BM8563:"); ESP_LOGCONFIG(TAG, "BM8563:");
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
ESP_LOGCONFIG(TAG, " setupComplete: %s", this->setupComplete ? "true" : "false"); ESP_LOGCONFIG(TAG, " setupComplete: %s",
this->setupComplete ? "true" : "false");
if (this->sleep_duration_.has_value()) { if (this->sleep_duration_.has_value()) {
uint32_t duration = *this->sleep_duration_; uint32_t duration = *this->sleep_duration_;
ESP_LOGCONFIG(TAG, " Sleep Duration: %u ms", duration); ESP_LOGCONFIG(TAG, " Sleep Duration: %u ms", duration);
@ -49,16 +52,16 @@ void BM8563::write_time() {
} }
BM8563_TimeTypeDef BM8563_TimeStruct = { BM8563_TimeTypeDef BM8563_TimeStruct = {
hours: int8_t(now.hour), hours : int8_t(now.hour),
minutes: int8_t(now.minute), minutes : int8_t(now.minute),
seconds: int8_t(now.second), seconds : int8_t(now.second),
}; };
BM8563_DateTypeDef BM8563_DateStruct = { BM8563_DateTypeDef BM8563_DateStruct = {
day: int8_t(now.day_of_month), day : int8_t(now.day_of_month),
week: int8_t(now.day_of_week), week : int8_t(now.day_of_week),
month: int8_t(now.month), month : int8_t(now.month),
year: int16_t(now.year) year : int16_t(now.year)
}; };
this->setTime(&BM8563_TimeStruct); this->setTime(&BM8563_TimeStruct);
@ -66,37 +69,36 @@ void BM8563::write_time() {
} }
void BM8563::read_time() { void BM8563::read_time() {
ESP_LOGI(TAG, "Status2: %x %d", ReadReg(0x01), ReadReg(0x0F));
BM8563_TimeTypeDef BM8563_TimeStruct; BM8563_TimeTypeDef BM8563_TimeStruct;
BM8563_DateTypeDef BM8563_DateStruct; BM8563_DateTypeDef BM8563_DateStruct;
getTime(&BM8563_TimeStruct); getTime(&BM8563_TimeStruct);
getDate(&BM8563_DateStruct); getDate(&BM8563_DateStruct);
ESP_LOGD(TAG, "BM8563: %i-%i-%i %i, %i:%i:%i", ESP_LOGD(TAG, "BM8563: %i-%i-%i %i, %i:%i:%i", BM8563_DateStruct.year,
BM8563_DateStruct.year, BM8563_DateStruct.month, BM8563_DateStruct.day,
BM8563_DateStruct.month, BM8563_DateStruct.week, BM8563_TimeStruct.hours,
BM8563_DateStruct.day, BM8563_TimeStruct.minutes, BM8563_TimeStruct.seconds);
BM8563_DateStruct.week,
BM8563_TimeStruct.hours,
BM8563_TimeStruct.minutes,
BM8563_TimeStruct.seconds
);
ESPTime rtc_time{.second = uint8_t(BM8563_TimeStruct.seconds), ESPTime rtc_time{
.minute = uint8_t(BM8563_TimeStruct.minutes), .second = uint8_t(BM8563_TimeStruct.seconds),
.hour = uint8_t(BM8563_TimeStruct.hours), .minute = uint8_t(BM8563_TimeStruct.minutes),
.day_of_week = uint8_t(BM8563_DateStruct.week), .hour = uint8_t(BM8563_TimeStruct.hours),
.day_of_month = uint8_t(BM8563_DateStruct.day), .day_of_week = uint8_t(BM8563_DateStruct.week),
.day_of_year = 1, // ignored by recalc_timestamp_utc(false) .day_of_month = uint8_t(BM8563_DateStruct.day),
.month = uint8_t(BM8563_DateStruct.month), .day_of_year = 1, // ignored by recalc_timestamp_utc(false)
.year = uint16_t(BM8563_DateStruct.year) .month = uint8_t(BM8563_DateStruct.month),
}; .year = uint16_t(BM8563_DateStruct.year),
.is_dst = false, // ignored by recalc_timestamp_utc()
.timestamp = 0 // result
};
rtc_time.recalc_timestamp_utc(false); rtc_time.recalc_timestamp_utc(false);
time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp); time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
} }
bool BM8563::getVoltLow() { bool BM8563::getVoltLow() {
uint8_t data = ReadReg(0x02); uint8_t data = ReadReg(0x02);
return data & 0x80; // RTCC_VLSEC_MASK return data & 0x80; // RTCC_VLSEC_MASK
} }
uint8_t BM8563::bcd2ToByte(uint8_t value) { uint8_t BM8563::bcd2ToByte(uint8_t value) {
@ -123,18 +125,16 @@ void BM8563::getTime(BM8563_TimeTypeDef* BM8563_TimeStruct) {
BM8563_TimeStruct->seconds = bcd2ToByte(buf[0] & 0x7f); BM8563_TimeStruct->seconds = bcd2ToByte(buf[0] & 0x7f);
BM8563_TimeStruct->minutes = bcd2ToByte(buf[1] & 0x7f); BM8563_TimeStruct->minutes = bcd2ToByte(buf[1] & 0x7f);
BM8563_TimeStruct->hours = bcd2ToByte(buf[2] & 0x3f); BM8563_TimeStruct->hours = bcd2ToByte(buf[2] & 0x3f);
} }
void BM8563::setTime(BM8563_TimeTypeDef* BM8563_TimeStruct) { void BM8563::setTime(BM8563_TimeTypeDef* BM8563_TimeStruct) {
if (BM8563_TimeStruct == NULL) { if (BM8563_TimeStruct == NULL) {
return; return;
} }
uint8_t buf[3] = { uint8_t buf[3] = {byteToBcd2(BM8563_TimeStruct->seconds),
byteToBcd2(BM8563_TimeStruct->seconds), byteToBcd2(BM8563_TimeStruct->minutes),
byteToBcd2(BM8563_TimeStruct->minutes), byteToBcd2(BM8563_TimeStruct->hours)};
byteToBcd2(BM8563_TimeStruct->hours)
};
this->write_register(0x02, buf, 3); this->write_register(0x02, buf, 3);
} }
@ -143,9 +143,9 @@ void BM8563::getDate(BM8563_DateTypeDef* BM8563_DateStruct) {
uint8_t buf[4] = {0}; uint8_t buf[4] = {0};
this->read_register(0x05, buf, 5); this->read_register(0x05, buf, 5);
BM8563_DateStruct->day = bcd2ToByte(buf[0] & 0x3f); BM8563_DateStruct->day = bcd2ToByte(buf[0] & 0x3f);
BM8563_DateStruct->week = bcd2ToByte(buf[1] & 0x07); BM8563_DateStruct->week = bcd2ToByte(buf[1] & 0x07);
BM8563_DateStruct->month = bcd2ToByte(buf[2] & 0x1f); BM8563_DateStruct->month = bcd2ToByte(buf[2] & 0x1f);
uint8_t year_byte = bcd2ToByte(buf[3] & 0xff); uint8_t year_byte = bcd2ToByte(buf[3] & 0xff);
ESP_LOGD(TAG, "Year byte is %i", year_byte); ESP_LOGD(TAG, "Year byte is %i", year_byte);
@ -161,20 +161,19 @@ void BM8563::setDate(BM8563_DateTypeDef* BM8563_DateStruct) {
return; return;
} }
uint8_t buf[4] = { uint8_t buf[4] = {
byteToBcd2(BM8563_DateStruct->day), byteToBcd2(BM8563_DateStruct->day),
byteToBcd2(BM8563_DateStruct->week), byteToBcd2(BM8563_DateStruct->week),
byteToBcd2(BM8563_DateStruct->month), byteToBcd2(BM8563_DateStruct->month),
byteToBcd2((uint8_t)(BM8563_DateStruct->year % 100)), byteToBcd2((uint8_t)(BM8563_DateStruct->year % 100)),
}; };
if (BM8563_DateStruct->year < 2000) { if (BM8563_DateStruct->year < 2000) {
buf[2] = byteToBcd2(BM8563_DateStruct->month) | 0x80; buf[2] = byteToBcd2(BM8563_DateStruct->month) | 0x80;
} else { } else {
buf[2] = byteToBcd2(BM8563_DateStruct->month) | 0x00; buf[2] = byteToBcd2(BM8563_DateStruct->month) | 0x00;
} }
ESP_LOGI(TAG, "WRiting year is %i", buf[3]); ESP_LOGI(TAG, "Writing year is %i", buf[3]);
this->write_register(0x05, buf, 4); this->write_register(0x05, buf, 4);
} }
@ -188,7 +187,7 @@ uint8_t BM8563::ReadReg(uint8_t reg) {
return data; return data;
} }
int BM8563::SetAlarmIRQ(int afterSeconds) { void BM8563::SetAlarmIRQ(int afterSeconds) {
ESP_LOGI(TAG, "Sleep Duration: %u ms", afterSeconds); ESP_LOGI(TAG, "Sleep Duration: %u ms", afterSeconds);
uint8_t reg_value = 0; uint8_t reg_value = 0;
reg_value = ReadReg(0x01); reg_value = ReadReg(0x01);
@ -198,26 +197,28 @@ int BM8563::SetAlarmIRQ(int afterSeconds) {
WriteReg(0x01, reg_value); WriteReg(0x01, reg_value);
reg_value = 0x03; reg_value = 0x03;
WriteReg(0x0E, reg_value); WriteReg(0x0E, reg_value);
return -1; return;
} }
uint8_t type_value = 2; uint8_t td;
uint8_t div = 1; if (afterSeconds <= (255 * 1000 / 64)) {
if (afterSeconds > 255) { td = 0x81;
div = 60; afterSeconds = afterSeconds * 64 / 1000;
type_value = 0x83; } else if (afterSeconds / 1000 <= 255) {
td = 0x82;
afterSeconds /= 1000;
} else { } else {
type_value = 0x82; td = 0x83;
afterSeconds /= 60000;
} }
afterSeconds = (afterSeconds / div) & 0xFF; WriteReg(0x0E, td);
WriteReg(0x0F, afterSeconds); WriteReg(0x0F, std::min(afterSeconds, 0xFF));
WriteReg(0x0E, type_value); ESP_LOGI(TAG, "%d %x", afterSeconds, td);
reg_value |= (1 << 0); reg_value |= (1 << 0);
reg_value &= ~(1 << 7); reg_value &= ~(1 << 7);
WriteReg(0x01, reg_value); WriteReg(0x01, reg_value);
return afterSeconds * div;
} }
void BM8563::clearIRQ() { void BM8563::clearIRQ() {
@ -232,4 +233,4 @@ void BM8563::disableIRQ() {
} }
} // namespace bm8563 } // namespace bm8563
} // namespace esphome } // namespace esphome

View file

@ -27,7 +27,7 @@ class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
void setup() override; void setup() override;
void update() override; void update() override;
void dump_config() override; void dump_config() override;
void set_sleep_duration(uint32_t time_ms); void set_sleep_duration(uint32_t time_ms);
void write_time(); void write_time();
void read_time(); void read_time();
@ -42,7 +42,7 @@ class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
void setTime(BM8563_TimeTypeDef* BM8563_TimeStruct); void setTime(BM8563_TimeTypeDef* BM8563_TimeStruct);
void setDate(BM8563_DateTypeDef* BM8563_DateStruct); void setDate(BM8563_DateTypeDef* BM8563_DateStruct);
int SetAlarmIRQ(int afterSeconds); void SetAlarmIRQ(int afterSeconds);
int SetAlarmIRQ(const BM8563_TimeTypeDef &BM8563_TimeStruct); int SetAlarmIRQ(const BM8563_TimeTypeDef &BM8563_TimeStruct);
int SetAlarmIRQ(const BM8563_DateTypeDef &BM8563_DateStruct, const BM8563_TimeTypeDef &BM8563_TimeStruct); int SetAlarmIRQ(const BM8563_DateTypeDef &BM8563_DateStruct, const BM8563_TimeTypeDef &BM8563_TimeStruct);

View file

@ -1,12 +1,19 @@
```yaml ```yaml
# example configuration: # example configuration:
sensor:
- platform: empty_spi_sensor
name: Empty SPI sensor
cs_pin: D8
spi: spi:
clk_pin: D5 clk_pin: GPIO14
miso_pin: D6 mosi_pin: GPIO12
miso_pin: GPIO13
display:
- platform: it8951e
id: m5paper_display
cs_pin: GPIO15
reset_pin: GPIO23
reset_duration: 100ms
busy_pin: GPIO27
rotation: 0
reversed: False
update_interval: never
``` ```

View file

@ -12,6 +12,7 @@ from esphome.const import (
CONF_BUSY_PIN, CONF_BUSY_PIN,
CONF_PAGES, CONF_PAGES,
CONF_LAMBDA, CONF_LAMBDA,
CONF_MODEL,
CONF_REVERSED, CONF_REVERSED,
) )
@ -23,6 +24,12 @@ IT8951ESensor = it8951e_ns.class_(
) )
ClearAction = it8951e_ns.class_("ClearAction", automation.Action) ClearAction = it8951e_ns.class_("ClearAction", automation.Action)
it8951eModel = it8951e_ns.enum("it8951eModel")
MODELS = {
"M5EPD": it8951eModel.M5EPD
}
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend( display.FULL_DISPLAY_SCHEMA.extend(
{ {
@ -35,6 +42,9 @@ CONFIG_SCHEMA = cv.All(
cv.positive_time_period_milliseconds, cv.positive_time_period_milliseconds,
cv.Range(max=core.TimePeriod(milliseconds=500)), cv.Range(max=core.TimePeriod(milliseconds=500)),
), ),
cv.Optional(CONF_MODEL, default="M5EPD"): cv.enum(
MODELS, upper=True, space="_"
),
} }
) )
.extend(cv.polling_component_schema("1s")) .extend(cv.polling_component_schema("1s"))
@ -43,7 +53,7 @@ CONFIG_SCHEMA = cv.All(
) )
@automation.register_action( @automation.register_action(
"IT8951E.clear", "it8951e.clear",
ClearAction, ClearAction,
automation.maybe_simple_id( automation.maybe_simple_id(
{ {
@ -51,7 +61,7 @@ CONFIG_SCHEMA = cv.All(
} }
), ),
) )
async def bm8563_read_time_to_code(config, action_id, template_arg, args): async def it8951e_clear_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID]) await cg.register_parented(var, config[CONF_ID])
return var return var
@ -63,6 +73,8 @@ async def to_code(config):
await display.register_display(var, config) await display.register_display(var, config)
await spi.register_spi_device(var, config) await spi.register_spi_device(var, config)
if CONF_MODEL in config:
cg.add(var.set_model(config[CONF_MODEL]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void

View file

@ -30,17 +30,6 @@ IT8951 Command defines
/*----------------------------------------------------------------------- /*-----------------------------------------------------------------------
IT8951 Mode defines IT8951 Mode defines
------------------------------------------------------------------------*/ ------------------------------------------------------------------------*/
// Rotate mode
#define IT8951_ROTATE_0 0
#define IT8951_ROTATE_90 1
#define IT8951_ROTATE_180 2
#define IT8951_ROTATE_270 3
// Direction mode
#define IT8951_DIRECTION_PORTRAIT 1
#define IT8951_DIRECTION_LANDSCAPE 0
//Pixel mode (Bit per Pixel) //Pixel mode (Bit per Pixel)
#define IT8951_2BPP 0 #define IT8951_2BPP 0
#define IT8951_3BPP 1 #define IT8951_3BPP 1

View file

@ -7,12 +7,6 @@
namespace esphome { namespace esphome {
namespace it8951e { namespace it8951e {
//TODO: create model M5EPD
#define M5EPD_PANEL_W 960
#define M5EPD_PANEL_H 540
#define M5EPD_PANEL_ADDRL 0x36E0
#define M5EPD_PANEL_ADDRH 0x0012
static const char *TAG = "it8951e.display"; static const char *TAG = "it8951e.display";
void IT8951ESensor::write_two_byte16(uint16_t type, uint16_t cmd) { void IT8951ESensor::write_two_byte16(uint16_t type, uint16_t cmd) {
@ -21,7 +15,7 @@ void IT8951ESensor::write_two_byte16(uint16_t type, uint16_t cmd) {
this->write_byte16(type); this->write_byte16(type);
this->wait_busy(); this->wait_busy();
this->write_byte16(cmd); this->write_byte16(cmd);
this->disable(); this->disable();
} }
@ -94,12 +88,9 @@ void IT8951ESensor::write_reg(uint16_t addr, uint16_t data) {
this->disable(); this->disable();
} }
void IT8951ESensor::set_target_memory_addr(uint32_t tar_addr) { void IT8951ESensor::set_target_memory_addr(uint16_t tar_addrL, uint16_t tar_addrH) {
uint16_t h = (uint16_t)((tar_addr >> 16) & 0x0000FFFF); this->write_reg(IT8951_LISAR + 2, tar_addrH);
uint16_t l = (uint16_t)(tar_addr & 0x0000FFFF); this->write_reg(IT8951_LISAR, tar_addrL);
this->write_reg(IT8951_LISAR + 2, h);
this->write_reg(IT8951_LISAR, l);
} }
void IT8951ESensor::write_args(uint16_t cmd, uint16_t *args, uint16_t length) { void IT8951ESensor::write_args(uint16_t cmd, uint16_t *args, uint16_t length) {
@ -109,34 +100,11 @@ void IT8951ESensor::write_args(uint16_t cmd, uint16_t *args, uint16_t length) {
} }
} }
void IT8951ESensor::set_rotation(uint16_t rotate) {
if (rotate < 4) {
this->m_rotate = rotate;
} else if (rotate < 90) {
this->m_rotate = IT8951_ROTATE_0;
} else if (rotate < 180) {
this->m_rotate = IT8951_ROTATE_90;
} else if (rotate < 270) {
this->m_rotate = IT8951_ROTATE_180;
} else {
this->m_rotate = IT8951_ROTATE_270;
}
if (this->m_rotate == IT8951_ROTATE_0 || this->m_rotate == IT8951_ROTATE_180) {
this->m_direction = IT8951_DIRECTION_PORTRAIT;
this->device_info_.usPanelW = M5EPD_PANEL_W;
this->device_info_.usPanelH = M5EPD_PANEL_H;
} else {
this->m_direction = IT8951_DIRECTION_LANDSCAPE;
this->device_info_.usPanelW = M5EPD_PANEL_H;
this->device_info_.usPanelH = M5EPD_PANEL_W;
}
}
void IT8951ESensor::set_area(uint16_t x, uint16_t y, uint16_t w, void IT8951ESensor::set_area(uint16_t x, uint16_t y, uint16_t w,
uint16_t h) { uint16_t h) {
uint16_t args[5]; uint16_t args[5];
args[0] = (this->m_endian_type << 8 | this->m_pix_bpp << 4 | this->m_rotate);
args[0] = (this->m_endian_type << 8 | this->m_pix_bpp << 4);
args[1] = x; args[1] = x;
args[2] = y; args[2] = y;
args[3] = w; args[3] = w;
@ -177,8 +145,8 @@ void IT8951ESensor::check_busy(uint32_t timeout) {
} }
void IT8951ESensor::update_area(uint16_t x, uint16_t y, uint16_t w, void IT8951ESensor::update_area(uint16_t x, uint16_t y, uint16_t w,
uint16_t h, m5epd_update_mode_t mode) { uint16_t h, update_mode_e mode) {
if (mode == UPDATE_MODE_NONE) { if (mode == update_mode_e::UPDATE_MODE_NONE) {
return; return;
} }
@ -195,35 +163,14 @@ void IT8951ESensor::update_area(uint16_t x, uint16_t y, uint16_t w,
h = this->get_height_internal() - y; h = this->get_height_internal() - y;
} }
uint16_t tmp_x = x;
uint16_t tmp_y = y;
switch (this->m_rotate) {
case IT8951_ROTATE_0:
tmp_x = x;
tmp_y = y;
break;
case IT8951_ROTATE_90:
tmp_x = y;
tmp_y = M5EPD_PANEL_H - w - x;
break;
case IT8951_ROTATE_180:
tmp_x = M5EPD_PANEL_W - w - x;
tmp_y = M5EPD_PANEL_H - h - y;
break;
case IT8951_ROTATE_270:
tmp_x = M5EPD_PANEL_W - h - y;
tmp_y = x;
break;
}
uint16_t args[7]; uint16_t args[7];
args[0] = tmp_x; args[0] = x;
args[1] = tmp_y; args[1] = y;
args[2] = w; args[2] = w;
args[3] = h; args[3] = h;
args[4] = mode; args[4] = mode;
args[5] = this->device_info_.usImgBufAddrL; args[5] = this->IT8951DevAll[this->model_].devInfo.usImgBufAddrL;
args[6] = this->device_info_.usImgBufAddrH; args[6] = this->IT8951DevAll[this->model_].devInfo.usImgBufAddrH;
this->write_args(IT8951_I80_CMD_DPY_BUF_AREA, args, 7); this->write_args(IT8951_I80_CMD_DPY_BUF_AREA, args, 7);
} }
@ -238,9 +185,9 @@ void IT8951ESensor::reset(void) {
uint32_t IT8951ESensor::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); } uint32_t IT8951ESensor::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); }
void IT8951ESensor::get_device_info(IT8951DevInfo *info) { void IT8951ESensor::get_device_info(struct IT8951DevInfo_s *info) {
this->write_command(IT8951_I80_CMD_GET_DEV_INFO); this->write_command(IT8951_I80_CMD_GET_DEV_INFO);
this->read_words(info, sizeof(IT8951DevInfo)/2);//Polling HRDY for each words(2-bytes) if possible this->read_words(info, sizeof(struct IT8951DevInfo_s)/2); // Polling HRDY for each words(2-bytes) if possible
} }
uint16_t IT8951ESensor::get_vcom() { uint16_t IT8951ESensor::get_vcom() {
@ -268,17 +215,8 @@ void IT8951ESensor::setup() {
this->busy_pin_->pin_mode(gpio::FLAG_INPUT); this->busy_pin_->pin_mode(gpio::FLAG_INPUT);
this->get_device_info(&(this->device_info_)); // this->get_device_info(&(this->device_info_));
this->dump_config(); this->dump_config();
if (!this->device_info_.usImgBufAddrH || !this->device_info_.usImgBufAddrL) {
// Sometime it fails to read the device info
ESP_LOGE(TAG, "FAILED to read panel image buffer address, try hard...");
this->device_info_.usPanelW = M5EPD_PANEL_W;
this->device_info_.usPanelH = M5EPD_PANEL_H;
this->device_info_.usImgBufAddrL = M5EPD_PANEL_ADDRL;
this->device_info_.usImgBufAddrH = M5EPD_PANEL_ADDRH;
}
this->set_rotation(IT8951_ROTATE_0);
this->write_command(IT8951_TCON_SYS_RUN); this->write_command(IT8951_TCON_SYS_RUN);
@ -309,19 +247,18 @@ void IT8951ESensor::setup() {
* @param y Update Y coordinate * @param y Update Y coordinate
* @param w width of gram, >>> Must be a multiple of 4 <<< * @param w width of gram, >>> Must be a multiple of 4 <<<
* @param h height of gram * @param h height of gram
* @param gram 4bpp garm data * @param gram 4bpp gram data
* @retval m5epd_err_t
*/ */
void IT8951ESensor::write_buffer_to_display(uint16_t x, uint16_t y, uint16_t w, void IT8951ESensor::write_buffer_to_display(uint16_t x, uint16_t y, uint16_t w,
uint16_t h, const uint8_t *gram) { uint16_t h, const uint8_t *gram) {
this->m_endian_type = IT8951_LDIMG_B_ENDIAN; this->m_endian_type = IT8951_LDIMG_B_ENDIAN;
this->m_pix_bpp = IT8951_4BPP; this->m_pix_bpp = IT8951_4BPP;
if (x > this->get_width_internal() || y > this->get_height_internal()) { if (x > this->get_width() || y > this->get_height()) {
ESP_LOGE(TAG, "Pos (%d, %d) out of bounds.", x, y); ESP_LOGE(TAG, "Pos (%d, %d) out of bounds.", x, y);
return; return;
} }
this->set_target_memory_addr(this->device_info_.usImgBufAddrL | (this->device_info_.usImgBufAddrH << 16)); this->set_target_memory_addr(this->IT8951DevAll[this->model_].devInfo.usImgBufAddrL, this->IT8951DevAll[this->model_].devInfo.usImgBufAddrH);
this->set_area(x, y, w, h); this->set_area(x, y, w, h);
uint32_t pos = 0; uint32_t pos = 0;
@ -344,27 +281,24 @@ void IT8951ESensor::write_buffer_to_display(uint16_t x, uint16_t y, uint16_t w,
} }
void IT8951ESensor::write_display() { void IT8951ESensor::write_display() {
//this->write_command(IT8951_TCON_SYS_RUN); this->write_command(IT8951_TCON_SYS_RUN);
this->write_buffer_to_display(0, 0, this->max_x, this->max_y, this->buffer_); this->write_buffer_to_display(0, 0, this->max_x, this->max_y, this->buffer_);
this->update_area(0, 0, this->max_x, this->max_y, UPDATE_MODE_GC16); this->update_area(0, 0, this->max_x, this->max_y, update_mode_e::UPDATE_MODE_GC16);
//this->update_area(0, 0, this->max_x, this->max_y, UPDATE_MODE_DU4);
this->max_x = 0; this->max_x = 0;
this->max_y = 0; this->max_y = 0;
//this->write_command(IT8951_TCON_SLEEP); this->write_command(IT8951_TCON_SLEEP);
} }
/** @brief Clear graphics buffer /** @brief Clear graphics buffer
* @param init Screen initialization, If is 0, clear the buffer without * @param init Screen initialization, If is 0, clear the buffer without initializing
* initializing
* @retval m5epd_err_t
*/ */
void IT8951ESensor::clear(bool init) { void IT8951ESensor::clear(bool init) {
this->m_endian_type = IT8951_LDIMG_L_ENDIAN; this->m_endian_type = IT8951_LDIMG_L_ENDIAN;
this->m_pix_bpp = IT8951_4BPP; this->m_pix_bpp = IT8951_4BPP;
this->set_target_memory_addr(this->device_info_.usImgBufAddrL | (this->device_info_.usImgBufAddrH << 16)); this->set_target_memory_addr(this->IT8951DevAll[this->model_].devInfo.usImgBufAddrL, this->IT8951DevAll[this->model_].devInfo.usImgBufAddrH);
this->set_area(0, 0, this->get_width_internal(), this->get_height_internal()); this->set_area(0, 0, this->get_width_internal(), this->get_height_internal());
uint32_t looping = (this->get_width_internal() * this->get_height_internal()) >> 2; uint32_t looping = (this->get_width_internal() * this->get_height_internal()) >> 2;
for (uint32_t x = 0; x < looping; x++) { for (uint32_t x = 0; x < looping; x++) {
@ -377,18 +311,20 @@ void IT8951ESensor::clear(bool init) {
this->write_command(IT8951_TCON_LD_IMG_END); this->write_command(IT8951_TCON_LD_IMG_END);
if (init) { if (init) {
this->update_area(0, 0, this->get_width_internal(), this->get_height_internal(), UPDATE_MODE_INIT); this->update_area(0, 0, this->get_width_internal(), this->get_height_internal(), update_mode_e::UPDATE_MODE_INIT);
} }
} }
void IT8951ESensor::update() { void IT8951ESensor::update() {
this->do_update_(); if (this->is_ready()) {
this->write_display(); this->do_update_();
this->write_display();
}
} }
void HOT IT8951ESensor::draw_absolute_pixel_internal(int x, int y, Color color) { void HOT IT8951ESensor::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) { if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) {
// Removed to avoid too much logging // Removed to avoid too much logging
// ESP_LOGE(TAG, "Drawing outside the screen size!"); // ESP_LOGE(TAG, "Drawing outside the screen size!");
return; return;
} }
@ -419,20 +355,27 @@ void HOT IT8951ESensor::draw_absolute_pixel_internal(int x, int y, Color color)
} }
int IT8951ESensor::get_width_internal() { int IT8951ESensor::get_width_internal() {
return this->device_info_.usPanelW; return this->IT8951DevAll[this->model_].devInfo.usPanelW;
} }
int IT8951ESensor::get_height_internal() { int IT8951ESensor::get_height_internal() {
return this->device_info_.usPanelH; return this->IT8951DevAll[this->model_].devInfo.usPanelH;
} }
void IT8951ESensor::dump_config() { void IT8951ESensor::dump_config() {
ESP_LOGI(TAG, "Height:%d Width:%d LUT: %s, FW: %s, Mem:%x", LOG_DISPLAY("", "IT8951E", this);
this->device_info_.usPanelH, switch (this->model_) {
this->device_info_.usPanelW, case it8951eModel::M5EPD:
this->device_info_.usLUTVersion, ESP_LOGCONFIG(TAG, " Model: M5EPD");
this->device_info_.usFWVersion, break;
this->device_info_.usImgBufAddrL | (this->device_info_.usImgBufAddrH << 16) default:
ESP_LOGCONFIG(TAG, " Model: unkown");
break;
}
ESP_LOGCONFIG(TAG, "LUT: %s, FW: %s, Mem:%x",
this->IT8951DevAll[this->model_].devInfo.usLUTVersion,
this->IT8951DevAll[this->model_].devInfo.usFWVersion,
this->IT8951DevAll[this->model_].devInfo.usImgBufAddrL | (this->IT8951DevAll[this->model_].devInfo.usImgBufAddrH << 16)
); );
} }

View file

@ -8,6 +8,12 @@
namespace esphome { namespace esphome {
namespace it8951e { namespace it8951e {
enum it8951eModel
{
M5EPD = 0,
it8951eModelsEND // MUST be last
};
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2023, 12, 0) #if ESPHOME_VERSION_CODE >= VERSION_CODE(2023, 12, 0)
class IT8951ESensor : public display::DisplayBuffer, class IT8951ESensor : public display::DisplayBuffer,
#else #else
@ -18,7 +24,7 @@ class IT8951ESensor : public PollingComponent, public display::DisplayBuffer,
spi::DATA_RATE_10MHZ> { spi::DATA_RATE_10MHZ> {
public: public:
float get_loop_priority() const override { return 0.0f; }; float get_loop_priority() const override { return 0.0f; };
float get_setup_priority() const override { return setup_priority::HARDWARE; }; float get_setup_priority() const override { return setup_priority::PROCESSOR; };
/* /*
---------------------------------------- Refresh mode description ---------------------------------------- Refresh mode description
@ -90,49 +96,47 @@ shown in Figure 1. The use of a white image in the transition from 4-bit to
*/ */
typedef struct struct IT8951DevInfo_s
{ {
uint16_t usPanelW; // these are incorrect uint16_t usPanelW;
uint16_t usPanelH; // on m5paper uint16_t usPanelH;
uint16_t usImgBufAddrL; uint16_t usImgBufAddrL;
uint16_t usImgBufAddrH; uint16_t usImgBufAddrH;
char usFWVersion[16]; // empty on m5paper char usFWVersion[16];
char usLUTVersion[16]; // empty on m5paper char usLUTVersion[16];
}IT8951DevInfo; };
typedef enum // Typical struct IT8951Dev_s
{ // Ghosting Update Time Usage {
UPDATE_MODE_INIT = 0, // * N/A 2000ms Display initialization, struct IT8951DevInfo_s devInfo;
UPDATE_MODE_DU = 1, // Low 260ms Monochrome menu, text display::DisplayType displayType;
// input, and touch screen input };
UPDATE_MODE_GC16 = 2, // * Very Low 450ms High quality images
UPDATE_MODE_GL16 = enum update_mode_e // Typical
3, // * Medium 450ms Text with white background { // Ghosting Update Time Usage
UPDATE_MODE_GLR16 = UPDATE_MODE_INIT = 0, // * N/A 2000ms Display initialization,
4, // Low 450ms Text with white background UPDATE_MODE_DU = 1, // Low 260ms Monochrome menu, text input, and touch screen input
UPDATE_MODE_GLD16 = UPDATE_MODE_GC16 = 2, // * Very Low 450ms High quality images
5, // Low 450ms Text and graphics with white background UPDATE_MODE_GL16 = 3, // * Medium 450ms Text with white background
UPDATE_MODE_DU4 = UPDATE_MODE_GLR16 = 4, // Low 450ms Text with white background
6, // * Medium 120ms Fast page flipping at reduced contrast UPDATE_MODE_GLD16 = 5, // Low 450ms Text and graphics with white background
UPDATE_MODE_A2 = 7, // Medium 290ms Anti-aliased text in menus UPDATE_MODE_DU4 = 6, // * Medium 120ms Fast page flipping at reduced contrast
// / touch and screen input UPDATE_MODE_A2 = 7, // Medium 290ms Anti-aliased text in menus / touch and screen input
UPDATE_MODE_NONE = 8 UPDATE_MODE_NONE = 8
} m5epd_update_mode_t; // The ones marked with * are more commonly used }; // The ones marked with * are more commonly used
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; }
void set_rotation(uint16_t rotate);
void set_reversed(bool reversed) { this->reversed_ = reversed; } void set_reversed(bool reversed) { this->reversed_ = reversed; }
void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; } void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
void set_model(it8951eModel model) { this->model_ = model; }
uint8_t get_rotate(void) { return m_rotate; };
uint8_t get_direction(void) { return m_direction; };
void setup() override; void setup() override;
void update() override; void update() override;
void dump_config() override; void dump_config() override;
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; }
display::DisplayType get_display_type() override { return IT8951DevAll[this->model_].displayType; }
void clear(bool init); void clear(bool init);
@ -147,14 +151,22 @@ typedef enum // Typical
private: private:
IT8951DevInfo device_info_; struct IT8951Dev_s IT8951DevAll[it8951eModel::it8951eModelsEND]
{ // it8951eModel::M5EPD
960, // .devInfo.usPanelW
540, // .devInfo.usPanelH
0x36E0, // .devInfo.usImgBufAddrL
0x0012, // .devInfo.usImgBufAddrH
"", // .devInfo.usFWVersion
"", // .devInfo.usFWVersion
display::DisplayType::DISPLAY_TYPE_GRAYSCALE // .displayType (M5EPD supports 16 gray scale levels)
};
uint8_t *should_write_buffer_{nullptr}; uint8_t *should_write_buffer_{nullptr};
void get_device_info(IT8951DevInfo *info); void get_device_info(struct IT8951DevInfo_s *info);
uint32_t max_x = 0; uint32_t max_x = 0;
uint32_t max_y = 0; uint32_t max_y = 0;
uint8_t m_rotate = 0;
uint8_t m_direction = 1;
uint16_t m_endian_type, m_pix_bpp; uint16_t m_endian_type, m_pix_bpp;
@ -163,6 +175,7 @@ typedef enum // Typical
bool reversed_ = false; bool reversed_ = false;
uint32_t reset_duration_{100}; uint32_t reset_duration_{100};
enum it8951eModel model_{it8951eModel::M5EPD};
void reset(void); void reset(void);
@ -180,12 +193,12 @@ typedef enum // Typical
void write_command(uint16_t cmd); void write_command(uint16_t cmd);
void write_word(uint16_t cmd); void write_word(uint16_t cmd);
void write_reg(uint16_t addr, uint16_t data); void write_reg(uint16_t addr, uint16_t data);
void set_target_memory_addr(uint32_t tar_addr); void set_target_memory_addr(uint16_t tar_addrL, uint16_t tar_addrH);
void write_args(uint16_t cmd, uint16_t *args, uint16_t length); void write_args(uint16_t cmd, uint16_t *args, uint16_t length);
void set_area(uint16_t x, uint16_t y, uint16_t w, uint16_t h); void set_area(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void update_area(uint16_t x, uint16_t y, uint16_t w, void update_area(uint16_t x, uint16_t y, uint16_t w,
uint16_t h, m5epd_update_mode_t mode); uint16_t h, update_mode_e mode);

View file

@ -2,18 +2,13 @@ import esphome.codegen as cg
from esphome import pins from esphome import pins
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.components import sensor
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
DEVICE_CLASS_VOLTAGE,
CONF_BATTERY_VOLTAGE,
UNIT_VOLT,
STATE_CLASS_MEASUREMENT,
) )
m5paper_ns = cg.esphome_ns.namespace('m5paper') m5paper_ns = cg.esphome_ns.namespace('m5paper')
M5PaperComponent = m5paper_ns.class_('M5PaperComponent', cg.PollingComponent) M5PaperComponent = m5paper_ns.class_('M5PaperComponent', cg.Component)
PowerAction = m5paper_ns.class_("PowerAction", automation.Action) PowerAction = m5paper_ns.class_("PowerAction", automation.Action)
CONF_MAIN_POWER_PIN = "main_power_pin" CONF_MAIN_POWER_PIN = "main_power_pin"
@ -22,14 +17,8 @@ CONF_BATTERY_POWER_PIN = "battery_power_pin"
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(M5PaperComponent), cv.GenerateID(): cv.declare_id(M5PaperComponent),
cv.Required(CONF_MAIN_POWER_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_MAIN_POWER_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_BATTERY_POWER_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_BATTERY_POWER_PIN): pins.gpio_output_pin_schema
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( })
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
)
}).extend(cv.polling_component_schema('60s'))
@automation.register_action( @automation.register_action(
"m5paper.shutdown_main_power", "m5paper.shutdown_main_power",
@ -55,7 +44,4 @@ async def to_code(config):
cg.add(var.set_main_power_pin(power)) cg.add(var.set_main_power_pin(power))
if CONF_BATTERY_POWER_PIN in config: if CONF_BATTERY_POWER_PIN in config:
power = await cg.gpio_pin_expression(config[CONF_BATTERY_POWER_PIN]) power = await cg.gpio_pin_expression(config[CONF_BATTERY_POWER_PIN])
cg.add(var.set_battery_power_pin(power)) cg.add(var.set_battery_power_pin(power))
if CONF_BATTERY_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
cg.add(var.set_battery_voltage(sens))

View file

@ -1,14 +1,10 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "m5paper.h" #include "m5paper.h"
#include "soc/adc_channel.h" #include "driver/gpio.h"
namespace esphome { namespace esphome {
namespace m5paper { namespace m5paper {
#define BASE_VOLATAGE 3600
#define SCALE 0.5//0.78571429
#define ADC_FILTER_SAMPLE 16
// hack to hold power lines up in deep sleep mode // hack to hold power lines up in deep sleep mode
// battery life isn't great with deep sleep, recommend bm8563 sleep // battery life isn't great with deep sleep, recommend bm8563 sleep
#define ALLOW_ESPHOME_DEEP_SLEEP true #define ALLOW_ESPHOME_DEEP_SLEEP true
@ -16,7 +12,7 @@ namespace m5paper {
static const char *TAG = "m5paper.component"; static const char *TAG = "m5paper.component";
void M5PaperComponent::setup() { void M5PaperComponent::setup() {
ESP_LOGE(TAG, "m5paper starting up!"); ESP_LOGCONFIG(TAG, "m5paper starting up!");
this->main_power_pin_->pin_mode(gpio::FLAG_OUTPUT); this->main_power_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->main_power_pin_->digital_write(true); this->main_power_pin_->digital_write(true);
@ -27,19 +23,10 @@ void M5PaperComponent::setup() {
gpio_hold_en(GPIO_NUM_2); gpio_hold_en(GPIO_NUM_2);
gpio_hold_en(GPIO_NUM_5); gpio_hold_en(GPIO_NUM_5);
} }
adc_power_acquire();
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_GPIO35_CHANNEL, ADC_ATTEN_DB_11);
this->_adc_chars = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, BASE_VOLATAGE, this->_adc_chars);
} }
void M5PaperComponent::shutdown_main_power() { void M5PaperComponent::shutdown_main_power() {
ESP_LOGE(TAG, "Shutting Down Power"); ESP_LOGE(TAG, "Shutting Down Power");
adc_power_release();
if (ALLOW_ESPHOME_DEEP_SLEEP) { if (ALLOW_ESPHOME_DEEP_SLEEP) {
gpio_hold_dis(GPIO_NUM_2); gpio_hold_dis(GPIO_NUM_2);
gpio_hold_dis(GPIO_NUM_5); gpio_hold_dis(GPIO_NUM_5);
@ -47,25 +34,8 @@ void M5PaperComponent::shutdown_main_power() {
this->main_power_pin_->digital_write(false); this->main_power_pin_->digital_write(false);
} }
void M5PaperComponent::update() {
uint32_t adc_raw_value = 0;
for (uint16_t i = 0; i < ADC_FILTER_SAMPLE; i++)
{
adc_raw_value += adc1_get_raw(ADC1_GPIO35_CHANNEL);
}
adc_raw_value = adc_raw_value / ADC_FILTER_SAMPLE;
uint32_t millivolts = (uint32_t)(esp_adc_cal_raw_to_voltage(adc_raw_value, _adc_chars) / SCALE);
float voltage = static_cast<float>(millivolts) * 0.001f;
if (this->battery_voltage_ != nullptr)
this->battery_voltage_->publish_state(voltage);
this->status_clear_warning();
}
void M5PaperComponent::dump_config() { void M5PaperComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Empty custom sensor"); ESP_LOGCONFIG(TAG, "M5Paper");
} }
} //namespace m5paper } //namespace m5paper

View file

@ -1,44 +1,33 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/gpio.h" #include "esphome/core/gpio.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#ifdef USE_ESP32
#include "driver/adc.h"
#include <esp_adc_cal.h>
#endif
namespace esphome { namespace esphome {
namespace m5paper { namespace m5paper {
class M5PaperComponent : public PollingComponent { class M5PaperComponent : public Component {
void setup() override; void setup() override;
void update() override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }; /* Very early setup as takes care of powering other components */
float get_setup_priority() const override { return setup_priority::BUS; };
public: public:
void set_battery_power_pin(GPIOPin *power) { this->battery_power_pin_ = power; } void set_battery_power_pin(GPIOPin *power) { this->battery_power_pin_ = power; }
void set_main_power_pin(GPIOPin *power) { this->main_power_pin_ = power; } void set_main_power_pin(GPIOPin *power) { this->main_power_pin_ = power; }
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; } void shutdown_main_power();
void shutdown_main_power();
private: private:
GPIOPin *battery_power_pin_{nullptr}; GPIOPin *battery_power_pin_{nullptr};
GPIOPin *main_power_pin_{nullptr}; GPIOPin *main_power_pin_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
esp_adc_cal_characteristics_t *_adc_chars;
}; };
template<typename... Ts> class PowerAction : public Action<Ts...>, public Parented<M5PaperComponent> { template<typename... Ts> class PowerAction : public Action<Ts...>, public Parented<M5PaperComponent> {
public: public:
void play(Ts... x) override { this->parent_->shutdown_main_power(); } void play(Ts... x) override { this->parent_->shutdown_main_power(); }
}; };
} //namespace m5paper } //namespace m5paper
} //namespace esphome } //namespace esphome

288
m5paper.yaml Normal file
View file

@ -0,0 +1,288 @@
esphome:
name: ${device_id}
name_add_mac_suffix: true
project:
name: "${project_name}"
version: "${project_version}"
includes:
- render.h
# on_boot:
# - priority: -100.0
# then:
# - delay: ${default_update_interval}
# - component.update: m5paper_display
# - delay: 1s
# - bm8563.apply_sleep_duration
# - m5paper.shutdown_main_power
# - deep_sleep.enter:
# sleep_duration: 30s
esp32:
board: m5stack-grey
framework:
type: arduino
external_components:
- source:
type: local
path: components
logger:
level: DEBUG
baud_rate: 921600
psram:
api:
ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: "HIGH"
fast_connect: true
globals:
- id: material_icons_map
type: std::map<std::string, std::string>
restore_value: no
initial_value: |
{
{"mdi-weather-night", "󰖔"},
{"mdi-weather-cloudy", "󰖐"},
{"mdi-weather-cloudy-alert", "󰼯"},
{"mdi-weather-fog", "󰖑"},
{"mdi-weather-hail", "󰖒"},
{"mdi-weather-lightning-rainy", "󰙾"},
{"mdi-weather-lightning", "󰖓"},
{"mdi-weather-partly-cloudy", "󰖕"},
{"mdi-weather-night-partly-cloudy", "󰼱"},
{"mdi-weather-pouring", "󰖖"},
{"mdi-weather-rainy", "󰖗"},
{"mdi-weather-snowy-rainy", "󰙿"},
{"mdi-weather-snowy", "󰖘"},
{"mdi-weather-sunny", "󰖙"},
{"mdi-weather-windy-variant", "󰖞"},
{"mdi-weather-windy", "󰖝"},
{"mdi-cloud-question", "󰨹"},
{"mdi-thermometer", "󰔏"},
{"mdi-water-percent", "󰖎"},
{"mdi-molecule-co2", "󰟤"},
{"mdi-wind-power-outline", "󱪉"},
{"mdi-home-outline", "󰚡"},
{"mdi-tree-outline", "󰹩"},
{"mdi-gauge", "󰊚"},
{"mdi-battery-high", "󱊣"},
{"mdi-battery-medium", "󱊢"},
{"mdi-battery-low", "󱊡"},
{"mdi-battery-alert-variant-outline", "󱃍"},
{"mdi-battery-charging-high", "󱊦"},
{"mdi-battery-unknown", "󰂑"},
{"mdi-shield-outline", "󰒙"},
{"mdi-shield-home-outline", "󰳋"},
{"mdi-shield-lock-outline", "󰳌"},
{"mdi-shield-moon-outline", "󱠩"},
{"mdi-shield-alert-outline", "󰻍"},
{"mdi-molecule-co2", "󰟤"},
{"mdi-radioactive", "󰐼"},
{"mdi-numeric-0-circle-outline", "󰲟"},
{"mdi-numeric-1-circle-outline", "󰲡"},
{"mdi-numeric-2-circle-outline", "󰲣"},
{"mdi-numeric-3-circle-outline", "󰲥"},
{"mdi-numeric-4-circle-outline", "󰲧"},
{"mdi-numeric-5-circle-outline", "󰲩"},
{"mdi-numeric-6-circle-outline", "󰲫"},
{"mdi-numeric-7-circle-outline", "󰲭"},
{"mdi-numeric-8-circle-outline", "󰲯"},
{"mdi-numeric-9-plus-circle-outline", "󰲳"},
}
font:
- file: 'gfonts://Roboto'
id: normal_font
size: 120
- file: 'gfonts://Roboto'
id: small_font
size: 50
- file: 'gfonts://Roboto'
id: clock_font
size: 260
glyphs: "0123456789:"
- file: "fonts/materialdesignicons-webfont.ttf"
id: battery_font
size: 40
glyphs: [
'󱊢', #mdi-battery-medium
'󱊡', #mdi-battery-low
'󱊣', # mdi-battery-high
'󱃍', #mdi-battery-alert-variant-outline
'󱊦', #mdi-battery-charging-high
'󰂑', #mdi-battery-unknown
]
spi:
clk_pin: GPIO14
mosi_pin: GPIO12
miso_pin: GPIO13
i2c:
sda: GPIO21
scl: GPIO22
display:
- platform: it8951e
id: m5paper_display
cs_pin: GPIO15
reset_pin: GPIO23
reset_duration: 5ms
busy_pin: GPIO27
rotation: 0
reversed: False
update_interval: never
lambda: |-
render(it);
touchscreen:
- platform: gt911
display: m5paper_display
id: gt911_touchscreen
interrupt_pin: GPIO36
switch:
- platform: restart
id: restart_switch
name: ${device_name} restart
- platform: gpio
pin: 32
name: "led"
inverted: true
time:
- platform: homeassistant
id: homeassistant_time
on_time_sync:
- bm8563.write_time
- platform: bm8563
id: rtc_time
sleep_duration: 250s
# on_time:
# - seconds: /6
# then:
# - component.update: m5paper_display
script:
- id: suspend
then:
- component.update: m5paper_display
- delay: 2s
- bm8563.apply_sleep_duration
- m5paper.shutdown_main_power
- delay: 30s
- deep_sleep.enter:
sleep_duration: ${sleep_duration}
interval:
- interval: 1s
then:
- if:
condition:
and:
# - lambda: "return !std::isnan(id(m5paper_battery_level).state);"
- lambda: "return id(rtc_time).now().is_valid();"
- lambda: "return !std::isnan(id(outside_temperature).state);"
- lambda: "return !std::isnan(id(lounge_temperature).state);"
then:
- script.execute: suspend
- interval: 3s
then:
- delay: 30s
- script.execute: suspend
m5paper:
battery_power_pin: GPIO5
main_power_pin: GPIO2
sensor:
- platform: adc
disabled_by_default: true
pin: GPIO35
name: ${device_name} battery voltage
id: m5paper_battery_voltage
update_interval: ${default_update_interval}
attenuation: 11db
filters:
- multiply: 2 #1,27272727
- platform: sht3xd
address: 0x44
temperature:
name: ${device_name} temperature
id: m5paper_temperature
device_class: "temperature"
state_class: "measurement"
icon: mdi:thermometer
humidity:
name: ${device_name} humidity
id: m5paper_humidity
device_class: "humidity"
state_class: "measurement"
icon: mdi:water-percent
update_interval: ${default_update_interval}
- platform: template
name: ${device_name} battery level
id: m5paper_battery_level
unit_of_measurement: '%'
device_class: "battery"
state_class: "measurement"
icon: mdi:battery-high
update_interval: 20s
lambda: |-
constexpr float min_level = 3.52;
constexpr float max_level = 4.15;
return ((id(m5paper_battery_voltage).state - min_level) / (max_level - min_level)) * 100.00;
filters:
- clamp:
min_value: 0
max_value: 100
- platform: homeassistant
name: Outside temperature
id: outside_temperature
entity_id: sensor.ruuvitag_1a2d_temperature
- platform: homeassistant
name: Lounge temperature
id: lounge_temperature
entity_id: sensor.ruuvitag_963b_temperature
- platform: homeassistant
name: solar power
id: solar_power
entity_id: sensor.solax_pv1_power
- platform: uptime
id: uptime_sensor
name: Uptime
update_interval: 3s
binary_sensor:
- platform: gpio
name: ${device_name} right button
id: right_button
icon: mdi:gesture-tap-button
pin:
number: GPIO37
inverted: true
- platform: gpio
name: ${device_name} left button
icon: mdi:gesture-tap-button
pin:
number: GPIO39
inverted: true
- platform: gpio
name: ${device_name} BTN/PWR button
icon: mdi:gesture-tap-button
pin:
number: GPIO38
inverted: true
deep_sleep:
run_duration: 120s
sleep_duration: ${sleep_duration}

9
pipish.yaml Normal file
View file

@ -0,0 +1,9 @@
substitutions:
device_name: Pipish
device_id: pipish
project_name: juju.pipish
project_version: "0.1"
default_update_interval: "10s"
sleep_duration: 10s
<<: !include m5paper.yaml

88
render.h Normal file
View file

@ -0,0 +1,88 @@
inline int measure_width(const char* text, esphome::display::BaseFont& font) {
int width;
int x_offset;
int baseline;
int height;
font.measure(text, &width, &x_offset, &baseline, &height);
return width;
}
inline int measure_height(const char* text, esphome::display::BaseFont& font) {
int width;
int x_offset;
int baseline;
int height;
font.measure(text, &width, &x_offset, &baseline, &height);
return height;
}
inline void render_sensor(esphome::display::Display& it, int x, int y,
float value, std::string unit,
const std::string& label) {
int normal_height = measure_height("X", id(normal_font));
char formatted[20];
if (std::isnan(value)) {
it.print(x, y, &id(normal_font), TextAlign::TOP_CENTER, "--");
} else {
char value_text[20];
if (std::abs(value) >= 1000) {
unit = "k" + unit;
value /= 1000;
}
if (std::abs(value) < 10) {
sprintf(value_text, "%.1f", value);
} else {
sprintf(value_text, "%.0f", value);
}
int value_width = measure_width(value_text, id(normal_font));
int unit_width = measure_width(unit.c_str(), id(small_font));
int width = value_width + unit_width + 3;
it.print(x - width / 2, y + normal_height, &id(normal_font),
TextAlign::BASELINE_LEFT, value_text);
it.print(x + width / 2 - unit_width, y + normal_height, &id(small_font),
TextAlign::BASELINE_LEFT, unit.c_str());
}
it.print(x, y + normal_height + 3, &id(small_font), TextAlign::TOP_CENTER,
label.c_str());
}
inline void render(esphome::display::Display& it) {
constexpr int kHeight = 540;
constexpr int kWidth = 960;
// Clock
it.strftime(25, 25, &id(clock_font), TextAlign::TOP_LEFT, "%H:%M",
id(rtc_time).now());
int right_width = measure_width("123||", id(normal_font));
render_sensor(it, kWidth - right_width / 2, kHeight * 0 / 10,
id(outside_temperature).state, "°C", "Outside");
render_sensor(it, kWidth - right_width / 2, kHeight * 3 / 10,
id(lounge_temperature).state, "°C", "Lounge");
render_sensor(it, kWidth - right_width / 2, kHeight * 6 / 10,
id(solar_power).state, "W", "Solar");
render_sensor(it, right_width / 2, kHeight * 6 / 10, id(uptime_sensor).state,
"s", "Uptime");
static const struct {
float level;
std::string icon;
} battery_levels[] = {
{10, "mdi-battery-alert-variant-outline"},
{40, "mdi-battery-low"},
{70, "mdi-battery-medium"},
{std::numeric_limits<float>::max(), "mdi-battery-high"},
};
// Battery
float battery_level = id(m5paper_battery_level).state;
if (!std::isnan(battery_level)) {
for (const auto& level : battery_levels) {
if (battery_level <= level.level) {
it.print(910, 10, &id(battery_font),
id(material_icons_map)[level.icon].c_str());
break;
}
}
}
}