Tinker Neopixel: a test

I have combined the Neopixel library with the Tinker app and am changing the color of the LEDs with input on the app! There’s some pretty strange behaviour though - I would appreciate having a fellow WS2812 enthusiast check this out.

This requires a bit of setup - on the board, pins A0, A1, A2 are jumpered up to A3, A4, A5 respectively. Then on the app, A0-A2 are set to analogWrite while A3-A5 are set to analogRead… this allows values to be adjusted on the analogWrite pins for input and use in the core program.

Code is as follows:

/**
 ******************************************************************************
 * @file    application.cpp
 * @authors  Satish Nair, Zachary Crockett and Mohit Bhoite
 * @version V1.0.0
 * @date    05-November-2013
 * @brief   Tinker application
 ******************************************************************************
  Copyright (c) 2013 Spark Labs, Inc.  All rights reserved.

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation, either
  version 3 of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this program; if not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************
 */

/* Includes ------------------------------------------------------------------*/  
#include "application.h"

/* Function prototypes -------------------------------------------------------*/
int tinkerDigitalRead(String pin);
int tinkerDigitalWrite(String command);
int tinkerAnalogRead(String pin);
int tinkerAnalogWrite(String command);


/*-------------------------------------------------------------------------
  Spark Core library to control WS2812 based RGB
  LED devices such as Adafruit NeoPixel strips.
  Currently handles 800 KHz bitstream on Spark Core, 
  with LEDs wired for GRB color order.

  Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
  Modified to work with Spark Core by Technobly.
  Contributions by PJRC and other members of the open source community.

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing products
  from Adafruit!
  --------------------------------------------------------------------*/
  
/* ======================= Adafruit_NeoPixel.h ======================= */  
/*--------------------------------------------------------------------
  This file is part of the Adafruit NeoPixel library.

  NeoPixel is free software: you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation, either version 3 of
  the License, or (at your option) any later version.

  NeoPixel is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with NeoPixel.  If not, see
  <http://www.gnu.org/licenses/>.
  --------------------------------------------------------------------*/


class Adafruit_NeoPixel {

 public:

  // Constructor: number of LEDs, pin number, LED type
  Adafruit_NeoPixel(uint16_t n, uint8_t p=6);
  ~Adafruit_NeoPixel();

  void
    begin(void),
    show(void),
    setPin(uint8_t p),
    setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b),
    setPixelColor(uint16_t n, uint32_t c),
    setBrightness(uint8_t);
  uint8_t
   *getPixels() const;
  uint16_t
    numPixels(void) const;
  static uint32_t
    Color(uint8_t r, uint8_t g, uint8_t b);
  uint32_t
    getPixelColor(uint16_t n) const;

 private:

  const uint16_t
    numLEDs,       // Number of RGB LEDs in strip
    numBytes;      // Size of 'pixels' buffer below
  uint8_t
    pin,           // Output pin number
    brightness,
   *pixels;        // Holds LED color values (3 bytes each)
  uint32_t
    endTime;       // Latch timing reference
};

/* ======================= Adafruit_NeoPixel.cpp ======================= */
/*-------------------------------------------------------------------------
  This file is part of the Adafruit NeoPixel library.

  NeoPixel is free software: you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation, either version 3 of
  the License, or (at your option) any later version.

  NeoPixel is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with NeoPixel.  If not, see
  <http://www.gnu.org/licenses/>.
  -------------------------------------------------------------------------*/

Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, uint8_t p) : numLEDs(n), numBytes(n), pin(p), pixels(NULL)
{
  if((pixels = (uint8_t *)malloc(numBytes))) {
    memset(pixels, 0, numBytes);
  }
}

Adafruit_NeoPixel::~Adafruit_NeoPixel() {
  if(pixels) free(pixels);
  pinMode(pin, INPUT);
}

void Adafruit_NeoPixel::begin(void) {
  pinMode(pin, OUTPUT);
  digitalWrite(pin, LOW);
}

void Adafruit_NeoPixel::show(void) {
  if(!pixels) return;

  // Data latch = 50+ microsecond pause in the output stream.  Rather than
  // put a delay at the end of the function, the ending time is noted and
  // the function will simply hold off (if needed) on issuing the
  // subsequent round of data until the latch time has elapsed.  This
  // allows the mainline code to start generating the next frame of data
  // rather than stalling for the latch.
  while((micros() - endTime) < 50L);
  // endTime is a private member (rather than global var) so that multiple
  // instances on different pins can be quickly issued in succession (each
  // instance doesn't delay the next).

  noInterrupts(); // Need 100% focus on instruction timing

  volatile uint32_t 
    c,    // 24-bit pixel color
    mask; // 8-bit mask
  volatile uint16_t i = numBytes; // Output loop counter
  volatile uint8_t
    j,              // 8-bit inner loop counter
   *ptr = pixels,   // Pointer to next byte
    g,              // Current green byte value
    r,              // Current red byte value
    b;              // Current blue byte value
  
  while(i) { // While bytes left...
    mask = 0x1000000; // reset the mask, start 1 higher than
    i--; // decrement bytes remaining
    g = *ptr++;   // Next green byte value
    r = *ptr++;   // Next red byte value
    b = *ptr++;   // Next blue byte value
    c = ((uint32_t)g << 16) | ((uint32_t)r <<  8) | b; // Pack the next 3 bytes to keep timing tight
    for (j=0; j<24; j++) { // iterate through 24-bits of next pixel, MSB to LSB.
      if (c & (mask >>= 1)) { // mask shifts first, then & with c
        // 700ns HIGH (meas. 694ns)
        PIN_MAP[pin].gpio_peripheral->BSRR = PIN_MAP[pin].gpio_pin; // HIGH
        asm volatile(
          "mov r0, r0" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" 
          "nop" "\n\t" "nop" "\n\t"
          ::: "r0", "cc", "memory");
        // 600ns LOW (meas. 598ns)
        PIN_MAP[pin].gpio_peripheral->BRR = PIN_MAP[pin].gpio_pin; // LOW
      } else {
        // 350ns HIGH (meas. 360ns)
        PIN_MAP[pin].gpio_peripheral->BSRR = PIN_MAP[pin].gpio_pin; // HIGH
        asm volatile(
          "mov r0, r0" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t"
          ::: "r0", "cc", "memory");
        // 800ns LOW (meas. 792ns)
        PIN_MAP[pin].gpio_peripheral->BRR = PIN_MAP[pin].gpio_pin; // LOW
        asm volatile(
          "mov r0, r0" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop" "\n\t"
          ::: "r0", "cc", "memory");
      }
    }
  } // end while(i)

  interrupts();
  endTime = micros(); // Save EOD time for latch on next call
}

// Set the output pin number
void Adafruit_NeoPixel::setPin(uint8_t p) {
  pinMode(pin, INPUT);
  pin = p;
  pinMode(p, OUTPUT);
  digitalWrite(p, LOW);
}

// Set pixel color from separate R,G,B components:
void Adafruit_NeoPixel::setPixelColor(
 uint16_t n, uint8_t r, uint8_t g, uint8_t b) {
  if(n < numLEDs) {
    if(brightness) { // See notes in setBrightness()
      r = (r * brightness) >> 8;
      g = (g * brightness) >> 8;
      b = (b * brightness) >> 8;
    }
    uint8_t *p = &pixels[n * 3];
    *p++ = g;
    *p++ = r;
    *p = b;
  }
}

// Set pixel color from 'packed' 32-bit RGB color:
void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint32_t c) {
  if(n < numLEDs) {
    uint8_t
      r = (uint8_t)(c >> 16),
      g = (uint8_t)(c >>  8),
      b = (uint8_t)c;
    if(brightness) { // See notes in setBrightness()
      r = (r * brightness) >> 8;
      g = (g * brightness) >> 8;
      b = (b * brightness) >> 8;
    }
    uint8_t *p = &pixels[n * 3];
    *p++ = g;
    *p++ = r;
    *p = b;
  }
}

// Convert separate R,G,B into packed 32-bit RGB color.
// Packed format is always RGB, regardless of LED strand color order.
uint32_t Adafruit_NeoPixel::Color(uint8_t r, uint8_t g, uint8_t b) {
  return ((uint32_t)r << 16) | ((uint32_t)g <<  8) | b;
}

// Query color from previously-set pixel (returns packed 32-bit RGB value)
uint32_t Adafruit_NeoPixel::getPixelColor(uint16_t n) const {

  if(n < numLEDs) {
    uint16_t ofs = n * 3;
    return (uint32_t)(pixels[ofs + 2]) |
      ((uint32_t)(pixels[ofs    ]) <<  8) |
      ((uint32_t)(pixels[ofs + 1]) << 16);
  }

  return 0; // Pixel # is out of bounds
}

uint8_t *Adafruit_NeoPixel::getPixels(void) const {
  return pixels;
}

uint16_t Adafruit_NeoPixel::numPixels(void) const {
  return numLEDs;
}

// Adjust output brightness; 0=darkest (off), 255=brightest.  This does
// NOT immediately affect what's currently displayed on the LEDs.  The
// next call to show() will refresh the LEDs at this level.  However,
// this process is potentially "lossy," especially when increasing
// brightness.  The tight timing in the WS2811/WS2812 code means there
// aren't enough free cycles to perform this scaling on the fly as data
// is issued.  So we make a pass through the existing color data in RAM
// and scale it (subsequent graphics commands also work at this
// brightness level).  If there's a significant step up in brightness,
// the limited number of steps (quantization) in the old data will be
// quite visible in the re-scaled version.  For a non-destructive
// change, you'll need to re-render the full strip data.  C'est la vie.
void Adafruit_NeoPixel::setBrightness(uint8_t b) {
  // Stored brightness value is different than what's passed.
  // This simplifies the actual scaling math later, allowing a fast
  // 8x8-bit multiply and taking the MSB.  'brightness' is a uint8_t,
  // adding 1 here may (intentionally) roll over...so 0 = max brightness
  // (color values are interpreted literally; no scaling), 1 = min
  // brightness (off), 255 = just below max brightness.
  uint8_t newBrightness = b + 1;
  if(newBrightness != brightness) { // Compare against prior value
    // Brightness has changed -- re-scale existing data in RAM
    uint8_t  c,
            *ptr           = pixels,
             oldBrightness = brightness - 1; // De-wrap old brightness value
    uint16_t scale;
    if(oldBrightness == 0) scale = 0; // Avoid /0
    else if(b == 255) scale = 65535 / oldBrightness;
    else scale = (((uint16_t)newBrightness << 8) - 1) / oldBrightness;
    for(uint16_t i=0; i<numBytes; i++) {
      c      = *ptr;
      *ptr++ = (c * scale) >> 8;
    }
    brightness = newBrightness;
  }
}

/* ======================= SparkPixel.cpp ======================= */

// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Pixels are wired for GRB bitstream
// 800 KHz bitstream (e.g. High Density LED strip) - WS2812 (6-pin part)
#define PIN D0
Adafruit_NeoPixel strip = Adafruit_NeoPixel(20, PIN);

int cRed=0;
int cGrn=0;
int cBlu=0;

/* This function is called once at start up ----------------------------------*/
void setup()
{
	//Setup the Tinker application here

	//Register all the Tinker functions
	Spark.function("digitalread", tinkerDigitalRead);
	Spark.function("digitalwrite", tinkerDigitalWrite);

	Spark.function("analogread", tinkerAnalogRead);
	Spark.function("analogwrite", tinkerAnalogWrite);
	
	strip.begin();
    strip.show(); // Initialize all pixels to 'off'
    
    pinMode(A5, INPUT);
	pinMode(A4, INPUT);
	pinMode(A3, INPUT);
	
	pinMode(A2, OUTPUT);
	pinMode(A1, OUTPUT);
	pinMode(A0, OUTPUT);

}

/* This function loops forever --------------------------------------------*/
void loop(){
	
	//rainbowCycle(20);
	cRed = analogRead(A5);
    cGrn = analogRead(A4);
    cBlu = analogRead(A3);
    
	colorWipe( strip.Color(cRed,cGrn,cBlu), 100);
  
    delay(100);
    
}

/*******************************************************************************
 * Function Name  : tinkerDigitalRead
 * Description    : Reads the digital value of a given pin
 * Input          : Pin 
 * Output         : None.
 * Return         : Value of the pin (0 or 1) in INT type
                    Returns a negative number on failure
 *******************************************************************************/
int tinkerDigitalRead(String pin)
{
	//convert ascii to integer
	int pinNumber = pin.charAt(1) - '0';
	//Sanity check to see if the pin numbers are within limits
	if (pinNumber< 0 || pinNumber >7) return -1;

	if(pin.startsWith("D"))
	{
		pinMode(pinNumber, INPUT_PULLDOWN);
		return digitalRead(pinNumber);
	}
	else if (pin.startsWith("A"))
	{
		pinMode(pinNumber+10, INPUT_PULLDOWN);
		return digitalRead(pinNumber+10);
	}
	return -2;
}

/*******************************************************************************
 * Function Name  : tinkerDigitalWrite
 * Description    : Sets the specified pin HIGH or LOW
 * Input          : Pin and value
 * Output         : None.
 * Return         : 1 on success and a negative number on failure
 *******************************************************************************/
int tinkerDigitalWrite(String command)
{
	bool value = 0;
	//convert ascii to integer
	int pinNumber = command.charAt(1) - '0';
	//Sanity check to see if the pin numbers are within limits
	if (pinNumber< 0 || pinNumber >7) return -1;

	if(command.substring(3,7) == "HIGH") value = 1;
	else if(command.substring(3,6) == "LOW") value = 0;
	else return -2;

	if(command.startsWith("D"))
	{
		pinMode(pinNumber, OUTPUT);
		digitalWrite(pinNumber, value);
		return 1;
	}
	else if(command.startsWith("A"))
	{
		pinMode(pinNumber+10, OUTPUT);
		digitalWrite(pinNumber+10, value);
		return 1;
	}
	else return -3;
}

/*******************************************************************************
 * Function Name  : tinkerAnalogRead
 * Description    : Reads the analog value of a pin
 * Input          : Pin 
 * Output         : None.
 * Return         : Returns the analog value in INT type (0 to 4095)
                    Returns a negative number on failure
 *******************************************************************************/
int tinkerAnalogRead(String pin)
{
	//convert ascii to integer
	int pinNumber = pin.charAt(1) - '0';
	//Sanity check to see if the pin numbers are within limits
	if (pinNumber< 0 || pinNumber >7) return -1;

	if(pin.startsWith("D"))
	{
		pinMode(pinNumber, INPUT);
		return analogRead(pinNumber);
	}
	else if (pin.startsWith("A"))
	{
		pinMode(pinNumber+10, INPUT);
		return analogRead(pinNumber+10);
	}
	return -2;
}

/*******************************************************************************
 * Function Name  : tinkerAnalogWrite
 * Description    : Writes an analog value (PWM) to the specified pin
 * Input          : Pin and Value (0 to 255)
 * Output         : None.
 * Return         : 1 on success and a negative number on failure
 *******************************************************************************/
int tinkerAnalogWrite(String command)
{
	//convert ascii to integer
	int pinNumber = command.charAt(1) - '0';
	//Sanity check to see if the pin numbers are within limits
	if (pinNumber< 0 || pinNumber >7) return -1;

	String value = command.substring(3);

	if(command.startsWith("D"))
	{
		pinMode(pinNumber, OUTPUT);
		analogWrite(pinNumber, value.toInt());
		return 1;
	}
	else if(command.startsWith("A"))
	{
		pinMode(pinNumber+10, OUTPUT);
		analogWrite(pinNumber+10, value.toInt());
		return 1;
	}
	else return -2;
}


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

 * NEOPIXEL FUNCTIONS
 
 *******************************************************************************/
 
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, c);
      strip.show();
      delay(wait);
  }
}

void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) { // 1 cycle of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}	

The program seems to behave strangely however - the colorWipe() works fine enough with its 100ms delay, but the lights flicker at random times and the Tinker inputs don’t always respond nicely - in fact, I can’t even get a steady reading out of A5. I am running a set of 20 LEDs with this, Neopixel init settings can be found on lines 332 and 333 of this code.

I would love to hear the results if anyone else tries this out!

On a side note, is there a way to drop this huge code block into a vertical scroll container?

try replacing noInterrupts(); with __disable_irq(); and interrupts(); with __enable_irq();
see https://community.spark.io/t/adafruit-neopixel-library/1143 for more information
There was a mention of wrong reading with analogRead(), which may cause some problems too.

Why not replace analogWrite for some pins to set R, G, B instead of using analogWrite/Read and connecting pins together? Same way you can control pattern or something just by replacing analogWrite with your code for another pin.

Replacing the interrupt calls didnt’ seem to have any effect on the running operation of the code. I had a revelation with the setup though - I am analogRead()ing analogWrite() values, which is a PWM, so depending on the timing of the read event the value is either max voltage or zero.

I don’t quite understand what you mean by replacing analogWrite though - do you mean by replacing analogWrite for some pins, like dropping a voltage divider pot in to manually adjust voltage input?

Really wish there was a way to read the analogWrite() values from within Tinker.

@emc2 grab the latest version of the NeoPixel library here with the __irq stuff and improved timing for irq use.

Also, you should low pass filter your PWM before trying to take an A/D reading on the average value.

Use something like a 1k ohm resistor between your PWM output and analog input, and a 1uF capacitor from your analog input to ground. Alternatively 10k and 0.1uF, or values slightly higher will work fine without too much lag in the voltage settling.

What I meant is to change tinkerAnalogWrite like this:
int tinkerAnalogWrite(String command) { ... if (pin.startsWith("A")) { switch(pinNumber) { 0: cBlue = value.toInt(); break; 1: cGreen = value.toInt(); break; 2: cRed = value.toInt(); break; default: pinMode(pinNumber+10, INPUT); return analogRead(pinNumber+10); break; } ... }

and remove analogRead calls from the loop

+1 on @jagor 's idea :smile: I recommended stuff like this before and it completely slipped my mind this time around. You are basically just intercepting the values for analogWrite() if they are for A0, A1 or A2, and redirecting the PWM values to your 0xFFFFFF RGB 24bit value. Simple, and glitch free.

Yes! Totally a great idea, it didn’t even occur to me to cut in at that point. FWIW, @zachary is giving a great rundown over at https://community.spark.io/t/read-value-from-tinker-analogwrite-pins/2188/6.

With help from @zachary and @jagor I have integrated the Neopixel library with the analogWrite command from the Tinker app.

Gist: https://gist.github.com/emcniece/8526703

However after the first colorWipe() call, the core disconnects from the network (flashes green for a few seconds before returning to breathing cyan) and the app pins all start timing out and showing the 404 error.

I think there’s some info on those issues around here somewhere, will take a look.

I think the Cloud probably expects a return value from the Core pretty fast. Much sooner than the other 10 - 15second blocking issue for the main loop();

Because you are color wiping 20 pixels with a 100ms delay after each, IN the function that is called for the digitalwrite funcKey, you are blocking that return for at least 2 seconds.

Try setting the int timOut = 100; to 1 instead to speed it up and see if this is in fact the issue.

If so, you are going to need to set a variable in your updateRGBLEDColor() function that gets picked up in the main loop() and showColor() can be called from the main loop as long as you don’t block the main loop for more than 10 seconds or so.

You’re right! https://gist.github.com/emcniece/8526703 updated, removed colorWipe() to the main loop and added a flag for analogWrite detection instead of handling blocking code right away.

I tried setting timout to 1 first, but it still blocked long enough to cause a reset.

The sketch now runs for much longer and I am able to change colors several times before hitting pin timeout… but no 404’s this time!

Well that’s great! However I really question why a 20 millisecond delay would cause a 404 error. Glad you got it working though :slight_smile:

@zachary what is the max delay that the cloud can tolerate blocking in Spark.function()'s before issuing a 404?

So we’re getting closer. I removed the countdown from @zachary’s example code and I can now run the sketch for much longer. I seem to hit timeout errors now much less frequently… but it seems to be when I analogWrite larger values.

From Tinker the value says 255 - what value does this equate to once in the core? I suspect that strip.color() does not like values larger than 255.

Update: tried placing a limit on input value to max out at 255 with no effect - core still times out.

The 0 to 255 I guess gets sent to the Core as a String, so the difference would be 1 to 3 characters. Not really anything that would cause a timeout in my opinion. Try adding something to the main loop that blinks the D7 blue LED on and off and you’ll have something to watch to see if the main loop gets blocked for more than 10 seconds.

You should only see it blocked for like 2 seconds based on your code.

void setup(){
  pinMode(D7,OUTPUT);
}
void loop(){
  // your code

  // blinky code
  digitalWrite(D7,HIGH);
  delay(100);
  digitalWrite(D7,LOW);
  delay(100);
}

The Spark Cloud waits 10 seconds for a Spark.function return value before responding with a 404.