Как сделать веб-страницы более привлекательными на веб-сервере ардуино?
У меня есть Teensy 4.1 с Ethernet, но у меня есть другие сетевые микроконтроллеры, такие как D1 mini, различные экраны esp8266 и ardunio Ethernet. мой вопрос в том, как я могу сделать более привлекательные веб-страницы на этих устройствах, где наличие файловой системы ограничено памятью или отсутствием файловой системы. в частности, я хотел бы добавить изображения svg. Меня интересует, как создавать такие вещи, как фоновые изображения. Я понимаю html/css, и я могу писать код на C, получать свои данные, что угодно, но я тоже хочу, чтобы страница выглядела хорошо.
Вот пример проблемы, связанной с отображением html. Вот html-страница:
<!DOCTYPE html>
<head>
<style>
body {
background-image: url(example.svg);
}
</style>
</head>
<body >
</body>
</html>
круто, хорошо написать, что в чем-то, что может визуализировать ardunio, вы используете инструкции печати. вы могли бы написать:
// прослушивание входящих клиентов
...//отказ от всех настроек сервера
EthernetClient client = server.available();
client.println("<!DOCTYPE html><head><style>");
client.println("body {background-image: url(example.svg);}");
client.println("</style></head><body></body></html>");
...
который не будет работать без файловой системы или не будет работать как есть. итак, я попробовал в линейном svg, во встроенном svg.
// прослушивание входящих клиентов
...//отказ от всех настроек сервера
EthernetClient client = server.available();
client.println("<!DOCTYPE html><head><style>");
client.println("body { ");
client.println("background-image: url("data:image/svg+xml;utf8,<svg>**Omited**</svg>);");
client.println("}");
client.println("</style></head><body></body></html>");
...
что иногда работает, но, похоже, также имеет ограничения.
Мне просто интересно, как я могу создавать красивые веб-страницы с таблицами стилей на ardunio, которые используют только инструкции печати или не требуют файловой системы.
@j0h, 👍2
Обсуждение5 ответов
Лучший ответ:
Ваш метод встраивания SVG кажется мне излишне сложным. Вам не нужно давать URL-адрес чего-либо. Ниже приведен пример размещения SVG на веб-странице:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<title>Test</title>
</head>
<body>
<div style="position:absolute; z-index:-1;">
<svg width="500" height="500">
<rect x="5" y="5" width="450" height="450" fill="lightblue" stroke="black"></rect>
</svg>
</div>
<p style="padding:10px;">
Hello, world
</body>
</html>
Стили "position:absolute" и z-index помещают SVG в фиксированное положение на странице (например, фоновое изображение) и под все остальное, что вы можете туда поместить.
ну, я все еще хочу узнать больше о написании красивых веб-страниц на сетевых устройствах ardunio.
На данный момент я уладил свою насущную проблему. у html/css, похоже, есть некоторые проблемы, которые я не совсем понимаю, поскольку они связаны с операторами печати, и я почти уверен, что атрибут url-это то, что меня привлекает.
Я нашел этот отличный проект на github, который кодирует svg для встроенных функций css. https://yoksel.github.io/url-encoder/
Я создал базовый пример веб-сервера, чтобы упростить код, на который я смотрел. это странно, на html-страницах я помещал полные таблицы стилей в одну строку, и у меня никогда не было проблем. здесь это не сработало бы, пока я не разбил таблицу стилей на несколько строк (даже с URL-адресом, закодированным svg).
Вот мой более простой пример для страницы с фоновым изображением:
#include <SPI.h>
#include <NativeEthernet.h>
void pageWrite(EthernetClient client);
void listenClient(EthernetClient client);
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(10, 1, 0, 177);
EthernetServer server(80);
void setup() {
delay(5000); //вы хотите эту задержку. причина: tl;dr
Serial.begin(9600);
Ethernet.begin(mac, ip);
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
}
// запустите сервер
server.begin();
delay(100);
Serial.print("server is at ");
Serial.println(Ethernet.localIP());
} //завершение настройки
void loop() {
EthernetClient client = server.available();
listenClient(client);
} //end loop
void listenClient(EthernetClient client){
if (client) {
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
// Serial.write(c); //сообщает о подключении клиента
if (c == '\n' && currentLineIsBlank) {
pageWrite(client);
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}//конечная доступность
} //конец соединения
// дайте браузеру время для получения данных
delay(1);
// close the connection:
client.stop();
} //end client
}//end listenClient()
void pageWrite(EthernetClient client){
client.println("<!DOCTYPE html><html><head><style>");
client.println("body {");
client.println("background-image:");
client.println("url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 227.35277 156.29323' height='6.1532764in' width='8.9508963in' %3E%3Cpath style='fill:%23ff0000; stroke:%23ff0000; stroke-width:1.32300019;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1' d='m 12.000819,0.66145838 c -6.2819637,0 -11.33936062,5.05739692 -11.33936062,11.33936062 V 144.29249 c 0,6.28197 5.05739692,11.33936 11.33936062,11.33936 H 215.35224 c 6.28197,0 11.33885,-5.05739 11.33885,-11.33936 V 12.000819 c 0,-6.2819637 -5.05688,-11.33936062 -11.33885,-11.33936062 z M 18.932696,12.696383 H 205.5854 c 5.02557,0 9.07128,4.04571 9.07128,9.071282 v 77.03923 c 0,5.025575 -4.04571,9.071285 -9.07128,9.071285 H 18.932696 c -5.025572,0 -9.0712819,-4.04571 -9.0712819,-9.071285 v -77.03923 c 0,-5.025572 4.0457099,-9.071282 9.0712819,-9.071282 z' /%3E%3Cellipse style='fill:%23ffffff;fill-opacity:1; stroke:%23ff0000; stroke-width:1.32300007; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1' cx='21.16666' cy='130.59085' rx='10.583334' ry='9.0714283' /%3E%3Cellipse style='fill:%23ffffff; fill-opacity:1; stroke:%23ff0000; stroke-width:1.32300007; stroke-miterlimit:4; stroke-dasharray:none;stroke-opacity:1' cx='200.62982' cy='131.57361' rx='10.583334' ry='9.0714283' /%3E%3C/svg%3E%0A\");");
client.println("background-repeat: no-repeat;");
client.println(" }");
client.println(" </style></head><body ></body></html>");
}
Вы можете обслуживать целое веб-приложение из spiffs. ESP не волнует, является ли это красивым (например, на основе javascript) веб-приложением или обычным html-файлом. Затем веб-приложение может взаимодействовать с ESP через сокетное соединение. У вас даже не будет большого количества кода, связанного с веб-приложением, в вашем скетче (помимо обслуживания файла веб-приложения). Лично я бы полностью забыл об этом синтезе строк в оперативной памяти., @Sim Son
очень интересно, я это проверю., @j0h
Я снова отвечу на свой вопрос, лол.
Я посмотрел на спиффса, но, похоже, это была просто экстрасенсорная штука.
во всяком случае, я понял, что <!DOCTYPE html>
вероятно, это был не единственный тип документа, и этот svg, вероятно, будет отлично отображаться без html или css. и это правда! Я использовал <!DOCTYPE svg>
и избавился от html и css, что означает, что мне не нужно было использовать какую-то причудливую встроенную кодировку css, а вместо этого я мог бы сделать гораздо более простые операторы println() и по-прежнему генерировать доступную "веб-страницу", написанную только в svg.
Вероятно, я мог бы очистить и svg, я нарисовал его в inkscape, который генерирует слишком сложные svg-данные.
Но сейчас проект работает... во всяком случае, в базовом смысле.
/*
An IoT Etch-A-Sketch
uses 2 rotory encoders to generate x,y data points
in a svg poly line.
*/
#include <SPI.h>
#include <NativeEthernet.h>
#include <SD.h>
File dataFile;
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(10, 1, 0, 177);
EthernetServer server(80);
//Function defs
void rotors();
void rotorA(EthernetClient client);
void rotorB(EthernetClient client);
void resetImg(); //erase datalog.txt
void Abuton();
void Bbuton();
void listenClient(EthernetClient client);
void pageWrite(EthernetClient client);
void polyLineBegin(EthernetClient client);
void startPage(EthernetClient client);
void endPage(EthernetClient client);
//int potX = A0;
//int potY = A1;
int sensorValX = 0;
int sensorValY = 0;
int oldX = 0;
int oldY = 0;
/**Rotor Vars**/
// Rotary Encoder Input defintions
#define CLKA 2
#define DTA 3
#define SWA 4
#define CLKB 5
#define DTB 6
#define SWB 7
int Acounter = 0;
int Bcounter = 0;
int AcurrentStateCLK;
int BcurrentStateCLK;
int AlastStateCLK;
int BlastStateCLK;
unsigned long AlastButtonPress = 0; //butons on rotory encoders
unsigned long BlastButtonPress = 0;
void setup() {
delay(10000); //you want this delay. reason: tl;dr
/** Set encoder pins as inputs **/
pinMode(CLKA,INPUT);
pinMode(DTA,INPUT);
pinMode(SWA, INPUT_PULLUP);
pinMode(CLKB,INPUT);
pinMode(DTB,INPUT);
pinMode(SWB, INPUT_PULLUP);
// Read the initial state of CLK
AlastStateCLK = digitalRead(CLKA);
BlastStateCLK = digitalRead(CLKB);
/***End Rotary Encoder vars****/
Serial.begin(9600);
Ethernet.begin(mac, ip);
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
}
// start the server
server.begin();
delay(100);
Serial.print("server is at ");
Serial.println(Ethernet.localIP());
Serial.print("Initializing SD card...");
if (!SD.begin(BUILTIN_SDCARD)) {
Serial.println("SD Card init failed!");
while (1);
}
/****SD CARD and Ethernet intialized*****/
//if data file (on SD Card) Doesn't exist, create it.
//if data file (on SD Card) does exist errase it and create a new one
if (SD.exists("datalog.txt")) {
Serial.println("datalog.txt exists: \n Removing it.");
while(SD.remove("datalog.txt")!=1){
Serial.println("Deleeting old file data");
if (SD.remove("datalog.txt")==1){
Serial.println ("old dataFile removed");
}
}
} else {
Serial.println("datalog.txt doesn't exist.");
}
// open a new file and immediately close it:
Serial.println("Creating datalog.txt...");
dataFile = SD.open("datalog.txt", FILE_WRITE);
dataFile.close();
// Check to see if the file exists:
if (SD.exists("datalog.txt")) {
Serial.println("datalog.txt exists.");
} else {
Serial.println("datalog.txt wasn't created.");
}
} //end setup
void loop() {
// listen for incoming clients
EthernetClient client = server.available();
rotors();
listenClient(client);
} //end loop
void listenClient(EthernetClient client){
if (client) {
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
// Serial.write(c); //tells about the client connection
if (c == '\n' && currentLineIsBlank) {
pageWrite(client);
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}//end avail
} //end conect
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
} //end client
}//end listener()
void pageWrite(EthernetClient client){
startPage(client);
polyLineBegin(client);
endPage(client);
}
void startPage(EthernetClient client){
// doctype svg no need for html
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close"); // the connection will be closed after completion of the response
client.println("Refresh: 1"); // refresh the page automatically every 5 sec
client.println();
client.println("<!DOCTYPE svg>");
client.println("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 227.35277 156.29323' height='6.1532764in' width='8.9508963in'><path style='fill:#ff0000; stroke:#ff0000; stroke-width:1.32300019;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1' d='m 12.000819,0.66145838 c -6.2819637,0 -11.33936062,5.05739692 -11.33936062,11.33936062 V 144.29249 c 0,6.28197 5.05739692,11.33936 11.33936062,11.33936 H 215.35224 c 6.28197,0 11.33885,-5.05739 11.33885,-11.33936 V 12.000819 c 0,-6.2819637 -5.05688,-11.33936062 -11.33885,-11.33936062 z M 18.932696,12.696383 H 205.5854 c 5.02557,0 9.07128,4.04571 9.07128,9.071282 v 77.03923 c 0,5.025575 -4.04571,9.071285 -9.07128,9.071285 H 18.932696 c -5.025572,0 -9.0712819,-4.04571 -9.0712819,-9.071285 v -77.03923 c 0,-5.025572 4.0457099,-9.071282 9.0712819,-9.071282 z' /><ellipse style='fill:#ffffff;fill-opacity:1; stroke:#ff0000; stroke-width:1.32300007; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1' cx='21.16666' cy='130.59085' rx='10.583334' ry='9.0714283' /><ellipse style='fill:#ffffff; fill-opacity:1; stroke:#ff0000; stroke-width:1.32300007; stroke-miterlimit:4; stroke-dasharray:none;stroke-opacity:1' cx='200.62982' cy='131.57361' rx='10.583334' ry='9.0714283'/>");
client.println("\n<polyline points='15,15");
}
void endPage(EthernetClient client){
client.print("' \nfill='none' stroke='#ff0000'/></svg>");
}
void polyLineBegin(EthernetClient client){
//read data points from SD card
File dataFile = SD.open("datalog.txt");
// if the file is available, read it:
if (dataFile) {
while (dataFile.available()) {
//Serial.write(dataFile.read());
client.write(dataFile.read());
}
dataFile.close();
}
}
int rotorA(){ //X coords
AcurrentStateCLK = digitalRead(CLKA);
if (AcurrentStateCLK != AlastStateCLK && AcurrentStateCLK == 1){
if (digitalRead(DTA) != AcurrentStateCLK) {
Acounter --;
}else{
Acounter ++;
}
}
// Remember last CLK state
AlastStateCLK = AcurrentStateCLK;
Abuton(); //errase the data file if A button pressed.
//X limits
if(Acounter<=15){
Acounter=15;
}
if(Acounter>=215){
Acounter=215;
}
return Acounter;
}//endA rotor
int rotorB(){ //Y coords
BcurrentStateCLK = digitalRead(CLKB);
if (BcurrentStateCLK != BlastStateCLK && BcurrentStateCLK == 1){
if (digitalRead(DTB) != BcurrentStateCLK) {
Bcounter --;
}else{
Bcounter ++;
}
//Serial.println(Bcounter);
}
BlastStateCLK = BcurrentStateCLK;
Bbuton();
//Y limits
if(Bcounter<=15){
Bcounter=15;
}
if(Bcounter>=210){
Bcounter=210;
}
return Bcounter;
}//EndB rotor
void rotors(){
//read rotors and write x,y points to datalog.txt
sensorValX=rotorA();
sensorValY=rotorB();
String dataString = "";
if(sensorValX!=oldX || sensorValY!=oldY){
dataString += ",";
dataString += String(sensorValX);
dataString += ", ";
dataString += String(sensorValY);
Serial.println(dataString);
dataFile = SD.open("datalog.txt", FILE_WRITE);
// if the file is available, write to it:
if (dataFile) {
dataFile.print(dataString);
dataFile.close();
//clear dataString
dataString="";
}else{
// if the file isn't open, pop up an error:
Serial.println("error opening datalog.txt");
}
dataFile.close();
}else{
;
}
oldX=sensorValX;
oldY=sensorValY;
}//end rotors
void resetImg(){
//clear datapoints by errasing datafile.
if (SD.exists("datalog.txt")) {
Serial.println("Deleting data.");
while(SD.remove("datalog.txt")!=1){
Serial.println("Deleeting old file data");
if (SD.remove("datalog.txt")==1){
Serial.println ("old dataFile removed");
}
}
} else {
Serial.println("datalog.txt doesn't exist.");
}
// open a new file and immediately close it:
Serial.println("Creating datalog.txt...");
dataFile = SD.open("datalog.txt", FILE_WRITE);
dataFile.close();
// Check to see if the file exists:
if (SD.exists("datalog.txt")) {
Serial.println("New datalog.txt created.");
} else {
Serial.println("datalog.txt wasn't created.");
}
}//end resetImg
void Abuton(){ //X buton
int AbtnState = digitalRead(SWA);
//If we detect LOW signal, button is pressed
if (AbtnState == LOW) {
if (millis() - AlastButtonPress > 500) {
Serial.println("Resting image!");
resetImg();
}
AlastButtonPress = millis();
}
} //end Abuton
void Bbuton(){ //Y buton
int BbtnState = digitalRead(SWB);
if (BbtnState == LOW) {
//if 50ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - BlastButtonPress > 50) {
Serial.println("Button B pressed!");
}
BlastButtonPress = millis();
}
}
Обратите внимание, что если вы отправляете SVG, вам не нужна строка DOCTYPE, но вы должны исправить тип контента: это должно быть "image/svg+xml", а не "text/html"., @Edgar Bonet
Вот альтернативный способ делать то, что мне нравится использовать:
Что мне нравится делать при работе с ардуино с приличным объемом памяти, как у SAMD21, так это создавать полностью автономную веб-страницу, то есть веб-страницу, содержащую все HTML, CSS и Javascript в одном файле, а также все растровые и SVG-изображения. тот самый файл. Короче говоря, файл веб-страницы, который полностью самодостаточен.
Затем я разархивирую этот файл (который сильно сжимает его), а затем base64 закодирует разархивированный файл. Затем страница в кодировке base64 копируется/вставляется в код / память в виде const char*
.
Когда страница должна быть обслужена, я base64-декодирую ее, а затем обслуживаю версию gzipped, которую принимают все браузеры, если вы помещаете Content-Encoding: gzip
в заголовок.
Я поместил сложный пример на Github, может быть, вы сможете черпать вдохновение из этого. Размер веб-страницы в примере составляет 26,8 КБ, а кодированная base64 строка gzipped webpage, введенная в код, составляет чуть менее 7 кБ.
Это интересный подход, но я не вижу пользы в использовании кодировки base64. Это заставляет ваш документ потреблять на 33% больше памяти, затем вы выделяете исходный размер в стеке, чтобы декодировать его (в этот момент он занимает 233% своего размера), и вам нужен дополнительный код для декодирования. Было бы более эффективно хранить gzipped-страницу в незашифрованном виде в виде массива байтов (а не строки C)., @Edgar Bonet
@EdgarBonet: Я знаю, но использование кодировки base64 облегчает копирование/вставку содержимого файла в код, не нарушая его из-за управляющих символов и т. Д. В файле gzip-ed. У меня было достаточно памяти, поэтому я не стал использовать более эффективный способ экранирования проблемных символов в выводе gzip. Я признаю здесь некоторую лень., @ocrdu
Насколько я понимаю, то, что вы хотите отправить, представляет собой большую строку (строки), хранящиеся в памяти. Вы можете довольно легко сохранить его во внешней памяти. Вы можете использовать энергонезависимую FRAM с модулями 32K x 8, доступными менее чем за 5,00 долларов. Они будут работать (чтение/запись) на скоростях I2C или SPI, без задержек при записи или чтении. Есть и другие типы памяти, которые также подойдут, но они просты и недороги.
В настоящее время я делаю это с проектом, над которым работаю. Он использует Arduino Nano с примерно 20 000 сообщений и несколькими данными приложений/программ, которые я храню. Flash не будет содержать сообщения и программы, не хватает места. Данные программы создаются с помощью датчиков и поворотного переключателя. Я загружаю данные в FRAM иногда по частям в зависимости от размера с помощью моего кода загрузки. Затем я перехожу к коду своего приложения, которое извлекает сообщения из FRAM и отображает их на ЖК-дисплее и терминале.
В определенном месте FRAM есть таблица указателей, указывающая на адрес, размер и т. д. каждого сообщения. Это позволяет изменять сообщения и другие данные без изменения основного кода. FRAM в моем случае представляет собой подключаемый модуль. Этот подход, хотя и немного запутанный, работает хорошо, и вам не нужно иметь файловую структуру. Ты можешь, если ты хочешь. Я не знаю, сработает ли это для вас, но это сработает для меня.
- Несколько клиентских серверов через Wi-Fi
- WebSocketsServer.h: No such file or directory
- Как публиковать запросы HTTP POST на моем веб-сайте?
- Простой запрос GET с ESP8266HTTPClient
- Как получить данные из базы данных моего сервера в переменную в моем Arduino?
- Как получить параметры запроса от ESPAsyncWebServer?
- контент» не захватывается
- ESP32 в Arduino-IDE с FS.h и SPIFFS
https://www.arduino.cc/reference/en/language/variables/utilities/progmem/, @jsotola
Я могу положить туда файлы? хранение, я думаю, не проблема. это то, что example.txt это указатель на файл, а не поток данных или строковый литерал, который ищет html / css. но это наводит меня на мысль, @j0h
Выполните поиск в Интернете по “необработанному строковому литералу C++”. Это удобный синтаксис для написания больших многострочных строк, которые вы можете выводить на одном " client.println ()"., @Edgar Bonet
https://stackoverflow.com/questions/12930978/array-of-strings-char-array-in-c-arduino-how-do-i-accomplish-it, @jsotola
о, спасибо, но встроенная функция html/css еще не работала для меня, делая это. Несколько дней назад я написал сценарий для выполнения такого рода операций. в основном это связано с тем, что url() в css, похоже, имеет ограничения на то, что он может делать с изображениями. Я все равно использовал svg, но если бы я этого не делал.. подождите, я, кажется, читал об этом в PoC||GTFO, @j0h
Я не уверен, как это работает на крошечных, но на чипах ESP вы должны использовать SPIFFS для хранения файлов вашего веб-сайта и просто отображать их в своем коде (без синтеза строк для представления клиентского html в коде). Вы даже можете сохранять там изображения, хотя изображения с высоким разрешением могут очень быстро заполнять хранилище. Также: Если вы хотите создавать красивые веб-сайты, взгляните на Bootstrap. Он предоставляет множество полезных инструментов, которые уже хорошо выглядят (кнопки, панели вкладок, формы и т. Д.). Если у клиента нет подключения к Интернету, вы даже можете загрузить bootstrap на ESP. Не такой уж он и большой., @chrisl
да, spiffs кажутся аккуратной функцией, но я не видел ничего подобного ни для чего, кроме чипов esp8266 и esp32. может быть, его не так уж трудно портировать, но я не стал вдаваться в подробности., @j0h