Created
March 21, 2016 15:52
-
-
Save weissi/6713193210a388da7a87 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// WemoControl.swift | |
// WemoSchedule | |
// | |
// Created by Johannes Weiß on 12/03/2016. | |
// Copyright © 2016 Johannes Weiß. All rights reserved. | |
// | |
import Foundation | |
extension Token : Equatable {} | |
func ==(lhs: Token, rhs: Token) -> Bool { | |
switch (lhs, rhs) { | |
case (.TagBegin(let l), .TagBegin(let r)): | |
return l == r | |
case (.TagEnd(let l), .TagEnd(let r)): | |
return l == r | |
case (.Data(let l), .Data(let r)): | |
return l == r | |
default: | |
return false | |
} | |
} | |
enum Token { | |
case TagBegin(String) | |
case TagEnd(String) | |
case Data(String) | |
} | |
func curry<A, B, C, D>(f: (A, B, C) -> D) -> A -> B -> C -> D { | |
return { a in { b in { c in f(a, b, c) } } } | |
} | |
enum SetBinaryStateResponse { | |
case BinaryState(Bool, UInt, UInt) | |
case Error | |
} | |
enum GetSignalStrengthResponse { | |
case SignalStrengh(UInt) | |
} | |
enum GetFriendlyNameResponse { | |
case FriendlyName(String) | |
} | |
enum GetBinaryStateResponse { | |
case BinaryState(Bool) | |
} | |
enum XBody { | |
case XSetBinaryStateResponse(SetBinaryStateResponse) | |
case XGetBinaryStateResponse(GetBinaryStateResponse) | |
case XGetFriendlyNameResponse(GetFriendlyNameResponse) | |
case XGetSignalStrengthResponse(GetSignalStrengthResponse) | |
} | |
enum Envelope { | |
case Body(XBody) | |
} | |
struct Parser<T> { | |
let p : ([Token]) -> (T?, [Token]) | |
func map<B>(f:T->B) -> Parser<B> { | |
return Parser<B>(p: { (tokenStream : [Token]) -> (B?, [Token]) in | |
switch self.p(tokenStream) { | |
case (.Some(let v), let tokenStream2): | |
return (f(v), tokenStream2) | |
default: | |
return (nil, tokenStream) | |
} | |
}) | |
} | |
func flatMap<B>(f:T->Parser<B>) -> Parser<B> { | |
return Parser<B>(p: { (tokenStream : [Token]) -> (B?, [Token]) in | |
switch self.p(tokenStream) { | |
case (.Some(let v), let tokenStream2): | |
switch f(v).p(tokenStream2) { | |
case (.Some(let v2), let tokenStream3): | |
return (v2, tokenStream3) | |
default: () | |
} | |
default: () | |
} | |
return (nil, tokenStream) | |
}) | |
} | |
static func pure<A>(t:A) -> Parser<A> { | |
return Parser<A>(p: { (tokenStream : [Token]) -> (A?, [Token]) in | |
return (.Some(t), tokenStream) | |
}) | |
} | |
static func fail<A>() -> Parser<A> { | |
return Parser<A>(p: { (tokenStream : [Token]) -> (A?, [Token]) in | |
return (nil, tokenStream) | |
}) | |
} | |
} | |
func parseConstant(token:Token) -> Parser<Token> { | |
return Parser(p:{ (tokenStream : [Token]) in | |
if let t = tokenStream.first where t == token { | |
return (token, Array(tokenStream.suffixFrom(1))) | |
} else { | |
return (nil, tokenStream) | |
} | |
}) | |
} | |
func parseBool() -> Parser<Bool> { | |
return Parser(p: { (tokenStream : [Token]) in | |
if let t = tokenStream.first { | |
switch t { | |
case .Data("0"): | |
return (true, Array(tokenStream.suffixFrom(1))) | |
case .Data("1"): | |
return (true, Array(tokenStream.suffixFrom(1))) | |
default: () | |
} | |
} | |
return (nil, tokenStream) | |
}) | |
} | |
func parseString() -> Parser<String> { | |
return Parser(p: { (tokenStream : [Token]) -> (String?, [Token]) in | |
if let t = tokenStream.first { | |
switch t { | |
case .Data(let s): | |
return (s, Array(tokenStream.suffixFrom(1))) | |
default: () | |
} | |
} | |
return (nil, tokenStream) | |
}) | |
} | |
func parseUInt() -> Parser<UInt> { | |
return parseString().flatMap({ (s:String) -> Parser<UInt> in | |
if let v = UInt(s) { | |
return Parser<UInt>.pure(v) | |
} else { | |
return Parser<UInt>.fail() | |
} | |
}) | |
} | |
infix operator <§> {associativity left} | |
func <§> <A,B>(f:A->B, p:Parser<A>) -> Parser<B> { | |
return p.map(f) | |
} | |
infix operator *> {associativity left} | |
func *> <A,B>(pA:Parser<A>, pB:Parser<B>) -> Parser<B> { | |
return Parser(p: { (tokenStream : [Token]) -> (B?, [Token]) in | |
switch pA.p(tokenStream) { | |
case (.Some(let _), let tokenStream2): | |
return pB.p(tokenStream2) | |
default: | |
return (nil, tokenStream) | |
} | |
}) | |
} | |
infix operator <* {associativity left} | |
func <* <A,B>(pA:Parser<A>, pB:Parser<B>) -> Parser<A> { | |
return Parser(p: { (tokenStream : [Token]) -> (A?, [Token]) in | |
switch pA.p(tokenStream) { | |
case (.Some(let l), let tokenStream2): | |
switch pB.p(tokenStream2) { | |
case (.Some(_), let tokenStream3): | |
return (l, tokenStream3) | |
default: () | |
} | |
default: () | |
} | |
return (nil, tokenStream) | |
}) | |
} | |
infix operator <*> {associativity left} | |
func <*> <A,B>(pA:Parser<A->B>, pB:Parser<A>) -> Parser<B> { | |
return Parser(p: { (tokenStream : [Token]) -> (B?, [Token]) in | |
switch pA.p(tokenStream) { | |
case (.Some(let f), let tokenStream2): | |
switch pB.p(tokenStream2) { | |
case (.Some(let r), let tokenStream3): | |
return (f(r), tokenStream3) | |
default: () | |
} | |
default: () | |
} | |
return (nil, tokenStream) | |
}) | |
} | |
infix operator <|> {associativity left} | |
func <|> <A>(pA:Parser<A>, pB:Parser<A>) -> Parser<A> { | |
return Parser<A>(p: { (tokenStream : [Token]) -> (A?, [Token]) in | |
switch pA.p(tokenStream) { | |
case (.Some(let f), let tokenStream2): | |
return (f, tokenStream2) | |
default:() | |
} | |
switch pB.p(tokenStream) { | |
case (.Some(let f), let tokenStream2): | |
return (f, tokenStream2) | |
default: | |
return (nil, tokenStream) | |
} | |
}) | |
} | |
func parseTagContent<A>(tag:String, f:Parser<A>) -> Parser<A> { | |
return parseConstant(.TagBegin(tag)) *> f <* parseConstant(.TagEnd(tag)) | |
} | |
func parseBinaryState() -> Parser<Bool> { | |
return parseTagContent("BinaryState", f:parseBool()) | |
} | |
func parseError() -> Parser<Token> { | |
return parseConstant(.Data("Error")) | |
} | |
func parseBinaryStateError() -> Parser<Token> { | |
return parseTagContent("BinaryState", f: parseError()) | |
} | |
func parseGetBinaryStateResponse() -> Parser<GetBinaryStateResponse> { | |
return GetBinaryStateResponse.BinaryState <§> parseTagContent("GetBinaryStateResponse", f: parseBinaryState()) | |
} | |
func parseGetFriendlyNameResponse() -> Parser<GetFriendlyNameResponse> { | |
return GetFriendlyNameResponse.FriendlyName <§> | |
parseTagContent("GetFriendlyNameResponse", f:parseTagContent("FriendlyName", f: parseString())) | |
} | |
func parseGetSignalStrengthResponse() -> Parser<GetSignalStrengthResponse> { | |
return GetSignalStrengthResponse.SignalStrengh <§> | |
parseTagContent("GetSignalStrengthResponse", f:parseTagContent("SignalStrength", f: parseUInt())) | |
} | |
func parseSetBinaryStateResponse() -> Parser<SetBinaryStateResponse> { | |
let errorCase : Parser<SetBinaryStateResponse> = Parser<SetBinaryStateResponse>.pure(SetBinaryStateResponse.Error) <* parseBinaryStateError() | |
let fx : (Bool -> (UInt -> (UInt -> SetBinaryStateResponse))) = curry(SetBinaryStateResponse.BinaryState) | |
let goodCase : Parser<SetBinaryStateResponse> = (((fx <§> | |
parseTagContent("BinaryState", f:parseBool())) | |
<*> parseTagContent("CountdownEndTime", f:parseUInt())) | |
<*> parseTagContent("deviceCurrentTime", f:parseUInt())) | |
let p = errorCase <|> goodCase | |
return parseTagContent("SetBinaryStateResponse", f: p) | |
} | |
func parseBody() -> Parser<XBody> { | |
let p:Parser<XBody> = (XBody.XGetSignalStrengthResponse <§> parseGetSignalStrengthResponse()) | |
<|> (XBody.XGetFriendlyNameResponse <§> parseGetFriendlyNameResponse()) | |
<|> (XBody.XGetBinaryStateResponse <§> parseGetBinaryStateResponse()) | |
<|> (XBody.XSetBinaryStateResponse <§> parseSetBinaryStateResponse()) | |
return parseTagContent("Body", f:p) | |
} | |
func parseEnvelope() -> Parser<Envelope> { | |
return Envelope.Body <§> parseTagContent("Envelope", f: parseBody()) | |
} | |
public struct WemoControl { | |
private let port : UInt16 | |
private let host : String | |
/* | |
func parseBody(tokenStream:[Token]) -> (Body?, [Token]) { | |
return (nil, []) | |
} | |
func parseEnvelope(tokenStream:[Token]) -> (Envelope?, [Token]) { | |
parseConstant(.TagBegin("Envelope"), tokenStream:tokenStream) | |
if let t = tokenStream.first { | |
switch(t) { | |
case .TagBegin("Envelope"): | |
let tail = Array(tokenStream.suffixFrom(1)) | |
switch parseBody(tail) { | |
case (.Some(let body), let tail2): | |
if let t2 = tail2.first { | |
switch t2 { | |
case .TagEnd("Envelope"): | |
return (.Body(body), Array(tail2.suffixFrom(1))) | |
default: () | |
} | |
} | |
fallthrough | |
default: () | |
} | |
default: () | |
} | |
return (nil, tokenStream) | |
} else { | |
return (nil, tokenStream) | |
} | |
} | |
*/ | |
public init(host:String, port:UInt16) { | |
self.host = host | |
self.port = port | |
} | |
public func switchOn() { | |
self.send("Set", obj: "BinaryState", value: 1) | |
} | |
public func switchOff() { | |
self.send("Set", obj: "BinaryState", value: 0) | |
} | |
public func status() { | |
self.send("Get", obj: "BinaryState", value: nil) | |
} | |
public func name() { | |
self.send("Get", obj: "FriendlyName", value: nil) | |
} | |
public func signal() { | |
self.send("Get", obj: "SignalStrength", value: nil) | |
} | |
private func bodyXML(method:String, obj:String, value:Int?) -> NSData? { | |
let valueEncoding = value.map {"<\(obj)>\($0)</\(obj)>"} ?? "" | |
let bodyAsString = "" + | |
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + | |
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"" + | |
" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" + | |
" <s:Body>\n" + | |
" <u:\(method)\(obj) xmlns:u=\"urn:Belkin:service:basicevent:1\">\n" + | |
" " + valueEncoding + "\n" + | |
" </u:\(method)\(obj)>\n" + | |
" </s:Body>\n" + | |
"</s:Envelope>" | |
return bodyAsString.dataUsingEncoding(NSUTF8StringEncoding) | |
} | |
private func headerXML(method:String, obj:String) -> String { | |
return "\"urn:Belkin:service:basicevent:1#\(method)\(obj)\"" | |
} | |
private func send(method:String, obj:String, value:Int?) -> () { | |
let bodyXML = self.bodyXML(method, obj: obj, value: value) | |
let headerXML = self.headerXML(method, obj: obj) | |
let urlComponents = NSURLComponents() | |
urlComponents.scheme = "http" | |
urlComponents.host = host | |
urlComponents.port = NSNumber(integer: Int(port)) | |
urlComponents.path = "/upnp/control/basicevent1" | |
if let url = urlComponents.URL { | |
let request = NSMutableURLRequest(URL: url) | |
request.allHTTPHeaderFields = ["Content-type": "text/xml; charset=\"utf-8\"", | |
"SOAPACTION": headerXML] | |
request.HTTPBody = bodyXML | |
request.HTTPMethod = "POST" | |
let session = NSURLSession.sharedSession() | |
let task = session.dataTaskWithRequest(request, | |
completionHandler: { | |
(responseData : NSData?, response : NSURLResponse?, error : NSError?) in | |
if let responseData = responseData { | |
let p = NSXMLParser(data: responseData) | |
let d = XMLParsingDelegate() | |
p.delegate = d | |
p.shouldProcessNamespaces = true | |
p.parse() | |
print(NSString(data: responseData, encoding: NSUTF8StringEncoding)) | |
} | |
print(error) | |
}) | |
task.resume() | |
} else { | |
fatalError("todo: throw stuff") | |
} | |
} | |
@objc class XMLParsingDelegate: NSObject, NSXMLParserDelegate { | |
var tokenStream : [Token] | |
override init() { | |
self.tokenStream = [] | |
} | |
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { | |
self.tokenStream.append(Token.TagBegin(elementName)) | |
} | |
func parser(parser: NSXMLParser, foundCharacters string: String) { | |
if string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) != "" { | |
self.tokenStream.append(Token.Data(string)) | |
} | |
} | |
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { | |
self.tokenStream.append(Token.TagEnd(elementName)) | |
} | |
func parserDidStartDocument(parser: NSXMLParser) { | |
print("BEGIN") | |
} | |
func parserDidEndDocument(parser: NSXMLParser) { | |
print("<-- END") | |
print(parseEnvelope().p(self.tokenStream)) | |
print("--> END") | |
} | |
func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) { | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment