Вопрос по алгоритму

0_о это в какой такой стране так??? Особенно в холодную зиму.

Так мы разве не в одной стране живем?
Газ 6.63 р/м3. С декабря на 9 % дорожает. Это не дорого, но 13 т в месяц намотает. А дров на 16 т две тракторные тележки верхом.

Стесняюсь спросить - вы ферму отапливаете в пару тысяч квадратов?

Всего лишь 300 м2 теплица. В сильные морозы требуется порядка 100 кВт на отопление.

1 лайк

Если такой расход по газу, то две тележки твои за пару недель в трубу вылетят…

На такую площадь я бы готовую систему с gsm искал, а не колхозил на esp-шках и облаке в другой галактике.

Да, это понятно.
Но эти пределы известны заранее и их может обработать каждый девайс самостоятельно и отправлять в аварийный канал только аварийную информацию.
Т.е. нет тревог - канал чист, если туда что-то упало это уже тревога. Дальше уже детализация.

Если нет возможности отличить ситуацию нормальной работы от ситуации потери связи, то такую систему вряд ли можно считать надежной.

конечно …
и уже писал:

но это было сказано с общесистемной точки зрения.

Что до частного случая, так например у себя в системе, я передаю в топик не только текущие параметры, но и постоянно инкрементируемый счетчик, при этом на графике легко фиксируется отклонение от нормальной работы.

Парни, разъясните пожалуйста для средних умов какие возможны проблемы. Я то со свой колокольни смотрю так: вроде бензин не жгу, каблуки не стираю, байты по пути туда-суда не усыхают…

Короткий путь завсегда лучше длинного.

Если в системе увеличивается количество компонентов, то её надёжность неуклонно падает. Неразумно намеренно ухудшать этот параметр. Тем более - если с неё получаешь доход.

А тут имеем и самопальные модули и алармы на бесплатном сервере, который в любой момент может разорвать с тобой отношения без предупреждения.

Я, конечно, не умру, если ваша теплица в сто квадратов померзнет из-за ошибки в типе переменной или из-за роскомпозора, запломбировавшего адреса бесплатного сервера, но Вы, наверняка, не обрадуетесь.

Сделал пока по своему алгоритму 3 девайса, пишущие в один канал. Публиковать можно не чаще 11 сек. Чтоб не возникло коллизий, у каждого устройства это время разное 15, 19 и 23 сек. Также разная задержка на старте (при одновременном включении) 0, 5 и 10 сек.
Особенность в том, что устройства работают самостоятельно, не зная друг о друге. При отключении или включении каждого устройства, оно вклинивается в очередь и не мешает другим.
Так как каждое устройство перезаписывает данные других полей, то может произойти так, что девайс отвалился, а его данные висят на канале. Для этого сделал чтоб очередные публикуемые данные отличались не менее чем на 0.0015. Если другие девайсы видят чьи то неизменные данные, то просто обнуляют их.
Пару недель отработало, проблем не заметил.

Спойлер
[code]

#include "ThingSpeak.h"
//#include "secrets.h"
#include <ESP8266WiFi.h>
#include <OneWire.h>

#define DEBUG 1  //закоментировать эту строку, если не нужна отладка
#ifdef DEBUG
#define DEBUG_BEGIN(x)     Serial.begin(x)
#define DEBUG_PRINT(x)     Serial.print(x)
#define DEBUG_PRINTLN(x)   Serial.println(x)
#else
#define DEBUG_BEGIN(x) 
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif 

#define PERIOD_WRITE_CHENEL 19; //15 сек для первого передатчика, 19, 23 для других 
#define START_DELAY 5 //0 сек для первого, 5 для второго, 10 для третьего
#define FIELD_MODIFY_1 1
#define FIELD_MODIFY_2 2// отсчет с 1, а массив с 0
#define FIELD_MODIFY_3 8//9
#define SECRET_SSID "TP-LINK_123456"    // replace MySSID with your WiFi network name
#define SECRET_PASS "12345678"  // replace MyPassword with your WiFi password
#define CHENEL_ID  1234567 //номер канала thingspeak
#define READ_APIKEY   "UXXXXXXXXXXXXX3"
#define WRITE_APIKEY  "0XXXXXXXXXXXBZ"

OneWire dsAir  (5);//D1  поле 2  (36,6->35) (0->(-0.8)) 
OneWire dsCoop (4);//D2  ПОЛЕ 1  (36,6->36.2)(0->0.125) 

float tempAir, tempCoop; 
char ssid[] = SECRET_SSID;   // your network SSID (name) 
char pass[] = SECRET_PASS;   // your network password
WiFiClient  client;

unsigned int lastSec, setLastSec = PERIOD_WRITE_CHENEL; 

unsigned long channelNumberGH_1 = CHENEL_ID;
const char * readAPIKey = READ_APIKEY;
const char * writeAPIKey = WRITE_APIKEY;
enum {WIFI_START_CONNECT, IS_WIFI_CONNECTED, GET_LAST_DATA_AGE, READ_WRITE_FIELDS, STOP};
int automataStatesEsp = WIFI_START_CONNECT;

String lastSecString;
unsigned long startTimeOutEsp, timeOutMs, periodWriteToChenel = 60000;
int  statusCode = 0;
float valFields[8], prevValFields[8];

void readSensors();  
void runAutomataEsp();
void setTimeOutForNextStep(unsigned long, int);
bool isTimerElapsedEsp();
void setTimeOutEsp(unsigned long);

void setup() {
  DEBUG_BEGIN(115200);
  WiFi.mode(WIFI_STA); 
  ThingSpeak.begin(client);  
  delay(START_DELAY);
}

void loop() {
  readSensors();
  runAutomataEsp();   
} 
//====================================
void runAutomataEsp(){//подкл ВИФИ, читаем lastSec, читаем поля, модифицируем, отправляем, ждем
  switch (automataStatesEsp) {
         
    case WIFI_START_CONNECT: //
      WiFi.disconnect();
      WiFi.begin(ssid, pass);
      setTimeOutForNextStep(10e3, IS_WIFI_CONNECTED);
      DEBUG_PRINTLN("WIFI_START_CONNECT");
      break;
      
    case IS_WIFI_CONNECTED: //
      if(WiFi.status() == WL_CONNECTED){
        setTimeOutForNextStep(1, GET_LAST_DATA_AGE);
        DEBUG_PRINTLN("WIFI_CONNECTED");
      }
      else if( isTimerElapsedEsp()){
        automataStatesEsp = WIFI_START_CONNECT;             
      }    
      break;

    case GET_LAST_DATA_AGE:
      if( isTimerElapsedEsp()){  
        int statusCode = 0;
        lastSecString = ThingSpeak.readRaw(channelNumberGH_1, "/feeds/last_data_age.txt?", readAPIKey);
        statusCode = ThingSpeak.getLastReadStatus();
       // Serial.println("last_sec: " + lastSecString + " second");
        DEBUG_PRINTLN(statusCode);
        if(statusCode == 200){
          lastSec = lastSecString.toInt();
          DEBUG_PRINT("lastSec=");  DEBUG_PRINTLN(lastSec);
          if(lastSec >= setLastSec){
            automataStatesEsp = READ_WRITE_FIELDS;
          }
          else{
            unsigned long timeMs = (setLastSec - lastSec) * 1000UL; //сколько мс до следующего запроса lastSec
            DEBUG_PRINT("timeMs=");  DEBUG_PRINTLN(timeMs);
            setTimeOutEsp(timeMs);//через timeMc код в этом кейсе выполнится снова
          }
        }
        else{
          setTimeOutForNextStep(2000, IS_WIFI_CONNECTED);
        }
      } 
      break;

    case READ_WRITE_FIELDS:
    {
      int error = 0;    
      error = readFields(); //читаем все поля,
      printFields();
      statusCode = ThingSpeak.getLastReadStatus();
      DEBUG_PRINT("statusCode=");  DEBUG_PRINTLN(statusCode);
      DEBUG_PRINTLN();
      if(error == 0){  
        setValFields();//  модифицируем.
        setFields();
        statusCode = ThingSpeak.writeFields(channelNumberGH_1, writeAPIKey);     
        if(statusCode == 200){
          setTimeOutForNextStep(periodWriteToChenel, STOP);//ждем следующую публикацию
          DEBUG_PRINTLN("STOP");
        }
        else setTimeOutForNextStep(2000, IS_WIFI_CONNECTED);
      }
      else setTimeOutForNextStep(2000, IS_WIFI_CONNECTED);
    }      
      break;
    case STOP:
      if( isTimerElapsedEsp()){
         automataStatesEsp = IS_WIFI_CONNECTED;
      }
      break;
  }
}   
//===================================
void setTimeOutForNextStep(unsigned long ms, int nextStep){
  setTimeOutEsp(ms);
  automataStatesEsp = nextStep;
}
//====================== 
 void setTimeOutEsp(unsigned long ms) {
  startTimeOutEsp = millis();
  timeOutMs = ms;
}
//==================
bool isTimerElapsedEsp() {
  if (millis() - startTimeOutEsp > timeOutMs) {
    timeOutMs = 0xFFFFFFFF;//останавливаем таймер
    return 1;
  }
  return 0;
}

//=================================================================
int readFields(){
  float deltaVal;
  int numArr = 0, error = 0;
  static byte countErrorFields[8] = {0};
  for(int i=1; i<=8; i++){   //модифицируемые поля читать не надо 
    if(i==FIELD_MODIFY_1 || i==FIELD_MODIFY_2 || i==FIELD_MODIFY_3) continue;
    numArr = i - 1;
    prevValFields[numArr] = valFields[numArr];
    valFields[numArr] = ThingSpeak.readFloatField(channelNumberGH_1, i, readAPIKey); 
    if(ThingSpeak.getLastReadStatus() != 200) {
      error = 1;
      DEBUG_PRINTLN("error=1");
      setTimeOutForNextStep(2000, IS_WIFI_CONNECTED);
      return error;
    }
    else{
      error = 0;
      if(valFields[numArr] > -0.001 && valFields[numArr] < 0.001){
        valFields[numArr] = 0;
        countErrorFields[numArr] = 0;
      }
      else{
        deltaVal = prevValFields[numArr] - valFields[numArr];
        if(deltaVal > -0.001 && deltaVal < 0.001){//если поле не обновилось
          if( ++countErrorFields[numArr] > 3){
            countErrorFields[numArr] = 0;
            valFields[numArr] = 0;
            prevValFields[numArr] = 0;
          }
        }     
        else{ //если поле обновилось
          countErrorFields[numArr] = 0;
        }
      }
    }
  }
  return error;
}
//===============================================================
void printFields(){
   DEBUG_PRINTLN(valFields[0]);
   DEBUG_PRINTLN(valFields[1]);
   DEBUG_PRINTLN(valFields[2]);
   DEBUG_PRINTLN(valFields[3]);
   DEBUG_PRINTLN(valFields[4]);
   DEBUG_PRINTLN(valFields[5]);
   DEBUG_PRINTLN(valFields[6]);
   DEBUG_PRINTLN(valFields[7]);
}
//=====================================================================
void setValFields(){
  static float delta = 0.0015;
  static byte flag = 0;
  float deltaVal;
  int n1, n2, n3;
  n1 = FIELD_MODIFY_1 - 1;
  n2 = FIELD_MODIFY_2 - 1;
  n3 = FIELD_MODIFY_3 - 1;

  prevValFields[n1] = valFields[n1];
  prevValFields[n2] = valFields[n2];
  prevValFields[n3] = valFields[n3];
  valFields[n1] = mapFloat(tempCoop, 0.125, 36.2, 0, 36.6);// с учетом калибровки
  valFields[n2] =  mapFloat(tempAir, -0.8, 35., 0, 36.6);
  valFields[n3] = millis() / 1000;
  deltaVal = prevValFields[n1] - valFields[n1];
  if(deltaVal > -0.001 && deltaVal < 0.001){
    valFields[n1] += delta;
    flag = 1;
  }
  deltaVal = prevValFields[n2] - valFields[n2];
  if(deltaVal > -0.001 && deltaVal < 0.001){
    valFields[n2] += delta;
    flag = 1;
  }
  deltaVal = prevValFields[n3] - valFields[n3];
  if(deltaVal > -0.001 && deltaVal < 0.001){
    valFields[n3] += delta;
    flag = 1;
  }
  if(flag) { delta *= -1; flag = 0; }
}
//======================================================================
void setFields(){
  int n=0;
  for(int i=1;  i<=8; i++){
    ThingSpeak.setField(i, valFields[n]);
    n++;
  }
}
//=======================================================================
void readSensors(){
  static byte flag = 0;
  static unsigned long periodSensors=0, prevMillis=0; 
  if(millis() - prevMillis >= periodSensors){ 
    DEBUG_PRINTLN("readSensors");
    prevMillis = millis();
    if(flag == 0){
      startConvert(dsAir);
      startConvert(dsCoop);
      flag = 1;
      periodSensors = 800;
    }else{
      tempAir = getTemp(dsAir); 
      tempCoop = getTemp(dsCoop); 
      flag = 0;
      periodSensors = 5200;
    }
  }
}
//=====================================================
void startConvert(OneWire & ds){
  ds.reset(); 
  ds.write(0xCC);
  ds.write(0x44);
}
//=======================================================
float getTemp(OneWire & ds){//возвр. полож. и отрицательную Т
  float t = 0;//При опросе раз в 2 сек Т датчика поднимается на 0.3-0.4С
  uint8_t bufData[9]; 
  ds.reset();     
  ds.write(0xCC);
  ds.write(0xBE);
  ds.read_bytes(bufData, 9); 
  if(OneWire::crc8(bufData, 8) == bufData[8] ) {  // проверка CRC
    t = (int16_t)((bufData[0] | (bufData[1]) << 8)) * 0.0625;  
  }
  else t = -33.33;   // ошибка измерения
  return t;
}
//===============================
float mapFloat(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

[/code]


В библиотеке ThingSpeak файл ThingSpeak.h нужно заменить на тот что ниже.

ThingSpeak.h

Спойлер
/*
  ThingSpeak(TM) Communication Library For Arduino, ESP8266 and ESP32

  Enables an Arduino or other compatible hardware to write or read data to or from ThingSpeak,
  an open data platform for the Internet of Things with MATLAB analytics and visualization. 

  ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and 
  analyze live data streams in the cloud.
  
  Copyright 2018, The MathWorks, Inc.
 
  See the accompaning licence file for licensing information.
*/

// #define PRINT_DEBUG_MESSAGES
// #define PRINT_HTTP

///////   ИЗМЕНЕНИЯ readCreatedAt() 1180 строка  и unsigned long timeoutTime вместо long

#ifndef ThingSpeak_h
#define ThingSpeak_h

#define TS_VER "1.5.0"

#include "Arduino.h"
#include <Client.h>

#define THINGSPEAK_URL "api.thingspeak.com"
#define THINGSPEAK_PORT_NUMBER 80

#ifdef ARDUINO_ARCH_AVR
    #ifdef ARDUINO_AVR_YUN
        #define TS_USER_AGENT "tslib-arduino/" TS_VER " (arduino yun)"
    #else
        #define TS_USER_AGENT "tslib-arduino/" TS_VER " (arduino uno or mega)"
    #endif
#elif defined(ARDUINO_ARCH_ESP8266)
    #define TS_USER_AGENT "tslib-arduino/" TS_VER " (ESP8266)"
#elif defined(ARDUINO_SAMD_MKR1000)
	#define TS_USER_AGENT "tslib-arduino/" TS_VER " (arduino mkr1000)"
#elif defined(ARDUINO_SAM_DUE)
	#define TS_USER_AGENT "tslib-arduino/" TS_VER " (arduino due)"
#elif defined(ARDUINO_ARCH_SAMD) 
	#define TS_USER_AGENT "tslib-arduino/" TS_VER " (arduino samd)"
#elif defined(ARDUINO_ARCH_SAM)
	#define TS_USER_AGENT "tslib-arduino/" TS_VER " (arduino sam)"
#elif defined(ARDUINO_ARCH_SAMD_BETA)
	#define TS_USER_AGENT "tslib-arduino/" TS_VER " (arduino samd_beta )"
#elif defined(ARDUINO_ARCH_ESP32)
	#define TS_USER_AGENT "tslib-arduino/" TS_VER " (ESP32)"
#elif defined(ARDUINO_ARCH_SAMD_BETA)
	#define TS_USER_AGENT "tslib-arduino/" TS_VER " (arduino vidor)"
#else
	#define TS_USER_AGENT "tslib-arduino/" TS_VER " (unknown)"
#endif

#define FIELDNUM_MIN 1
#define FIELDNUM_MAX 8
#define FIELDLENGTH_MAX 255  // Max length for a field in ThingSpeak is 255 bytes (UTF-8)

#define TIMEOUT_MS_SERVERRESPONSE 5000  // Wait up to five seconds for server to respond

#define OK_SUCCESS              200     // OK / Success
#define ERR_BADAPIKEY           400     // Incorrect API key (or invalid ThingSpeak server address)
#define ERR_BADURL              404     // Incorrect API key (or invalid ThingSpeak server address)
#define ERR_OUT_OF_RANGE        -101    // Value is out of range or string is too long (> 255 bytes)
#define ERR_INVALID_FIELD_NUM   -201    // Invalid field number specified
#define ERR_SETFIELD_NOT_CALLED -210    // setField() was not called before writeFields()
#define ERR_CONNECT_FAILED      -301    // Failed to connect to ThingSpeak
#define ERR_UNEXPECTED_FAIL     -302    // Unexpected failure during write to ThingSpeak
#define ERR_BAD_RESPONSE        -303    // Unable to parse response
#define ERR_TIMEOUT             -304    // Timeout waiting for server to respond
#define ERR_NOT_INSERTED        -401    // Point was not inserted (most probable cause is the rate limit of once every 15 seconds)

// Enables an Arduino, ESP8266, ESP32 or other compatible hardware to write or read data to or from ThingSpeak, an open data platform for the Internet of Things with MATLAB analytics and visualization. 
class ThingSpeakClass
{
  public:
	ThingSpeakClass()
	{
		resetWriteFields();
	    this->lastReadStatus = OK_SUCCESS;
	};


	/*
	Function: begin
	
	Summary:
	Initializes the ThingSpeak library and network settings using the ThingSpeak.com service.
	
	Parameters:
	client - EthernetClient, YunClient, TCPClient, or WiFiClient created earlier in the sketch
	
	Returns:
	Always returns true
	
	Notes:
	This does not validate the information passed in, or generate any calls to ThingSpeak.

	*/	 
	bool begin(Client & client)
	{
				
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.println("ts::tsBegin");
		#endif
		this->setClient(&client);
		this->setPort(THINGSPEAK_PORT_NUMBER);
		
		resetWriteFields();
		this->lastReadStatus = OK_SUCCESS;
		
		return true;
	};
	
	/*
	Function: begin
	
	Summary:
	Initializes the ThingSpeak library and network settings using the ThingSpeak.com service.
	
	Parameters:
	client - EthernetClient, YunClient, TCPClient, or WiFiClient created earlier in the sketch
	port - TCP port of server
	
	Returns:
	Always returns true
	
	Notes:
	This does not validate the information passed in, or generate any calls to ThingSpeak.

	*/
	bool begin(Client & client, unsigned int port)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::tsBegin");
		#endif
		this->setClient(&client);
		this->setPort(port);
		resetWriteFields();
		this->lastReadStatus = OK_SUCCESS;
		return true;	
	};
	
	 
	/*
	Function: writeField
	
	Summary:
	Write an integer value to a single field in a ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to write to.
	value - Integer value (from -32,768 to 32,767) to write.
	writeAPIKey - Write API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	HTTP status code of 200 if successful.
	
	Notes:
	See getLastReadStatus() for other possible return values.
	*/
	int writeField(unsigned long channelNumber, unsigned int field, int value, const char * writeAPIKey)
	{
		char valueString[10];  // int range is -32768 to 32768, so 7 bytes including terminator, plus a little extra
		itoa(value, valueString, 10);
		return writeField(channelNumber, field, valueString, writeAPIKey);
	};

	 
	/*
	Function: writeField
	
	Summary:
	Write a long value to a single field in a ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to write to.
	value - Long value (from -2,147,483,648 to 2,147,483,647) to write.
	writeAPIKey - Write API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	HTTP status code of 200 if successful.
	
	Notes:
	See getLastReadStatus() for other possible return values.
	*/
	int writeField(unsigned long channelNumber, unsigned int field, long value, const char * writeAPIKey)
	{
		char valueString[15];  // long range is -2147483648 to 2147483647, so 12 bytes including terminator
   		ltoa(value, valueString, 10);
		return writeField(channelNumber, field, valueString, writeAPIKey);
	};
	 
	/*
	Function: writeField
	
	Summary:
	Write a floating point value to a single field in a ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to write to.
	value - Floating point value (from -999999000000 to 999999000000) to write.  If you need more accuracy, or a wider range, you should format the number using <tt>dtostrf</tt> and writeField().
	writeAPIKey - Write API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	HTTP status code of 200 if successful.
	
	Notes:
	See getLastReadStatus() for other possible return values.
	*/
	int writeField(unsigned long channelNumber, unsigned int field, float value, const char * writeAPIKey)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::writeField (channelNumber: "); Serial.print(channelNumber); Serial.print(" writeAPIKey: "); Serial.print(writeAPIKey); Serial.print(" field: "); Serial.print(field); Serial.print(" value: "); Serial.print(value,5); Serial.println(")");
		#endif
		char valueString[20]; // range is -999999000000.00000 to 999999000000.00000, so 19 + 1 for the terminator
		int status = convertFloatToChar(value, valueString);
		if(status != OK_SUCCESS) return status;

		return writeField(channelNumber, field, valueString, writeAPIKey);
	};

	
	/*
	Function: writeField
	
	Summary:
	Write a string to a single field in a ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to write to.
	value - String to write (UTF8 string).  ThingSpeak limits this field to 255 bytes.
	writeAPIKey - Write API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	HTTP status code of 200 if successful.
	
	Notes:
	See getLastReadStatus() for other possible return values.
	*/
	int writeField(unsigned long channelNumber, unsigned int field, const char * value, const char * writeAPIKey)
	{
		return writeField(channelNumber, field, String(value), writeAPIKey);
	};
 
	/*
	Function: writeField
	
	Summary:
	Write a string to a single field in a ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to write to.
	value - String to write (UTF8 string).  ThingSpeak limits this field to 255 bytes.
	writeAPIKey - Write API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	HTTP status code of 200 if successful.
	
	Notes:
	See getLastReadStatus() for other possible return values.
	*/
	int writeField(unsigned long channelNumber, unsigned int field, String value, const char * writeAPIKey)
	{
		// Invalid field number specified
		if(field < FIELDNUM_MIN || field > FIELDNUM_MAX) return ERR_INVALID_FIELD_NUM;
		// Max # bytes for ThingSpeak field is 255
		if(value.length() > FIELDLENGTH_MAX) return ERR_OUT_OF_RANGE;
		
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::writeField (channelNumber: "); Serial.print(channelNumber); Serial.print(" writeAPIKey: "); Serial.print(writeAPIKey); Serial.print(" field: "); Serial.print(field); Serial.print(" value: \""); Serial.print(value); Serial.println("\")");
		#endif
		String postMessage = String("field");
		postMessage.concat(field);
		postMessage.concat("=");
		postMessage.concat(value);
		return writeRaw(channelNumber, postMessage, writeAPIKey);
 	};

    	 
	/*
	Function: setField
	
	Summary:
	Set the value of a single field that will be part of a multi-field update.
	
	Parameters:
	field - Field number (1-8) within the channel to set.
	value - Integer value (from -32,768 to 32,767) to set.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if value is out of range or string is too long (> 255 bytes)
	
	*/	
	int setField(unsigned int field, int value)
	{

		char valueString[10];  // int range is -32768 to 32768, so 7 bytes including terminator
		itoa(value, valueString, 10);
		
		return setField(field, valueString);

	};
 
	/*
	Function: setField
	
	Summary:
	Set the value of a single field that will be part of a multi-field update.
	
	Parameters:
	field - Field number (1-8) within the channel to set.
	value - Long value (from -2,147,483,648 to 2,147,483,647) to write.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if value is out of range or string is too long (> 255 bytes)
	
	*/	 
	int setField(unsigned int field, long value)
	{
		char valueString[15];  // long range is -2147483648 to 2147483647, so 12 bytes including terminator
	    ltoa(value, valueString, 10);
		return setField(field, valueString);
	};

	/*
	Function: setField
	
	Summary:
	Set the value of a single field that will be part of a multi-field update.
	
	Parameters:
	field - Field number (1-8) within the channel to set.
	value - Floating point value (from -999999000000 to 999999000000) to write.  If you need more accuracy, or a wider range, you should format the number yourself (using <tt>dtostrf</tt>) and setField() using the resulting string.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if value is out of range or string is too long (> 255 bytes)
	
	*/
    int setField(unsigned int field, float value)
	{
		char valueString[20]; // range is -999999000000.00000 to 999999000000.00000, so 19 + 1 for the terminator
		int status = convertFloatToChar(value, valueString);
		if(status != OK_SUCCESS) return status;

		return setField(field, valueString);
	};

	 
	/*
	Function: setField
	
	Summary:
	Set the value of a single field that will be part of a multi-field update.
	
	Parameters:
	field - Field number (1-8) within the channel to set.
	value - String to write (UTF8).  ThingSpeak limits this to 255 bytes.
	
	Returns:
	Code of 200 if successful.
	Code 0f -101 if value is out of range or string is too long (> 255 bytes)
	
	*/ 
    int setField(unsigned int field, const char * value)
	{
		return setField(field, String(value));
	};

	 
	/*
	Function: setField
	
	Summary:
	Set the value of a single field that will be part of a multi-field update.
	
	Parameters:
	field - Field number (1-8) within the channel to set.
	value - String to write (UTF8).  ThingSpeak limits this to 255 bytes.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if value is out of range or string is too long (> 255 bytes)
	
	*/  
    int setField(unsigned int field, String value)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::setField   (field: "); Serial.print(field); Serial.print(" value: \""); Serial.print(value); Serial.println("\")");
		#endif
		if(field < FIELDNUM_MIN || field > FIELDNUM_MAX) return ERR_INVALID_FIELD_NUM;
		// Max # bytes for ThingSpeak field is 255 (UTF-8)
		if(value.length() > FIELDLENGTH_MAX) return ERR_OUT_OF_RANGE;
		this->nextWriteField[field - 1] = value;
		return OK_SUCCESS;
	};

	 
	/*
	Function: setLatitude
	
	Summary:
	Set the latitude of a multi-field update.
	
	Parameters:
	latitude - Latitude of the measurement as a floating point value (degrees N, use negative values for degrees S)	
	
	Returns:
	Always return 200
	
	Notes:
	To record latitude, longitude and elevation of a write, call setField() for each of the fields you want to write. Then setLatitude(), setLongitude(), setElevation() and then call writeFields()
	
	*/
	int setLatitude(float latitude)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::setLatitude(latitude: "); Serial.print(latitude,3); Serial.println("\")");
		#endif
		this->nextWriteLatitude = latitude;
		return OK_SUCCESS;
	};

 
	/*
	Function: setLongitude
	
	Summary:
	Set the longitude of a multi-field update.
	
	Parameters:
	longitude - Longitude of the measurement as a floating point value (degrees E, use negative values for degrees W)
	
	Returns:
	Always return 200
	
	Notes:
	To record latitude, longitude and elevation of a write, call setField() for each of the fields you want to write. Then setLatitude(), setLongitude(), setElevation() and then call writeFields()
	
	*/ 
	int setLongitude(float longitude)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::setLongitude(longitude: "); Serial.print(longitude,3); Serial.println("\")");
		#endif
		this->nextWriteLongitude = longitude;
		return OK_SUCCESS;
	};

	 
	/*
	Function: setElevation
	
	Summary:
	Set the elevation of a multi-field update.
	
	Parameters:
	elevation - Elevation of the measurement as a floating point value (meters above sea level)
	
	Returns:
	Always return 200
	
	Notes:
	To record latitude, longitude and elevation of a write, call setField() for each of the fields you want to write. Then setLatitude(), setLongitude(), setElevation() and then call writeFields()
	
	*/ 
	int setElevation(float elevation)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::setElevation(elevation: "); Serial.print(elevation,3); Serial.println("\")");
		#endif
		this->nextWriteElevation = elevation;
		return OK_SUCCESS;
	};

	
	/*
	Function: setStatus
	
	Summary:
	Set the status field of a multi-field update.
	
	Parameters:
	status - String to write (UTF8).  ThingSpeak limits this to 255 bytes.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if string is too long (> 255 bytes)
	
	Notes:
	To record a status message on a write, call setStatus() then call writeFields(). 
	Use status to provide additonal details when writing a channel update.
	Additonally, status can be used by the ThingTweet App to send a message to Twitter.
	
	*/
    int setStatus(const char * status)
	{
		return setStatus(String(status));
	};

	 
	/*
	Function: setStatus
	
	Summary:
	Set the status field of a multi-field update.
	
	Parameters:
	status - String to write (UTF8).  ThingSpeak limits this to 255 bytes.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if string is too long (> 255 bytes)
	
	Notes:
	To record a status message on a write, call setStatus() then call writeFields(). 
	Use status to provide additonal details when writing a channel update.
	Additonally, status can be used by the ThingTweet App to send a message to Twitter.
	
	*/ 
    int setStatus(String status)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::setStatus(status: "); Serial.print(status); Serial.println("\")");
		#endif
		// Max # bytes for ThingSpeak field is 255 (UTF-8)
		if(status.length() > FIELDLENGTH_MAX) return ERR_OUT_OF_RANGE;
		this->nextWriteStatus = status;
		return OK_SUCCESS;
	};
	

	/*
	Function: setTwitterTweet
	
	Summary:
	Set the Twitter account and message to use for an update to be tweeted.
	
	Parameters:
	twitter - Twitter account name as a String.
	tweet - Twitter message as a String (UTF-8) limited to 140 character.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if string is too long (> 255 bytes)
	
	Notes:
	To send a message to twitter call setTwitterTweet() then call writeFields().
	Prior to using this feature, a twitter account must be linked to your ThingSpeak account. Do this by logging into ThingSpeak and going to Apps, then ThingTweet and clicking Link Twitter Account.
	*/
	int setTwitterTweet(const char * twitter, const char * tweet)
	{
		return setTwitterTweet(String(twitter), String(tweet));
	};

	/*
	Function: setTwitterTweet
	
	Summary:
	Set the Twitter account and message to use for an update to be tweeted.
	
	Parameters:
	twitter - Twitter account name as a String.
	tweet - Twitter message as a String (UTF-8) limited to 140 character.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if string is too long (> 255 bytes)
	
	Notes:
	To send a message to twitter call setTwitterTweet() then call writeFields().
	Prior to using this feature, a twitter account must be linked to your ThingSpeak account. Do this by logging into ThingSpeak and going to Apps, then ThingTweet and clicking Link Twitter Account.
	*/
	int setTwitterTweet(String twitter, const char * tweet)
	{
		return setTwitterTweet(twitter, String(tweet));
	};

	/*
	Function: setTwitterTweet
	
	Summary:
	Set the Twitter account and message to use for an update to be tweeted.
	
	Parameters:
	twitter - Twitter account name as a String.
	tweet - Twitter message as a String (UTF-8) limited to 140 character.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if string is too long (> 255 bytes)
	
	Notes:
	To send a message to twitter call setTwitterTweet() then call writeFields().
	Prior to using this feature, a twitter account must be linked to your ThingSpeak account. Do this by logging into ThingSpeak and going to Apps, then ThingTweet and clicking Link Twitter Account.
	*/
	int setTwitterTweet(const char * twitter, String tweet)
	{
		return setTwitterTweet(String(twitter), tweet);
	};

	/*
	Function: setTwitterTweet
	
	Summary:
	Set the Twitter account and message to use for an update to be tweeted.
	
	Parameters:
	twitter - Twitter account name as a String.
	tweet - Twitter message as a String (UTF-8) limited to 140 character.
	
	Returns:
	Code of 200 if successful.
	Code of -101 if string is too long (> 255 bytes)
	
	Notes:
	To send a message to twitter call setTwitterTweet() then call writeFields().
	Prior to using this feature, a twitter account must be linked to your ThingSpeak account. Do this by logging into ThingSpeak and going to Apps, then ThingTweet and clicking Link Twitter Account.
	*/
	int setTwitterTweet(String twitter, String tweet){
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::setTwitterTweet(twitter: "); Serial.print(twitter); Serial.print(", tweet: "); Serial.print(tweet); Serial.println("\")");
		#endif
		// Max # bytes for ThingSpeak field is 255 (UTF-8)
		if((twitter.length() > FIELDLENGTH_MAX) || (tweet.length() > FIELDLENGTH_MAX)) return ERR_OUT_OF_RANGE;
		
		this->nextWriteTwitter = twitter;
		this->nextWriteTweet = tweet;
		
		return OK_SUCCESS;	
	};
	
		
	/*
	Function: setCreatedAt
	
	Summary:
	Set the created-at date of a multi-field update.
	
	Parameters:
	createdAt - Desired timestamp to be included with the channel update as a String.  The timestamp string must be in the ISO 8601 format. Example "2017-01-12 13:22:54"
	
	Returns:
	Code of 200 if successful.
	Code of -101 if string is too long (> 255 bytes)
	
	Notes:
	Timezones can be set using the timezone hour offset parameter. For example, a timestamp for Eastern Standard Time is: "2017-01-12 13:22:54-05".  
	If no timezone hour offset parameter is used, UTC time is assumed.
	
	*/
	int setCreatedAt(const char * createdAt)
	{
		return setCreatedAt(String(createdAt));
	}
	
	
	/*
	Function: setCreatedAt
	
	Summary:
	Set the created-at date of a multi-field update.
	
	Parameters:
	createdAt - Desired timestamp to be included with the channel update as a String.  The timestamp string must be in the ISO 8601 format. Example "2017-01-12 13:22:54"
	
	Returns:
	Code of 200 if successful.
	Code of -101 if string is too long (> 255 bytes)
	
	Notes:
	Timezones can be set using the timezone hour offset parameter. For example, a timestamp for Eastern Standard Time is: "2017-01-12 13:22:54-05".  
	If no timezone hour offset parameter is used, UTC time is assumed.
	
	*/
	int setCreatedAt(String createdAt)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::setCreatedAt(createdAt: "); Serial.print(createdAt); Serial.println("\")");
		#endif
		
		// the ISO 8601 format is too complicated to check for valid timestamps here
		// we'll need to reply on the api to tell us if there is a problem
		// Max # bytes for ThingSpeak field is 255 (UTF-8)
		if(createdAt.length() > FIELDLENGTH_MAX) return ERR_OUT_OF_RANGE;
		this->nextWriteCreatedAt = createdAt;
		
		return OK_SUCCESS;
	}
	
 
	/*
	Function: writeFields
	
	Summary:
	Write a multi-field update.
	
	Parameters:
	channelNumber - Channel number
	writeAPIKey - Write API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	200 - successful.
	404 - Incorrect API key (or invalid ThingSpeak server address)
	-101 - Value is out of range or string is too long (> 255 characters)
	-201 - Invalid field number specified
	-210 - setField() was not called before writeFields()
	-301 - Failed to connect to ThingSpeak
	-302 - Unexpected failure during write to ThingSpeak
	-303 - Unable to parse response
    -304 - Timeout waiting for server to respond
	-401 - Point was not inserted (most probable cause is the rate limit of once every 15 seconds)
	
	
	Notes:
	Call setField(), setLatitude(), setLongitude(), setElevation() and/or setStatus() and then call writeFields()
	
	*/
	int writeFields(unsigned long channelNumber, const char * writeAPIKey)
	{
		if(!connectThingSpeak()){
			// Failed to connect to ThingSpeak
			return ERR_CONNECT_FAILED;
		}
		
		// Get the content length of the payload
		int contentLen = getWriteFieldsContentLength();		
		
		if(contentLen == 0){
			// setField was not called before writeFields
			return ERR_SETFIELD_NOT_CALLED;
		}
		
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::writeFields   (channelNumber: "); Serial.print(channelNumber); Serial.print(" writeAPIKey: "); Serial.println(writeAPIKey);
		#endif
		
		// Post data to thingspeak
		if(!this->client->print("POST /update HTTP/1.1\r\n")) return abortWriteRaw();
		if(!writeHTTPHeader(writeAPIKey)) return abortWriteRaw();
		if(!this->client->print("Content-Type: application/x-www-form-urlencoded\r\n")) return abortWriteRaw();
		if(!this->client->print("Content-Length: ")) return abortWriteRaw();
		if(!this->client->print(contentLen)) return abortWriteRaw();
		if(!this->client->print("\r\n\r\n")) return abortWriteRaw();
			
		bool fFirstItem = true;
		for(size_t iField = 0; iField < FIELDNUM_MAX; iField++){
			if(this->nextWriteField[iField].length() > 0){
				if(!fFirstItem){
					if(!this->client->print("&")) return abortWriteRaw();
				}
				if(!this->client->print("field")) return abortWriteRaw();
				if(!this->client->print(iField + 1)) return abortWriteRaw();
				if(!this->client->print("=")) return abortWriteRaw();
				if(!this->client->print(this->nextWriteField[iField])) return abortWriteRaw();
				fFirstItem = false;
			}
		}
		
		if(!isnan(this->nextWriteLatitude)){
			if(!fFirstItem){
				if(!this->client->print("&")) return abortWriteRaw();
			}
			if(!this->client->print("lat=")) return abortWriteRaw();
			if(!this->client->print(this->nextWriteLatitude)) return abortWriteRaw();
			fFirstItem = false;
		}

		if(!isnan(this->nextWriteLongitude)){
			if(!fFirstItem){
				if(!this->client->print("&")) return abortWriteRaw();
			}
			if(!this->client->print("long=")) return abortWriteRaw();
			if(!this->client->print(this->nextWriteLongitude)) return abortWriteRaw();
			fFirstItem = false;
		}


		if(!isnan(this->nextWriteElevation)){
			if(!fFirstItem){
				if(!this->client->print("&")) return abortWriteRaw();
			}
			if(!this->client->print("elevation=")) return abortWriteRaw();
			if(!this->client->print(this->nextWriteElevation)) return abortWriteRaw();
			fFirstItem = false;
		}
		
		if(this->nextWriteStatus.length() > 0){
			if(!fFirstItem){
				if(!this->client->print("&")) return abortWriteRaw();
			}
			if(!this->client->print("status=")) return abortWriteRaw();
			if(!this->client->print(this->nextWriteStatus)) return abortWriteRaw();
			fFirstItem = false;
		}
		
		if(this->nextWriteTwitter.length() > 0){
			if(!fFirstItem){
				if(!this->client->print("&")) return abortWriteRaw();
			}
			if(!this->client->print("twitter=")) return abortWriteRaw();
			if(!this->client->print(this->nextWriteTwitter)) return abortWriteRaw();
			fFirstItem = false;
		}
		
		if(this->nextWriteTweet.length() > 0){
			if(!fFirstItem){
				if(!this->client->print("&")) return abortWriteRaw();
			}
			if(!this->client->print("tweet=")) return abortWriteRaw();
			if(!this->client->print(this->nextWriteTweet)) return abortWriteRaw();
			fFirstItem = false;
		}
		
		if(this->nextWriteCreatedAt.length() > 0){
			if(!fFirstItem){
				if(!this->client->print("&")) return abortWriteRaw();
			}
			if(!this->client->print("created_at=")) return abortWriteRaw();
			if(!this->client->print(this->nextWriteCreatedAt)) return abortWriteRaw();
			fFirstItem = false;
		}

		
		if(!this->client->print("&headers=false")) return abortWriteRaw();
		
		resetWriteFields();
		
		return finishWrite();
		
	}
	
	 
	/*
	Function: writeRaw
	
	Summary:
	Write a raw POST to a ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	postMessage - Raw URL to write to ThingSpeak as a string.  See the documentation at https://thingspeak.com/docs/channels#update_feed.
	writeAPIKey - Write API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	200 - successful.
	404 - Incorrect API key (or invalid ThingSpeak server address)
	-101 - Value is out of range or string is too long (> 255 characters)
	-201 - Invalid field number specified
	-210 - setField() was not called before writeFields()
	-301 - Failed to connect to ThingSpeak
	-302 - Unexpected failure during write to ThingSpeak
	-303 - Unable to parse response
    -304 - Timeout waiting for server to respond
	-401 - Point was not inserted (most probable cause is the rate limit of once every 15 seconds)
	
	Notes:
	This is low level functionality that will not be required by most users.
	
	*/
	int writeRaw(unsigned long channelNumber, const char * postMessage, const char * writeAPIKey)
	{
		return writeRaw(channelNumber, String(postMessage), writeAPIKey);
	};

	 
	/*
	Function: writeRaw
	
	Summary:
	Write a raw POST to a ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	postMessage - Raw URL to write to ThingSpeak as a string.  See the documentation at https://thingspeak.com/docs/channels#update_feed.
	writeAPIKey - Write API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	200 - successful.
	404 - Incorrect API key (or invalid ThingSpeak server address)
	-101 - Value is out of range or string is too long (> 255 characters)
	-201 - Invalid field number specified
	-210 - setField() was not called before writeFields()
	-301 - Failed to connect to ThingSpeak
	-302 - Unexpected failure during write to ThingSpeak
	-303 - Unable to parse response
    -304 - Timeout waiting for server to respond
	-401 - Point was not inserted (most probable cause is the rate limit of once every 15 seconds)
	
	Notes:
	This is low level functionality that will not be required by most users.
	
	*/
	int writeRaw(unsigned long channelNumber, String postMessage, const char * writeAPIKey)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::writeRaw   (channelNumber: "); Serial.print(channelNumber); Serial.print(" writeAPIKey: "); Serial.println(writeAPIKey);
		#endif

		if(!connectThingSpeak())
		{
			// Failed to connect to ThingSpeak
			return ERR_CONNECT_FAILED;
		}

		postMessage.concat("&headers=false");
		
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("               POST \"");Serial.print(postMessage);Serial.println("\"");
		#endif


		// Post data to thingspeak
		if(!this->client->print("POST /update HTTP/1.1\r\n")) return abortWriteRaw();
		if(!writeHTTPHeader(writeAPIKey)) return abortWriteRaw();
		if(!this->client->print("Content-Type: application/x-www-form-urlencoded\r\n")) return abortWriteRaw();
		if(!this->client->print("Content-Length: ")) return abortWriteRaw();
		if(!this->client->print(postMessage.length())) return abortWriteRaw();
		if(!this->client->print("\r\n\r\n")) return abortWriteRaw();
		if(!this->client->print(postMessage)) return abortWriteRaw();
		
		resetWriteFields();
		
		return finishWrite();
		
	};
	
	 
	/*
	Function: readStringField
	
	Summary:
	Read the latest string from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to read from.
	readAPIKey - Read API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	Value read (UTF8 string), or empty string if there is an error.  Use getLastReadStatus() to get more specific information.
	
	*/
    String readStringField(unsigned long channelNumber, unsigned int field, const char * readAPIKey)
	{
		if(field < FIELDNUM_MIN || field > FIELDNUM_MAX)
		{
			this->lastReadStatus = ERR_INVALID_FIELD_NUM;
			return("");
		}
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::readStringField(channelNumber: "); Serial.print(channelNumber); 
			if(NULL != readAPIKey)
			{
				Serial.print(" readAPIKey: "); Serial.print(readAPIKey);
			}
			Serial.print(" field: "); Serial.print(field); Serial.println(")");
		#endif
		String urlSuffix = String("/fields/");
		urlSuffix.concat(field);
		urlSuffix.concat("/last");
		return readRaw(channelNumber, urlSuffix, readAPIKey);
	}


	 
	/*
	Function: readStringField
	
	Summary:
	Read the latest string from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to read from.
	
	Returns:
	Value read (UTF8 string), or empty string if there is an error.  Use getLastReadStatus() to get more specific information.
	
	*/
	String readStringField(unsigned long channelNumber, unsigned int field)
	{
		return readStringField(channelNumber, field, NULL);
	};

	 
	/*
	Function: readFloatField
	
	Summary:
	ead the latest floating point value from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to read from.
	readAPIKey - Read API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	Value read, or 0 if the field is text or there is an error.  Use getLastReadStatus() to get more specific information.  Note that NAN, INFINITY, and -INFINITY are valid results.
	
	*/ 
    float readFloatField(unsigned long channelNumber, unsigned int field, const char * readAPIKey)
	{
		return convertStringToFloat(readStringField(channelNumber, field, readAPIKey));
	};

    
	/*
	Function: readFloatField
	
	Summary:
	Read the latest floating point value from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to read from.
	
	Returns:
	Value read, or 0 if the field is text or there is an error.  Use getLastReadStatus() to get more specific information.  Note that NAN, INFINITY, and -INFINITY are valid results.
	
	*/
	float readFloatField(unsigned long channelNumber, unsigned int field)
	{
		return readFloatField(channelNumber, field, NULL);
	};

	 
	/*
	Function: readLongField
	
	Summary:
	Read the latest long value from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to read from.
	readAPIKey - Read API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	Value read, or 0 if the field is text or there is an error.  Use getLastReadStatus() to get more specific information.  Note that NAN, INFINITY, and -INFINITY are valid results.
	
	*/  
    long readLongField(unsigned long channelNumber, unsigned int field, const char * readAPIKey)
	{
        // Note that although the function is called "toInt" it really returns a long.
		return readStringField(channelNumber, field, readAPIKey).toInt();
	}

	 
	/*
	Function: readLongField
	
	Summary:
	Read the latest long value from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to read from.
	
	Returns:
	Value read, or 0 if the field is text or there is an error.  Use getLastReadStatus() to get more specific information.  Note that NAN, INFINITY, and -INFINITY are valid results.
	
	*/ 
	long readLongField(unsigned long channelNumber, unsigned int field)
	{
		return readLongField(channelNumber, field, NULL);
	};

	 
	/*
	Function: readIntField
	
	Summary:
	Read the latest int value from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to read from.
	readAPIKey - Read API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	Value read, or 0 if the field is text or there is an error.  Use getLastReadStatus() to get more specific information.  Note that NAN, INFINITY, and -INFINITY are valid results.
	
	*/   
    int readIntField(unsigned long channelNumber, unsigned int field, const char * readAPIKey)
	{
		return readLongField(channelNumber, field, readAPIKey);
	}

	 
	/*
	Function: readIntField
	
	Summary:
	Read the latest int value from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	field - Field number (1-8) within the channel to read from.
	
	Returns:
	Value read, or 0 if the field is text or there is an error.  Use getLastReadStatus() to get more specific information.  Note that NAN, INFINITY, and -INFINITY are valid results.
	
	*/  
    int readIntField(unsigned long channelNumber, unsigned int field)
	{
		return readLongField(channelNumber, field, NULL);
	};


	/*
	Function: readStatus
	
	Summary:
	Read the latest status from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	readAPIKey - Read API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Results:
	Value read (UTF8 string). An empty string is returned if there was no status written to the channel or in case of an error.  Use getLastReadStatus() to get more specific information.

	*/
	String readStatus(unsigned long channelNumber, const char * readAPIKey)
	{
		String content = readRaw(channelNumber, "/feeds/last.txt?status=true", readAPIKey);
		
		if(getLastReadStatus() != OK_SUCCESS){
			return String("");
		}
		
		return getJSONValueByKey(content, "status");
	};
	
	 
	/*
	Function: readStatus
	
	Summary:
	Read the latest status from a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	
	Results:
	Value read (UTF8 string). An empty string is returned if there was no status written to the channel or in case of an error.  Use getLastReadStatus() to get more specific information.

	*/ 
	String readStatus(unsigned long channelNumber)
	{
		return readStatus(channelNumber, NULL);
	};
	

	/*
	Function: readCreatedAt
	
	Summary:
	Read the created-at timestamp associated with the latest update to a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	readAPIKey - Read API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Results:
	Value read (UTF8 string). An empty string is returned if there was no created-at timestamp written to the channel or in case of an error.  Use getLastReadStatus() to get more specific information.
	
	*/
	String readCreatedAt(unsigned long channelNumber, const char * readAPIKey)
	{
		//String content = readRaw(channelNumber, "/feeds/last.txt", readAPIKey);
		String content = readRaw(channelNumber, "/feeds/last_data_age.txt?", readAPIKey);
		
		if(getLastReadStatus() != OK_SUCCESS){
			return String("");
		}
		
		//return getJSONValueByKey(content, "created_at");
		return content;//.toInt();
	};

	
	/*
	Function: readCreatedAt
	
	Summary:
	Read the created-at timestamp associated with the latest update to a private ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
		
	Results:
	Value read (UTF8 string). An empty string is returned if there was no created-at timestamp written to the channel or in case of an error.  Use getLastReadStatus() to get more specific information.
	
	*/
	String readCreatedAt(unsigned long channelNumber)
	{
		return readCreatedAt(channelNumber, NULL);
	};
	
	 
	/*
	Function: readRaw
	
	Summary:
	Read a raw response from a public ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	URLSuffix - Raw URL to write to ThingSpeak as a String.  See the documentation at https://thingspeak.com/docs/channels#get_feed
	
	Returns:
	Response if successful, or empty string. Use getLastReadStatus() to get more specific information.
	
	Notes:
	This is low level functionality that will not be required by most users.
	
	*/
	String readRaw(unsigned long channelNumber, String URLSuffix)
	{
		return readRaw(channelNumber, URLSuffix, NULL);
	}
	
	 
	/*
	Function: readRaw
	
	Summary:
	Read a raw response from a public ThingSpeak channel
	
	Parameters:
	channelNumber - Channel number
	URLSuffix - Raw URL to write to ThingSpeak as a String.  See the documentation at https://thingspeak.com/docs/channels#get_feed
	readAPIKey - Read API key associated with the channel.  *If you share code with others, do _not_ share this key*
	
	Returns:
	Response if successful, or empty string. Use getLastReadStatus() to get more specific information.
	
	Notes:
	This is low level functionality that will not be required by most users.
	
	*/ 
	String readRaw(unsigned long channelNumber, String URLSuffix, const char * readAPIKey)
	{
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("ts::readRaw   (channelNumber: "); Serial.print(channelNumber);
			if(NULL != readAPIKey)
			{
				Serial.print(" readAPIKey: "); Serial.print(readAPIKey);
			}
			Serial.print(" URLSuffix: \""); Serial.print(URLSuffix); Serial.println("\")");
		#endif

		if(!connectThingSpeak())
		{
			this->lastReadStatus = ERR_CONNECT_FAILED;
			return String("");
		}

		String URL = String("/channels/");
		URL.concat(channelNumber);
		URL.concat(URLSuffix);
		
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("               GET \"");Serial.print(URL);Serial.println("\"");
		#endif

		// Post data to thingspeak
		if(!this->client->print("GET ")) return abortReadRaw();
		if(!this->client->print(URL)) return abortReadRaw();
		if(!this->client->print(" HTTP/1.1\r\n")) return abortReadRaw();
		if(!writeHTTPHeader(readAPIKey)) return abortReadRaw();
		if(!this->client->print("\r\n")) return abortReadRaw();
 
		String content = String();
		int status = getHTTPResponse(content);
			
		this->lastReadStatus = status;

		emptyStream();
		
		#ifdef PRINT_DEBUG_MESSAGES
			if(status == OK_SUCCESS)
			{
				Serial.print("Read: \""); Serial.print(content); Serial.println("\"");
			}
		#endif
				
		this->client->stop();
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.println("disconnected.");
		#endif

		if(status != OK_SUCCESS)
		{
			// return status;
			return String("");
		}

    	return content;
	};
	
 
	/*
	Function: getLastReadStatus
	
	Summary:
	Get the status of the previous read.
	
	Returns:
	Generally, these are HTTP status codes.  Negative values indicate an error generated by the library.
	Possible response codes...
	200 - OK / Success
	404 - Incorrect API key (or invalid ThingSpeak server address)
	-101 - Value is out of range or string is too long (> 255 characters)
	-201 - Invalid field number specified
	-210 - setField() was not called before writeFields()
	-301 - Failed to connect to ThingSpeak
	-302 -  Unexpected failure during write to ThingSpeak
	-303 - Unable to parse response
    -304 - Timeout waiting for server to respond
	-401 - Point was not inserted (most probable cause is exceeding the rate limit)
	
	Notes:
	The read functions will return zero or empty if there is an error.  Use this function to retrieve the details.
	*/	
	int getLastReadStatus()
	{
		return this->lastReadStatus;
	};
private:
		
	int getWriteFieldsContentLength(){
		size_t iField;
		int contentLen = 0;
		
		for(iField = 0; iField < FIELDNUM_MAX; iField++){
			if(this->nextWriteField[iField].length() > 0){
				contentLen = contentLen + 8 + this->nextWriteField[iField].length();	// &fieldX=[value]
				
				// future-proof in case ThingSpeak allows 999 fields someday
				if(iField > 9){
					contentLen = contentLen + 1;
				}
				else if(iField > 99){
					contentLen = contentLen + 2;
				}
				
			}
		}
		
		if(!isnan(this->nextWriteLatitude)){
			contentLen = contentLen + 5 + String(this->nextWriteLatitude).length(); // &lat=[value]
		}
		
		if(!isnan(this->nextWriteLongitude)){
			contentLen = contentLen + 6 + String(this->nextWriteLongitude).length(); // &long=[value]
		}
		
		if(!isnan(this->nextWriteElevation)){
			contentLen = contentLen + 11 + String(this->nextWriteElevation).length(); // &elevation=[value]
		}
		
		if(this->nextWriteStatus.length() > 0){
			contentLen = contentLen + 8 + this->nextWriteStatus.length();	// &status=[value]
		}
		
		if(this->nextWriteTwitter.length() > 0){
			contentLen = contentLen + 9 + this->nextWriteTwitter.length();	// &twitter=[value]
		}
		
		if(this->nextWriteTweet.length() > 0){
			contentLen = contentLen + 7 + this->nextWriteTweet.length();	// &tweet=[value]
		}		
		
		if(this->nextWriteCreatedAt.length() > 0){
			contentLen = contentLen + 12 + this->nextWriteCreatedAt.length();	// &created_at=[value]
		}
		
		if(contentLen == 0){
			return 0;
		}
		
		contentLen = contentLen + 13; // add 14 for '&headers=false', subtract 1 for missing first '&'  
		
		return contentLen;
		
	}
	
	void emptyStream(){
		while(this->client->available() > 0){
			this->client->read();
		}
	}
	
	int finishWrite(){
		String entryIDText = String();
		int status = getHTTPResponse(entryIDText);
		
		emptyStream();
		
		if(status != OK_SUCCESS)
		{
			this->client->stop();
			return status;
		}
		long entryID = entryIDText.toInt();

		#ifdef PRINT_DEBUG_MESSAGES
		Serial.print("               Entry ID \"");Serial.print(entryIDText);Serial.print("\" (");Serial.print(entryID);Serial.println(")");
		#endif
		
		this->client->stop();
		
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.println("disconnected.");
		#endif
		if(entryID == 0)
		{
			// ThingSpeak did not accept the write
			status = ERR_NOT_INSERTED;
		}
		return status;
	}
	
	
	String getJSONValueByKey(String textToSearch, String key)
	{	
		if(textToSearch.length() == 0){
			return String("");
		} 
		
		String searchPhrase = String("\"");
		searchPhrase.concat(key);
		searchPhrase.concat("\":\"");
		
		int fromPosition = textToSearch.indexOf(searchPhrase,0);
		
		if(fromPosition == -1){
			// return because there is no status or it's null
			return String("");
		}
		
		fromPosition = fromPosition + searchPhrase.length();
				
		int toPosition = textToSearch.indexOf("\"", fromPosition);
		
		
		if(toPosition == -1){
			// return because there is no end quote
			return String("");
		}
		
		textToSearch.remove(toPosition);
		
		return textToSearch.substring(fromPosition);	
	}
	
    int abortWriteRaw()
    {
        while(this->client->available() > 0){
			this->client->read();
		}
		this->client->stop();
		resetWriteFields();
        return ERR_UNEXPECTED_FAIL;
    }

    String abortReadRaw()
    {
		while(this->client->available() > 0){
			this->client->read();
		}
		this->client->stop();
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.println("ReadRaw abort - disconnected.");
		#endif
		this->lastReadStatus = ERR_UNEXPECTED_FAIL;
		return String("");
    }

	void setPort(unsigned int port)
	{
		this->port = port;
	}
	
	
	void setClient(Client * client) {this->client = client;};

	Client * client = NULL;
    unsigned int port = THINGSPEAK_PORT_NUMBER;
	String nextWriteField[8];
	float nextWriteLatitude;
	float nextWriteLongitude;
	float nextWriteElevation;
	int lastReadStatus;
	String nextWriteStatus;
	String nextWriteTwitter;
	String nextWriteTweet;
	String nextWriteCreatedAt;

	bool connectThingSpeak()
	{
		bool connectSuccess = false;
		
		#ifdef PRINT_DEBUG_MESSAGES
			Serial.print("               Connect to default ThingSpeak: ");
			Serial.print(THINGSPEAK_URL);
			Serial.print(":");
			Serial.print(this->port);
			Serial.print("...");
		#endif
				
		connectSuccess = client->connect(const_cast<char *>(THINGSPEAK_URL), this->port);
            

		#ifdef PRINT_DEBUG_MESSAGES
		if (connectSuccess)
		{
			Serial.println("Success.");
		}
		else
		{
			Serial.println("Failed.");
		}
		#endif
		return connectSuccess;
	};

	bool writeHTTPHeader(const char * APIKey)
	{
 
		if (!this->client->print("Host: api.thingspeak.com\r\n")) return false;
		if (!this->client->print("User-Agent: ")) return false;
		if (!this->client->print(TS_USER_AGENT)) return false;
		if (!this->client->print("\r\n")) return false;
		if(NULL != APIKey)
		{
			if (!this->client->print("X-THINGSPEAKAPIKEY: ")) return false;
			if (!this->client->print(APIKey)) return false;
			if (!this->client->print("\r\n")) return false;
		}
		return true;
	};

	int getHTTPResponse(String & response)
	{

		// make sure all of the HTTP request is pushed out of the buffer before looking for a response
		this->client->flush();
		
		unsigned long timeoutTime = millis() + TIMEOUT_MS_SERVERRESPONSE;
		
		while(this->client-> available() < 17){
			delay(2);
			if(millis() > timeoutTime){
				return ERR_TIMEOUT;
			}
		}
		
		if(!this->client->find(const_cast<char *>("HTTP/1.1")))
		{
			#ifdef PRINT_HTTP
				Serial.println("ERROR: Didn't find HTTP/1.1");
    		#endif
			return ERR_BAD_RESPONSE; // Couldn't parse response (didn't find HTTP/1.1)
		}
		int status = this->client->parseInt();
		#ifdef PRINT_HTTP
			Serial.print("Got Status of ");Serial.println(status);
		#endif
		if(status != OK_SUCCESS)
		{
			return status;
		}

		// Find Content-Length
		if(!this->client->find(const_cast<char *>("Content-Length:"))){
			#ifdef PRINT_HTTP
			Serial.println("ERROR: Didn't find Content-Length header");
    		#endif
			return ERR_BAD_RESPONSE; // Couldn't parse response (didn't find HTTP/1.1)
		}
		int contentLength = this->client->parseInt();
		
		#ifdef PRINT_HTTP
			Serial.print("Content Length: ");
			Serial.println(contentLength);
    	#endif
		
		if(!this->client->find(const_cast<char *>("\r\n\r\n")))
		{
			#ifdef PRINT_HTTP
				Serial.println("ERROR: Didn't find end of headers");
			#endif
			return ERR_BAD_RESPONSE;
		}
		#ifdef PRINT_HTTP
			Serial.println("Found end of header");
		#endif
		
		timeoutTime = millis() + TIMEOUT_MS_SERVERRESPONSE;
		
		while(this->client->available() < contentLength){
			delay(2);
			if(millis() > timeoutTime){
				return ERR_TIMEOUT;
			}
		}
		
		String tempString = String("");
		char y = 0;
		for(int i = 0; i < contentLength; i++){
			y = client->read();
			tempString.concat(y);
		}
		response = tempString;
		
		
		#ifdef PRINT_HTTP
    		Serial.print("Response: \"");Serial.print(response);Serial.println("\"");
		#endif
		return status;
	};
	
	
	int convertFloatToChar(float value, char *valueString)
	{
		// Supported range is -999999000000 to 999999000000
		if(0 == isinf(value) && (value > 999999000000 || value < -999999000000))
		{
			// Out of range
			return ERR_OUT_OF_RANGE;
		}
		// assume that 5 places right of decimal should be sufficient for most applications

        #if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM)
		  sprintf(valueString, "%.5f", value);
		#else
		  dtostrf(value,1,5, valueString);
        #endif
		return OK_SUCCESS;
	};

	float convertStringToFloat(String value)
	{
		// There's a bug in the AVR function strtod that it doesn't decode -INF correctly (it maps it to INF)
		float result = value.toFloat();
		
		if(1 == isinf(result) && *value.c_str() == '-')
		{
			result = (float)-INFINITY;
		}
		return result;
	};

	void resetWriteFields()
	{
		for(size_t iField = 0; iField < FIELDNUM_MAX; iField++)
		{
			this->nextWriteField[iField] = "";
		}
		this->nextWriteLatitude = NAN;
		this->nextWriteLongitude = NAN;
		this->nextWriteElevation = NAN;
		this->nextWriteStatus = "";
		this->nextWriteTwitter = "";
		this->nextWriteTweet = "";
		this->nextWriteCreatedAt = "";
	};
};

extern ThingSpeakClass ThingSpeak;

#endif //ThingSpeak_h

Ну, это оверкилл. Устройства включаются несинхронно, миллис убегает. Вероятность возникновения коллизии что с разнопериодными отправками, что с однопериодными, думаю примерно одинакова. Delay(random(15, 23)) между отправками такой же эффект даст.

@sadman41 , устройства синхронизируются по моменту последней публикации. Милис только для далласов и периода публикации, который для всех девайсов 1 минута.

Надо синхронизировать часы.

рассинхронизация времени в пределах системы иногда приводит к таким последствиям, что “мама не горюй”. Я уже про это рассказывал, но если, вдруг, Вы пропустили, посмотрите (@Pyotr - Вы тоже) - очень интересно и поучительно как раз про рассинхронизацию.

Я не против. И даже предлагал общую шину с подобием CSMA/CD.

Но, раз выбор был сделан в сторону автономности и хардварно заданных интервалов, то предложил способ попроще, без правки исходников под каждый экземпляр ноды.

Дискуссионный вопрос, конечно - реальна ли синхронизация через считывание значения поля с сервера по не рилтайм протоколу под операционкой с неприоритетным юзерспейсом и одним ядром.

Кстати, @sadman41 Вы вроде спрашивали почему “error 1202 and 1201” называют самой дорогой ошибкой в истории программирования. В новый год мне позвонил старый приятель, который всю жизнь в НАСА проработал, я у него спросил. Он ответил, что на работы по выяснению причин бага и последующему его исправлению был потрачен рекордный за всю историю НАСА бюджет.

Но эта (на которую я выше сослался) ошибка с расхождением суммируемых в разных местах чисел 0.1 мне нравится больше. Да и стоимость её - 28 убитых и за сотню раненых, тоже нехило. Не всё же одним баблом измерять.
.

К слову - у нас сегодня - 37 ночью ожидается и это не будет ошибкой измерения. ))