Add existing code
This commit is contained in:
commit
925ed68df7
|
@ -0,0 +1,144 @@
|
|||
#include "SSDP_discover.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include <utils/utils.h>
|
||||
#define _DEBUG_PRINT(x) utils::debug_print(x)
|
||||
#else
|
||||
#define _DEBUG_PRINT(x)
|
||||
#endif
|
||||
|
||||
namespace Control_System
|
||||
{
|
||||
|
||||
SSDP_discover::SSDP_discover() : discoveredServices(new std::vector<IPAddress>()), server(new WiFiUDP()) {}
|
||||
|
||||
SSDP_discover::~SSDP_discover()
|
||||
{
|
||||
server->flush();
|
||||
yield();
|
||||
server->stopAll();
|
||||
}
|
||||
|
||||
bool SSDP_discover::discover()
|
||||
{
|
||||
auto success = false;
|
||||
|
||||
for (size_t i = 0; i < SEARCH_RETRY && success == false; i++)
|
||||
{
|
||||
_DEBUG_PRINT("Sending M-SEARCH");
|
||||
_DEBUG_PRINT(IPAddress(MULTICAST_ADDRESS).toString());
|
||||
auto mcast1 = server->beginPacketMulticast(IPAddress(MULTICAST_ADDRESS), SSDP_PORT, WiFi.localIP());
|
||||
if (mcast1 != 1)
|
||||
{
|
||||
_DEBUG_PRINT("begin packet failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto write = server->write(SSDP_MSEARCH, strnlen(SSDP_MSEARCH, 500));
|
||||
if (write <= 0)
|
||||
{
|
||||
_DEBUG_PRINT("write failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto end = server->endPacket();
|
||||
if (end != 1)
|
||||
{
|
||||
_DEBUG_PRINT("end failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
await_response();
|
||||
|
||||
_DEBUG_PRINT("done awaiting");
|
||||
|
||||
if (discoveredServices->empty())
|
||||
{
|
||||
_DEBUG_PRINT("No services found");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &c : *discoveredServices)
|
||||
{
|
||||
_DEBUG_PRINT(c.toString());
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void SSDP_discover::await_response()
|
||||
{
|
||||
server->begin(SSDP_PORT);
|
||||
byte buffer[INPUT_BUFFER_SIZE + 1] = {0};
|
||||
size_t bufferContentLength = 0;
|
||||
_DEBUG_PRINT("UDP Await");
|
||||
|
||||
//Count down from delay time to 0, assume this takes ~1ms
|
||||
for (auto delaytime = SEARCH_TIMEOUT * 1000; delaytime > 0; delaytime--)
|
||||
{
|
||||
auto correctService = false;
|
||||
auto location = IPAddress();
|
||||
|
||||
// Process a packet
|
||||
while (server->available() > 0)
|
||||
{
|
||||
bufferContentLength = server->readBytesUntil('\n', buffer, INPUT_BUFFER_SIZE);
|
||||
buffer[bufferContentLength] = '\0';
|
||||
_DEBUG_PRINT((char *)buffer);
|
||||
|
||||
// strlen, but they're contexpr, so its ok?...
|
||||
if (bufferContentLength >= strlen(SERVICE) &&
|
||||
memcmp(SERVICE, buffer, strlen(SERVICE)) == 0)
|
||||
{
|
||||
_DEBUG_PRINT(F("Service line"));
|
||||
|
||||
if (strstr(reinterpret_cast<char*>(buffer), SERVICE_NAME) != nullptr)
|
||||
{
|
||||
_DEBUG_PRINT("Service valid");
|
||||
correctService = true;
|
||||
}
|
||||
}
|
||||
// + 2 for ": "
|
||||
else if (bufferContentLength >= strlen(LOCATION) + 2 &&
|
||||
memcmp(LOCATION, buffer, strlen(LOCATION)) == 0)
|
||||
{
|
||||
_DEBUG_PRINT(F("Location Line"));
|
||||
|
||||
auto offset = buffer + (strlen(LOCATION) + 2);
|
||||
char ipBuffer[IP4ADDR_STRLEN_MAX + 1] = {0};
|
||||
|
||||
strncpy(ipBuffer, reinterpret_cast<char*>(offset), IP4ADDR_STRLEN_MAX);
|
||||
|
||||
_DEBUG_PRINT((char *)offset);
|
||||
if (IPAddress::isValid(ipBuffer))
|
||||
{
|
||||
location.fromString(ipBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_DEBUG_PRINT("Resolve hostname");
|
||||
// If the ip was invalid, maybe its a hostname?
|
||||
if (WiFi.hostByName(reinterpret_cast<char*>(offset), location) != 1)
|
||||
{
|
||||
// Nope it was just invalid
|
||||
_DEBUG_PRINT("Invalid Service");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (location.isSet() && correctService)
|
||||
{
|
||||
_DEBUG_PRINT(F("Add as valid response"));
|
||||
discoveredServices->push_back(location);
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
server->stop();
|
||||
}
|
||||
} // namespace Control_System
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
namespace Control_System
|
||||
{
|
||||
class SSDP_discover
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<std::vector<IPAddress>> discoveredServices;
|
||||
|
||||
SSDP_discover();
|
||||
~SSDP_discover();
|
||||
bool discover();
|
||||
|
||||
private:
|
||||
static constexpr const char *SSDP_MSEARCH =
|
||||
"M-SEARCH * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
"MAN: \"ssdp:discover\"\r\n"
|
||||
"MX: 3\r\n"
|
||||
"ST: urn:control-system-custom_sm:service:control-server:" __CONTROL_VERSION__ "\r\n"
|
||||
"USER-AGENT: ESP_ARDUINO/" __VERSION__ " UPnP/1.1 CONTROL_SERVER/" __CONTROL_VERSION__ "\r\n\r\n";
|
||||
|
||||
static constexpr const uint32_t MULTICAST_ADDRESS = 0xFAFFFFEF;
|
||||
static constexpr const uint16_t SSDP_PORT = 1900;
|
||||
static constexpr const uint8_t SSDP_TTL = 3;
|
||||
|
||||
static constexpr const uint8_t SEARCH_TIMEOUT = 4;
|
||||
static constexpr const uint8_t SEARCH_RETRY = 1;
|
||||
|
||||
static constexpr const uint8_t INPUT_BUFFER_SIZE = 150;
|
||||
|
||||
static constexpr const char* LOCATION = "LOCATION";
|
||||
static constexpr const char* SERVICE = "ST";
|
||||
static constexpr const char* SERVICE_NAME = "urn:control-system-custom_sm:service:control-server";
|
||||
|
||||
const std::unique_ptr<WiFiUDP> server;
|
||||
|
||||
void await_response();
|
||||
};
|
||||
} // namespace Control_System
|
|
@ -0,0 +1,147 @@
|
|||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import queue
|
||||
import time
|
||||
import datetime
|
||||
import uuid
|
||||
import random
|
||||
|
||||
class ssdp_response():
|
||||
|
||||
SSDP_GROUP = '239.255.255.250'
|
||||
SSDP_PORT = 1900
|
||||
|
||||
|
||||
TEMPLATE ="""HTTP/1.1 200 OK
|
||||
CACHE-CONTROL: max-age={}
|
||||
DATE: {}
|
||||
EXT:
|
||||
LOCATION: {}
|
||||
SERVER: {}
|
||||
ST: {}
|
||||
USN: {}
|
||||
BOOTID.UPNP.ORG: {}
|
||||
|
||||
"""
|
||||
MAX_AGE = 500
|
||||
LOCATION = ""
|
||||
SERVER = "PYTHON/3 UPnP/1.1 CONTROL_SERVER/1.0.0"
|
||||
USN = uuid.uuid4()
|
||||
BOOTID = 0
|
||||
|
||||
|
||||
queues = {}
|
||||
queues_lock = threading.Lock()
|
||||
|
||||
requests = queue.Queue()
|
||||
|
||||
filters = {"HEAD":"M-SEARCH"}
|
||||
|
||||
def __init__(self, filter, location=None):
|
||||
self.filters.update(filter)
|
||||
if not location:
|
||||
self.LOCATION = socket.gethostbyname_ex(socket.getfqdn())[2]
|
||||
else:
|
||||
self.LOCATION = location
|
||||
|
||||
def start_listener(self):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind(("", self.SSDP_PORT))
|
||||
|
||||
multicast_request = struct.pack("4sl", socket.inet_aton(self.SSDP_GROUP), socket.INADDR_ANY)
|
||||
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, multicast_request)
|
||||
#start listen and respond thread
|
||||
|
||||
while True:
|
||||
try:
|
||||
data,addr = sock.recvfrom(1024)
|
||||
if addr in self.queues:
|
||||
self.queues[addr].put(data)
|
||||
else:
|
||||
q = queue.Queue()
|
||||
q.put(data)
|
||||
self.queues_lock.acquire()
|
||||
self.queues[addr] = q
|
||||
self.queues_lock.release()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("recv error")
|
||||
|
||||
def filter(self, request):
|
||||
ret = True
|
||||
for k in self.filters.keys():
|
||||
val = request.get(k)
|
||||
if not val:
|
||||
ret = False
|
||||
break
|
||||
else:
|
||||
if self.filters[k] not in val:
|
||||
ret = False
|
||||
break
|
||||
return ret
|
||||
|
||||
def parse_request(self):
|
||||
requests_buffer = {}
|
||||
while True:
|
||||
time.sleep(1)
|
||||
self.queues_lock.acquire()
|
||||
timeouts = []
|
||||
for k, v in self.queues.items():
|
||||
try:
|
||||
data = v.get(timeout=5)
|
||||
string = data.decode()
|
||||
lines = string.splitlines()
|
||||
for line in lines:
|
||||
val = line.split(':', 1)
|
||||
if len(val) == 1:
|
||||
if "M-SEARCH" in val[0]:
|
||||
requests_buffer[k] = {}
|
||||
requests_buffer[k]["HEAD"] = val[0]
|
||||
else:
|
||||
requests_buffer[k]["CLIENT"] = k[0]
|
||||
if self.filter(requests_buffer[k]):
|
||||
print(requests_buffer[k])
|
||||
self.requests.put(requests_buffer.pop(k))
|
||||
else:
|
||||
requests_buffer[k][val[0]] = val[1]
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("no data")
|
||||
timeouts.append(k)
|
||||
for k in timeouts:
|
||||
del self.queues[k]
|
||||
self.queues_lock.release()
|
||||
|
||||
def construct_response(self, request):
|
||||
response = self.TEMPLATE.format(
|
||||
self.MAX_AGE,
|
||||
datetime.datetime.now().replace(microsecond=0).isoformat(),
|
||||
self.LOCATION,
|
||||
self.SERVER,
|
||||
request.get("ST"),
|
||||
self.USN,
|
||||
self.BOOTID)
|
||||
self.BOOTID = self.BOOTID + 1
|
||||
return response
|
||||
|
||||
def send_response(self):
|
||||
while True:
|
||||
request = self.requests.get()
|
||||
time.sleep(random.randrange(int(request.get("MX"))))
|
||||
response = self.construct_response(request)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
address = (request.get("CLIENT"), self.SSDP_PORT)
|
||||
print(response)
|
||||
sock.connect(address)
|
||||
sock.sendall(response.encode())
|
||||
sock.close();
|
||||
|
||||
|
||||
s = ssdp_response({"ST":"service:control-server"}, "192.168.0.184")
|
||||
t = threading.Thread(target=s.start_listener)
|
||||
t.start()
|
||||
t2 = threading.Thread(target=s.parse_request)
|
||||
t2.start()
|
||||
s.send_response()
|
|
@ -0,0 +1,18 @@
|
|||
#!/sbin/openrc-run
|
||||
name="ssdp-responder"
|
||||
command="python3"
|
||||
command_args="/bin/control_system/Home_Control_Flask/SSDP_Response.py"
|
||||
command_background=true
|
||||
pidfile="/run/${RC_SVCNAME}.pid"
|
||||
extra_started_commands="reload"
|
||||
error_log="/var/log/ssdp-responder.err"
|
||||
|
||||
depend(){
|
||||
need net
|
||||
}
|
||||
|
||||
reload() {
|
||||
ebegin "Reloading ${RC_SVCNAME}"
|
||||
start-stop-daemon --signal HUP --pidfile "${pidfile}"
|
||||
eend $?
|
||||
}
|
Loading…
Reference in New Issue