Is ESPNOW supported to control pattern selection from another ESP32? Or backup plan, wired GPIO

My goal is to do simple pattern selection from a master ESP32 by sending simple commands over the ESP-NOW protocol. Is this supported? The idea is to have the simplest possible wireless setup by avoiding an extra computer or RasPi (Firestorm seems to require that).

Barring that, the next simplest thing I can imagine would be physically wiring in GPIO or serial pins to have the master ESP32 send hardwired pattern selection. Examples of that setup would be helpful too :pray: Thanks!

1 Like

I doubt that this uses ESP-NOW because it has never been mentioned, but PixelBlaze now does this natively with no extra computers required! See Significant feature release: Sync multiple Pixelblazes

1 Like

Love your work, by the way. I enjoyed your episode on the Luminous Arts Podcast. Thanks for using Pixelblaze.

Sorcerer is right, no ESPNOW for now, and sync mode is probably the simplest way to do what you want.

If you want to have full external programmatic control of pattern selection and don’t want to or can’t run a RPi with Firestorm, there’s two ways I can think of.

First, you could develop your own ESP32 program that sends the websocket commands for changing patterns. There are a few more internal API commands that might help (like “next”) - It might be easiest to observe it over the wire with a browser’s inspector or Wireshark, but Firestorm is open source, so here are the relevant lines if you prefer JS:

Another great open source resource is ZRanger’s python library for controlling Pixelblaze, pixelblaze-client.


The second way, using GPIO, would involve copy-pasting a small block of sequencer index management code into each of your patterns (because there’s unfortunately not a shared import or global code block we can set that’s always running… yet). Inside that block, you’d declare your IO input pins and trigger one or more of the Pixelblaze language API sequencer management commands added recently. See playlistSetPosition(position) and the previous() example there.

1 Like

Thanks so much Jeff! Also very cool that you saw that podcast. I appreciate the sample code and links. I’ll try the GPIO route first, and update here on how it goes :pray:

In the language API you referenced, I see this example for Playlists:
playlistSetPosition(position)

However I prefer to directly trigger saved patterns when not in a Playlist.
I see this noted above on the same reference, but not sure it does what I want.
SEQ_PLAYLIST: 2

FYI the only I reason I prefer to avoid the Playlist here is because I never want it to auto-advance to the next pattern, and it seems Playlists require timeouts. So an alternate solution could be to disable timeouts / autoadvance in the playlists, if that’s at all possible.

Any more tips here would be great - Thanks!

1 Like

Hi @rddt ,
You can put ann absurdly large number for timeout. Unless you are running for several weeks you can treat it as a “stop here” kind of thing.

2 Likes

Thanks a lot, @wizard and @jeff – I’m helping @rddt on the same project (he’s doing the hard coding and technical implementation, I’m doing the patterns and LED install), and I’m really excited to give this a try.

1 Like

The long timeout works well, but perhaps I’m missing something basic here in my attempt to use GPIO’s 0, 25 & 26 for setting playlist position.

I wrote what I think is the simplest possible code for the 8 playlist items we have, and added it to the top of each the 8 patterns (before the render or beforerender)

I ensured I was reliably setting voltages from the control arduino (I tried both 5V & 3.3V, not sure if 5V is safe?). And the only thing it seems to do is cause the web browser playlist selector behave unpredictably (switching to a diff pattern than the one I clicked).

Can you advise on fixing my code? Thanks!


Ah! Looks pretty close!

You need to check the pins inside beforeRender() because the code outside of render() and beforerender() only runs once, on pattern initialization.

Looking at it, you’ll also probably need to exclude the one for the current pattern. Let me go grab a Pixelblaze - I’ll have something a little more portable for you in an hour.

2 Likes

The code in the main body executes once, when the patter first starts. It’s good for initialization code, like the pinMode() stuff. If you want to run something that responds to changes over time, you have to run it from inside beforeRender() which is called for every animation frame. Just toss all those ifs into a function and call it from beforeRender(), then any time the states change it will change the playlist position.

You could also do some bit math if you want less code. Treat gpio 26 as your 1s bit, gpio 25 as your 2s bit, and gpio 0 as your 4s bit. Something like this would work:

function updatePlaylistPosition() {
  var position = digitalRead(26) | (digitalRead(25)<<1) | (digitalRead(0)<<2)
  playlistSetPosition(position)
}
1 Like

It checks and does nothing if you set the position to the current position, or a position outside of the playlist, if thats what you were going to test :magic_wand: It also does nothing if it’s not in playlist mode. It’s harmless to call repeatedly.

2 Likes

Came here to post my solution and you beat me to it!

so @rddt - if it’s not obvious, you’d use it like this:

function updatePlaylistPosition() {
  var position = digitalRead(26) | (digitalRead(25)<<1) | (digitalRead(0)<<2)
  playlistSetPosition(position)
}

export function beforeRender(delta) {
  updatePlaylistPosition()
  // ... rest of this pattern's beforeRender() code ...
}
3 Likes

This is fantastic, it works! Thanks for the amazing responsiveness and the math elegance, @jeff & @wizard :sunglasses:
How about that 5v input to the GPIO’s, will that be OK or critical to level shift to 3.3v first?

1 Like

I’d definitely drop it to 3.3v. Can do that with a pair of resistors as a divider.

2 Likes

Well something very strange started happening, and I’m really not quite sure how I triggered it - probably when I was swapping wires between the arduino and the esp32; which I did do very carefully;
Basically it can now only switch between playlist item 0 and 4 (the 1st & 5th item), no matter what the combination of the 3 GPIO pins are. Half the pin combos switch to 0, the other switch to 4.

I triple checked that I’m getting good 3.3v values to the GPIO’s for HIGH’s, and I wired the 3x GPIO’s as well as 3.3v & Ground to the microcontroller to be safe (though it works fine w/o 3.3v at least). It’s behaving as if the only PIN it can read is IO0.
Is there a way to print out what it sees somehow to a log?

The code remains the same, but can you please take a look again to make sure there’s nothing missing here? Barring any code errors, is there any way to Factory Reset this PixelBlaze v3.6? It’s already updated to the latest Firmware. In the meantime I’m hoping to get my hands on another PixelBlaze soon to A/B test this with.

Simplest Full Code Example Below, the all use the same format:

I think you are missing the pinMode() initialization code. That part can be in the main body of code like before.

BTW it’s easier to pass code around on the forum if you paste it in here with three backticks before and after. There’s also a preformatted text button in the formatting toolbar that can add those, it looks like “</>”.

If you want to see a variable, there’s a few ways of doing that. Any exported global variable can be watched with the Vars Watch sidebar on the Editor tab. For example, if you move the position variable declaration out of the function, and add the export keyword, but continue to update it in your updatePlaylistPosition() function, you can watch the value.

You can also add a control to the UI that shows numbers. Here’s both together:

pinMode(26, INPUT)
pinMode(25, INPUT)
pinMode(0, INPUT)

export var position //exported var can be seen in vars watch
export function showNumberPosition() { //this creates a UI control
  return position
}
function updatePlaylistPosition() {
  position = digitalRead(26) | (digitalRead(25)<<1) | (digitalRead(0)<<2)
  //disable actually changing the running pattern for debug:
  //playlistSetPosition(position)
}
export function beforeRender(delta) {
  updatePlaylistPosition()
}

Note that I’ve commented out the playlistSetPosition(position) for debugging because jumping around in the playlist would change which pattern is running, probably loading one of your existing patterns that didn’t have the exported var or UI control.

Here’s where they show up:

3 Likes

I’m trying to test this paradigm separate from the GPIO hardware, and I thought I could use the same code to change positions within the playlist using the UI controllers.

I’d like to be able to call a function like this:

export function inputNumberPosition(v) { //this creates a UI control.  Changing it can break things!
  if (playPosition < playlistGetLength()) { 
    playPosition = v 
    playlistSetPosition(playPosition)
  }
  else { playPosition = 0 }
}

While I have the playlistSetPosition call in yourupdatePlaylistPosition function commented out (like in your code above). Doing that is… a very bad idea and I’m not sure why.

Trying to put a number there (where all the playlist items have the same helper code) crashes the PixelBlaze entirely, and gets in a reset loop that eventually turned off all the playlists and almost bricked the PixelBlaze. Instead of showing a whole number for the playlist position, it changed from my input of, say, “2” to a random looking long number like 1.922390 (I didn’t write down the actual number) and starts glitching.

It then reset it into “http://192.168.4.1/?min” as the URL after a few reboots, and, and didn’t really recover till I turned it off and back on, and that pattern itself became really unstable when called.

I am not sure how that global function call of playlistSetPosition works, but obviously you can’t use it the way I was trying to… what does it actually do, and is there a stable way to change patterns in code or via a UI control like I was doing?

The ui controls are sticky/stateful. The handler functions get called when the pattern loads with the previous values. So you could easily create an infinite loop by bouncing between two patterns with different stored values.

You could still do it with UI controls, but you’d have to do it in two steps. Remember the playlist from the inputNumber control, then call setPlaylistPosition insider a trigger type control, which makes a button. Buttons don’t have state and won’t get called on pattern load, only when you click on them.

In general though, I don’t know why you’d want to do it this way. There are already controls for the playlist in the interface and also a websocket API that wouldn’t have this problem.

1 Like

Thanks @wizard – I misunderstood how the handler functions work, but the good news is after a bit of cursing and final troubleshooting today, @rddt and I got everything working beautifully this afternoon and are excited for the event next week! (We’ll share some photos and video afterwards).

The problem was partly that, and partly some other issues with a pattern with a weird bug in one of the playlists even after I removed that code block, which had gotten me into this this type of error message, which was very much not a good state to be in. (I have one controller – because I had the same code on all of them – that’s still stuck in a bit of a un-connectable state, but that’s a problem to diagnose and fix later):

@wizard we have the code you shared integrated, but there are definitely some unpredictable states we encounter when starting up the system. As a sanity check I just want to double check that it’s wired as expected:

PixelBlaze——ESP32-Fern (3.3V)
IO0——D1
IO25—-D2
IO26—-D3
Gnd——Gnd

Is the ground essential? It might even be causing problems - seems to work without it.

In the meantime we’ll follow up after looking a bit closer on startup behaviors.