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
fonts
__pycache__
mlhx/
v1/

View file

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

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

View file

@ -27,7 +27,7 @@ class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
void setup() override;
void update() override;
void dump_config() override;
void set_sleep_duration(uint32_t time_ms);
void write_time();
void read_time();
@ -42,7 +42,7 @@ class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
void setTime(BM8563_TimeTypeDef* BM8563_TimeStruct);
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_DateTypeDef &BM8563_DateStruct, const BM8563_TimeTypeDef &BM8563_TimeStruct);

View file

@ -1,12 +1,19 @@
```yaml
# example configuration:
sensor:
- platform: empty_spi_sensor
name: Empty SPI sensor
cs_pin: D8
spi:
clk_pin: D5
miso_pin: D6
clk_pin: GPIO14
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_PAGES,
CONF_LAMBDA,
CONF_MODEL,
CONF_REVERSED,
)
@ -23,6 +24,12 @@ IT8951ESensor = it8951e_ns.class_(
)
ClearAction = it8951e_ns.class_("ClearAction", automation.Action)
it8951eModel = it8951e_ns.enum("it8951eModel")
MODELS = {
"M5EPD": it8951eModel.M5EPD
}
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
{
@ -35,6 +42,9 @@ CONFIG_SCHEMA = cv.All(
cv.positive_time_period_milliseconds,
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"))
@ -43,7 +53,7 @@ CONFIG_SCHEMA = cv.All(
)
@automation.register_action(
"IT8951E.clear",
"it8951e.clear",
ClearAction,
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)
await cg.register_parented(var, config[CONF_ID])
return var
@ -63,6 +73,8 @@ async def to_code(config):
await display.register_display(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:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void

View file

@ -30,17 +30,6 @@ IT8951 Command 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)
#define IT8951_2BPP 0
#define IT8951_3BPP 1

View file

@ -7,12 +7,6 @@
namespace esphome {
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";
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->wait_busy();
this->write_byte16(cmd);
this->write_byte16(cmd);
this->disable();
}
@ -94,12 +88,9 @@ void IT8951ESensor::write_reg(uint16_t addr, uint16_t data) {
this->disable();
}
void IT8951ESensor::set_target_memory_addr(uint32_t tar_addr) {
uint16_t h = (uint16_t)((tar_addr >> 16) & 0x0000FFFF);
uint16_t l = (uint16_t)(tar_addr & 0x0000FFFF);
this->write_reg(IT8951_LISAR + 2, h);
this->write_reg(IT8951_LISAR, l);
void IT8951ESensor::set_target_memory_addr(uint16_t tar_addrL, uint16_t tar_addrH) {
this->write_reg(IT8951_LISAR + 2, tar_addrH);
this->write_reg(IT8951_LISAR, tar_addrL);
}
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,
uint16_t h) {
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[2] = y;
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,
uint16_t h, m5epd_update_mode_t mode) {
if (mode == UPDATE_MODE_NONE) {
uint16_t h, update_mode_e mode) {
if (mode == update_mode_e::UPDATE_MODE_NONE) {
return;
}
@ -195,35 +163,14 @@ void IT8951ESensor::update_area(uint16_t x, uint16_t y, uint16_t w,
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];
args[0] = tmp_x;
args[1] = tmp_y;
args[0] = x;
args[1] = y;
args[2] = w;
args[3] = h;
args[4] = mode;
args[5] = this->device_info_.usImgBufAddrL;
args[6] = this->device_info_.usImgBufAddrH;
args[5] = this->IT8951DevAll[this->model_].devInfo.usImgBufAddrL;
args[6] = this->IT8951DevAll[this->model_].devInfo.usImgBufAddrH;
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(); }
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->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() {
@ -268,17 +215,8 @@ void IT8951ESensor::setup() {
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();
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);
@ -309,19 +247,18 @@ void IT8951ESensor::setup() {
* @param y Update Y coordinate
* @param w width of gram, >>> Must be a multiple of 4 <<<
* @param h height of gram
* @param gram 4bpp garm data
* @retval m5epd_err_t
* @param gram 4bpp gram data
*/
void IT8951ESensor::write_buffer_to_display(uint16_t x, uint16_t y, uint16_t w,
uint16_t h, const uint8_t *gram) {
this->m_endian_type = IT8951_LDIMG_B_ENDIAN;
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);
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);
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() {
//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->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_DU4);
this->update_area(0, 0, this->max_x, this->max_y, update_mode_e::UPDATE_MODE_GC16);
this->max_x = 0;
this->max_y = 0;
//this->write_command(IT8951_TCON_SLEEP);
this->write_command(IT8951_TCON_SLEEP);
}
/** @brief Clear graphics buffer
* @param init Screen initialization, If is 0, clear the buffer without
* initializing
* @retval m5epd_err_t
* @param init Screen initialization, If is 0, clear the buffer without initializing
*/
void IT8951ESensor::clear(bool init) {
this->m_endian_type = IT8951_LDIMG_L_ENDIAN;
this->m_pix_bpp = IT8951_4BPP;
this->set_target_memory_addr(this->device_info_.usImgBufAddrL | (this->device_info_.usImgBufAddrH << 16));
this->set_area(0, 0, this->get_width_internal(), this->get_height_internal());
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());
uint32_t looping = (this->get_width_internal() * this->get_height_internal()) >> 2;
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);
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() {
this->do_update_();
this->write_display();
if (this->is_ready()) {
this->do_update_();
this->write_display();
}
}
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) {
// Removed to avoid too much logging
// Removed to avoid too much logging
// ESP_LOGE(TAG, "Drawing outside the screen size!");
return;
}
@ -419,20 +355,27 @@ void HOT IT8951ESensor::draw_absolute_pixel_internal(int x, int y, Color color)
}
int IT8951ESensor::get_width_internal() {
return this->device_info_.usPanelW;
return this->IT8951DevAll[this->model_].devInfo.usPanelW;
}
int IT8951ESensor::get_height_internal() {
return this->device_info_.usPanelH;
return this->IT8951DevAll[this->model_].devInfo.usPanelH;
}
void IT8951ESensor::dump_config() {
ESP_LOGI(TAG, "Height:%d Width:%d LUT: %s, FW: %s, Mem:%x",
this->device_info_.usPanelH,
this->device_info_.usPanelW,
this->device_info_.usLUTVersion,
this->device_info_.usFWVersion,
this->device_info_.usImgBufAddrL | (this->device_info_.usImgBufAddrH << 16)
LOG_DISPLAY("", "IT8951E", this);
switch (this->model_) {
case it8951eModel::M5EPD:
ESP_LOGCONFIG(TAG, " Model: M5EPD");
break;
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 it8951e {
enum it8951eModel
{
M5EPD = 0,
it8951eModelsEND // MUST be last
};
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2023, 12, 0)
class IT8951ESensor : public display::DisplayBuffer,
#else
@ -18,7 +24,7 @@ class IT8951ESensor : public PollingComponent, public display::DisplayBuffer,
spi::DATA_RATE_10MHZ> {
public:
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
@ -90,49 +96,47 @@ shown in Figure 1. The use of a white image in the transition from 4-bit to
*/
typedef struct
{
uint16_t usPanelW; // these are incorrect
uint16_t usPanelH; // on m5paper
uint16_t usImgBufAddrL;
uint16_t usImgBufAddrH;
char usFWVersion[16]; // empty on m5paper
char usLUTVersion[16]; // empty on m5paper
}IT8951DevInfo;
struct IT8951DevInfo_s
{
uint16_t usPanelW;
uint16_t usPanelH;
uint16_t usImgBufAddrL;
uint16_t usImgBufAddrH;
char usFWVersion[16];
char usLUTVersion[16];
};
typedef enum // Typical
{ // Ghosting Update Time Usage
UPDATE_MODE_INIT = 0, // * N/A 2000ms Display initialization,
UPDATE_MODE_DU = 1, // Low 260ms Monochrome menu, text
// input, and touch screen input
UPDATE_MODE_GC16 = 2, // * Very Low 450ms High quality images
UPDATE_MODE_GL16 =
3, // * Medium 450ms Text with white background
UPDATE_MODE_GLR16 =
4, // Low 450ms Text with white background
UPDATE_MODE_GLD16 =
5, // Low 450ms Text and graphics with white background
UPDATE_MODE_DU4 =
6, // * Medium 120ms Fast page flipping at reduced contrast
UPDATE_MODE_A2 = 7, // Medium 290ms Anti-aliased text in menus
// / touch and screen input
UPDATE_MODE_NONE = 8
} m5epd_update_mode_t; // The ones marked with * are more commonly used
struct IT8951Dev_s
{
struct IT8951DevInfo_s devInfo;
display::DisplayType displayType;
};
enum update_mode_e // Typical
{ // Ghosting Update Time Usage
UPDATE_MODE_INIT = 0, // * N/A 2000ms Display initialization,
UPDATE_MODE_DU = 1, // Low 260ms Monochrome menu, text input, and touch screen input
UPDATE_MODE_GC16 = 2, // * Very Low 450ms High quality images
UPDATE_MODE_GL16 = 3, // * Medium 450ms Text with white background
UPDATE_MODE_GLR16 = 4, // Low 450ms Text with white background
UPDATE_MODE_GLD16 = 5, // Low 450ms Text and graphics with white background
UPDATE_MODE_DU4 = 6, // * Medium 120ms Fast page flipping at reduced contrast
UPDATE_MODE_A2 = 7, // Medium 290ms Anti-aliased text in menus / touch and screen input
UPDATE_MODE_NONE = 8
}; // The ones marked with * are more commonly used
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
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_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
uint8_t get_rotate(void) { return m_rotate; };
uint8_t get_direction(void) { return m_direction; };
void set_model(it8951eModel model) { this->model_ = model; }
void setup() override;
void update() 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);
@ -147,14 +151,22 @@ typedef enum // Typical
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};
void get_device_info(IT8951DevInfo *info);
void get_device_info(struct IT8951DevInfo_s *info);
uint32_t max_x = 0;
uint32_t max_y = 0;
uint8_t m_rotate = 0;
uint8_t m_direction = 1;
uint16_t m_endian_type, m_pix_bpp;
@ -163,6 +175,7 @@ typedef enum // Typical
bool reversed_ = false;
uint32_t reset_duration_{100};
enum it8951eModel model_{it8951eModel::M5EPD};
void reset(void);
@ -180,12 +193,12 @@ typedef enum // Typical
void write_command(uint16_t cmd);
void write_word(uint16_t cmd);
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 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,
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
import esphome.config_validation as cv
from esphome import automation
from esphome.components import sensor
from esphome.const import (
CONF_ID,
DEVICE_CLASS_VOLTAGE,
CONF_BATTERY_VOLTAGE,
UNIT_VOLT,
STATE_CLASS_MEASUREMENT,
CONF_ID,
)
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)
CONF_MAIN_POWER_PIN = "main_power_pin"
@ -22,14 +17,8 @@ CONF_BATTERY_POWER_PIN = "battery_power_pin"
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(M5PaperComponent),
cv.Required(CONF_MAIN_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'))
cv.Required(CONF_BATTERY_POWER_PIN): pins.gpio_output_pin_schema
})
@automation.register_action(
"m5paper.shutdown_main_power",
@ -55,7 +44,4 @@ async def to_code(config):
cg.add(var.set_main_power_pin(power))
if CONF_BATTERY_POWER_PIN in config:
power = await cg.gpio_pin_expression(config[CONF_BATTERY_POWER_PIN])
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))
cg.add(var.set_battery_power_pin(power))

View file

@ -1,14 +1,10 @@
#include "esphome/core/log.h"
#include "m5paper.h"
#include "soc/adc_channel.h"
#include "driver/gpio.h"
namespace esphome {
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
// battery life isn't great with deep sleep, recommend bm8563 sleep
#define ALLOW_ESPHOME_DEEP_SLEEP true
@ -16,7 +12,7 @@ namespace m5paper {
static const char *TAG = "m5paper.component";
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_->digital_write(true);
@ -27,19 +23,10 @@ void M5PaperComponent::setup() {
gpio_hold_en(GPIO_NUM_2);
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() {
ESP_LOGE(TAG, "Shutting Down Power");
adc_power_release();
if (ALLOW_ESPHOME_DEEP_SLEEP) {
gpio_hold_dis(GPIO_NUM_2);
gpio_hold_dis(GPIO_NUM_5);
@ -47,25 +34,8 @@ void M5PaperComponent::shutdown_main_power() {
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() {
ESP_LOGCONFIG(TAG, "Empty custom sensor");
ESP_LOGCONFIG(TAG, "M5Paper");
}
} //namespace m5paper

View file

@ -1,44 +1,33 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/gpio.h"
#include "esphome/core/automation.h"
#ifdef USE_ESP32
#include "driver/adc.h"
#include <esp_adc_cal.h>
#endif
namespace esphome {
namespace m5paper {
class M5PaperComponent : public PollingComponent {
class M5PaperComponent : public Component {
void setup() override;
void update() 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:
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_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
void shutdown_main_power();
public:
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 shutdown_main_power();
private:
GPIOPin *battery_power_pin_{nullptr};
GPIOPin *main_power_pin_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
esp_adc_cal_characteristics_t *_adc_chars;
private:
GPIOPin *battery_power_pin_{nullptr};
GPIOPin *main_power_pin_{nullptr};
};
template<typename... Ts> class PowerAction : public Action<Ts...>, public Parented<M5PaperComponent> {
public:
void play(Ts... x) override { this->parent_->shutdown_main_power(); }
public:
void play(Ts... x) override { this->parent_->shutdown_main_power(); }
};
} //namespace m5paper
} //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;
}
}
}
}