Add existing code

This commit is contained in:
Simon 2020-03-12 21:32:15 +00:00
commit 925ed68df7
4 changed files with 354 additions and 0 deletions

144
Client/SSDP_Discover.cpp Normal file
View File

@ -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

45
Client/SSDP_discover.h Normal file
View File

@ -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

147
Server/SSDP_Response.py Normal file
View File

@ -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()

18
Server/ssdp_responder.rc Normal file
View File

@ -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 $?
}