#lighthack wifi

Hello, I am trying to create a wifi box. Using the available code, I made some modifications to use esp8266, I got it to work halfway. In the EOS terminal I see that it receives ping and subcrisciption of parameters. The problem is in the last part of the code when we ask that you receive parameters from EOS.

the original code says

// Then we check to see if any OSC commands have come from Eos
// and update the display accordingly.
size = SLIPSerial.available();
if (size > 0)
{
// Fill the msg with all of the available bytes
while (size--)
curMsg += (char)(SLIPSerial.read());
}
if (SLIPSerial.endofPacket())
{
parseOSCMessage(curMsg);
lastMessageRxTime = millis();
// We only care about the ping if we haven't heard recently
// Clear flag when we get any traffic
timeoutPingSent = false;
curMsg = String();
}
if (lastMessageRxTime > 0)
{
unsigned long diff = millis() - lastMessageRxTime;
//We first check if it's been too long and we need to time out
if (diff > TIMEOUT_AFTER_IDLE_INTERVAL)
{
connectedToConsole = ConsoleNone;
lastMessageRxTime = 0;
updateDisplay = true;
timeoutPingSent = false;
}
//It could be the console is sitting idle. Send a ping once to
// double check that it's still there, but only once after 2.5s have passed
if (!timeoutPingSent && diff > PING_AFTER_IDLE_INTERVAL)
{
OSCMessage ping("/eos/ping");
ping.add(BOX_NAME_STRING "_hello"); // This way we know who is sending the ping
SLIPSerial.beginPacket();
ping.send(SLIPSerial);
SLIPSerial.endPacket();
timeoutPingSent = true;
}
}
if (updateDisplay)
displayStatus();
}

and I'm trying to do it that way

size = Udp.available();
if (size > 0)
{
// Fill the msg with all of the available bytes
while (size--)
curMsg += (char)(Udp.read());
}
if (Udp.endofPacket())
{
parseOSCMessage(curMsg);
lastMessageRxTime = millis();
// We only care about the ping if we haven't heard recently
// Clear flag when we get any traffic
timeoutPingSent = false;
curMsg = String();
}

if (lastMessageRxTime > 0)
{
unsigned long diff = millis() - lastMessageRxTime;
//We first check if it's been too long and we need to time out
if (diff > TIMEOUT_AFTER_IDLE_INTERVAL)
{
connectedToConsole = ConsoleNone;
lastMessageRxTime = 0;
updateDisplay = true;
timeoutPingSent = false;
}

//It could be the console is sitting idle. Send a ping once to
// double check that it's still there, but only once after 2.5s have passed
if (!timeoutPingSent && diff > PING_AFTER_IDLE_INTERVAL)
{
OSCMessage ping("/eos/ping");
ping.add(BOX_NAME_STRING "_hello"); // This way we know who is sending the ping
Udp.beginPacket(outIp, outPort);
ping.send(Udp);
Udp.endPacket();
timeoutPingSent = true;
}
}

if (updateDisplay)
displayStatus();
}
}

I have the problem in the line marked in red. what would be the correct way to translate this for "Udp"?

Thanks!!

  • There is no method endOfPacket() for UDP. Let the if statement away.

  • And how should I do it then?

  • size = Udp.available();
    if (size > 0)
    {
    // Fill the msg with all of the available bytes
    while (size--)
    curMsg += (char)(Udp.read());
    }
    if (Udp.endofPacket())
    {
    parseOSCMessage(curMsg);
    lastMessageRxTime = millis();
    // We only care about the ping if we haven't heard recently
    // Clear flag when we get any traffic
    timeoutPingSent = false;
    curMsg = String();
    }

    try 

    size = Udp.available();
    if (size > 0)
    {
    // Fill the msg with all of the available bytes
    while (size--)
    curMsg += (char)(Udp.read());
    parseOSCMessage(curMsg);
    lastMessageRxTime = millis();
    // We only care about the ping if we haven't heard recently
    // Clear flag when we get any traffic
    timeoutPingSent = false;
    curMsg = String();
    }

  • there is no luck .. in my lcd there appears a moment the values ​​of pan and tilt when it starts but they are not updated after they disappear and do not appear again.
    I also don't see the handshake in NOMAD terminal, although if I see ping and parameter subcription, I don't know if it will have anything to do with it.

  • Delete the part of the handshake code, it is not needed for UDP/TCP.
    If you see the ping, it is fine.

  • Hi Sstaub
    I'm still fighting, I have problems with the display on the screen of Panandtil values. just see box1 ...
    On the serial monitor I see packets arrive, so I think the problem is routing and updating.

    I leave my code in case you could take a look
    Thank you!

    #if defined(ESP8266)
    #include <ESP8266WiFi.h>
    #else
    #include <WiFi.h>
    #endif
    #include <WiFiUdp.h>

    #include <OSCBoards.h>
    #include <OSCBundle.h>
    #include <OSCData.h>
    #include <OSCMatch.h>
    #include <OSCMessage.h>
    #include <OSCTiming.h>
    #include <Wire.h>
    #include <LiquidCrystal_I2C.h>
    #include <string.h>

    /*******************************************************************************
    Macros and Constants
    ******************************************************************************/
    #//define LCD_CHARS 16
    #//define LCD_LINES 2 // Currently assume at least 2 lines

    #define NEXT_BTN 4
    #define LAST_BTN 5
    #define SHIFT_BTN 3

    #define SUBSCRIBE ((int32_t)1)
    #define UNSUBSCRIBE ((int32_t)0)

    #define EDGE_DOWN ((int32_t)1)
    #define EDGE_UP ((int32_t)0)

    #define FORWARD 0
    #define REVERSE 1

    // Change these values to switch which direction increase/decrease pan/tilt
    #define PAN_DIR FORWARD
    #define TILT_DIR FORWARD

    // Use these values to make the encoder more coarse or fine.
    // This controls the number of wheel "ticks" the device sends to the console
    // for each tick of the encoder. 1 is the default and the most fine setting.
    // Must be an integer.
    #define PAN_SCALE 3
    #define TILT_SCALE 3

    #define SIG_DIGITS 3 // Number of significant digits displayed

    #define OSC_BUF_MAX_SIZE 512

    const String HANDSHAKE_QUERY = "ETCOSC?";
    const String HANDSHAKE_REPLY = "OK";

    //See displayScreen() below - limited to 10 chars (after 6 prefix chars)
    #define VERSION_STRING "2.0.0.1"

    #define BOX_NAME_STRING "box1"

    // Change these values to alter how long we wait before sending an OSC ping
    // to see if Eos is still there, and then finally how long before we
    // disconnect and show the splash screen
    // Values are in milliseconds
    #define PING_AFTER_IDLE_INTERVAL 2500
    #define TIMEOUT_AFTER_IDLE_INTERVAL 5000


    char ssid[] = "TBA Wifi ofi"; // your network SSID (name)
    char pass[] = "10100TBA"; // your network password

    WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
    const IPAddress outIp(192,168,1,51); // remote IP of your computer
    const unsigned int outPort = 8000; // remote port to receive OSC
    const unsigned int localPort = 9000; // local port to listen for OSC packets (actually not used for sending)


    /*******************************************************************************
    Local Types
    ******************************************************************************/
    enum WHEEL_TYPE { TILT, PAN };
    enum WHEEL_MODE { COARSE, FINE };

    struct Encoder
    {
    uint8_t pinA;
    uint8_t pinB;
    int pinAPrevious;
    int pinBPrevious;
    float pos;
    uint8_t direction;
    };
    struct Encoder panWheel;
    struct Encoder tiltWheel;

    enum ConsoleType
    {
    ConsoleNone,
    ConsoleEos,
    ConsoleCobalt,
    ConsoleColorSource
    };

    /*******************************************************************************
    Global Variables
    ******************************************************************************/

    // initialize the library with the numbers of the interface pins
    LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address of the first lcd to 0x20

    bool updateDisplay = false;
    ConsoleType connectedToConsole = ConsoleNone;
    unsigned long lastMessageRxTime = 0;
    bool timeoutPingSent = false;

    /*******************************************************************************
    Local Functions
    ******************************************************************************/

    /*******************************************************************************
    Issues all our subscribes to Eos. When subscribed, Eos will keep us updated
    with the latest values for a given parameter.

    Parameters: none

    Return Value: void

    ******************************************************************************/

    void issueEosSubscribes()
    {
    // Add a filter so we don't get spammed with unwanted OSC messages from Eos
    OSCMessage filter("/eos/filter/add");
    filter.add("/eos/out/param/*");
    filter.add("/eos/out/ping");
    Udp.beginPacket(outIp, outPort);
    filter.send(Udp);
    Udp.endPacket();

    // subscribe to Eos pan & tilt updates
    OSCMessage subPan("/eos/subscribe/param/pan");
    subPan.add(SUBSCRIBE);
    Udp.beginPacket(outIp, outPort);
    subPan.send(Udp);
    Udp.endPacket();

    OSCMessage subTilt("/eos/subscribe/param/tilt");
    subTilt.add(SUBSCRIBE);
    Udp.beginPacket(outIp, outPort);
    subTilt.send(Udp);
    Udp.endPacket();
    }

    /*******************************************************************************
    Given a valid OSCMessage (relevant to Pan/Tilt), we update our Encoder struct
    with the new position information.

    Parameters:
    msg - The OSC message we will use to update our internal data
    addressOffset - Unused (allows for multiple nested roots)

    Return Value: void

    ******************************************************************************/

    void parseFloatPanUpdate(OSCMessage& msg, int addressOffset)
    {
    panWheel.pos = msg.getOSCData(0)->getFloat();
    updateDisplay = true;
    }

    void parseFloatTiltUpdate(OSCMessage& msg, int addressOffset)
    {
    tiltWheel.pos = msg.getOSCData(0)->getFloat();
    updateDisplay = true;
    }

    void parseEos(OSCMessage& msg, int addressOffset)
    {
    // If we don't think we're connected, reconnect and subscribe
    if (connectedToConsole != ConsoleEos)
    {
    issueEosSubscribes();
    connectedToConsole = ConsoleEos;
    updateDisplay = true;
    }

    if (!msg.route("/out/param/pan", parseFloatPanUpdate, addressOffset))
    msg.route("/out/param/tilt", parseFloatTiltUpdate, addressOffset);
    }

    /******************************************************************************/

    void parseCobalt(OSCMessage& msg, int addressOffset)
    {
    // Cobalt doesn't currently send anything other than ping
    connectedToConsole = ConsoleCobalt;
    updateDisplay = true;
    }

    void parseColorSource(OSCMessage& msg, int addressOffset)
    {
    // ColorSource doesn't currently send anything other than ping
    connectedToConsole = ConsoleColorSource;
    updateDisplay = true;
    }

    /*******************************************************************************
    Given an unknown OSC message we check to see if it's a handshake message.
    If it's a handshake we issue a subscribe, otherwise we begin route the OSC
    message to the appropriate function.

    Parameters:
    msg - The OSC message of unknown importance

    Return Value: void

    ******************************************************************************/

    void parseOSCMessage(String& msg)
    {
    // check to see if this is the handshake string
    if (msg.indexOf(HANDSHAKE_QUERY) != -1)
    {
    // handshake string found!

    Udp.beginPacket(outIp, outPort);
    Udp.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length());
    Udp.endPacket();


    // An Eos would do nothing until subscribed
    // Let Eos know we want updates on some things
    issueEosSubscribes();

    updateDisplay = true;
    }
    else
    {
    // prepare the message for routing by filling an OSCMessage object with our message string
    OSCMessage oscmsg;
    oscmsg.fill((uint8_t*)msg.c_str(), (int)msg.length());
    // route pan/tilt messages to the relevant update function

    // Try the various OSC routes
    if (oscmsg.route("/eos", parseEos))
    return;
    if (oscmsg.route("/cobalt", parseCobalt))
    return;
    if (oscmsg.route("/cs", parseColorSource))
    return;
    }
    }

    /*******************************************************************************
    Updates the display with the latest pan and tilt positions.

    Parameters: none

    Return Value: void

    ******************************************************************************/
    void displayStatus()
    {
    lcd.clear();


    switch (connectedToConsole)
    {
    case ConsoleNone:
    {
    // display a splash message before the Eos connection is open
    lcd.setCursor(0, 0);
    lcd.print(BOX_NAME_STRING " v" VERSION_STRING);
    lcd.setCursor(0, 1);
    lcd.print("Waiting...");
    } break;

    case ConsoleEos:
    {
    // put the cursor at the begining of the first line
    lcd.setCursor(0, 0);
    lcd.print("Pan: ");
    lcd.print(panWheel.pos, SIG_DIGITS);

    // put the cursor at the begining of the second line
    lcd.setCursor(0, 1);
    lcd.print("Tilt: ");
    lcd.print(tiltWheel.pos, SIG_DIGITS);
    } break;

    case ConsoleCobalt:
    {
    lcd.setCursor(7, 0);
    lcd.print("Cobalt");
    lcd.setCursor(0, 1);
    lcd.print("Pan");
    lcd.setCursor(12, 1);
    lcd.print("Tilt");
    } break;

    case ConsoleColorSource:
    {
    lcd.setCursor(2, 0);
    lcd.print("ColorSource");
    lcd.setCursor(0, 1);
    lcd.print("Pan");
    lcd.setCursor(12, 1);
    lcd.print("Tilt");
    } break;

    }

    updateDisplay = false;
    }

    /*******************************************************************************
    Initializes a given encoder struct to the requested parameters.

    Parameters:
    encoder - Pointer to the encoder we will be initializing
    pinA - Where the A pin is connected to the Arduino
    pinB - Where the B pin is connected to the Arduino
    direction - Determines if clockwise or counterclockwise is "forward"

    Return Value: void

    ******************************************************************************/

    void initEncoder(struct Encoder* encoder, uint8_t pinA, uint8_t pinB, uint8_t direction)
    {
    encoder->pinA = pinA;
    encoder->pinB = pinB;
    encoder->pos = 0;
    encoder->direction = direction;

    pinMode(pinA, INPUT_PULLUP);
    pinMode(pinB, INPUT_PULLUP);

    encoder->pinAPrevious = digitalRead(pinA);
    encoder->pinBPrevious = digitalRead(pinB);
    }

    /*******************************************************************************
    Checks if the encoder has moved by comparing the previous state of the pins
    with the current state. If they are different, we know there is movement.
    In the event of movement we update the current state of our pins.

    Parameters:
    encoder - Pointer to the encoder we will be checking for motion

    Return Value:
    encoderMotion - Returns the 0 if the encoder has not moved
    1 for forward motion
    -1 for reverse motion

    ******************************************************************************/

    int8_t updateEncoder(struct Encoder* encoder)
    {
    int8_t encoderMotion = 0;
    int pinACurrent = digitalRead(encoder->pinA);
    int pinBCurrent = digitalRead(encoder->pinB);

    // has the encoder moved at all?
    if (encoder->pinAPrevious != pinACurrent)
    {
    // Since it has moved, we must determine if the encoder has moved forwards or backwards
    encoderMotion = (encoder->pinAPrevious == encoder->pinBPrevious) ? -1 : 1;

    // If we are in reverse mode, flip the direction of the encoder motion
    if (encoder->direction == REVERSE)
    encoderMotion = -encoderMotion;
    }
    encoder->pinAPrevious = pinACurrent;
    encoder->pinBPrevious = pinBCurrent;

    return encoderMotion;
    }

    /*******************************************************************************
    Sends a message to Eos informing them of a wheel movement.

    Parameters:
    type - the type of wheel that's moving (i.e. pan or tilt)
    ticks - the direction and intensity of the movement

    Return Value: void

    ******************************************************************************/

    void sendOscMessage(const String &address, float value)
    {
    OSCMessage msg(address.c_str());
    msg.add(value);
    Udp.beginPacket(outIp, outPort);
    msg.send(Udp);
    Udp.endPacket();
    }

    void sendEosWheelMove(WHEEL_TYPE type, float ticks)
    {
    String wheelMsg("/eos/wheel");

    if (digitalRead(SHIFT_BTN) == LOW)
    wheelMsg.concat("/fine");
    else
    wheelMsg.concat("/coarse");

    if (type == PAN)
    wheelMsg.concat("/pan");
    else if (type == TILT)
    wheelMsg.concat("/tilt");
    else
    // something has gone very wrong
    return;

    sendOscMessage(wheelMsg, ticks);
    }

    void sendCobaltWheelMove(WHEEL_TYPE type, float ticks)
    {
    String wheelMsg("/cobalt/param");

    if (type == PAN)
    wheelMsg.concat("/pan/wheel");
    else if (type == TILT)
    wheelMsg.concat("/tilt/wheel");
    else
    // something has gone very wrong
    return;

    if (digitalRead(SHIFT_BTN) != LOW)
    ticks = ticks * 16;

    sendOscMessage(wheelMsg, ticks);
    }

    void sendColorSourceWheelMove(WHEEL_TYPE type, float ticks)
    {
    String wheelMsg("/cs/param");

    if (type == PAN)
    wheelMsg.concat("/pan/wheel");
    else if (type == TILT)
    wheelMsg.concat("/tilt/wheel");
    else
    // something has gone very wrong
    return;

    if (digitalRead(SHIFT_BTN) != LOW)
    ticks = ticks * 2;

    sendOscMessage(wheelMsg, ticks);
    }

    /******************************************************************************/

    void sendWheelMove(WHEEL_TYPE type, float ticks)
    {
    switch (connectedToConsole)
    {
    default:
    case ConsoleEos:
    sendEosWheelMove(type, ticks);
    break;
    case ConsoleCobalt:
    sendCobaltWheelMove(type, ticks);
    break;
    case ConsoleColorSource:
    sendColorSourceWheelMove(type, ticks);
    break;
    }
    }

    /*******************************************************************************
    Sends a message to the console informing them of a key press.

    Parameters:
    down - whether a key has been pushed down (true) or released (false)
    key - the OSC key name that has moved

    Return Value: void

    ******************************************************************************/

    void sendKeyPress(bool down, const String &key)
    {
    String keyAddress;
    switch (connectedToConsole)
    {
    default:
    case ConsoleEos:
    keyAddress = "/eos/key/" + key;
    break;
    case ConsoleCobalt:
    keyAddress = "/cobalt/key/" + key;
    break;
    case ConsoleColorSource:
    keyAddress = "/cs/key/" + key;
    break;
    }
    OSCMessage keyMsg(keyAddress.c_str());

    if (down)
    keyMsg.add(EDGE_DOWN);
    else
    keyMsg.add(EDGE_UP);

    Udp.beginPacket(outIp, outPort);
    keyMsg.send(Udp);
    Udp.endPacket();
    }

    /*******************************************************************************
    Checks the status of all the relevant buttons (i.e. Next & Last)

    NOTE: This does not check the shift key. The shift key is used in tandem with
    the encoder to determine coarse/fine mode and thus does not report directly.

    Parameters: none

    Return Value: void

    ******************************************************************************/

    void checkButtons()
    {
    // OSC configuration
    const int keyCount = 2;
    const int keyPins[2] = {NEXT_BTN, LAST_BTN};
    const String keyNames[4] = {
    "NEXT", "LAST",
    "soft6", "soft4"
    };

    static int keyStates[2] = {HIGH, HIGH};

    // Eos and Cobalt buttons are the same
    // ColorSource is different
    int firstKey = (connectedToConsole == ConsoleColorSource) ? 2 : 0;

    // Loop over the buttons
    for (int keyNum = 0; keyNum < keyCount; ++keyNum)
    {
    // Has the button state changed
    if (digitalRead(keyPins[keyNum]) != keyStates[keyNum])
    {
    // Notify console of this key press
    if (keyStates[keyNum] == LOW)
    {
    sendKeyPress(false, keyNames[firstKey + keyNum]);
    keyStates[keyNum] = HIGH;
    }
    else
    {
    sendKeyPress(true, keyNames[firstKey + keyNum]);
    keyStates[keyNum] = LOW;
    }
    }
    }
    }

    /*******************************************************************************
    Here we setup our encoder, lcd, and various input devices. We also prepare
    to communicate OSC with Eos by setting up SLIPSerial. Once we are done with
    setup() we pass control over to loop() and never call setup() again.

    NOTE: This function is the entry function. This is where control over the
    Arduino is passed to us (the end user).

    Parameters: none

    Return Value: void

    ******************************************************************************/
    void setup()
    {
    lcd.init(); // initialize the first lcd
    lcd.backlight();


    Serial.begin(115200);
    Udp.begin(115200);
    // This is a hack around an Arduino bug. It was taken from the OSC library
    //examples

    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid, pass);

    while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    }
    Serial.println("");

    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());

    Serial.println("Starting UDP");
    Udp.begin(localPort);
    Serial.print("Local port: ");
    #ifdef ESP32
    Serial.println(localPort);
    #else
    Serial.println(Udp.localPort());
    #endif
    // This is necessary for reconnecting a device because it needs some time
    // for the serial port to open. The handshake message may have been sent
    // from the console before #lighthack was ready

    Udp.beginPacket(outIp, outPort);
    Udp.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length());
    Udp.endPacket();

    // If it's an Eos, request updates on some things
    issueEosSubscribes();

    initEncoder(&panWheel, 0, 2, PAN_DIR);
    initEncoder(&tiltWheel, 12, 14, TILT_DIR);

    pinMode(NEXT_BTN, INPUT_PULLUP);
    pinMode(LAST_BTN, INPUT_PULLUP);
    pinMode(SHIFT_BTN, INPUT_PULLUP);

    displayStatus();
    }

    /*******************************************************************************
    Here we service, monitor, and otherwise control all our peripheral devices.
    First, we retrieve the status of our encoders and buttons and update Eos.
    Next, we check if there are any OSC messages for us.
    Finally, we update our display (if an update is necessary)

    NOTE: This function is our main loop and thus this function will be called
    repeatedly forever

    Parameters: none

    Return Value: void

    ******************************************************************************/
    void loop()

    {

    static String curMsg;
    int size;
    // get the updated state of each encoder
    int32_t panMotion = updateEncoder(&panWheel);
    int32_t tiltMotion = updateEncoder(&tiltWheel);

    // Scale the result by a scaling factor
    panMotion *= PAN_SCALE;
    tiltMotion *= TILT_SCALE;

    // check for next/last updates
    checkButtons();

    // now update our wheels
    if (tiltMotion != 0)
    sendWheelMove(TILT, tiltMotion);

    if (panMotion != 0)
    sendWheelMove(PAN, panMotion);

    // Then we check to see if any OSC commands have come from Eos
    // and update the display accordingly.

    size = Udp.available();
    if (size > 0)
    {
    // Fill the msg with all of the available bytes
    while (size--)
    curMsg += (char)(Udp.read());
    parseOSCMessage(curMsg);
    lastMessageRxTime = millis();
    // We only care about the ping if we haven't heard recently
    // Clear flag when we get any traffic
    timeoutPingSent = false;
    curMsg = String();
    }

    if (lastMessageRxTime > 0)
    {
    unsigned long diff = millis() - lastMessageRxTime;
    //We first check if it's been too long and we need to time out
    if (diff > TIMEOUT_AFTER_IDLE_INTERVAL)
    {
    connectedToConsole = ConsoleNone;
    lastMessageRxTime = 0;
    updateDisplay = true;
    timeoutPingSent = false;
    }

    //It could be the console is sitting idle. Send a ping once to
    // double check that it's still there, but only once after 2.5s have passed
    if (!timeoutPingSent && diff > PING_AFTER_IDLE_INTERVAL)
    {
    OSCMessage ping("/eos/ping");
    ping.add(BOX_NAME_STRING "_hello"); // This way we know who is sending the ping
    Udp.beginPacket(outIp, outPort);
    ping.send(Udp);
    Udp.endPacket();
    timeoutPingSent = true;
    }
    }

    if (updateDisplay)
    displayStatus();

    }

  • On the first look, I think the problem is the I2C connection of the display. You can try to print the parameter values to the Serial port instead to the lcd display, so you can see if you reach data. Maybe the I2C connection is blocked or it is a timing problem, but sorry I have never worked with I2C displays on lighthack.

  • In serial I see the first value it has when it starts, then it doesn't update again. I think the problem is in the sequence where the data is routed

  • Can you change the values with your encoders?

  • Yes, everything works.

  • Are the parameter subscribed? (you see it in Tab99)?

  • yes, it subscribes correctly to the parameters of panandtil

  • can you send the code as a file?

  • which board did you use? any special library? which IDE?

Related