Author Topic: Multisync start and intermediate packet formats.  (Read 2199 times)

Offline Barnabybear

  • Newbie
  • *
  • Join Date: May 2016
  • Location:
  • Posts: 20
  • Kudos: 0
Multisync start and intermediate packet formats.
« on: June 02, 2016, 02:06:20 PM »
 Hi, I've looked all over the place for the format of the multisync packets and not found them. As always they will be under my nose but could someone please put me out of my misery and point me in the right direction.
Thanks Phil.

 

Offline David Pitts

  • Administrator
  • *****
  • Join Date: Mar 2013
  • Location: Falcon, CO
  • Posts: 3,736
  • Kudos: 63
Re: Multisync start and intermediate packet formats.
« Reply #1 on: June 02, 2016, 02:12:16 PM »
There is no formal document that explains the sync packets because as of now you are the only one that have asked. But the code is available on Github.

Look here at this file.
https://github.com/FalconChristmas/fpp/blob/master/src/controlrecv.c
https://github.com/FalconChristmas/fpp/blob/master/src/controlrecv.h

It is a pretty basic structure. Basically a UPD packet sent/received on a port.

ProcessSyncPacket is the main function.
PixelController, LLC
PixelController.com

Offline Barnabybear

  • Newbie
  • *
  • Join Date: May 2016
  • Location:
  • Posts: 20
  • Kudos: 0
Re: Multisync start and intermediate packet formats.
« Reply #2 on: June 02, 2016, 03:02:23 PM »
 Hi, thanks that is just what I needed. I'm correct in thinking:
1. The frame number is an increment of the 'StepLen' starting from 0?
2. Any correction for out of sync between the master and slave is an instantaneous correction to the current position of the frame counter. So it will skip or repeat frames if that is required to sync?
3. 'secondsElapsed' is not used?
 
Thanks Phil

Offline David Pitts

  • Administrator
  • *****
  • Join Date: Mar 2013
  • Location: Falcon, CO
  • Posts: 3,736
  • Kudos: 63
Re: Multisync start and intermediate packet formats.
« Reply #3 on: June 02, 2016, 03:07:54 PM »
The frame sync system is a slow sync. The remotes try not to ever jump or skip frames. The duration of sleep between frames is constantly adjusted to bring the remote into sync with master. It is never good to just jump to another place in the file. Remotes should try to read every frame and output to lights and avoid skipping or repeating frames. Adjust your periodic timer to speed up or slow down a bit. 

The sync packet may not come every frame and also because of network congestion may not come for several seconds (worst case). So the remotes should keep their own timing and be able to run stand alone for some time. Then when a sync packet arrives sync back with master.

Offline Barnabybear

  • Newbie
  • *
  • Join Date: May 2016
  • Location:
  • Posts: 20
  • Kudos: 0
Re: Multisync start and intermediate packet formats.
« Reply #4 on: June 02, 2016, 03:38:10 PM »
Hi, that all makes sense. So after the 'Start packet' the remote should start running outputs independant of the master based on the infomation in the file, one of being the timing of frames, but apply two corrections:
1. A slow correction to the frame time interval to bring back the sync.
2. A longer term adjustment to the frame time interval to compensate for a fast or slow running clock on the slave.

Do you build in any filter to reject 'sync packets' that have obviously not arrived at the correct time to prevent them having an adverse effect - or have I just answered one of my questions - that's what the 'secondsElapsed' is for?
Thanks Phil.


Offline David Pitts

  • Administrator
  • *****
  • Join Date: Mar 2013
  • Location: Falcon, CO
  • Posts: 3,736
  • Kudos: 63
Re: Multisync start and intermediate packet formats.
« Reply #5 on: June 02, 2016, 03:41:28 PM »
In your typical master/remote network the UDP packet should get there in a timely fashion and in order. Filters could be used maybe the Captain uses them? A deeper look at the code I would have to take to answer that for sure. They are used in many applications though sounds reasonable.

Offline Barnabybear

  • Newbie
  • *
  • Join Date: May 2016
  • Location:
  • Posts: 20
  • Kudos: 0
Re: Multisync start and intermediate packet formats.
« Reply #6 on: June 02, 2016, 04:12:09 PM »
It's my wife’s 50th this year and I was just going to do some stuff around the marque that could later be incorporated into the display and garden lighting. As you will know once you get into a project it can expand exponentially. This has done, and gone into the realms of wearables for all the guests. Given that I only have eight weeks and I can't cover the whole area with WiFi, I figure there are two ways to go:
1. Develop mesh networking to pass on the sync.
2. Assume that everyone will go to the bar or the toilet (which are next to each other) and receive a sync.

The second sounds a better option as I could have 1, 2 even 10 second timing. Everyone would still be impressed.
As it would only be a small pixel count the files could be stored in flash memory and sync'ed by FPP - still a lot to workout.

That being the most pressing, I'm still looking to progress an ESP8266 FPP remote for a couple of other things I would like to do.
Thank for a realy informative chat - I'll do some tests and I'm sure will have some more questions - if not something to report.

Phil.

 
« Last Edit: June 03, 2016, 02:49:15 AM by Barnabybear »

Online CaptainMurdoch

  • Administrator
  • *****
  • Join Date: Sep 2013
  • Location: Washington
  • Posts: 8,324
  • Kudos: 155
Re: Multisync start and intermediate packet formats.
« Reply #7 on: June 03, 2016, 09:11:59 AM »
A couple more points of info...

There currently is no out of order detection, although it would be trivial to implement.

The remote is responsible for all sync, the master currently doesn't even know if the remote is listening out there (other than an ARP reply) since this is a UDP packet.

The sequence sync protocol will switch from being frame based to being time based.  I hope to have this done for FPP v2.0.  Since we are currently frame based, it means that all remotes must have the same sequence timing.  Once we switch to time based then you could have a P10 matrix on a BBB running at 100ms timing kept in sync with your main sequences running at 25ms timing.  The media sync already uses time based sync.

I am starting to document the control port packets which include the sync packets.  This doc will be in the docs directory on github once I finish and commit it.
-
Chris

Offline Barnabybear

  • Newbie
  • *
  • Join Date: May 2016
  • Location:
  • Posts: 20
  • Kudos: 0
Re: Multisync start and intermediate packet formats.
« Reply #8 on: June 12, 2016, 04:09:57 PM »
Hi, thanks for the information. I've had a good run at this over the weekend and just have a few bits left to sort. Mainly the adjustment of timing to reflect the sync packets. At the moment the ESP is selecting files to play from the FPP sync and running slightly faster than the required sequence. To correct I'm just over-writing the ESP's frame number with the sync packet frame number when it comes in. This is very clunky and displays a visible correction, sometimes showing the same frame twice or even stepping back one. This turned out to be quite useful as its obvious it works. I just need something a little more subtle now. I'll try and get a write up done and post the code this week.
 
« Last Edit: June 12, 2016, 04:40:11 PM by Barnabybear »

Offline Barnabybear

  • Newbie
  • *
  • Join Date: May 2016
  • Location:
  • Posts: 20
  • Kudos: 0
Re: Multisync start and intermediate packet formats.
« Reply #9 on: June 14, 2016, 03:14:12 PM »
Hi, as promised.
This is a work in progress code that with a change of ESP8266 enables a pixelstick type device to function as a FPP remote (not all features). That is instead of receiving e1.31 packets of data over a network, it reads data from an xlights .PSEQ file on an SD card. The file name and synchronization are dictated by a single packet sent every 16 frames by FPP.

Simple over view:
Set some variables.
Initialize stuff.
Read a file called 'fortytwo.txt.' from the SD card. This contains the number of the first channel & the number of channels we will be using. These are set as variables. In future some other stuff will be added.

Main loop:
Listen for an Udp packet FPP on channel 32320, when this arrives it will be one of three types:
Start: Read file name - open file. Set the show running flag & current frame number to 0.
Stop: Clear the show running flag & close file.
Sync: Amend current frame number if required.

At this point we have two options:
The show isn't running: Do nothing & go back to listen for Udp packets.
The show is running: Read data from the SD card based on frame number and output to lights.

Code: [Select]
/*
The following code is still under development by Barnabybear, use it in your show at your own risk..
This will enable a device like a pixelstick to function as a FPP remote:
Reading it's data from an SD card sync'ed by a packet from an FPP master in remote mode.
 */

#include <SPI.h>
#include <SD.h>
#include <NeoPixelBus.h>
#include <ESP8266WiFi.h>
#include <WiFiUDP.h>

#define pixelCount; // number of channels (read from file).
#define pixelPin 2  // make sure to set this to the correct pin changed from 8 by //PC//
bool Debug = 1; // minimum debug.
bool PSEQ_Headder_Debug = 0; // debug .PSEQ headder.
bool Show_Running = 0; // flag 1= show running.
const char* ssid = "your SSID";  // your network SSID
const char* pass = "your PASSWORD";  // your network password
IPAddress IP_Multicast_Out(239, 255, 0, 1); // doesn't realy do much in this code.
unsigned int Port_Multicast_Out = 5568; // doesn't realy do much in this code.
unsigned int localPort = 32320;      // local port to listen on
//const int chipSelect = 15;  // only needed if you don't use GPIO 15 as CS (Chip Select).
long int First_Channel = 0;  // first channel number in .PSEQ file (read from file).
long int Number_Pix = 170; // number of pixels for 'NeoPixelBus'.
long int Data_Offset;  // start of data in .PSEQ file (length of headder) (read from file).
long int Number_Channels;  //  total number of channels in .PSEQ file (read from file).
long int Step_Lenght;  // number of frames in .PSEQ file (read from file).
int Frame_Time;  // FPS of of frames in .PSEQ file (read from file).
unsigned long Time_Start;
int j;
char PSEQ_Data_File[13];
char packetBuffer[31]; // buffer to hold incoming packet
long int Current_Step;  // code current step / frame number.
long int Frame_Number;  // FPP packet current step / frame number.
File dataFile;  // required by SD.h.
NeoPixelBus strip = NeoPixelBus(Number_Pix, pixelPin); // required by NeoPixelBus.h.
WiFiUDP Udp;

void setup()
{
   Serial_Initialize(); // Initialize serial.
   Neo_Pixel_Initialize(); // Initalize Neo pixels.
   SD_Initialize(); // Initialize SD card.
   dataFile = SD.open("fortytwo.txt"); // open SD card file "fortytwo.txt"
   fortytwo_Read();  //read setup data from file fortytwo and set variables.
   dataFile.close(); // close SD card file "fortytwo.txt"
   WiFi_Initialize(); // Initialize WiFi.
   Udp.begin(localPort); // start lisening for Udp packets.
}

void loop()
{
      int packetSize = Udp.parsePacket(); // parse packet from FPP.
  if (packetSize) { // check there is a packet.
    int len = Udp.read(packetBuffer, 31); // read the packet into buffer.

      switch(packetBuffer[7]){ // 3 actions dependant on type of FPP packet.
       
      case 0: // Start Packet
      for(int i = 17; i < 29; i++){ // file name bytes.
      PSEQ_Data_File[i -17] = (packetBuffer[i]); // read FPP file name.
      }
      Serial.print("0x00 = Start_Packet");  // debug print.
      Serial.print("  "); // debug print.
      Serial.println(PSEQ_Data_File); // debug print.
      PESQ_Headder_Read(); //call 'void PESQ_Headder_Read'.
      Show_Running = 1; // set fag to show running.
      Current_Step = 0; // reset current step / frame to zero.
      break;
         
      case 1: // End Packet //
      Serial.println("0x01 = End___Packet"); // debug print.
      Show_Running = 0; // set fag to show NOT running.
      dataFile.close(); // close current SD file.
      break;             

      case 2: // Sync Packet //
      Serial.println("0x02 = Sync__Packet"); // debug print.
      Frame_Number = (packetBuffer[10] * 256) + (packetBuffer[9]); // add 2 bytes to get frame number.
      Current_Step = Frame_Number; // overwrite currrent frame number with FPP sync frame number.
      break;           
    }     
  }   
      switch (Show_Running){ // 2 actions dependant on show running flag.

        case 0: // show NOT running, go back and check for FPP packets.
        break;

        case 1: // show running get data from SD card and output to lights.
        if (dataFile) { // check file open.
        while(Current_Step < (Step_Lenght+1)) { // if NOT at the end of the sequence.
        dataFile.seek(((Data_Offset) -1) + First_Channel + ((Number_Channels) * Current_Step)); // first read postion = (after the file headder) + (miss out unused channels). Subcequent read postion = as above + (the number of channels in file * the number of previous reads).
        for (j =0; j < Number_Pix; j++){ // loop based on number of channels / pixels to read
        strip.SetPixelColor(j,dataFile.read(),dataFile.read(),dataFile.read()); // read 3 current pixel values into Neo pixel buffer.             
       }
        delay(Frame_Time - 20); // delay untill correct time to send to pixels.
        strip.Show(); // send data to pixels.
       Current_Step++; // add 1 to the current step postion.
       Serial.print("."); // debug print.
       break; // to check for new packets for FPP. 
       }
//       Show_Running = 0;
//       Current_Step = 0;
       }
       }}
//*************** void Serial_Initialize ***************//
  void Serial_Initialize(){
  // Open serial communications and wait for port to open:
  Serial.begin(115200); // debug print.
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only.
  }
  Serial.println("Serial initialized @ 115200.");
  }
/////////////////////////////////////////////////////////

//*************** void WiFi_Initialize ***************//
void WiFi_Initialize(){
  WiFi.begin(ssid, pass);
  Serial.print("[Connecting]"); // debug print.
  Serial.print(ssid); // debug print.
  int tries=0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print("."); // debug print.
    tries++;
    if (tries > 30){
      break; 
    }}
    Serial.println("Connected."); // debug print.
    }
/////////////////////////////////////////////////////////

//*************** void Neo_Pixel_Initialize ***************//
  void Neo_Pixel_Initialize(){   
    strip.Begin(); // start neo pixel.
    strip.Show(); // set all to off.
    Serial.println("Neo Pixel initialized & set to all off."); // debug print.
  }
/////////////////////////////////////////////////////////

//*************** void SD_Initialize ***************//
    void SD_Initialize() {
  Serial.print("Initializing SD card... "); // debug print.
  if (!SD.begin()) {  // see if the card is present and can be initialized.
    Serial.println("Card failed, or not present"); // debug print.
    return;
  }
  Serial.println("Card initialized."); // debug print.
    }
/////////////////////////////////////////////////////////

//*************** void fortytwo_Read ***************//
    void fortytwo_Read() { // file on SD card with setup data - first channel number - number of channels.
    if (dataFile) {
      dataFile.seek(9); // set read pionter to correct byte.
      First_Channel = (((dataFile.read()-48) *1000) + ((dataFile.read()-48) *100) + ((dataFile.read()-48) *10) + (dataFile.read()-48));
      // read first channel: number and convert from ascii then correct values and add together.
      dataFile.seek(14); // set read pionter to correct byte.
      Number_Pix = (((dataFile.read()-48) *1000) + ((dataFile.read()-48) *100) + ((dataFile.read()-48) *10) + (dataFile.read()-48));
      // read number of pixels: number and convert from ascii then correct values and add together.     
      while (Debug){ // debug print.
      dataFile.seek(0); // set read pionter to correct byte.
      Serial.println(); // debug print.
      Serial.print("File fortytwo data: "); // debug print.
      for (int i = 0; i < 22; i++){
      Serial.write(dataFile.read()); // debug print.
      }
      Serial.print(" First channel: "); // debug print.
      Serial.print(First_Channel); // debug print.
      Serial.print(" Number of pixels: "); // debug print.
      Serial.print(Number_Pix); // debug print.
      Serial.println();
      break;
      }}
     // if the file isn't open, pop up an error:
     else {
     while (Debug){ // debug print.
     Serial.println("error opening fortytwo.txt"); // debug print.
     break; 
}}}
/////////////////////////////////////////////////////////

//*************** void PESQ_Headder_Read ***************//
void PESQ_Headder_Read(){
  Serial.print("Opened "); // debug print.
  dataFile = SD.open(PSEQ_Data_File); // open fire referenced by FPP.
  if (dataFile) { // check file open.
//-----------------------------------------
      dataFile.seek(4); // set read pionter to correct byte.
      Data_Offset = (dataFile.read()); // read LSB.
      Data_Offset = Data_Offset + (dataFile.read() *256); // read MSB & add to LSB.
      while (PSEQ_Headder_Debug){ // debug print.
      Serial.println(); // debug print.
      Serial.print("Data Offset: "); // debug print.
      Serial.print(Data_Offset); // debug print.
      Serial.print(" "); // debug print.
      break;
      }
//-----------------------------------------
      dataFile.seek(10); // set read pionter to correct byte.
      Number_Channels = (dataFile.read()); // read LSB.
      Number_Channels = Number_Channels + (dataFile.read() *256); // read MSB & add to LSB.
      while (PSEQ_Headder_Debug){ // debug print.
      Serial.println(); // debug print.
      Serial.print("Channels: "); // debug print.
      Serial.print(Number_Channels); // debug print.
      Serial.print(" "); // debug print.
      break;
      }
//-----------------------------------------
      dataFile.seek(14); // set read pionter to correct byte.
      Step_Lenght = (dataFile.read()); // read LSB.
      Step_Lenght = Step_Lenght + (dataFile.read() *256); // read MSB & add to LSB.
      while (PSEQ_Headder_Debug){ // debug print.
      Serial.println(); // debug print.
      Serial.print("Step Length: "); // debug print.
      Serial.print(Step_Lenght); // debug print.
      Serial.print(" "); // debug print.
      break;
      }
//-----------------------------------------
      dataFile.seek(18); // set read pionter to correct byte.
      Frame_Time = (dataFile.read()); // Read FPS.
      while (PSEQ_Headder_Debug){ // debug print.
      Serial.println(); // debug print.
      Serial.print("Frame Timing: "); // debug print.
      Serial.print(Frame_Time, DEC); // debug print.
      Serial.print(" "); // debug print.
      break;
      }
//-----------------------------------------
     while (Debug){ // debug print.
     Serial.print(PSEQ_Data_File); // debug print.
     Serial.println(" and variables set."); // debug print.
     delay(10);
     break;
     }}
  // if the file isn't open, pop up an error:
     else {
     while (Debug){ // debug print.
     Serial.print("error opening "); // debug print.
     Serial.println(PSEQ_Data_File); // debug print.
     break;
     }}
     return;
     }
/////////////////////////////////////////////////////////

File fortytwo:
Code: [Select]
fortytwo:4036:0072:11

Byte 0 -> 7    Header to check file is correct for setup data.
Byte 9 -> 12    First channel (Decimal number must be 4 digits).
Byte 14 -> 17    Number of pixels (Decimal number must be 4 digits).
Byte 19        Debug main (1 on).
Byte 20        Debug advanced (1 on).

The details of the replacement for the ESP can be found in this post. http://doityourselfchristmas.com/forums/showthread.php?43345-Using-SD-cards-with-ESP8266-s

I still have some work to do on the timing correction it's very clunky at the moment (check back for updates). I think I'll add an option to join a show, this means that if the start packet is missed for any reason but a sync packet is received and the show is not running, it will implement the start routine and join in even if this is half way through.

This is development I would not base my show at this point in time until more testing has been done!

I'll post a video tomorrow.[/i][/i]
« Last Edit: June 14, 2016, 03:26:33 PM by Barnabybear »

Online CaptainMurdoch

  • Administrator
  • *****
  • Join Date: Sep 2013
  • Location: Washington
  • Posts: 8,324
  • Kudos: 155
Re: Multisync start and intermediate packet formats.
« Reply #10 on: June 14, 2016, 03:26:30 PM »
Nice.

How are you handling sync, do you speed up and slow down based on the sync packet received from FPP or do you skip ahead if you are behind and repeat frames if you are ahead?

Offline Barnabybear

  • Newbie
  • *
  • Join Date: May 2016
  • Location:
  • Posts: 20
  • Kudos: 0
Re: Multisync start and intermediate packet formats.
« Reply #11 on: June 14, 2016, 05:24:37 PM »
 Hi, the time sync is the last bit to sort out. In the code posted; each time a new sync frame number is received it overwrites the code generated frame number. This results in jumps backwards and repeated frames at the moment as the code is running to fast. Easier to see in the video I’ll post tomorrow.
It does this because at the moment, I’m not really sure the best way to progress the timing. It's documented that the ESP's cycle frequency can vary by about 4 minutes a day dependant on temperature. Which over a 3 minute sequence is not a lot (6 mS) so it’s not a bad base to work from and I can add correction factors to this.
The first being based on the actual time in millis from the start packet of the previous sequence to the end packet of the previous packet and make an adjustment to the overall timing based on this. This should keep the internal timing of the next sequence fairly close.
The next correction is the one I can't get my head round at the moment, as it partly depends on the FPS and the application, and boils down to two options; instantly correct or slowly correct. Assuming that the code timing is close then an instant correct shouldn't be a problem, however this may be noticeable. Whereas a slow correction wouldn’t be noticeable but may be quite annoying if part of the show is running ahead or behind. As with everything it will boil down to testing and have to include some veting of the packets as I've had four arrive at the same time.

The safe option might be to code both and have it as a selectable in file ‘fortytwo’ as to which fits best.
 
« Last Edit: June 14, 2016, 05:32:43 PM by Barnabybear »

 

Back to top