From 107de1715979e94296a0ad9dd254e7673efa1d43 Mon Sep 17 00:00:00 2001 From: pepev-nrt Date: Sat, 25 Jan 2025 15:26:37 +0100 Subject: [PATCH] First commit -> 2025-01-25 15:26:36 --- LICENSE | 201 ++++++++++++++++++++++ cal/__init__.py | 0 cal/cal.py | 0 config.json | 15 ++ display/DEV_Config_32.so | Bin 0 -> 12852 bytes display/DEV_Config_64.so | Bin 0 -> 71208 bytes display/display.py | 42 +++++ display/epd2in13_V4.py | 350 +++++++++++++++++++++++++++++++++++++++ display/epdconfig.py | 322 +++++++++++++++++++++++++++++++++++ raspagenda.py | 0 test/test.py | 0 11 files changed, 930 insertions(+) create mode 100644 LICENSE create mode 100644 cal/__init__.py create mode 100644 cal/cal.py create mode 100644 config.json create mode 100644 display/DEV_Config_32.so create mode 100644 display/DEV_Config_64.so create mode 100644 display/display.py create mode 100644 display/epd2in13_V4.py create mode 100644 display/epdconfig.py create mode 100644 raspagenda.py create mode 100644 test/test.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c0b54c5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 pepev-nrt + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cal/__init__.py b/cal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cal/cal.py b/cal/cal.py new file mode 100644 index 0000000..e69de29 diff --git a/config.json b/config.json new file mode 100644 index 0000000..dab61bb --- /dev/null +++ b/config.json @@ -0,0 +1,15 @@ +{ + "displayTZ": "Spain/Madrid", + "thresholdHours": 24, + "maxEventsPerDay": 3, + "isDisplayToScreen": false, + "isShutdownOnComplete": false, + "weekStartDay": 6, + "dayOfWeekText": ["L", "M", "X", "J", "V", "S", "D"], + "screenWidth": 250, + "screenHeight": 122, + "imageWidth": 250, + "imageHeight": 122, + "rotateAngle": 180, + "is24h": false, +} diff --git a/display/DEV_Config_32.so b/display/DEV_Config_32.so new file mode 100644 index 0000000000000000000000000000000000000000..76251cee135337c5b8027b1b07757469e760c5af GIT binary patch literal 12852 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 literal 0 HcmV?d00001 diff --git a/display/DEV_Config_64.so b/display/DEV_Config_64.so new file mode 100644 index 0000000000000000000000000000000000000000..c3886c01dc5c3895c2bed4d3f84d3679f496fd06 GIT binary patch literal 71208 zcmeHLeQ;dWb-%l7jPa*zjIG$lS}Sfa#*t-@6D(}-S|64LwxviiAC2i_rG1hXR=exn z6-b!Ig3=6urnQJ$+!j+0O(!M;6EewAdmur8J8_HuP=Qi+YLm604KpDby8(g%V*5My z-6P$7(k7WUf3!2_+E3@6-}$)b{_cJEy?y%|c5Dj<0*YHm{k@vdl!_Qb&%6mVqBg2a zVOgq{Xx~dXcnZ03sRy!DCOc)Dj_c5vXFoLNII*X1r3o^bi4P zEX{QE+!g{Sx*B)wR<9h~w&Ym*rk_8vw$Oj#>q{1Y_r#Bq6gMB&VqBEwQe5Fc;Enm= zOXtRBm)*NzVaF=6U5RTst_ob#R^Y0{^;ulR&+p;73fC%JSL0fZi`zBmRDWk=^v-$h zPkr;&J2pSQ`I(1LC&NeIzTvHh8XLZR;%wz-s?IdzAG*CY{cOeI&pxtuX6-ch{`=m1 zrt+^2hfh4X>ebuN{P26%f3WSI=f3jh*(JLVfBEj}WlLx5{SN1!N`};Bs(Q|3OGsUz zj_H|B*of!wggV~ah91q)-|hs#3;ZDT3%I7juPfodzXYBwf&a7w{(6aV;jY5C{Ow?g zJOd^0n@Zr*v0IW~r4|cttIQPA!Uy@|^Vf|B=Dp@Zn{epINGfZXh&1cm5Q8%5&Q1pD_3-AO7#A zo{ftri&EvLo*$#XK+RGg&C{^j;0d)GlJcMM<+gW6x?qWaJH>i?F( z^FEyTpgem_o{f}*n&Wi$rjkxJ7tQ1xM>$RHJDr%Dal7N$oSSLiS(iv9-S%il!u4!Z zTb$1RsM8fsMicRSUDepq)a-1JCSwR{YiZ(cLrcBW+Scy0H0@HG(w8xY0>!GX?0W?M%gZn7TwX-rJN+_vK7#4K(-V2+^aviRggSn}xZr zE#bOp)s;%SN!691a=JPbsjREgnRqhSrLwsU{wsuLb*vt&DH-=NQ8{gHGM225=Azop zND)AD8wXHnU&3{CjQLO^*`=0=GocDLx25W%1jjY12^)pUuvY&sw!MXDyuGd%0DFL;o_i@5QIa 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 R7j5dF>v_`edAs@J{~JRu%((yn literal 0 HcmV?d00001 diff --git a/display/display.py b/display/display.py new file mode 100644 index 0000000..7b0f17e --- /dev/null +++ b/display/display.py @@ -0,0 +1,42 @@ +#!/usr/bin python3 +# -*- coding: utf-8 -*- +""" +This part of the code exposes functions to interface with the eink display +""" + +import display.epd2in13_V4 as eink +from PIL import Image +import logging + + +class DisplayHelper: + + def __init__(self, width, height): + # Initialise the display + self.logger = logging.getLogger('raspagenda') + self.screenwidth = width + self.screenheight = height + self.epd = eink.EPD() + self.epd.Init() + + def update(self, img): + # Updates the display with the grayscale and red images + # start displaying on eink display + # self.epd.clear() + self.epd.display(mg) + self.logger.info('E-Ink display update complete.') + + def calibrate(self, cycles=1): + # Calibrates the display to prevent ghosting + white = Image.new('1', (self.screenwidth, self.screenheight), 'white') + black = Image.new('1', (self.screenwidth, self.screenheight), 'black') + for _ in range(cycles): + self.epd.display(black, white) + self.epd.display(white, black) + self.epd.display(white, white) + self.logger.info('E-Ink display calibration complete.') + + def sleep(self): + # send E-Ink display to deep sleep + self.epd.EPD_Sleep() + self.logger.info('E-Ink display entered deep sleep.') diff --git a/display/epd2in13_V4.py b/display/epd2in13_V4.py new file mode 100644 index 0000000..a9fd210 --- /dev/null +++ b/display/epd2in13_V4.py @@ -0,0 +1,350 @@ +# ***************************************************************************** +# * | File : epd2in13_V4.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2023-06-25 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + + +import logging +from . import epdconfig + +# Display resolution +EPD_WIDTH = 122 +EPD_HEIGHT = 250 + +logger = logging.getLogger(__name__) + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + ''' + function :Hardware reset + parameter: + ''' + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(20) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(2) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(20) + + ''' + function :send command + parameter: + command : Command register + ''' + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + ''' + function :send data + parameter: + data : Write data + ''' + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + # send a lot of data + def send_data2(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte2(data) + epdconfig.digital_write(self.cs_pin, 1) + + ''' + function :Wait until the busy_pin goes LOW + parameter: + ''' + def ReadBusy(self): + logger.debug("e-Paper busy") + while(epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy + epdconfig.delay_ms(10) + logger.debug("e-Paper busy release") + + ''' + function : Turn On Display + parameter: + ''' + def TurnOnDisplay(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xf7) + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Turn On Display Fast + parameter: + ''' + def TurnOnDisplay_Fast(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xC7) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Turn On Display Part + parameter: + ''' + def TurnOnDisplayPart(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xff) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + + ''' + function : Setting the display window + parameter: + xstart : X-axis starting position + ystart : Y-axis starting position + xend : End position of X-axis + yend : End position of Y-axis + ''' + def SetWindow(self, x_start, y_start, x_end, y_end): + self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION + # x point must be the multiple of 8 or the last 3 bits will be ignored + self.send_data((x_start>>3) & 0xFF) + self.send_data((x_end>>3) & 0xFF) + + self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION + self.send_data(y_start & 0xFF) + self.send_data((y_start >> 8) & 0xFF) + self.send_data(y_end & 0xFF) + self.send_data((y_end >> 8) & 0xFF) + + ''' + function : Set Cursor + parameter: + x : X-axis starting position + y : Y-axis starting position + ''' + def SetCursor(self, x, y): + self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER + # x point must be the multiple of 8 or the last 3 bits will be ignored + self.send_data(x & 0xFF) + + self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER + self.send_data(y & 0xFF) + self.send_data((y >> 8) & 0xFF) + + ''' + function : Initialize the e-Paper register + parameter: + ''' + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.ReadBusy() + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x01) #Driver output control + self.send_data(0xf9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) #data entry mode + self.send_data(0x03) + + self.SetWindow(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x3c) + self.send_data(0x05) + + self.send_command(0x21) # Display update control + self.send_data(0x00) + self.send_data(0x80) + + self.send_command(0x18) + self.send_data(0x80) + + self.ReadBusy() + + return 0 + + ''' + function : Initialize the e-Paper fast register + parameter: + ''' + def init_fast(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x18) # Read built-in temperature sensor + self.send_command(0x80) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.SetWindow(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x22) # Load temperature value + self.send_data(0xB1) + self.send_command(0x20) + self.ReadBusy() + + self.send_command(0x1A) # Write to temperature register + self.send_data(0x64) + self.send_data(0x00) + + self.send_command(0x22) # Load temperature value + self.send_data(0x91) + self.send_command(0x20) + self.ReadBusy() + + return 0 + ''' + function : Display images + parameter: + image : Image data + ''' + def getbuffer(self, image): + img = image + imwidth, imheight = img.size + if(imwidth == self.width and imheight == self.height): + img = img.convert('1') + elif(imwidth == self.height and imheight == self.width): + # image has correct dimensions, but needs to be rotated + img = img.rotate(90, expand=True).convert('1') + else: + logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height)) + # return a blank buffer + return [0x00] * (int(self.width/8) * self.height) + + buf = bytearray(img.tobytes('raw')) + return buf + + ''' + function : Sends the image buffer in RAM to e-Paper and displays + parameter: + image : Image data + ''' + def display(self, image): + self.send_command(0x24) + self.send_data2(image) + self.TurnOnDisplay() + + ''' + function : Sends the image buffer in RAM to e-Paper and fast displays + parameter: + image : Image data + ''' + def display_fast(self, image): + self.send_command(0x24) + self.send_data2(image) + self.TurnOnDisplay_Fast() + ''' + function : Sends the image buffer in RAM to e-Paper and partial refresh + parameter: + image : Image data + ''' + def displayPartial(self, image): + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(1) + epdconfig.digital_write(self.reset_pin, 1) + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x80) + + self.send_command(0x01) # Driver output control + self.send_data(0xF9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.SetWindow(0, 0, self.width - 1, self.height - 1) + self.SetCursor(0, 0) + + self.send_command(0x24) # WRITE_RAM + self.send_data2(image) + self.TurnOnDisplayPart() + + ''' + function : Refresh a base image + parameter: + image : Image data + ''' + def displayPartBaseImage(self, image): + self.send_command(0x24) + self.send_data2(image) + + self.send_command(0x26) + self.send_data2(image) + self.TurnOnDisplay() + + ''' + function : Clear screen + parameter: + ''' + def Clear(self, color=0xFF): + if self.width%8 == 0: + linewidth = int(self.width/8) + else: + linewidth = int(self.width/8) + 1 + # logger.debug(linewidth) + + self.send_command(0x24) + self.send_data2([color] * int(self.height * linewidth)) + self.TurnOnDisplay() + + ''' + function : Enter sleep mode + parameter: + ''' + def sleep(self): + self.send_command(0x10) #enter deep sleep + self.send_data(0x01) + + epdconfig.delay_ms(2000) + epdconfig.module_exit() + +### END OF FILE ### + diff --git a/display/epdconfig.py b/display/epdconfig.py new file mode 100644 index 0000000..b390252 --- /dev/null +++ b/display/epdconfig.py @@ -0,0 +1,322 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.2 +# * | Date : 2022-10-29 +# * | Info : +# ****************************************************************************** +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import os +import logging +import sys +import time +import subprocess + +from ctypes import * + +logger = logging.getLogger(__name__) + + +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + MOSI_PIN = 10 + SCLK_PIN = 11 + + def __init__(self): + import spidev + import gpiozero + + self.SPI = spidev.SpiDev() + self.GPIO_RST_PIN = gpiozero.LED(self.RST_PIN) + self.GPIO_DC_PIN = gpiozero.LED(self.DC_PIN) + # self.GPIO_CS_PIN = gpiozero.LED(self.CS_PIN) + self.GPIO_PWR_PIN = gpiozero.LED(self.PWR_PIN) + self.GPIO_BUSY_PIN = gpiozero.Button(self.BUSY_PIN, pull_up = False) + + + + def digital_write(self, pin, value): + if pin == self.RST_PIN: + if value: + self.GPIO_RST_PIN.on() + else: + self.GPIO_RST_PIN.off() + elif pin == self.DC_PIN: + if value: + self.GPIO_DC_PIN.on() + else: + self.GPIO_DC_PIN.off() + # elif pin == self.CS_PIN: + # if value: + # self.GPIO_CS_PIN.on() + # else: + # self.GPIO_CS_PIN.off() + elif pin == self.PWR_PIN: + if value: + self.GPIO_PWR_PIN.on() + else: + self.GPIO_PWR_PIN.off() + + def digital_read(self, pin): + if pin == self.BUSY_PIN: + return self.GPIO_BUSY_PIN.value + elif pin == self.RST_PIN: + return self.RST_PIN.value + elif pin == self.DC_PIN: + return self.DC_PIN.value + # elif pin == self.CS_PIN: + # return self.CS_PIN.value + elif pin == self.PWR_PIN: + return self.PWR_PIN.value + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.writebytes(data) + + def spi_writebyte2(self, data): + self.SPI.writebytes2(data) + + def DEV_SPI_write(self, data): + self.DEV_SPI.DEV_SPI_SendData(data) + + def DEV_SPI_nwrite(self, data): + self.DEV_SPI.DEV_SPI_SendnData(data) + + def DEV_SPI_read(self): + return self.DEV_SPI.DEV_SPI_ReadData() + + def module_init(self, cleanup=False): + self.GPIO_PWR_PIN.on() + + if cleanup: + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.DEV_SPI = None + for find_dir in find_dirs: + val = int(os.popen('getconf LONG_BIT').read()) + logging.debug("System is %d bit"%val) + if val == 64: + so_filename = os.path.join(find_dir, 'DEV_Config_64.so') + else: + so_filename = os.path.join(find_dir, 'DEV_Config_32.so') + if os.path.exists(so_filename): + self.DEV_SPI = CDLL(so_filename) + break + if self.DEV_SPI is None: + RuntimeError('Cannot find DEV_Config.so') + + self.DEV_SPI.DEV_Module_Init() + + else: + # SPI device, bus = 0, device = 0 + self.SPI.open(0, 0) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + + def module_exit(self, cleanup=False): + logger.debug("spi end") + self.SPI.close() + + self.GPIO_RST_PIN.off() + self.GPIO_DC_PIN.off() + self.GPIO_PWR_PIN.off() + logger.debug("close 5V, Module enters 0 power consumption ...") + + if cleanup: + self.GPIO_RST_PIN.close() + self.GPIO_DC_PIN.close() + # self.GPIO_CS_PIN.close() + self.GPIO_PWR_PIN.close() + self.GPIO_BUSY_PIN.close() + + + + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + + def __init__(self): + import ctypes + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.SPI = None + for find_dir in find_dirs: + so_filename = os.path.join(find_dir, 'sysfs_software_spi.so') + if os.path.exists(so_filename): + self.SPI = ctypes.cdll.LoadLibrary(so_filename) + break + if self.SPI is None: + raise RuntimeError('Cannot find sysfs_software_spi.so') + + import Jetson.GPIO + self.GPIO = Jetson.GPIO + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.SYSFS_software_spi_transfer(data[0]) + + def spi_writebyte2(self, data): + for i in range(len(data)): + self.SPI.SYSFS_software_spi_transfer(data[i]) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + self.SPI.SYSFS_software_spi_begin() + return 0 + + def module_exit(self): + logger.debug("spi end") + self.SPI.SYSFS_software_spi_end() + + logger.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + self.GPIO.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN]) + + +class SunriseX3: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + Flag = 0 + + def __init__(self): + import spidev + import Hobot.GPIO + + self.GPIO = Hobot.GPIO + self.SPI = spidev.SpiDev() + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(pin) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.writebytes(data) + + def spi_writebyte2(self, data): + # for i in range(len(data)): + # self.SPI.writebytes([data[i]]) + self.SPI.xfer3(data) + + def module_init(self): + if self.Flag == 0: + self.Flag = 1 + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + + self.GPIO.output(self.PWR_PIN, 1) + + # SPI device, bus = 0, device = 0 + self.SPI.open(2, 0) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + else: + return 0 + + def module_exit(self): + logger.debug("spi end") + self.SPI.close() + + logger.debug("close 5V, Module enters 0 power consumption ...") + self.Flag = 0 + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + self.GPIO.output(self.PWR_PIN, 0) + + self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN) + + +if sys.version_info[0] == 2: + process = subprocess.Popen("cat /proc/cpuinfo | grep Raspberry", shell=True, stdout=subprocess.PIPE) +else: + process = subprocess.Popen("cat /proc/cpuinfo | grep Raspberry", shell=True, stdout=subprocess.PIPE, text=True) +output, _ = process.communicate() +if sys.version_info[0] == 2: + output = output.decode(sys.stdout.encoding) + +if "Raspberry" in output: + implementation = RaspberryPi() +elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'): + implementation = SunriseX3() +else: + implementation = JetsonNano() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) + +### END OF FILE ### diff --git a/raspagenda.py b/raspagenda.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..e69de29