diff --git a/.gitignore b/.gitignore index 331afb8..880d7e8 100644 --- a/.gitignore +++ b/.gitignore @@ -255,4 +255,5 @@ $RECYCLE.BIN/ *.lnk -# End of https://www.gitignore.io/api/node,macos,python,windows \ No newline at end of file +# End of https://www.gitignore.io/api/node,macos,python,windows +black.png diff --git a/black.png b/black.png deleted file mode 100644 index 6364077..0000000 Binary files a/black.png and /dev/null differ diff --git a/config.js b/config.js index 4823078..f1ab52f 100644 --- a/config.js +++ b/config.js @@ -1,12 +1,14 @@ var config = { "magicmirror_port": 8080, // magic mirror port - "display_width": 640, // eink display height in px - "display_height": 384, // eink display width in px + "display_width": 400, // eink display height in px + "display_height": 300, // eink display width in px + "display_type": "epd4in2b", // which eink display, currently supported: epd4in2b, epd7in5 + "display_invert": true, "wait_to_load": 18, // wait seconds to load the site and display all data - "refresh_interval": "0 */10 * * * *", // update eink every 10 minutes + "refresh_interval": "0 */1 * * * *", // update eink every 10 minutes // https://github.com/kelektiv/node-cron#cron-ranges "timezone": "Europe / Berlin" // https://www.inmotionhosting.com/support/website/general-server-setup/tz-ref-table }; /*************** DO NOT EDIT THE LINE BELOW ***************/ -if (typeof module !== "undefined") {module.exports = config;} \ No newline at end of file +if (typeof module !== "undefined") {module.exports = config;} diff --git a/ePaperPython/epd4in2b.py b/ePaperPython/epd4in2b.py new file mode 100644 index 0000000..550b3fc --- /dev/null +++ b/ePaperPython/epd4in2b.py @@ -0,0 +1,169 @@ +## +# @filename : epd4in2b.py +# @brief : Implements for Dual-color e-paper library +# @author : Yehui from Waveshare +# +# Copyright (C) Waveshare August 15 2017 +# +# 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 RPi.GPIO as GPIO + +import epdif + +EPD_WIDTH = 400 +EPD_HEIGHT = 300 + +# EPD4IN2B commands +PANEL_SETTING = 0x00 +POWER_SETTING = 0x01 +POWER_OFF = 0x02 +POWER_OFF_SEQUENCE_SETTING = 0x03 +POWER_ON = 0x04 +POWER_ON_MEASURE = 0x05 +BOOSTER_SOFT_START = 0x06 +DEEP_SLEEP = 0x07 +DATA_START_TRANSMISSION_1 = 0x10 +DATA_STOP = 0x11 +DISPLAY_REFRESH = 0x12 +DATA_START_TRANSMISSION_2 = 0x13 +VCOM_LUT = 0x20 +W2W_LUT = 0x21 +B2W_LUT = 0x22 +W2B_LUT = 0x23 +B2B_LUT = 0x24 +PLL_CONTROL = 0x30 +TEMPERATURE_SENSOR_CALIBRATION = 0x40 +TEMPERATURE_SENSOR_SELECTION = 0x41 +TEMPERATURE_SENSOR_WRITE = 0x42 +TEMPERATURE_SENSOR_READ = 0x43 +VCOM_AND_DATA_INTERVAL_SETTING = 0x50 +LOW_POWER_DETECTION = 0x51 +TCON_SETTING = 0x60 +RESOLUTION_SETTING = 0x61 +GSST_SETTING = 0x65 +GET_STATUS = 0x71 +AUTO_MEASURE_VCOM = 0x80 +VCOM_VALUE = 0x81 +VCM_DC_SETTING = 0x82 +PARTIAL_WINDOW = 0x90 +PARTIAL_IN = 0x91 +PARTIAL_OUT = 0x92 +PROGRAM_MODE = 0xA0 +ACTIVE_PROGRAM = 0xA1 +READ_OTP_DATA = 0xA2 +POWER_SAVING = 0xE3 + + +class EPD: + def __init__(self): + self.reset_pin = epdif.RST_PIN + self.dc_pin = epdif.DC_PIN + self.busy_pin = epdif.BUSY_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + def digital_write(self, pin, value): + epdif.epd_digital_write(pin, value) + + def digital_read(self, pin): + return epdif.epd_digital_read(pin) + + def delay_ms(self, delaytime): + epdif.epd_delay_ms(delaytime) + + def send_command(self, command): + self.digital_write(self.dc_pin, GPIO.LOW) + # the parameter type is list but not int + # so use [command] instead of command + epdif.spi_transfer([command]) + + def send_data(self, data): + self.digital_write(self.dc_pin, GPIO.HIGH) + # the parameter type is list but not int + # so use [data] instead of data + epdif.spi_transfer([data]) + + def init(self): + if (epdif.epd_init() != 0): + return -1 + self.reset() + self.send_command(BOOSTER_SOFT_START) + self.send_data(0x17) + self.send_data(0x17) + self.send_data(0x17) # 07 0f 17 1f 27 2F 37 2f + self.send_command(POWER_ON) + self.wait_until_idle() + self.send_command(PANEL_SETTING) + self.send_data(0x0F) # LUT from OTP + + def wait_until_idle(self): + while (self.digital_read(self.busy_pin) == 0): # 0: busy, 1: idle + self.delay_ms(100) + + def reset(self): + self.digital_write(self.reset_pin, GPIO.LOW) # module reset + self.delay_ms(200) + self.digital_write(self.reset_pin, GPIO.HIGH) + self.delay_ms(200) + + def get_frame_buffer(self, image): + buf = [0xFF] * int(self.width * self.height / 8) + # Set buffer to value of Python Imaging Library image. + # Image must be in mode 1. + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + if imwidth != self.width or imheight != self.height: + raise ValueError('Image must be same dimensions as display. Expected:' + '({0}x{1}); actual: ({2}x{3}).'.format(self.width, self.height, imwidth, imheight)) + + pixels = image_monocolor.load() + for y in range(self.height): + for x in range(self.width): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + return buf + + def display_frame(self, frame_buffer_black, frame_buffer_red = None): + if frame_buffer_black is not None: + self.send_command(DATA_START_TRANSMISSION_1) + self.delay_ms(2) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(frame_buffer_black[i]) + self.delay_ms(2) + if frame_buffer_red is not None: + self.send_command(DATA_START_TRANSMISSION_2) + self.delay_ms(2) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(frame_buffer_red[i]) + self.delay_ms(2) + + self.send_command(DISPLAY_REFRESH) + self.wait_until_idle() + + # after this, call epd.init() to awaken the module + def sleep(self): + self.send_command(VCOM_AND_DATA_INTERVAL_SETTING) + self.send_data(0xF7) # border floating + self.send_command(POWER_OFF) + self.wait_until_idle() + self.send_command(DEEP_SLEEP) + self.send_data(0xA5) # check code diff --git a/ePaperPython/main.py b/ePaperPython/main.py index 9eff15c..f403b5b 100644 --- a/ePaperPython/main.py +++ b/ePaperPython/main.py @@ -1,59 +1,45 @@ -## - # @filename : main.cpp - # @brief : 7.5inch e-paper display demo - # @author : Yehui from Waveshare - # - # Copyright (C) Waveshare July 28 2017 - # - # 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 argparse import epd7in5 -import Image -import ImageDraw -import ImageFont -#import imagedata +import epd4in2b -EPD_WIDTH = 640 -EPD_HEIGHT = 384 +from PIL import Image +import PIL.ImageOps + + +def invert(image): + if image.mode == 'RGBA': + r,g,b,a = image.split() + rgb_image = Image.merge('RGB', (r,g,b)) + inverted_image = PIL.ImageOps.invert(rgb_image) + r2,g2,b2 = inverted_image.split() + return Image.merge('RGBA', (r2,g2,b2,a)) + else: + return PIL.ImageOps.invert(image) def main(): - epd = epd7in5.EPD() + parser = argparse.ArgumentParser() + parser.add_argument("screen") + parser.add_argument("--invert", help="invert screen", action="store_true") + args = parser.parse_args() + if args.screen == "epd7in5": + imagecount = 1 + epd = epd7in5.EPD() + elif args.screen == "epd4in2b": + imagecount = 2 + epd = epd4in2b.EPD() + else: + raise Exception("Unsupported screen, please submit a code patch") epd.init() - # For simplicity, the arguments are explicit numerical coordinates - # image = Image.new('1', (EPD_WIDTH, EPD_HEIGHT), 1) # 1: clear the frame - # draw = ImageDraw.Draw(image) - # font = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf', 24) - # draw.rectangle((0, 6, 640, 30), fill = 0) - # draw.text((200, 10), 'e-Paper demo', font = font, fill = 255) - # draw.rectangle((200, 80, 600, 280), fill = 0) - # draw.arc((240, 120, 580, 220), 0, 360, fill = 255) - # draw.rectangle((0, 80, 160, 280), fill = 255) - # draw.arc((40, 80, 180, 220), 0, 360, fill = 0) - # epd.display_frame(epd.get_frame_buffer(image)) - image = Image.open('black.png') - epd.display_frame(epd.get_frame_buffer(image)) + if args.invert: + image = invert(image) - # You can get frame buffer from an image or import the buffer directly: - #epd.display_frame(imagedata.MONOCOLOR_BITMAP) + if imagecount == 1: + epd.display_frame(epd.get_frame_buffer(image)) + elif imagecount == 2: + epd.display_frame(epd.get_frame_buffer(image), epd.get_frame_buffer(image)) if __name__ == '__main__': main() diff --git a/index.js b/index.js index bd4de78..c431c07 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,11 @@ const job = new CronJob({ await browser.close(); - const childPython = spawn('python', ['./ePaperPython/main.py']); + pythonArgs = ['./ePaperPython/main.py', global.gConfig.display_type] + if (global.gConfig.display_invert) { + pythonArgs.push("--invert") + } + const childPython = spawn('python', pythonArgs); childPython.stdout.on('data', (data) => { debugChildP(data.toString()); diff --git a/white.png b/white.png deleted file mode 100644 index 1fe17fc..0000000 Binary files a/white.png and /dev/null differ