temporary state of new ssdp implementation

pull/46/head
cschwinne 2018-01-07 22:52:48 +01:00
rodzic 4f8f5e7e3a
commit 78b344f437
10 zmienionych plików z 275 dodań i 410 usunięć

Wyświetl plik

@ -1,8 +0,0 @@
#ifndef CALLBACKFUNCTION_H
#define CALLBACKFUNCTION_H
#include <Arduino.h>
typedef void (*CallbackFunction) ();
#endif

Wyświetl plik

@ -1,23 +0,0 @@
https://github.com/kakopappa/arduino-esp8266-alexa-multiple-wemo-switch
The MIT License (MIT)
Copyright (c) 2015
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Wyświetl plik

@ -1,211 +0,0 @@
#include "Switch.h"
#include "CallbackFunction.h"
//#define DEBUG
#ifdef DEBUG
#define DEBUG_PRINT(x) Serial.print (x)
#define DEBUG_PRINTLN(x) Serial.println (x)
#define DEBUG_PRINTF(x) Serial.printf (x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINTF(x)
#endif
//<<constructor>>
Switch::Switch(){
DEBUG_PRINTLN("default constructor called");
}
//Switch::Switch(String alexaInvokeName,unsigned int port){
Switch::Switch(String alexaInvokeName, unsigned int port, CallbackFunction oncb, CallbackFunction offcb){
uint32_t chipId = ESP.getChipId();
char uuid[64];
sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"),
(uint16_t) ((chipId >> 16) & 0xff),
(uint16_t) ((chipId >> 8) & 0xff),
(uint16_t) chipId & 0xff);
serial = String(uuid);
persistent_uuid = "Socket-1_0-" + serial+"-"+ String(port);
device_name = alexaInvokeName;
localPort = port;
onCallback = oncb;
offCallback = offcb;
startWebServer();
}
//<<destructor>>
Switch::~Switch(){/*nothing to destruct*/}
void Switch::serverLoop(){
if (server != NULL) {
server->handleClient();
delay(1);
}
}
void Switch::startWebServer(){
server = new ESP8266WebServer(localPort);
server->on("/", [&]() {
handleRoot();
});
server->on("/setup.xml", [&]() {
handleSetupXml();
});
server->on("/upnp/control/basicevent1", [&]() {
handleUpnpControl();
});
server->on("/eventservice.xml", [&]() {
handleEventservice();
});
//server->onNotFound(handleNotFound);
server->begin();
DEBUG_PRINTLN("WebServer started on port: ");
DEBUG_PRINTLN(localPort);
}
void Switch::handleEventservice(){
DEBUG_PRINTLN(" ########## Responding to eventservice.xml ... ########\n");
String eventservice_xml = "<?scpd xmlns=\"urn:Belkin:service-1-0\"?>"
"<actionList>"
"<action>"
"<name>SetBinaryState</name>"
"<argumentList>"
"<argument>"
"<retval/>"
"<name>BinaryState</name>"
"<relatedStateVariable>BinaryState</relatedStateVariable>"
"<direction>in</direction>"
"</argument>"
"</argumentList>"
"<serviceStateTable>"
"<stateVariable sendEvents=\"yes\">"
"<name>BinaryState</name>"
"<dataType>Boolean</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"<stateVariable sendEvents=\"yes\">"
"<name>level</name>"
"<dataType>string</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"</serviceStateTable>"
"</action>"
"</scpd>\r\n"
"\r\n";
server->send(200, "text/plain", eventservice_xml.c_str());
}
void Switch::handleUpnpControl(){
DEBUG_PRINTLN("########## Responding to /upnp/control/basicevent1 ... ##########");
//for (int x=0; x <= HTTP.args(); x++) {
// DEBUG_PRINTLN(HTTP.arg(x));
//}
String request = server->arg(0);
DEBUG_PRINT("request:");
DEBUG_PRINTLN(request);
if(request.indexOf("<BinaryState>1</BinaryState>") > 0) {
DEBUG_PRINTLN("Got Turn on request");
onCallback();
}
if(request.indexOf("<BinaryState>0</BinaryState>") > 0) {
DEBUG_PRINTLN("Got Turn off request");
offCallback();
}
server->send(200, "text/plain", "");
}
void Switch::handleRoot(){
server->send(200, "text/plain", "You should tell Alexa to discover devices");
}
void Switch::handleSetupXml(){
DEBUG_PRINTLN(" ########## Responding to setup.xml ... ########\n");
IPAddress localIP = WiFi.localIP();
char s[16];
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
String setup_xml = "<?xml version=\"1.0\"?>"
"<root>"
"<device>"
"<deviceType>urn:Belkin:device:controllee:1</deviceType>"
"<friendlyName>"+ device_name +"</friendlyName>"
"<manufacturer>Belkin International Inc.</manufacturer>"
"<modelName>Emulated Socket</modelName>"
"<modelNumber>3.1415</modelNumber>"
"<UDN>uuid:"+ persistent_uuid +"</UDN>"
"<serialNumber>221517K0101769</serialNumber>"
"<binaryState>0</binaryState>"
"<serviceList>"
"<service>"
"<serviceType>urn:Belkin:service:basicevent:1</serviceType>"
"<serviceId>urn:Belkin:serviceId:basicevent1</serviceId>"
"<controlURL>/upnp/control/basicevent1</controlURL>"
"<eventSubURL>/upnp/event/basicevent1</eventSubURL>"
"<SCPDURL>/eventservice.xml</SCPDURL>"
"</service>"
"</serviceList>"
"</device>"
"</root>\r\n"
"\r\n";
server->send(200, "text/xml", setup_xml.c_str());
DEBUG_PRINT("Sending :");
DEBUG_PRINTLN(setup_xml);
}
String Switch::getAlexaInvokeName() {
return device_name;
}
void Switch::respondToSearch(IPAddress& senderIP, unsigned int senderPort) {
DEBUG_PRINTLN("");
DEBUG_PRINT("Sending response to ");
DEBUG_PRINTLN(senderIP);
DEBUG_PRINT("Port : ");
DEBUG_PRINTLN(senderPort);
IPAddress localIP = WiFi.localIP();
char s[16];
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
String response =
"HTTP/1.1 200 OK\r\n"
"CACHE-CONTROL: max-age=86400\r\n"
"DATE: Sat, 26 Nov 2016 04:56:29 GMT\r\n"
"EXT:\r\n"
"LOCATION: http://" + String(s) + ":" + String(localPort) + "/setup.xml\r\n"
"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n"
"01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n"
"SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
"ST: urn:Belkin:device:**\r\n"
"USN: uuid:" + persistent_uuid + "::urn:Belkin:device:**\r\n"
"X-User-Agent: redsonic\r\n\r\n";
UDP.beginPacket(senderIP, senderPort);
UDP.write(response.c_str());
UDP.endPacket();
DEBUG_PRINTLN("Response sent !");
}

Wyświetl plik

@ -1,36 +0,0 @@
#ifndef SWITCH_H
#define SWITCH_H
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiUDP.h>
#include "CallbackFunction.h"
class Switch {
private:
ESP8266WebServer *server = NULL;
WiFiUDP UDP;
String serial;
String persistent_uuid;
String device_name;
unsigned int localPort;
CallbackFunction onCallback;
CallbackFunction offCallback;
void startWebServer();
void handleEventservice();
void handleUpnpControl();
void handleRoot();
void handleSetupXml();
public:
Switch();
Switch(String alexaInvokeName, unsigned int port, CallbackFunction onCallback, CallbackFunction offCallback);
~Switch();
String getAlexaInvokeName();
void serverLoop();
void respondToSearch(IPAddress& senderIP, unsigned int senderPort);
};
#endif

Wyświetl plik

@ -1,98 +0,0 @@
#include "UpnpBroadcastResponder.h"
#include "Switch.h"
#include <functional>
//#define DEBUG
#ifdef DEBUG
#define DEBUG_PRINT(x) Serial.print (x)
#define DEBUG_PRINTLN(x) Serial.println (x)
#define DEBUG_PRINTF(x) Serial.printf (x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINTF(x)
#endif
// Multicast declarations
IPAddress ipMulti(239, 255, 255, 250);
const unsigned int portMulti = 1900;
char packetBuffer[512];
#define MAX_SWITCHES 14
Switch switches[MAX_SWITCHES] = {};
int numOfSwitchs = 0;
//#define numOfSwitchs (sizeof(switches)/sizeof(Switch)) //array size
//<<constructor>>
UpnpBroadcastResponder::UpnpBroadcastResponder(){
}
//<<destructor>>
UpnpBroadcastResponder::~UpnpBroadcastResponder(){/*nothing to destruct*/}
bool UpnpBroadcastResponder::beginUdpMulticast(){
boolean state = false;
DEBUG_PRINTLN("Begin multicast ..");
if(UDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti)) {
DEBUG_PRINT("Udp multicast server started at ");
DEBUG_PRINT(ipMulti);
DEBUG_PRINT(":");
DEBUG_PRINTLN(portMulti);
state = true;
}
else{
DEBUG_PRINTLN("Connection failed");
}
return state;
}
//Switch *ptrArray;
void UpnpBroadcastResponder::addDevice(Switch& device) {
DEBUG_PRINT("Adding switch : ");
DEBUG_PRINT(device.getAlexaInvokeName());
DEBUG_PRINT(" index : ");
DEBUG_PRINTLN(numOfSwitchs);
switches[numOfSwitchs] = device;
numOfSwitchs++;
}
void UpnpBroadcastResponder::serverLoop(){
int packetSize = UDP.parsePacket();
if (packetSize <= 0)
return;
IPAddress senderIP = UDP.remoteIP();
unsigned int senderPort = UDP.remotePort();
// read the packet into the buffer
UDP.read(packetBuffer, packetSize);
// check if this is a M-SEARCH for WeMo device
String request = String((char *)packetBuffer);
if(request.indexOf('M-SEARCH') > 0) {
if(request.indexOf("urn:Belkin:device:**") > 0) {
DEBUG_PRINTLN("Got UDP Belkin Request..");
// int arrSize = sizeof(switchs) / sizeof(Switch);
for(int n = 0; n < numOfSwitchs; n++) {
Switch &sw = switches[n];
if (&sw != NULL) {
sw.respondToSearch(senderIP, senderPort);
}
}
}
}
}

Wyświetl plik

@ -1,20 +0,0 @@
#ifndef UPNPBROADCASTRESPONDER_H
#define UPNPBROADCASTRESPONDER_H
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUDP.h>
#include "Switch.h"
class UpnpBroadcastResponder {
private:
WiFiUDP UDP;
public:
UpnpBroadcastResponder();
~UpnpBroadcastResponder();
bool beginUdpMulticast();
void serverLoop();
void addDevice(Switch& device);
};
#endif

Wyświetl plik

@ -17,15 +17,12 @@
#include "src/dependencies/time/Time.h"
#include "src/dependencies/time/TimeLib.h"
#include "src/dependencies/timezone/Timezone.h"
#include "src/dependencies/alexa-multiple/switch.h"
#include "src/dependencies/alexa-multiple/UpnpBroadcastResponder.h"
#include "src/dependencies/alexa-multiple/CallbackFunction.h"
#include "htmls00.h"
#include "htmls01.h"
#include "WS2812FX.h"
//version in format yymmddb (b = daily build)
#define VERSION 1712300
#define VERSION 1712310
//AP and OTA default passwords (change them!)
String appass = "wled1234";
@ -34,10 +31,10 @@ String otapass = "wledota";
//If you have an RGBW strip, uncomment first line in WS2812FX.h!
//overlays, needed for clocks etc.
//#define USEOVERLAYS
#define USEOVERLAYS
//support for the CRONIXIE clock by Diamex (disable overlays!)
#define CRONIXIE
//#define CRONIXIE
//spiffs FS only useful for debug
//#define USEFS
@ -217,8 +214,12 @@ boolean useGammaCorrectionRGB = true;
int arlsOffset = -22; //10: -22 assuming arls52
//alexa
Switch *alexa = NULL;
UpnpBroadcastResponder upnpBroadcastResponder;
WiFiUDP UDP;
IPAddress ipMulti(239, 255, 255, 250);
unsigned int portMulti = 1900;
unsigned int localPort = 1900;
char packetBuffer[UDP_TX_PACKET_MAX_SIZE];
String escapedMac;
ESP8266WebServer server(80);
ESP8266HTTPUpdateServer httpUpdater;

Wyświetl plik

@ -160,6 +160,7 @@ void wledInit()
server.send(404, "text/plain", "FileNotFound");
}
});
alexaInit();
server.begin();
DEBUG_PRINTLN("HTTP server started");

Wyświetl plik

@ -46,7 +46,7 @@ void handleNotifications()
if(packetSize && notifierUdp.remoteIP() != WiFi.localIP())
{
notifierUdp.read(udpIn, packetSize);
if (udpIn[0] == 0) //wled notifier
if (udpIn[0] == 0 && !arlsTimeout) //wled notifier, block if realtime packets active
{
col[0] = udpIn[3];
col[1] = udpIn[4];

Wyświetl plik

@ -6,9 +6,11 @@ void alexaInit()
{
if (alexaEnabled && WiFi.status() == WL_CONNECTED)
{
upnpBroadcastResponder.beginUdpMulticast();
alexa = new Switch(alexaInvocationName, 81, alexaOn, alexaOff);
upnpBroadcastResponder.addDevice(*alexa);
prepareIds();
udpConnected = connectUDP();
if (udpConnected) alexaInitPages();
}
}
@ -16,8 +18,25 @@ void handleAlexa()
{
if (alexaEnabled && WiFi.status() == WL_CONNECTED)
{
upnpBroadcastResponder.serverLoop();
alexa->serverLoop();
if(udpConnected){
// if theres data available, read a packet
int packetSize = UDP.parsePacket();
if(packetSize) {
IPAddress remote = UDP.remoteIP();
int len = UDP.read(packetBuffer, 255);
if (len > 0) {
packetBuffer[len] = 0;
}
String request = packetBuffer;
if(request.indexOf("M-SEARCH") >= 0) {
if((request.indexOf("urn:Belkin:device:**") > 0) || (request.indexOf("ssdp:all") > 0) || (request.indexOf("upnp:rootdevice") > 0)) {
Serial.println("Responding to search request ...");
respondToSearch();
}
}
}
}
}
}
@ -30,6 +49,18 @@ void alexaOn()
{
applyMacro(alexaOnMacro);
}
String body =
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body>\r\n"
"<u:SetBinaryStateResponse xmlns:u=\"urn:Belkin:service:basicevent:1\">\r\n"
"<BinaryState>1</BinaryState>\r\n"
"</u:SetBinaryStateResponse>\r\n"
"</s:Body> </s:Envelope>";
server.send(200, "text/xml", body.c_str());
Serial.print("Sending :");
Serial.println(body);
}
void alexaOff()
@ -41,6 +72,18 @@ void alexaOff()
{
applyMacro(alexaOffMacro);
}
String body =
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body>\r\n"
"<u:SetBinaryStateResponse xmlns:u=\"urn:Belkin:service:basicevent:1\">\r\n"
"<BinaryState>0</BinaryState>\r\n"
"</u:SetBinaryStateResponse>\r\n"
"</s:Body> </s:Envelope>";
server.send(200, "text/xml", body.c_str());
Serial.print("Sending :");
Serial.println(body);
}
void alexaDim(uint8_t bri)
@ -50,3 +93,219 @@ void alexaDim(uint8_t bri)
handleSet(ct);
}
void prepareIds() {
escapedMac = WiFi.macAddress();
escapedMac.replace(":", "");
escapedMac.toLowerCase();
}
void respondToSearch() {
Serial.println("");
Serial.print("Sending response to ");
Serial.println(UDP.remoteIP());
Serial.print("Port : ");
Serial.println(UDP.remotePort());
IPAddress localIP = WiFi.localIP();
char s[16];
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
String response =
"HTTP/1.1 200 OK\r\n"
"EXT:\r\n"
"CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL
"LOCATION: http://"+ String(s) +":80/description.xml\r\n"
"SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber
"hue-bridgeid: "+ escapedMac +"\r\n"
"ST: urn:schemas-upnp-org:device:basic:1\r\n" // _deviceType
"USN: uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"::upnp:rootdevice\r\n" // _uuid::_deviceType
"\r\n";
UDP.beginPacket(UDP.remoteIP(), UDP.remotePort());
UDP.write(response.c_str());
UDP.endPacket();
Serial.println("Response sent !");
}
void alexaInitPages() {
server.on("/upnp/control/basicevent1", HTTP_POST, []() {
Serial.println("########## Responding to /upnp/control/basicevent1 ... ##########");
String request = server.arg(0);
Serial.print("request:");
Serial.println(request);
if(request.indexOf("SetBinaryState") >= 0) {
if(request.indexOf("<BinaryState>1</BinaryState>") >= 0) {
Serial.println("Got Turn on request");
alexaOn();
}
if(request.indexOf("<BinaryState>0</BinaryState>") >= 0) {
Serial.println("Got Turn off request");
alexaOff();
}
}
if(request.indexOf("GetBinaryState") >= 0) {
Serial.println("Got binary state request");
sendState();
}
server.send(200, "text/plain", "");
});
server.on("/eventservice.xml", HTTP_GET, [](){
Serial.println(" ########## Responding to eventservice.xml ... ########\n");
String eventservice_xml = "<scpd xmlns=\"urn:Belkin:service-1-0\">"
"<actionList>"
"<action>"
"<name>SetBinaryState</name>"
"<argumentList>"
"<argument>"
"<retval/>"
"<name>BinaryState</name>"
"<relatedStateVariable>BinaryState</relatedStateVariable>"
"<direction>in</direction>"
"</argument>"
"</argumentList>"
"</action>"
"<action>"
"<name>GetBinaryState</name>"
"<argumentList>"
"<argument>"
"<retval/>"
"<name>BinaryState</name>"
"<relatedStateVariable>BinaryState</relatedStateVariable>"
"<direction>out</direction>"
"</argument>"
"</argumentList>"
"</action>"
"</actionList>"
"<serviceStateTable>"
"<stateVariable sendEvents=\"yes\">"
"<name>BinaryState</name>"
"<dataType>Boolean</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"<stateVariable sendEvents=\"yes\">"
"<name>level</name>"
"<dataType>string</dataType>"
"<defaultValue>0</defaultValue>"
"</stateVariable>"
"</serviceStateTable>"
"</scpd>\r\n"
"\r\n";
server.send(200, "text/plain", eventservice_xml.c_str());
});
server.on("/description.xml", HTTP_GET, [](){
Serial.println(" ########## Responding to setup.xml ... ########\n");
IPAddress localIP = WiFi.localIP();
char s[16];
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
String setup_xml = "<?xml version=\"1.0\" ?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion><major>1</major><minor>0</minor></specVersion>"
"<URLBase>http://"+ String(s) +":80/</URLBase>"
"<device>"
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
"<friendlyName>Philips hue ("+ String(s) +")</friendlyName>"
"<manufacturer>Royal Philips Electronics</manufacturer>"
"<manufacturerURL>http://www.philips.com</manufacturerURL>"
"<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
"<modelName>Philips hue bridge 2012</modelName>"
"<modelNumber>929000226503</modelNumber>"
"<modelURL>http://www.meethue.com</modelURL>"
"<serialNumber>"+ escapedMac +"</serialNumber>"
"<UDN>uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"</UDN>"
"<presentationURL>index.html</presentationURL>"
"<iconList>"
" <icon>"
" <mimetype>image/png</mimetype>"
" <height>48</height>"
" <width>48</width>"
" <depth>24</depth>"
" <url>hue_logo_0.png</url>"
" </icon>"
" <icon>"
" <mimetype>image/png</mimetype>"
" <height>120</height>"
" <width>120</width>"
" <depth>24</depth>"
" <url>hue_logo_3.png</url>"
" </icon>"
"</iconList>"
"</device>"
"</root>";
server.send(200, "text/xml", setup_xml.c_str());
Serial.print("Sending :");
Serial.println(setup_xml);
});
// openHAB support
server.on("/on.html", HTTP_GET, [](){
Serial.println("on req");
server.send(200, "text/plain", "turned on");
alexaOn();
});
server.on("/off.html", HTTP_GET, [](){
Serial.println("off req");
server.send(200, "text/plain", "turned off");
alexaOff();
});
server.on("/status.html", HTTP_GET, [](){
Serial.println("Got status request");
String statrespone = "0";
if (bri > 0) {
statrespone = "1";
}
server.send(200, "text/plain", statrespone);
});
}
boolean connectUDP(){
boolean state = false;
Serial.println("");
Serial.println("Con UDP");
if(UDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti)) {
Serial.println("Con success");
state = true;
}
else{
Serial.println("Con failed");
}
return state;
}
void sendState() {
String body =
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body>\r\n"
"<u:GetBinaryStateResponse xmlns:u=\"urn:Belkin:service:basicevent:1\">\r\n"
"<BinaryState>";
body += ((bri>0) ? "1" : "0");
body += "</BinaryState>\r\n"
"</u:GetBinaryStateResponse>\r\n"
"</s:Body> </s:Envelope>\r\n";
server.send(200, "text/xml", body.c_str());
}