Fix issue where stop command is issued when seeking the current tilt position #239

This commit is contained in:
Robert Strouse 2024-01-14 13:13:51 -08:00
parent 1b35c7d835
commit 01895d0ec5
7 changed files with 89 additions and 65 deletions

125
SSDP.cpp
View file

@ -32,14 +32,18 @@ static const char _ssdp_bye_template[] PROGMEM =
"NTS: ssdp:byebye\r\n" "NTS: ssdp:byebye\r\n"
"NT: %s\r\n" "NT: %s\r\n"
"USN: %s\r\n" "USN: %s\r\n"
"BOOTID.UPNP.ORG: %ul\r\n"
"CONFIGID.UPNP.ORG: %d\r\n"
"\r\n"; "\r\n";
static const char _ssdp_packet_template[] PROGMEM = static const char _ssdp_packet_template[] PROGMEM =
"%s" // _ssdp_response_template / _ssdp_notify_template "%s" // _ssdp_response_template / _ssdp_notify_template
"CACHE-CONTROL: max-age=%u\r\n" // _interval "CACHE-CONTROL: max-age=%u\r\n" // _interval
"SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber "SERVER: Arduino/1.0 UPnP/1.1 ESPSomfyRTS/2.0\r\n"
"USN: %s\r\n" // _uuid "USN: %s\r\n" // _uuid
"%s: %s\r\n" // "NT" or "ST", _deviceType "%s: %s\r\n" // "NT" or "ST", _deviceType
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
"BOOTID.UPNP.ORG: %ul\r\n"
"CONFIGID.UPNP.ORG: %d\r\n"
"\r\n"; "\r\n";
static const char _ssdp_device_schema_template[] PROGMEM = static const char _ssdp_device_schema_template[] PROGMEM =
"<device>" "<device>"
@ -105,6 +109,20 @@ void UPNPDeviceType::setModelURL(const char *url) { strlcpy(modelURL, url, sizeo
void UPNPDeviceType::setManufacturer(const char *name) { strlcpy(manufacturer, name, sizeof(manufacturer)); } void UPNPDeviceType::setManufacturer(const char *name) { strlcpy(manufacturer, name, sizeof(manufacturer)); }
void UPNPDeviceType::setManufacturerURL(const char *url) { strlcpy(manufacturerURL, url, sizeof(manufacturerURL)); } void UPNPDeviceType::setManufacturerURL(const char *url) { strlcpy(manufacturerURL, url, sizeof(manufacturerURL)); }
//char * UPNPDeviceType::getUSN() { return this->getUSN(this->deviceType); } //char * UPNPDeviceType::getUSN() { return this->getUSN(this->deviceType); }
char * UPNPDeviceType::getUSN(response_types_t responseType) {
switch(responseType) {
case response_types_t::root:
snprintf_P(this->m_usn, sizeof(this->m_usn) - 1, _ssdp_usn_root_template, this->uuid);
break;
case response_types_t::deviceType:
snprintf_P(this->m_usn, sizeof(this->m_usn) -1, _ssdp_usn_urn_template, this->uuid, this->deviceType);
break;
default:
snprintf_P(this->m_usn, sizeof(this->m_usn) - 1, _ssdp_usn_uuid_template, this->uuid);
break;
}
return this->m_usn;
}
char * UPNPDeviceType::getUSN(const char *st) { char * UPNPDeviceType::getUSN(const char *st) {
//#ifdef DEBUG_SSDP //#ifdef DEBUG_SSDP
//DEBUG_SSDP.print("GETUSN ST: "); //DEBUG_SSDP.print("GETUSN ST: ");
@ -157,6 +175,8 @@ bool SSDPClass::begin() {
#endif #endif
return false; return false;
} }
this->bootId = Timestamp::epoch();
this->configId = (settings.fwVersion.major * 100) + (settings.fwVersion.minor * 10) + settings.fwVersion.build;
_server.onPacket([](void * arg, AsyncUDPPacket& packet) { ((SSDPClass*)(arg))->_processRequest(packet); }, this); _server.onPacket([](void * arg, AsyncUDPPacket& packet) { ((SSDPClass*)(arg))->_processRequest(packet); }, this);
if(!_server.listenMulticast(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT)) { if(!_server.listenMulticast(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT)) {
#ifdef DEBUG_SSDP #ifdef DEBUG_SSDP
@ -368,7 +388,7 @@ IPAddress SSDPClass::localIP()
} }
return IPAddress(ip.ip.addr); return IPAddress(ip.ip.addr);
} }
void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, char *st, bool sendUUID) { void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType) {
char buffer[1460]; char buffer[1460];
IPAddress ip = this->localIP(); IPAddress ip = this->localIP();
char *pbuff = (char *)malloc(strlen_P(_ssdp_response_template)+1); char *pbuff = (char *)malloc(strlen_P(_ssdp_response_template)+1);
@ -380,40 +400,17 @@ void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d,
} }
strcpy_P(pbuff, _ssdp_response_template); strcpy_P(pbuff, _ssdp_response_template);
#ifdef DEBUG_SSDP
//3FFC40B8
DEBUG_SSDP.print("CACHE-CONTROL: max-age=");
DEBUG_SSDP.println(this->_interval);
DEBUG_SSDP.print("SERVER: Arduino/1.0 UPNP/1.1 ");
DEBUG_SSDP.print(d->modelName);
DEBUG_SSDP.print("/");
DEBUG_SSDP.println(d->modelNumber);
DEBUG_SSDP.print("USN: ");
DEBUG_SSDP.println(d->getUSN(st));
DEBUG_SSDP.print("ST: ");
DEBUG_SSDP.println((sendUUID) ? d->uuid : st);
DEBUG_SSDP.printf("LOCATION: http://%d.%d.%d.%d:%d/", ip[0], ip[1], ip[2], ip[3], this->_port);
//DEBUG_SSDP.print(ip[0]);
//DEBUG_SSDP.print(ip[1]);
//DEBUG_SSDP.print(ip[2]);
//DEBUG_SSDP.print(ip[3]);
//DEBUG_SSDP.print(":");
//DEBUG_SSDP.print(this->_port);
DEBUG_SSDP.println(d->schemaURL);
#endif
// Don't use ip.toString as this fragments the heap like no tomorrow. // Don't use ip.toString as this fragments the heap like no tomorrow.
int len = snprintf_P(buffer, sizeof(buffer)-1, int len = snprintf_P(buffer, sizeof(buffer)-1,
_ssdp_packet_template, _ssdp_packet_template,
pbuff, pbuff,
this->_interval, this->_interval,
d->modelName, d->modelNumber, d->getUSN(responseType),
d->getUSN(st), "ST", st,
"ST", (sendUUID) ? d->uuid : st, ip[0], ip[1], ip[2], ip[3], this->_port, d->schemaURL,
ip[0], ip[1], ip[2], ip[3], this->_port, d->schemaURL); this->bootId, this->configId);
#ifdef DEBUG_SSDP buffer[sizeof(buffer) - 1] = '\0';
DEBUG_SSDP.println(buffer); this->_sendResponse(addr, port, buffer);
#endif
free(pbuff); free(pbuff);
/* /*
static const char _ssdp_packet_template[] PROGMEM = static const char _ssdp_packet_template[] PROGMEM =
@ -424,10 +421,9 @@ static const char _ssdp_packet_template[] PROGMEM =
"%s: %s\r\n" // "NT" or "ST", _deviceType "%s: %s\r\n" // "NT" or "ST", _deviceType
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
"\r\n"; "\r\n";
*/
buffer[sizeof(buffer) - 1] = '\0';
#ifdef DEBUG_SSDP #ifdef DEBUG_SSDP
DEBUG_SSDP.print("Sending Response to "); DEBUG_SSDP.print("Sending Response to ");
DEBUG_SSDP.print(IPAddress(addr)); DEBUG_SSDP.print(IPAddress(addr));
@ -437,6 +433,18 @@ static const char _ssdp_packet_template[] PROGMEM =
#endif #endif
_server.writeTo((const uint8_t *)buffer, len, addr, port); _server.writeTo((const uint8_t *)buffer, len, addr, port);
*/
}
void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, const char *buff) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.print("Sending Response to ");
DEBUG_SSDP.print(IPAddress(addr));
DEBUG_SSDP.print(":");
DEBUG_SSDP.println(port);
DEBUG_SSDP.println(buff);
#endif
_server.writeTo((const uint8_t *)buff, strlen(buff), addr, port);
} }
void SSDPClass::_sendNotify() { void SSDPClass::_sendNotify() {
//Serial.printf("sendNotify %d\n", this->m_cdeviceTypes); //Serial.printf("sendNotify %d\n", this->m_cdeviceTypes);
@ -509,10 +517,9 @@ void SSDPClass::_sendNotify(UPNPDeviceType *d, bool root) {
_ssdp_packet_template, _ssdp_packet_template,
pbuff, pbuff,
this->_interval, this->_interval,
d->modelName, d->modelNumber, d->getUSN(response_types_t::root), // USN
d->getUSN("upnp:rootdevice"), // USN
"NT", "upnp:rootdevice", "NT", "upnp:rootdevice",
ip[0], ip[1], ip[2], ip[3], this->_port, d->schemaURL); ip[0], ip[1], ip[2], ip[3], this->_port, d->schemaURL, this->bootId, this->configId);
this->_sendNotify(buffer); this->_sendNotify(buffer);
} }
// Send 1 for uuid // Send 1 for uuid
@ -520,22 +527,18 @@ void SSDPClass::_sendNotify(UPNPDeviceType *d, bool root) {
_ssdp_packet_template, _ssdp_packet_template,
pbuff, pbuff,
this->_interval, this->_interval,
d->modelName, d->getUSN(response_types_t::uuid),
d->modelNumber,
d->getUSN(d->uuid),
"NT", d->uuid, "NT", d->uuid,
ip[0], ip[1], ip[2], ip[3], _port, d->schemaURL); ip[0], ip[1], ip[2], ip[3], _port, d->schemaURL, this->bootId, this->configId);
this->_sendNotify(buffer); this->_sendNotify(buffer);
// Send 1 for deviceType // Send 1 for deviceType
snprintf_P(buffer, sizeof(buffer), snprintf_P(buffer, sizeof(buffer),
_ssdp_packet_template, _ssdp_packet_template,
pbuff, pbuff,
this->_interval, this->_interval,
d->modelName, d->getUSN(response_types_t::deviceType),
d->modelNumber,
d->getUSN(d->deviceType),
"NT", d->deviceType, "NT", d->deviceType,
ip[0], ip[1], ip[2], ip[3], _port, d->schemaURL); ip[0], ip[1], ip[2], ip[3], _port, d->schemaURL, this->bootId, this->configId);
this->_sendNotify(buffer); this->_sendNotify(buffer);
d->lastNotified = millis(); d->lastNotified = millis();
} }
@ -588,23 +591,23 @@ void SSDPClass::_sendByeBye(UPNPDeviceType *d, bool root) {
// Send 1 for root // Send 1 for root
snprintf_P(buffer, sizeof(buffer)-1, snprintf_P(buffer, sizeof(buffer)-1,
_ssdp_bye_template, _ssdp_bye_template,
"upnp:rootdevice", d->getUSN("upnp:rootdevice")); "upnp:rootdevice", d->getUSN(response_types_t::root), this->bootId, this->configId);
this->_sendNotify(buffer); this->_sendNotify(buffer);
} }
// Send 1 for uuid // Send 1 for uuid
snprintf_P(buffer, sizeof(buffer)-1, snprintf_P(buffer, sizeof(buffer)-1,
_ssdp_bye_template, _ssdp_bye_template,
d->getUSN(d->uuid), d->getUSN(response_types_t::uuid),
d->getUSN(d->uuid)); d->getUSN(response_types_t::uuid), this->bootId, this->configId);
this->_sendNotify(buffer); this->_sendNotify(buffer);
// Send 1 for deviceType // Send 1 for deviceType
snprintf_P(buffer, sizeof(buffer)-1, snprintf_P(buffer, sizeof(buffer)-1,
_ssdp_bye_template, _ssdp_bye_template,
d->deviceType, d->deviceType,
d->getUSN(d->deviceType)); d->getUSN(response_types_t::deviceType), this->bootId, this->configId);
this->_sendNotify(buffer); this->_sendNotify(buffer);
} }
void SSDPClass::_addToSendQueue(IPAddress addr, uint16_t port, UPNPDeviceType *d, char *st, uint8_t sec, bool sendUUID) { void SSDPClass::_addToSendQueue(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType, uint8_t sec) {
/* /*
typedef struct ssdp_response_t { typedef struct ssdp_response_t {
IPAddress address; IPAddress address;
@ -619,7 +622,7 @@ void SSDPClass::_addToSendQueue(IPAddress addr, uint16_t port, UPNPDeviceType *d
if(this->sendQueue[i].waiting) { if(this->sendQueue[i].waiting) {
// Check to see if this is a reply to the same place. // Check to see if this is a reply to the same place.
ssdp_response_t *q = &this->sendQueue[i]; ssdp_response_t *q = &this->sendQueue[i];
if(q->address == addr && q->port == port && q->sendUUID == sendUUID) { if(q->address == addr && q->port == port && q->responseType == responseType) {
#ifdef DEBUG_SSDP #ifdef DEBUG_SSDP
DEBUG_SSDP.printf("There is already a response to this query in slot %u\n", i); DEBUG_SSDP.printf("There is already a response to this query in slot %u\n", i);
#endif #endif
@ -639,7 +642,7 @@ void SSDPClass::_addToSendQueue(IPAddress addr, uint16_t port, UPNPDeviceType *d
q->sendTime = millis() + (random(0, sec - 1) * 1000L); q->sendTime = millis() + (random(0, sec - 1) * 1000L);
q->address = addr; q->address = addr;
q->port = port; q->port = port;
q->sendUUID = sendUUID; q->responseType = responseType;
strlcpy(q->st, st, sizeof(ssdp_response_t::st)-1); strlcpy(q->st, st, sizeof(ssdp_response_t::st)-1);
q->waiting = true; q->waiting = true;
return; return;
@ -660,7 +663,7 @@ void SSDPClass::_sendQueuedResponses() {
DEBUG_SSDP.print("Sending SSDP queued response "); DEBUG_SSDP.print("Sending SSDP queued response ");
DEBUG_SSDP.println(i); DEBUG_SSDP.println(i);
#endif #endif
this->_sendResponse(q->address, q->port, q->dev, q->st, q->sendUUID); this->_sendResponse(q->address, q->port, q->dev, q->st, q->responseType);
q->waiting = false; q->waiting = false;
return; return;
} }
@ -708,8 +711,12 @@ void SSDPClass::_processRequest(AsyncUDPPacket &p) {
this->_printPacket(&pkt); this->_printPacket(&pkt);
#endif #endif
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) { for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
if(strlen(this->deviceTypes[i].deviceType) > 0) UPNPDeviceType *dev = &this->deviceTypes[i];
this->_addToSendQueue(p.remoteIP(), p.remotePort(), &this->deviceTypes[i], pkt.st, pkt.mx, false); if(strlen(this->deviceTypes[i].deviceType) > 0) {
this->_addToSendQueue(p.remoteIP(), p.remotePort(), dev, pkt.st, response_types_t::root, pkt.mx);
this->_addToSendQueue(p.remoteIP(), p.remotePort(), dev, pkt.st, response_types_t::uuid, pkt.mx);
this->_addToSendQueue(p.remoteIP(), p.remotePort(), dev, pkt.st, response_types_t::deviceType, pkt.mx);
}
} }
} }
else if(strcmp("upnp:rootdevice", pkt.st) == 0) { else if(strcmp("upnp:rootdevice", pkt.st) == 0) {
@ -719,9 +726,9 @@ void SSDPClass::_processRequest(AsyncUDPPacket &p) {
this->_printPacket(&pkt); this->_printPacket(&pkt);
#endif #endif
if(pkt.type == MULTICAST) if(pkt.type == MULTICAST)
this->_addToSendQueue(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT, dev, pkt.st, pkt.mx, false); this->_addToSendQueue(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT, dev, pkt.st, response_types_t::root, pkt.mx);
else else
this->_sendResponse(p.remoteIP(), p.remotePort(), dev, pkt.st, false); this->_sendResponse(p.remoteIP(), p.remotePort(), dev, pkt.st, response_types_t::root);
} }
else { else {
UPNPDeviceType *dev = nullptr; UPNPDeviceType *dev = nullptr;
@ -737,15 +744,13 @@ void SSDPClass::_processRequest(AsyncUDPPacket &p) {
DEBUG_SSDP.println("-------------- ACCEPT --------------------"); DEBUG_SSDP.println("-------------- ACCEPT --------------------");
#endif #endif
if(pkt.type == MULTICAST) if(pkt.type == MULTICAST)
this->_addToSendQueue(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT, dev, pkt.st, pkt.mx, useUUID); this->_addToSendQueue(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT, dev, pkt.st, useUUID ? response_types_t::uuid : response_types_t::root, pkt.mx);
else { else {
this->_sendResponse(p.remoteIP(), p.remotePort(), dev, pkt.st, useUUID); this->_sendResponse(p.remoteIP(), p.remotePort(), dev, pkt.st, useUUID ? response_types_t::uuid : response_types_t::root);
} }
} }
} }
} }
else if(pkt.method == SEARCH) {
}
} }
void SSDPClass::loop() { void SSDPClass::loop() {
this->_sendQueuedResponses(); this->_sendQueuedResponses();

14
SSDP.h
View file

@ -30,6 +30,11 @@ typedef enum {
SEARCH, SEARCH,
NOTIFY NOTIFY
} ssdp_method_t; } ssdp_method_t;
typedef enum {
root,
uuid,
deviceType
} response_types_t;
#define SSDP_FIELD_LEN 128 #define SSDP_FIELD_LEN 128
#define SSDP_LOCATION_LEN 256 #define SSDP_LOCATION_LEN 256
@ -90,6 +95,7 @@ class UPNPDeviceType {
void setManufacturerURL(const char *url); void setManufacturerURL(const char *url);
//char *getUSN(); //char *getUSN();
char *getUSN(const char *st); char *getUSN(const char *st);
char *getUSN(response_types_t responseType);
void setChipId(uint32_t chipId); void setChipId(uint32_t chipId);
}; };
struct ssdp_response_t { struct ssdp_response_t {
@ -100,6 +106,7 @@ struct ssdp_response_t {
bool sendUUID; bool sendUUID;
unsigned long sendTime; unsigned long sendTime;
char st[SSDP_DEVICE_TYPE_SIZE]; char st[SSDP_DEVICE_TYPE_SIZE];
response_types_t responseType;
}; };
class SSDPClass { class SSDPClass {
uint8_t m_cdeviceTypes = SSDP_CHILD_DEVICES + 1; uint8_t m_cdeviceTypes = SSDP_CHILD_DEVICES + 1;
@ -119,7 +126,8 @@ class SSDPClass {
void _sendByeBye(UPNPDeviceType *d, bool root); void _sendByeBye(UPNPDeviceType *d, bool root);
void _sendByeBye(const char *); void _sendByeBye(const char *);
void _sendQueuedResponses(); void _sendQueuedResponses();
void _sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, char *st, bool sendUUID); void _sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType);
void _sendResponse(IPAddress addr, uint16_t port, const char *msg);
AsyncUDP _server; AsyncUDP _server;
hw_timer_t* _timer = nullptr; hw_timer_t* _timer = nullptr;
uint16_t _port = SSDP_HTTP_PORT; uint16_t _port = SSDP_HTTP_PORT;
@ -129,7 +137,7 @@ class SSDPClass {
void _parsePacket(ssdp_packet_t *pkt, AsyncUDPPacket &p); void _parsePacket(ssdp_packet_t *pkt, AsyncUDPPacket &p);
void _printPacket(ssdp_packet_t *pkt); void _printPacket(ssdp_packet_t *pkt);
bool _startsWith(const char* pre, const char* str); bool _startsWith(const char* pre, const char* str);
void _addToSendQueue(IPAddress addr, uint16_t port, UPNPDeviceType *d, char *st, uint8_t sec, bool sendUUID); void _addToSendQueue(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType, uint8_t sec);
public: public:
SSDPClass(); SSDPClass();
@ -139,6 +147,8 @@ class SSDPClass {
void loop(); void loop();
void end(); void end();
bool isStarted = false; bool isStarted = false;
unsigned long bootId = 0;
int configId = 0;
IPAddress localIP(); IPAddress localIP();
UPNPDeviceType* getDeviceTypes(uint8_t ndx); UPNPDeviceType* getDeviceTypes(uint8_t ndx);
UPNPDeviceType* getDeviceType(uint8_t ndx); UPNPDeviceType* getDeviceType(uint8_t ndx);

View file

@ -2702,6 +2702,7 @@ void SomfyShade::moveToTiltTarget(float target) {
else if(target > this->currentTiltPos) else if(target > this->currentTiltPos)
cmd = somfy_commands::Down; cmd = somfy_commands::Down;
if(target >= 0.0f && target <= 100.0f) { if(target >= 0.0f && target <= 100.0f) {
// Only send a command if the lift is not moving.
if(this->currentPos == this->target || this->tiltType == tilt_types::tiltmotor) { if(this->currentPos == this->target || this->tiltType == tilt_types::tiltmotor) {
if(cmd != somfy_commands::My) { if(cmd != somfy_commands::My) {
Serial.print("Moving Tilt to "); Serial.print("Moving Tilt to ");
@ -2712,12 +2713,12 @@ void SomfyShade::moveToTiltTarget(float target) {
Serial.println(translateSomfyCommand(cmd)); Serial.println(translateSomfyCommand(cmd));
SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->repeats); SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->repeats);
} }
else // If the blind is currently moving then the command to stop it
SomfyRemote::sendCommand(cmd, this->repeats); // will occur on its own when the tilt target is set.
} }
this->p_tiltTarget(target); this->p_tiltTarget(target);
} }
this->settingTiltPos = true; if(cmd != somfy_commands::My) this->settingTiltPos = true;
} }
void SomfyShade::moveToTarget(float pos, float tilt) { void SomfyShade::moveToTarget(float pos, float tilt) {
somfy_commands cmd = somfy_commands::My; somfy_commands cmd = somfy_commands::My;

Binary file not shown.

Binary file not shown.

View file

@ -6,6 +6,13 @@
/********************************************************************* /*********************************************************************
* Timestamp class members * Timestamp class members
********************************************************************/ ********************************************************************/
unsigned long Timestamp::epoch() {
struct tm tmNow;
time_t now;
if(!getLocalTime(&tmNow)) return 0;
time(&now);
return now;
}
time_t Timestamp::now() { time_t Timestamp::now() {
struct tm tmNow; struct tm tmNow;
getLocalTime(&tmNow); getLocalTime(&tmNow);

View file

@ -63,6 +63,7 @@ class Timestamp {
static time_t mkUTCTime(struct tm *dt); static time_t mkUTCTime(struct tm *dt);
static int calcTZOffset(time_t *dt); static int calcTZOffset(time_t *dt);
static time_t now(); static time_t now();
static unsigned long epoch();
}; };
// Sort an array // Sort an array
template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray); template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray);