ABB Aurora Web Inverter Monitor (WIM): WebSocket and Web Server – 7

Spread the love

A web interface without a web server cannot exist, so first I implemented the server then I created a simple React / Redux web application to manage and display the data.

ABB PowerOne Aurora Web Inverter Centraline WebSocket and Web Server
ABB PowerOne Aurora Web Inverter Centraline WebSocket and Web Server

The web structure is served enterely via esp8266, you can refer the tutorial on “How to build a Web Server with esp8266 and esp32” to have a lot of information and idea.

I don’t add authentication because I use It in my LAN network, but the tutorial explain you you how to add security layer.

All the site is multilanguage (English/Italian for now).

The interface have a widget management, you can select a single widget to put on you home page, and you can move resize all the elements.

Here an example of the home page.

ABB Aurora inverter centraline Home page
ABB Aurora inverter centraline Home page

To put the widget in home page you must click on the heart, and than you can find It in homepage.

Manage static content

To manage static content I create another server on port 80, and all the content is managed by 3 simple functions, the first check the file type, if nothing is passed set index.html page as default one, and for all elements check if exist a gzip version of the file.

bool handleFileRead(String path){  // send the right file to the client (if it exists)
  DEBUG_PRINT(F("handleFileRead: "));
  DEBUG_PRINTLN(path);

  if(path.endsWith("/")) path += F("index.html");           // If a folder is requested, send the index file
  String contentType = getContentType(path);             // Get the MIME type
  String pathWithGz = path + F(".gz");
  if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){  // If the file exists, either as a compressed archive, or normal
    if(SPIFFS.exists(pathWithGz))                          // If there's a compressed version available
      path += F(".gz");                                         // Use the compressed version
    fs::File file = SPIFFS.open(path, "r");                    // Open the file
    size_t sent = httpServer.streamFile(file, contentType);    // Send it to the client
    file.close();                                          // Close the file again
    DEBUG_PRINTLN(String(F("\tSent file: ")) + path + String(F(" of size ")) + sent);
    return true;
  }
  DEBUG_PRINTLN(String(F("\tFile Not Found: ")) + path);
  return false;                                          // If the file doesn't exist, return false
}

if file is found stream the content

void streamFile(const String filename){
	if (SPIFFS.exists(filename)){
		fs::File fileToStream = SPIFFS.open(filename, "r");
		if (fileToStream){
			if (fileToStream.available()){
				DEBUG_PRINT(F("Stream file..."));
				const String appContext = getContentType(filename);
				httpRestServer.streamFile(fileToStream, appContext);
				DEBUG_PRINTLN(F("done."));
			}else{
				DEBUG_PRINTLN(F("Data not available!"));
				httpRestServer.send(204, F("text/html"), F("Data not available!"));
			}
			fileToStream.close();
		}else{
			DEBUG_PRINTLN(F("File not found!"));
			httpRestServer.send(204, "text/html", "No content found!");
		}
	}else{
		DEBUG_PRINTLN(F("File not found!"));
		httpRestServer.send(204, "text/html", "File not exist!");
	}
}

and set the correct mime type

String getContentType(String filename){
  if(filename.endsWith(F(".htm"))) 		return F("text/html");
  else if(filename.endsWith(F(".html"))) 	return F("text/html");
  else if(filename.endsWith(F(".css"))) 	return F("text/css");
  else if(filename.endsWith(F(".js"))) 	return F("application/javascript");
  else if(filename.endsWith(F(".json"))) 	return F("application/json");
  else if(filename.endsWith(F(".png"))) 	return F("image/png");
  else if(filename.endsWith(F(".gif"))) 	return F("image/gif");
  else if(filename.endsWith(F(".jpg"))) 	return F("image/jpeg");
  else if(filename.endsWith(F(".ico"))) 	return F("image/x-icon");
  else if(filename.endsWith(F(".xml"))) 	return F("text/xml");
  else if(filename.endsWith(F(".pdf"))) 	return F("application/x-pdf");
  else if(filename.endsWith(F(".zip"))) 	return F("application/x-zip");
  else if(filename.endsWith(F(".gz"))) 	return F("application/x-gzip");
  return F("text/plain");
}

It’s quite simple but if you want go in deep you can refer to the series of articles “How to build a Web Server with esp8266 and esp32“.

Upload the site

You can refer to WeMos D1 mini (esp8266), integrated SPIFFS Filesystem to have more information on how to upload the site, but you can enable FTP server on WIC, and upload the site via FTP like described on FTP server on esp8266 and esp32.

Widgets

ABB Aurora Widget
ABB Aurora Widget

I have created 5 sections (for now)

  • Homepage: where you can add all single widget to create a personalized view
  • Daily chart: with
    • chart of power
    • chart of current
    • chart of voltage
    • total daily production
    • realtime production
  • Monthly chart: with
    • chart of monthly production, for the month you can view every day the total of production and the peak of the production
    • Total lifetime prduction
    • Total yearly production
    • Total monthly production
    • Total weekly production
  • Information and state: with
    • chart of the battery voltage
    • Inverter info
    • Inverter state
  • Configuration: that we already view.

Layout

All the site si created with react and material UI, so It is responsive

You can move with drag and drop all the widgets, and you can resize the charts, all the operation is saved on the device, so if you move and save in the phone this layout remain in the phone.

You can create a perfect layout for each resolution, you have a change in the layout of the page spaces according to the resolution, and when you move and resize the widgets they snap to the resolution specific columns.

You have a specified number of column for various resolution

>1800: 5 columns
>1400: 4 columns
>1100: 3 columns
>720: 2 columns
>0: 1 columns

I use this approach in many projects and it turns out beautiful and versatile.

Realtime

I’d like to spent a note for “Realtime data widget”, in difference from the other widget that work with REST system this widget is updated (like notification) with WebSocket, so you can have a realtime data of power production.

As usual I write a tutorial of the argument, and you can find It on “WebSocket on Arduino, esp8266 and esp32“.

The core part is the onEvent function, that have the classic set of event of the ws

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {

    switch(type) {
        case WStype_DISCONNECTED:
			webSocket.sendTXT(num, "{\"connection\": false}");

            DEBUG_PRINT(F(" Disconnected "));
            DEBUG_PRINTLN(num, DEC);

//            DEBUG_PRINTF_AI(F("[%u] Disconnected!\n"), num);
            break;
        case WStype_CONNECTED:
            {
                IPAddress ip = webSocket.remoteIP(num);
//                DEBUG_PRINTF_AI(F("[%u] Connected from %d.%d.%d.%d url: %s\n"), num, ip[0], ip[1], ip[2], ip[3], payload);

                DEBUG_PRINT(num);
                DEBUG_PRINT(F("Connected from: "));
                DEBUG_PRINT(ip.toString());
                DEBUG_PRINT(F(" "));
                DEBUG_PRINTLN((char*)payload);

				// send message to client
				webSocket.sendTXT(num, "{\"connection\": true}");
            }
            break;
        case WStype_TEXT:
//        	DEBUG_PRINTF_AI(F("[%u] get Text: %s\n"), num, payload);

            // send message to client
            // webSocket.sendTXT(num, "message here");

            // send data to all connected clients
            // webSocket.broadcastTXT("message here");
            break;
        case WStype_BIN:
//        	DEBUG_PRINTF_AI(F("[%u] get binary length: %u\n"), num, length);
            hexdump(payload, length);

            // send message to client
            // webSocket.sendBIN(num, payload, length);
            break;
        case WStype_ERROR:
        case WStype_FRAGMENT_TEXT_START:
        case WStype_FRAGMENT_BIN_START:
        case WStype_FRAGMENT:
        case WStype_FRAGMENT_FIN:
        case WStype_PING:
        case WStype_PONG:

//        	DEBUG_PRINTF_AI(F("[%u] get binary length: %u\n"), num, length);
        	DEBUG_PRINT(F("WS : "))
        	DEBUG_PRINT(type)
        	DEBUG_PRINT(F(" - "))
			DEBUG_PRINTLN((char*)payload);

            // send message to client
            // webSocket.sendBIN(num, payload, length);
            break;
    }

}

I send messages in boradcast

		webSocket.broadcastTXT(buf);

so all the clients subscribed receive the message, and the widget of realtime production is updated all the time.

Thanks

  1. ABB Aurora Web Inverter Monitor (WIM): project introduction
  2. ABB Aurora Web Inverter Monitor (WIM): wiring Arduino to RS-485
  3. ABB Aurora Web Inverter Monitor (WIM): storage devices
  4. ABB Aurora Web Inverter Monitor (WIM): debug and notification
  5. ABB Aurora Web Inverter Monitor (WIM): set time and UPS
  6. ABB Aurora Web Inverter Monitor (WIM): WIFI configuration and REST Server
  7. ABB Aurora Web Inverter Monitor (WIM): WebSocket and Web Server
  8. ABB Aurora Web Inverter Monitor (WIM): Wiring and PCB soldering
  9. ABB Aurora Web Inverter Monitor (WIM): upload the sketch and front end
  10. ABB Aurora web inverter Monitor (WIM): 3D printed case to complete project
  11. ABB Aurora web inverter monitor (WIM): repair E013 error

GitHub repository with all code BE and FE transpiled


Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *