Serial communication

And FWIW, setPixelCount() has always been in the python API. I just chose not to document it because it (1) didn’t seem broadly useful at the time, and (2) reset my Pixelblazes a few times during testing.

@pixie, you’re making me update the python client! :slight_smile:

I’ve documented setPixelCount(), added pause() and unpause() and am putting in the underpinnings for more file-related stuff, including on-the-fly map switching, (which might yet turn out to be the Worst Idea Ever!) Anything else you want in there while we’re “improving” things?

I needed to understand the file format in order to create a human-readable version for the Midnight Commander VFS…and now that you’ve explained it, I understand where the frequency fields in the APA102 structs went to.

There are a few more undocumented websocket commands I’m exploring, but first I think I should let @wizard say whether or not he wants to encourage poking around with them.

Er, not 100% sure all of the above will be officially supported. Some of these things were designed for efficiency and expediency as internal formats, while others were designed with intent and a level of implied support. My concern is being bound to a particular file format or risk breaking an ecosystem built on it, mostly around the file formats.

Not that anyone needs my permission, but feel free to explore and share your tools.

That said, @Petros has a VERY COOL setup. It is pretty unique even for a modular LED system. I totally want to support any/all hackery and see where this can go, but at the same time haven’t seen that this level of hackery is necessary/useful for most folks.

1 Like

@wizard, completely agree. At very least, I’ll add “not officially supported” disclaimers to anything undocumented that comes from RE, and if something looks potentially destructive, I’ll ask before publishing.

I’m willing to brick an occasional Pixelblaze in service of art, but I don’t want to drag anybody else down that path unless they fully understand the risk.

(Long ago, I was on a team that crashed virtually every university in the US with the initial release of a reverse engineered network server. We made the cover of PC week! Students quickly discovered that they could bring up servers on the school net with the same identifiers as the official servers, and mayhem ensued. I’m a lot more cautious about such things these days.)

2 Likes

I don’t think you owe us any promises of eternal compatibility; you haven’t asked us to pay an annual fee to be in the “Pixelblaze Developer Program” or a testing/licensing fee to display the “Pixelblaze compatible” logo on our (FOSS) offerings. If you need to change something, do; if our tools break, anyone who needs to can update or fork them.

2 Likes

Remembering the conversation about saving variables, I understand why you’re wary of supporting things that might kill the flash from overuse, but this is a small price to pay in my case and the whole project is based on the idea of modularity, we come up with new shapes every week or 10 days and whenever friends come over they do the same, so I’m already changing the pixel count via the web interface, won’t make a difference if it’s done via web socket-it might even be better, I’m going for the current sensing implementation which will be accurate and done only once per change of setup, whereas now sometimes we mis-calculate the led count and have to correct it.
To be honest, I came up with the whole one-button solution not only for my kids but also for my girlfriend, she’s still struggling to understand the layout of the expander, what number goes where etc:)

Back to the forking dilemma, after spending a few hours searching for standalone SK6812 ICs and finding nothing(as opposed to WS2811, what a shame), I’ll go ahead and try to kill a few SK6812 leds, see if their IC still functions as expected, and if so, fit them in a connector. I’ll leave the fancier micro idea you had for V3:)

I’ll post my progress on my kids version, it will have quite a few improvements over V1 and will be battery operated for starters, no AC around, I found some 20v power tool batteries that will slide nicely under the board. My girlfriend got me an Ender 3 as present which opened a gigantic can of worms filled with new possible shapes:)

@pixie , you are fantastic, thanks a lot for the write-up, I’ll order a few ESP-01s, play around and see how I can send the binary packets, if I get stuck I might pester you again:)

2 Likes

My ESP01s arrived and I started playing around with it, many issues to resolve:)

I’m trying to figure out how web sockets work at the moment, if I connect to PB using websocat or similar clients, it’s constantly refreshing the fps, making it impossible to send commands. I don’t have the web interface open in any devices and tried turning on/off the PB a few times but this persists. This is what I get:

{"fps":436,"vmerr":0,"vmerrpc":-1,"mem":10233,"exp":1,"renderType":1,"uptime":3606201,"storageUsed":767558,"storageSize":1378241,"rr0":1,"rr1":14,"rebootCounter":0}

Any idea how to solve this?

Hi @Petros ,
Thats normal, Pixelblaze will send you all manner of asynchronous messages, not only in reply to commands. These status messages are unprompted and cannot be disabled, and are sent once a second. It doubles as a heartbeat, letting you know the connection is alive.

It should not prevent you from sending commands. If you are doing this on the command line with websocat, and typing commands is difficult, I would write them in a text editor and copy/paste.

For a programatic client running on your ESP-01, you will want to write a websocket message handler that reads these, or at least accepts them from the websocket. It is safe to ignore messages you don’t care about. Messages will be either text, which should be parsable as JSON, or binary.

I guess it’s a matter of filtering out the status messages. How come you don’t have them in your Websocket demo video btw, custom firmware?

I’m sending/receiving all commands fine, what I can’t find is @pixie 's pixel count string, if I change it through Settings there is no change in the packets received, same status ones. Does PB notify Websocket clients of such changes when they happen through the UI and not the client itself?

Not yet, but that may happen in the future.

Sending getConfig:true will send back all of the config parameters including pixel count.

I’ve been meaning to ask for a while but this seems like an ideal opportunity! I haven’t seen it documented but what does sendUpdates:false do? it’s in the Firestorm code:

Apologies if it’s slightly O/T. Many thanks.

That turns off the preview pixels. In older versions it would also turn off the updates, but this caused other problems.

1 Like

While playing around with websockets, I’m having connection issues between PB and the ESP01s and can’t figure out on which side lies the problem.

On every other attempt I’m getting the following:

[WS] WebsocketsClient::connect: CloseReason_ProtocolError

Can it be that PB gets overwhelmed if the connections happen too often or the client polls too often? I noticed that if I restart PB things are a bit better, but not every time.

I’m pasting my code, please be gentle, it’s a work in progress, I’m trying to learn how to setup server and client, send and receive frames, parse them, etc:)

I got to say, for someone as code-literate as I am, Arduino IDE’s process of having to compile and upload in order to check something is miles away from PB’s real time method:((


#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>


#include <WebSockets2_Generic.h> .  // CLIENT

using namespace websockets2_generic;   //CLIENT

WebsocketsClient client;    //CLIENT


IPAddress local_IP(192, 168, 1, 95);    //SERVER
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

int button = 0;
int auxButtonState = 0;    // temporary variable for reading the button pin status



const char MAIN_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<body>

<h2>PB Websocket Test Page</h2>

<form action="/action_page">
<p>PB Command:<br /><input name="pbcommand" type="text" value="{getConfig:true}" />
<br /><br />

<input type="submit" value="Submit" /></p>


</form> 

</body>
</html>
)=====";

//SSID and Password of your WiFi router
const char* ssid = "COSMOTE-782506";
const char* password = "6sb4r2s46p88psre";
const char* hostname = "CurrentSensor";
const char* websockets_server_host = "192.168.1.12"; //Enter server address CLIENT
const uint16_t websockets_server_port = 81; // Enter server port

ESP8266WebServer server(80); //Server on port 80


const int buttonPin = 0;    // the number of the pushbutton pin

int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers



void onEventsCallback(WebsocketsEvent event, String data)    //CLIENT
{
  (void) data;
  
  if (event == WebsocketsEvent::ConnectionOpened) 
  {
    Serial.println("Connnection Opened");
  } 
  else if (event == WebsocketsEvent::ConnectionClosed) 
  {
    Serial.println("Connnection Closed");
  } 
  else if (event == WebsocketsEvent::GotPing) 
  {
    Serial.println("Got a Ping!");
  } 
  else if (event == WebsocketsEvent::GotPong) 
  {
    Serial.println("Got a Pong!");
  }
}



//===============================================================
// This routine is executed when you open its IP in browser
//===============================================================
void handleRoot() {
 String s = MAIN_page; //Read HTML contents
 server.send(200, "text/html", s); //Send web page
}
//===============================================================
// This routine is executed when you press submit
//===============================================================
void handleForm() {
 String PBCommand = server.arg("pbcommand"); 
 
client.onMessage([&](WebsocketsMessage message) 
  {
    client.send(PBCommand);
    client.poll();
    Serial.print("PB Command:");
    Serial.println(PBCommand);
  });  


 String s = "<a href='/'> Go Back </a>";
 server.send(200, "text/html", s); //Send web page


}



//==============================================================
//                  SETUP
//==============================================================
void setup(void){
  Serial.begin(115200);


pinMode(button, INPUT);

  WiFi.mode(WIFI_STA);
  WiFi.config(local_IP, gateway, subnet);
  WiFi.hostname(hostname);
  WiFi.begin(ssid, password);     //Connect to your WiFi router
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  //If connection successful show IP address in serial monitor
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println("WiFi");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //IP address assigned to your ESP

  server.on("/", handleRoot);      //Which routine to handle at root location
  server.on("/action_page", handleForm); //form action is handled here

  server.begin();                  //Start server
  Serial.println("HTTP server started");


Serial.print("Connected to Wifi, Connecting to WebSockets Server @");   //CLIENT
  Serial.println(websockets_server_host);


 // run callback when messages are received
  client.onMessage([&](WebsocketsMessage message) 
  {
    //Serial.print("Got Message: ");
    //Serial.println(message.data());
  });         
 
  // run callback when events are occuring
          client.onEvent(onEventsCallback);
 
  
  // try to connect to Websockets server
  bool connected = client.connect(websockets_server_host, websockets_server_port, "/");
  
  if (connected) 
  {
    Serial.println("Connected!");

    String WS_msg = String("Hello Server!");
    client.send(WS_msg);
  } 
  else 
  {
    Serial.println("Not Connected!");
  }


  
}
//==============================================================
//                     LOOP
//==============================================================
void loop(void){
  server.handleClient();          //Handle client requests


  // let the websockets client check for incoming messages
  if (client.available()) 
  {
    
    client.poll();
  }

// read the state of the switch into a local variable:
   int reading = digitalRead(buttonPin);

   // If the switch changed, due to noise or pressing:
   if (reading != lastButtonState) {
     // reset the debouncing timer
     lastDebounceTime = millis();
   }

   if ((millis() - lastDebounceTime) > debounceDelay) {
     // whatever the reading is at, it's been there for longer
     // than the debounce delay, so take it as the actual current state:

     // if the button state has changed:
     if (reading != buttonState) {
       buttonState = reading;

       // only toggle the LED if the new button state is HIGH
       if (buttonState == LOW) {
         Serial.print("Button Pressed");
         client.send("{getConfig:true}");
         //client.poll();
      client.onMessage([&](WebsocketsMessage message) 
  {
    String payload = message.data();    //Get the response payload from server
    Serial.println(payload);    //Print request response payload
    const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
    DynamicJsonBuffer jsonBuffer(capacity);
  
   // Parse JSON object
    JsonObject& root = jsonBuffer.parseObject(payload);
    if (!root.success()) {
      Serial.println(F("Parsing failed!"));
      return;
    }
  
    // Decode JSON/Extract values
    Serial.println(F("Response:"));
    Serial.println(root["pixelCount"].as<char*>());
    Serial.println(root["maxBrightness"].as<char*>());
  });  
       }
     }

   }

   // save the reading.  Next time through the loop,
   // it'll be the lastButtonState:
   lastButtonState = reading;
      



  
}

I’m not sure changing on the onMessage handler will cause issues. You might want a single onMessage, and branch out from there based on the state you expect.

Around line 100 you set it to this:

client.onMessage([&](WebsocketsMessage message) 
  {
    client.send(PBCommand);
    client.poll();
    Serial.print("PB Command:");
    Serial.println(PBCommand);
  });  

I could see that causing problems, this will send a command for every message received, which probably isn’t what you want to do, as it could flood the PB.

Yeah, I don’t know yet how to do something as simple as that, send a frame once and not have it repeat forever, same thing happens with the button input I have in the loop if you notice. Parsing incoming messages is also a pain to figure out!

So is this the reason I’m getting the connection issues? Am I flooding the PB? What is the process in cases like this, does it recover after a while or does it need a restart?