我正在使用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
尽管告诉我它将字节写入屏幕,但屏幕始终保持白色。有人可以帮我吗?