将像素写入电子纸显示屏

时间:2018-09-11 18:54:00

标签: php esp32 e-ink

我正在使用ESP32开发板和7.5英寸电子纸显示屏。我目前正在运行将黑白图像转换为字节字符串的PHP服务器。然后,我的ESP应该从服务器获取字符串并将像素绘制到显示器上。

图像转换代码:

    <?php
//To activate productionMode (display entering deep sleep), set http-header X-productionMode: true
#header("X-productionMode: true");
//To stop productionMode (no deep sleep, web config), set http-header X-productionMode: false
#header("X-productionMode: false");
// Set the sleep interval for the doorsigns via the server
#header("X-sleepInterval: 60 ");
error_reporting('E_ERROR');
# Supported displays:
# 1.54 inches: https://www.waveshare.com/wiki/1.54inch_e-Paper_Module
# 2.9 inches: https://www.waveshare.com/wiki/2.9inch_e-Paper_Module
# 4.2 inches: https://www.waveshare.com/wiki/4.2inch_e-Paper_Module
# 7.5 inches: https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT
const DISPLAYS = array( "7.5"=>array("size"=>"640x384","rotate"=>"false"),
                        "7.5bwr"=>array("size"=>"640x384","rotate"=>"false", "red"=>"true"),
                        "4.2"=>array("size"=>"400x300","rotate"=>"false"),
                        "4.2bwr"=>array("size"=>"400x300","rotate"=>"false", "red"=>"true"),
                        "2.9"=>array("size"=>"296x128","rotate"=>"true"),
                        "1.5"=>array("size"=>"200x200","rotate"=>"true")
                        );
// Use Googles Noto fonts as the default font face
$DEFAULT_FONT = array(
    "regular"=>realpath("./fonts/noto/NotoSans-Regular.ttf"),
    "bold"=>realpath("./fonts/noto/NotoSans-Bold.ttf"),
    "italic"=>realpath("./fonts/noto/NotoSans-Italic.ttf"),
    "bolditalic"=>realpath("./fonts/noto/NotoSans-BoldItalic.ttf"),
    "symbols"=>realpath("./fonts/noto/NotoSansSymbols-Regular.ttf"),
    "emoji"=>realpath("./fonts/noto/NotoEmoji-Regular.ttf"),
    "weathericons"=>realpath("./fonts/weathericons-regular-webfont.ttf")
    );
// To use LiberationSans font, uncomment the following lines
/*
$DEFAULT_FONT = array(
    "regular"=>realpath("./fonts/LiberationSans-Regular.ttf"),
    "bold"=>realpath("./fonts/LiberationSans-Bold.ttf"),
    "italic"=>realpath("./fonts/LiberationSans-Italic.ttf"),
    "weathericons"=>realpath("./fonts/weathericons-regular-webfont.ttf")
    );
*/
const THRESHOLDS = array("black" => 150, "red" => 240);
if (!extension_loaded('gd')) {
    echo "GD library is not installed. Please install GD on your server (http://php.net/manual/de/image.installation.php)";
    exit;
}
//Function to check if FreeType is installed. Not needed by static_image
function checkFreeType(){
    $gdInfo = gd_info();
    if($gdInfo['FreeType Support'] != 1){
        echo "FreeType is not enabled. FreeType is needed for creating text in images(http://php.net/manual/de/function.imagettftext.php)";
        exit;
    }
}
if(strlen($_GET['scale']) AND is_numeric($_GET['scale'])){
    $scale = $_GET['scale'];
}else{
    $scale = $_GET['scale'] = 32;
}
$displayType = $_GET['display'];
if(!isset(DISPLAYS[$displayType])){
    echo ("Not a valid display size. <br />");
    echo ("display=[");
    foreach (array_keys(DISPLAYS) as $display_key){
        echo ($display_key.", ");
    }
    echo ("]");
    exit;
}
//Read existing contents
$contents = scandir('contents');
if(!count($contents)){
     echo "No content definitions";
     exit;
}
foreach ($contents as $content) {
    $contentFile = pathinfo("contents/".$content);
    if($contentFile['extension'] == "php"){
    $allContents[$contentFile['filename']] = "contents/".$content;
    }
}
$selectedContent = $allContents[$_GET['content']];
$displayWidth = explode("x",DISPLAYS[$displayType]['size'])[0];
$displayHeight = explode("x",DISPLAYS[$displayType]['size'])[1];
$im = imagecreate($displayWidth, $displayHeight);
$background_color = ImageColorAllocate ($im, 255, 255, 255);
$black = ImageColorAllocate($im, 0, 0, 0);
$red = ImageColorAllocate($im, 0xFF, 0x00, 0x00);
if(is_file($selectedContent)){
    include($selectedContent);
}else{
    echo "Not a valid content.";
    imagedestroy($im);
    exit;
}
if($_GET['debug'] == 'true'){
    header("Content-type: image/png");
    imagepng($im);
}
else{
    if(DISPLAYS[$displayType]['rotate'] == "true"){
        $im = imagerotate($im, 90, 0);
    }
    $im = imagerotate($im, 0, 0);
    //if you are using an older version of GD library you have to rotate the image 360°. Otherwise you get a white image due to a bug in GD library. Uncomment next lines:
    //$im = imagerotate($im, 180, 0);
    //$im = imagerotate($im, 180, 0);
    echo rawImage($im, DISPLAYS[$displayType]['red'] );
}
imagedestroy($im);
function rawImage($im, $hasRed) {
    $bits = "";
    $bytes = "";
    $pixelcount = 0;
    for ($y = 0; $y < imagesy($im); $y++) {
        for ($x = 0; $x < imagesx($im); $x++) {
            $rgb = imagecolorat($im, $x, $y);
            $r = ($rgb >> 16) & 0xFF;
            $g = ($rgb >> 8 ) & 0xFF;
            $b = $rgb & 0xFF;
            $gray = ($r + $g + $b) / 3;
            if($hasRed == "true"){
                if(($r >= THRESHOLDS['red']) && ($g < 50) && ($b <50)) {
                    $bits .= "01";
                } else {
                    if ($gray < THRESHOLDS['black']) {
                        $bits .= "11";
                    }else {
                        $bits .= "00";
                    }
                }
            $pixelcount = $pixelcount+2;
            }else{
                  if ($gray < THRESHOLDS['black']) {
                $bits .= "1";
            }else {
                $bits .= "0";
            }
                $pixelcount++;
            }
            if ($pixelcount % 8 == 0) {
                $bytes .= pack('H*', str_pad(base_convert($bits, 2, 16),2, "0", STR_PAD_LEFT));
                $bits = "";
            }
        }
    }
    $size = strlen($bytes);
    header("Content-length: $size");
    return $bytes;
}
?>

以及用于绘制图像的代码:

#include <AsyncTCP.h>

#define DEBUG 1

#include <Basecamp.hpp>

// Define your display type here: 2.9, 4.2 (bw and bwr) or 7.5 (bw or bwr) inches are supported:
// Default: 4.2bwr
#define DISPLAY_TYPE '7.5'

// Default 5
#define CHIP_SELECT 5

// Default -1: disabled
#define STATUS_PIN -1

#define FactorSeconds 1000000LL
#define BASECAMP_NOMQTT

// This is the upper limit for the sleep time set by the server to prevent accidentally letting the display sleep for several days
#define MAX_SLEEP_TIME (60*60*24)

// Encrypted setup WiFi network
//Basecamp iot{Basecamp::SetupModeWifiEncryption::secured};

// Unencrypted setup WiFi network (default)
Basecamp iot;
AsyncClient client;

volatile bool tcpClientConnected = false;             //* We are currently receiving data
volatile bool tcpClientConnectionInProgress = false;  //* We are establishing a connection
volatile bool requestDoneInPeriod = false;            //* We already received data in this period

bool connection = false;                              //* WiFi connection established
bool production = false;                              //* We are in production mode and will go to deep sleep
bool setupMode  = false;                              //* Setup mode: The web interface has to be accessible. Not going to deep sleep

String sleepIntervalHeader = "X-sleepInterval:";      //* Name of the header for the sleep interval
long   sleepIntervalSetbyHeader = 0;                  //* Changed if the sleep interval is set by the server via the header

#include <GxEPD.h>


#if DISPLAY_TYPE == '7.5'
#include <GxGDEW075T8/GxGDEW075T8.cpp>      // 7.5" b/w
bool hasRed = false;
String displayType = "7.5";
#endif

#include <GxIO/GxIO_SPI/GxIO_SPI.cpp>
#include <GxIO/GxIO.cpp>
#include <Fonts/FreeMonoBold9pt7b.h>

GxIO_Class io(SPI, CHIP_SELECT, 17, 16);
GxEPD_Class display(io, 16, 4);

void setup() {
  iot.begin();
  display.init();

  if (STATUS_PIN >= 0){
    pinMode(STATUS_PIN, OUTPUT);
  }

  const GFXfont* f = &FreeMonoBold9pt7b;
  display.setTextColor(GxEPD_BLACK);
  iot.web.addInterfaceElement("ImageHost", "input", "Server to load image from (host name or IP address):", "#configform", "ImageHost");
  iot.web.addInterfaceElement("ImageAddress", "input", "Address to load image from (path on server, starting with / e.g.: /index.php/?debug=false&[...] ):", "#configform", "ImageAddress");
  iot.web.addInterfaceElement("ImageWait", "input", "Sleep time (to next update) in seconds:", "#configform", "ImageWait");
  iot.web.addInterfaceElement("ProductionMode", "input", "Production mode  (if set to 'true', deep sleep will be activated, this config page will be down.)", "#configform", "ProductionMode");


  if (iot.configuration.get("ProductionMode") != "true" ) {

    if (iot.configuration.get("ImageWait").toInt() < 10) {
      iot.configuration.set("ImageWait", "60");
    }
    if (iot.configuration.get("ProductionMode").length() != 0 ) {
      iot.configuration.set("ProductionMode", "false");
    }

    if (iot.configuration.get("WifiConfigured") != "True") {
      setupMode = true;
      display.fillScreen(GxEPD_WHITE);
      display.setRotation(1);
      display.setFont(f);
      display.setCursor(0, 0);
      display.println();
      display.println("Wifi not configured!");
      display.println("Connect to hotspot 'ESP32' with the secret '" + iot.configuration.get("APSecret") + "' and open 192.168.4.1");
      display.update();
    } else {

      int retry = 0;
      while ((WiFi.status() != WL_CONNECTED) && (retry < 20)) {
        retry++;
        delay(500);
      }
      if (retry == 20 )
      {
        connection = false;
        display.fillScreen(GxEPD_WHITE);
        display.setRotation(1);
        display.setFont(f);
        display.setCursor(0, 0);
        display.println();
        display.println("");
        display.println("Could not connect to " + iot.configuration.get("WifiEssid") );
        display.update();


      } else {
        connection = true;
        if (iot.configuration.get("ImageHost").length() < 1 || iot.configuration.get("ImageAddress").length() < 1 ) {
          display.fillScreen(GxEPD_WHITE);
          display.setRotation(1);
          display.setFont(f);
          display.setCursor(0, 0);
          display.println();
          display.println("");
          display.println("Image server not configured.");
          display.println("Open " + WiFi.localIP().toString() + " in your browser and set server address and path.");
          display.update();
          connection = false;
        }

      }
    }
  } else {
    production = true;
    int retry = 0;
    while ((WiFi.status() != WL_CONNECTED) && (retry < 20)) {
      Serial.println(".");
      retry++;
      delay(500);
    }
    if (retry < 20 ) {
      connection = true;
    }

  }

  // Create client
  client.setRxTimeout(10);            // 5 sec timeout
  client.onConnect(&onConnectHandler);
  client.onData(&onDataHandler);
  client.onDisconnect(&onDisconnectHandler);
  client.onTimeout(&onTimeoutHandler);
  client.onError(&onErrorHandler);
}


/** Draw the pixels to the screen
 *  
 *  @param  char *data    A char array
 *  @param  size_t len    Length of the char array
 *  @param  boolean start True if the begin of a new screen
 * 
 */
void drawPixels(char *data, size_t len, boolean start){
  static int x;
  static int y;
  if (start){
    x = 0;
    y = 0;
    // Not required
    //display.eraseDisplay(true);
  }

  Serial.println(String("Printing ") + len + " Bytes to the screen");
  for (size_t i=0; i<len; i++){
      for (int b = 7; b >= 0; b--) {
        int bit = bitRead(data[i], b);
        if (bit == 1) {
          display.drawPixel(x, y, GxEPD_BLACK);
        } else {
          display.drawPixel(x, y, GxEPD_WHITE);
        }
        x++;
        if  (x == GxEPD_WIDTH) {
          y++;
          x = 0;
        }
     }
  }
}

/**
 *  Handler called after connection is established
 */
void onConnectHandler(void *r, AsyncClient *client){
  Serial.println("OnConnect\n");
//    Serial.println(byte);
  tcpClientConnected = true;
  tcpClientConnectionInProgress = false;
  if (STATUS_PIN >= 0){
    digitalWrite(5, HIGH);
  }
  String url =  iot.configuration.get("ImageAddress") + "&display=" + displayType;
  String query = String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + iot.configuration.get("ImageHost") + "\r\n" +
                 "Connection: close\r\n\r\n";

  client->write(query.c_str());
}

/**
 * Handler called for each received data packet
 */
void onDataHandler(void *r, AsyncClient *client, void *data, size_t len){ 
  Serial.println(String("OnData: ") + len + " Bytes");
  int16_t endHeader = findEndHeader((char*) data, len);

  if (endHeader > 0){
    // We got the header
    char subbuf[endHeader + 1];
    memcpy(subbuf, data, endHeader);
    subbuf[endHeader] = 0;
    String header = String(subbuf);
    Serial.println(header);

    // handle header data
    if (header.indexOf("X-productionMode: false") > 0) {
      iot.configuration.set("ProductionMode", "false");
      production = false;
    }
    if (header.indexOf("X-productionMode: true") > 0) {
      iot.configuration.set("ProductionMode", "true");
      production = true;
    }
    Serial.println(String("ProductionMode: ") + production);

    // Let the user set the sleep interval via a HTTP-header
    if (header.indexOf(sleepIntervalHeader) > 0){
      int sleepStartIndex = header.indexOf(sleepIntervalHeader) + sleepIntervalHeader.length();
      int sleepStopIndex = header.indexOf("\r\n", sleepStartIndex);
      if (sleepStopIndex < 0){
        // End of header, use length as delimiter
        sleepStopIndex = endHeader;
      }

      // toInt() sets sleepIntervalSetbyHeader to zero in case of errors during the conversion
      sleepIntervalSetbyHeader = header.substring(sleepStartIndex, sleepStopIndex).toInt();
      if (sleepIntervalSetbyHeader > MAX_SLEEP_TIME){
        Serial.println("Too long sleep time. Limiting...");
        sleepIntervalSetbyHeader = MAX_SLEEP_TIME;
      }

    }

    // Handle remaining data bytes. 4 bytes for \r\n\r\n separation
    drawPixels((char*)data+endHeader+4, len-endHeader-4, true);
  } else {
    // No header -> directly draw to display
    drawPixels((char*)data, len, false);
  }
}

/**
 * Handler called in case of a timeout
 */
void onTimeoutHandler(void *r, AsyncClient *client, uint32_t timeout){
  Serial.println(String("Timeout ") + timeout);
  transmitDone();
}

/**
 * Handler called after a disconnect event
 */
void onDisconnectHandler(void *r, AsyncClient *client){
  Serial.println("OnDisconnect");
  display.update();
  transmitDone();
}

/**
 * Handler for the error cases
 */
void onErrorHandler(void *r, AsyncClient *client, int8_t error){
  Serial.println(String("Error:") + error);
  transmitDone();
}

/** Find the end to the HTTP header marked by "\r\n\r\n"
 *  
 *  @param char *buf    A char array
 *  @param  size_t len  Length of the char array
 *  @return The position of \r\n\r\n
 */
int16_t findEndHeader(char *buf, size_t len){
  const char *endString = "\r\n\r\n";
  for (int16_t i=0; i<len-4; i++){
    if (
      buf[i] == endString[0] &&
      buf[i+1] == endString[1] &&
      buf[i+2] == endString[2] &&
      buf[i+3] == endString[3]
      ) {
        return i;
      }
  }
  return -1;
}

/**
 * Called after the transmission is finished (error, connection closed, timeout)
 */
void transmitDone(){
  Serial.println("transmitDone");
  if (STATUS_PIN >= 0){
    digitalWrite(5, LOW);
  }
  tcpClientConnected = false;
  tcpClientConnectionInProgress = false;
  requestDoneInPeriod = true;
}


/**
 * The main loop
 */
void loop() {

  if (
    WiFi.status() == WL_CONNECTED &&
    !tcpClientConnected &&
    !tcpClientConnectionInProgress &&
    !requestDoneInPeriod
    ) {
      const int httpPort = 80;
      const char* host = iot.configuration.get("ImageHost").c_str();

      if (!client.connect(host, httpPort)) {
        Serial.println("connection failed");
        return;
      } else {
        Serial.println("Wait till the client is connected");
        tcpClientConnectionInProgress = true;
      }    
    }

  requestDoneInPeriod = false;

  if (
    !production ||                          // Not in production mode
    tcpClientConnected ||                   // We are connected to the server
    setupMode ||                            // We are in setup mode
    tcpClientConnectionInProgress           // Connection is currently being established
    ) {
      Serial.print("Not going to deep sleep. Reason: ");
      if (!production) Serial.println("Not in production mode");
      if (tcpClientConnected) Serial.println("Ongoing connection");
      if (setupMode) Serial.println("In setup mode");
      if (tcpClientConnectionInProgress) Serial.println("Connection setup in progres");
      delay(2000);
    } else {
      if (sleepIntervalSetbyHeader > 0){
        Serial.println(String("Using sleep interval set by header \"") + sleepIntervalHeader + "\":" + sleepIntervalSetbyHeader);
        esp_sleep_enable_timer_wakeup(FactorSeconds * (uint64_t)sleepIntervalSetbyHeader);
      } else {
        // No sleep time set via header or invalid value
        int SleepTime = iot.configuration.get("ImageWait").toInt();
        esp_sleep_enable_timer_wakeup(FactorSeconds * (uint64_t)SleepTime);
      }
      Serial.println("Going to deep sleep now...");
      Serial.flush();
      esp_deep_sleep_start();
    }
};

我检查了Arduino IDE上的串行监视器,看来ESP至少从服务器上得到了一些东西:

Basecamp V.0.1.6
MAC-Address: xxxxx, Hardware MAC: xxxxx
*******************************************
* ACCESS POINT PASSWORD: :xxxxx
*******************************************

Wait till the client is connected
Not going to deep sleep. Reason: Not in production mode
Connection setup in progres
OnConnect

OnData: 1436 Bytes
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Tue, 11 Sep 2018 18:42:28 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 30720
Connection: close
ProductionMode: 0
Printing 1267 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 1436 Bytes
Printing 1436 Bytes to the screen
OnData: 733 Bytes
Printing 733 Bytes to the screen
OnDisconnect
Not going to deep sleep. Reason: Not in production mode

尽管告诉我它将字节写入屏幕,但屏幕始终保持白色。有人可以帮我吗?

0 个答案:

没有答案