Hi Scott, On Thu, 2018-05-17 at 21:09 -0400, Scott Talbert wrote: > If there's anyone still out there,
I'm here! I've got a Harmony Ultimate One and use some of the library bits on the odd occasion I need to fiddle with my remote setup. > I would appreciate if you could test > the latest git master and let me know if you run into any problems. If > you want to use Python 3, you'll need the latest git master of > concordance, too. I'm trying to get Phil to do a release of concordance > too. I have some local mods (some selectively picked from the harmony_touch branch) which I've rebased from e62e77c8c9d0 onto c32a5ce8572e and adjusted to fit and they seem to be ok with a my local app (which uses uses mhmanager) that lets me pull the config into a file. I have another app which lets me program (somewhat) arbitrary IR codes without learning that I've not tried since the rebase this morning. I've pasted the raw diff from c32a5ce8572e below, I suspect there is a bunch of redundant (to this branch at least) stuff from the harmony_touch branch which I could drop. I've been sitting on these changes for far too long and should submit them. Would it be useful if I pulled at least the wsdl and xsd changes into a PR ASAP or would you prefer to release what you have now and take a look at this stuff later on? The "apps" themselves are very skanky and specific to my mythtv setup and some other tooling I wrote to help myself keep the key bindings straight in my head. CHeers, Ian. diff --git a/congruity/harmony.wsdl b/congruity/harmony.wsdl index 2cf79aa..ff8a38f 100644 --- a/congruity/harmony.wsdl +++ b/congruity/harmony.wsdl @@ -126,10 +126,7 @@ <sequence> <element name="DisplayName" type="xsd:string"/> <element name="DisplayPriority" type="xsd:string"/> - <element name="IsProSKUEnabled" type="xsd:boolean"/> <element name="Name" type="xsd:string"/> - <element name="ProSKUDisplayName" type="xsd:string" nillable="true"/> - <element name="ProductFamily" type="xsd:string"/> <element name="ProductId" type="xsd:string"/> <element name="ProductIdentifier" type="xsd:string"/> <element name="SkinId" type="xsd:string"/> @@ -161,9 +158,11 @@ </sequence> </complexType> + <!-- Contains a KeyValueOfDeviceIdArrayOfCommandXXXX where XXXX varies, not + sure how to deal with other than using an any.--> <complexType name="GetCommandsResult"> <sequence> - <element name="KeyValueOfDeviceIdArrayOfCommand8_PSBnnKs" type="ns9:KeyValueOfDeviceIdArrayOfCommand"/> + <any/> </sequence> </complexType> @@ -220,6 +219,20 @@ </sequence> </complexType> + <complexType name="GetJson2UrisResult"> + <sequence> + <element name="ServiceDescription" type="ns2:ServiceDescription" minOccurs="0" maxOccurs="unbounded"/> + </sequence> + </complexType> + + <complexType name="GetAllServicesResult"> + <sequence> + <element name="Json2Services" type="ns2:ServiceDescriptions"/> + <element name="JsonServices" type="ns2:ServiceDescriptions"/> + <element name="SoapServices" type="ns2:ServiceDescriptions"/> + </sequence> + </complexType> + </schema> </types> @@ -541,11 +554,35 @@ <message name="SaveButtonMapsResponse"> </message> +<message name="GetJson2Uris"> + <part name="clientTypeId" type="xsd:string"/> + <part name="clientId" type="xsd:string"/> +</message> +<message name="GetJson2UrisResponse"> + <part name="GetJson2UrisResult" type="GetJson2UrisResult"/> +</message> + +<message name="GetAllServices"> + <part name="clientTypeId" type="xsd:string"/> + <part name="clientId" type="xsd:string"/> +</message> +<message name="GetAllServicesResponse"> + <part name="GetAllServicesResult" type="GetAllServicesResult"/> +</message> + <portType name="DiscoveryPortType"> <operation name="GetSoapServices"> <input message="ns:GetSoapServices"/> <output message="ns:GetSoapServicesResponse"/> </operation> + <operation name="GetJson2Uris"> + <input message="ns:GetJson2Uris"/> + <output message="ns:GetJson2UrisResponse"/> + </operation> + <operation name="GetAllServices"> + <input message="ns:GetAllServices"/> + <output message="ns:GetAllServicesResponse"/> + </operation> </portType> <portType name="AuthenticationServicePortType"> <operation name="Login"> @@ -749,6 +786,24 @@ <SOAP:body use="literal" namespace="http://tempuri.org/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>; </output> </operation> + <operation name="GetJson2Uris"> + <SOAP:operation style="rpc" soapAction="http://tempuri.org/IDiscovery/GetJson2Uris"/>; + <input> + <SOAP:body use="literal" namespace="http://tempuri.org/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>; + </input> + <output> + <SOAP:body use="literal" namespace="http://tempuri.org/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>; + </output> + </operation> + <operation name="GetAllServices"> + <SOAP:operation style="rpc" soapAction="http://tempuri.org/IDiscovery/GetAllServices"/>; + <input> + <SOAP:body use="literal" namespace="http://tempuri.org/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>; + </input> + <output> + <SOAP:body use="literal" namespace="http://tempuri.org/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>; + </output> + </operation> </binding> <binding name="AuthenticationServiceBinding" type="ns:AuthenticationServicePortType"> <SOAP:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>; diff --git a/congruity/mhgui.py b/congruity/mhgui.py index 0f22af3..def104f 100644 --- a/congruity/mhgui.py +++ b/congruity/mhgui.py @@ -57,7 +57,9 @@ WATCH_TV_BUTTON_SKIN_IDS = [78, 79, 80, 81, 104] try: import argparse except: - use_local_wsdl = False + use_local_wsdl = True + if '--use-global-wsdl' in sys.argv: + use_local_wsdl = False if '--use-local-wsdl' in sys.argv: use_local_wsdl = True suds_debug = False diff --git a/congruity/mhmanager.py b/congruity/mhmanager.py index ec0e2de..341e8cd 100644 --- a/congruity/mhmanager.py +++ b/congruity/mhmanager.py @@ -27,6 +27,7 @@ import sys import random import datetime import json +import collections from six.moves.html_parser import HTMLParser from suds.cache import ObjectCache from suds.client import Client @@ -119,6 +120,13 @@ ROLE_STRINGS = { "VolumeActivityRole" : "control volume", } +# Change urllib.quote to use lowercase characters instead of uppercase. This +# seems to be what the official software does and may be required. +#urllib._safe_map = {} +#for i, c in zip(xrange(256), str(bytearray(xrange(256)))): +# urllib._safe_map[c] = c if (i < 128 and c in urllib.always_safe) else '%{:02x}'.format(i) +#urllib._safe_quoters = {} + class MHPlugin(MessagePlugin): def fix_elements(self, prefix, elements): for element in elements: @@ -308,7 +316,7 @@ class MHManager(): self.client.service['UserButtonMappingManager'] \ .UpdateDeviceModeButtonMaps(buttonMaps) - def GetUserButtonMap(self, deviceId): + def GetAllUserButtonMap(self, deviceId, activityId = None): DeviceId = self.client.factory.create('{' + DATA_NS + '}DeviceId') DeviceId.IsPersisted = deviceId.IsPersisted DeviceId.Value = deviceId.Value @@ -317,9 +325,21 @@ class MHManager(): accountId = self.GetAccountIdForDevice(deviceId) remote = self.GetRemoteForAccountId(accountId) surfaceId = remote.Surfaces.Surface[0].Id.Value + if activityId is not None: + ActivityId = self.client.factory.create('{' + DATA_NS + '}ActivityId') + ActivityId.IsPersisted = activityId.IsPersisted + ActivityId.Value = activityId.Value + activityIds = self.client.factory.create('{' + DATA_NS + '}abstractIds') + activityIds.AbstractId.append(ActivityId) + else: + activityIds = "" return self.client.service['UserButtonMappingManager'] \ - .GetButtonMaps(deviceIds, "", remote.SkinId, accountId, - surfaceId).AbstractButtonMap[0] + .GetButtonMaps(deviceIds, activityIds, remote.SkinId, accountId, + surfaceId).AbstractButtonMap + def GetUserButtonMap(self, deviceId): + return self.GetAllUserButtonMap(deviceId=deviceId)[0] + def GetActivityButtonMap(self, deviceId, activityId): + return self.GetAllUserButtonMap(deviceId, activityId)[1] def UpdateUserButtonMap(self, userButtonMap, button, command): accountId = self.GetAccountIdForDevice(userButtonMap.DeviceId) @@ -710,7 +730,7 @@ class MHManager(): deviceWithCapabilities.PrioritizedCapabilities = \ device.DeviceCapabilitiesWithPriority devicesWithCapabilities.DeviceWithCapabilities.append( - deviceWithCapabilities) + deviceWithCapabilities) return (self.client.service['ActivityManager'].GetActivityRoles( account.Id, activityTypes, devicesWithCapabilities).\ KeyValueOfActivityTypeRoleToDeviceMapping_SFvkcgrh[0].Value.\ @@ -844,7 +864,7 @@ class MHManager(): activity.Type = saveActivityTemplate.activityType activity.Name = saveActivityTemplate.activityName activity.Roles = self.CreateRolesByTemplate(saveActivityTemplate) - return self.SaveActivity(remoteId, activity) + return self.SaveActivity(remoteId, activity) # Saves the specified activity def SaveActivity(self, remoteId, activity): @@ -923,7 +943,7 @@ class MHManager(): duration = "0" else: duration = action.Duration - actions.append(("IRPressAction", action.IRCommandName, + actions.append(("IRPressAction", action.IRCommandName, duration)) elif action.__class__.__name__ == "IRDelayAction": actions.append(("IRDelayAction", None, action.Delay)) @@ -992,6 +1012,23 @@ class MHManager(): return "UpdateMultiple failed" return None + def UpdateIRCommandKC(self, commandName, keyCode , deviceId): + operation = self.client.factory.create('{' + OPERATION_NS + + '}OperationBag') + operation.ParentAccount = self.GetAccountIdForDevice(deviceId) + operation.Items.Operation = self.client.factory.create( + '{' + DM_OPERATION_NS + '}AddCommandOperation' + ) + operation.Items.Operation.ParentAccount = operation.ParentAccount + operation.Items.Operation.DeviceId = deviceId + operation.Items.Operation.KeyCode = keyCode + operation.Items.Operation.Name = commandName + operation.Items.Operation.RawInfrared = keyCode + result = self.client.service['DeviceManager'].UpdateMultiple(operation) + if result is None: + return "UpdateMultiple failed" + return None + # Deletes an IR command (if it is a user-added one) or removes the override # if the command is an officially provided one. def DeleteIRCommand(self, commandId, deviceId): @@ -1019,6 +1056,101 @@ class MHManager(): return "UpdateMultiple failed" return None + def GetJson2Uris(self, clientTypeId): + return self.client.service['Discovery'].GetJson2Uris(clientTypeId, \ + None).ServiceDescription + + def GetJson2Data(self, uris, requests): + responses = [] + for request in requests: + realUri = self.GetRealUri(request["uri"], uris) + httpRequest = urllib2.Request(realUri) + try: + httpRequest.add_header("If-None-Match", request["etag"]) + except KeyError: + pass + if httpRequest.get_host() == "content.dhg.myharmony.com": + httpRequest.add_header("Logitech-API-Key", + self.contentServiceAuthKey) + self.client.options.transport.cookiejar.add_cookie_header( + httpRequest) + + response = collections.OrderedDict() + try: + fp = urllib2.urlopen(httpRequest) + headers = fp.info() + resource = json.loads(fp.read(), + object_pairs_hook=collections.OrderedDict) + statusCode = str(fp.getcode()) + except urllib2.HTTPError as error: + headers = error.hdrs + resource = None + statusCode = str(error.code) + response["cache-Control"] = "" + response["body-type"] = "application/json" + response["etag"] = headers.get("ETag") + response["uri"] = request["uri"] + response["statusCode"] = statusCode + response["resource"] = resource + + responses.append(response) + + jsonStr = json.dumps({ "responses" : responses }, + separators=(',', ':'), ensure_ascii=False) + # The dates appear to be getting changed to 'local' time but poorly. + # Only the local offset gets inserted, the timestamp doesn't change. + # Also, the slash needs to be escaped. + offset = '{0:0=+5}'.format(time.timezone / -36) + jsonStr = re.sub(r'\"/Date\(([0-9]+)[+-]([0-9]){4}\)/\"', + r'"\\/Date(\1' + offset + r')\\/"', jsonStr) + return urllib.quote_plus(jsonStr.encode("utf-8"), "()") + + def GetRealUri(self, fakeUri, uris): + realUri = None + queryString = None + secondaryUri = None + if '?' in fakeUri: + queryString = fakeUri.split('?')[1] + fakeUri = fakeUri.split('?')[0] + if ';' in fakeUri: + secondaryUri = urllib.quote(fakeUri.split(';')[1], '') + fakeUri = fakeUri.split(';')[0] + if re.match("content://", fakeUri): + requestName = fakeUri.split('/')[3] + else: + requestName = fakeUri.split('/')[-1] + for uri in uris: + if str(uri.Name.lower()) == str(requestName.lower()): + realUri = uri.Address + + # Fill out the placeholder fields + fakeUriSplit = fakeUri.split('/') + placeholders = ( ("{remoteId}", "Remote"), + ("{accountId}", "Account"), + ("{productId}", "product"), + ("{unitId}", "unit"), + ("{providerId}", "service"), + ("{countryCode}", "Country"), + ("{zipcode}", "Region"), + ("{stationId}", "station"), ) + for placeholderTag, previous in placeholders: + if placeholderTag in realUri: + realData = fakeUriSplit[fakeUriSplit.index(previous) + 1] + realUri = realUri.replace(placeholderTag, realData) + uriPlaceholders = ("{userProfileUri}", "{deviceProfileUri}", + "{accountUri}") + for placeholderTag in uriPlaceholders: + if placeholderTag in realUri: + realUri = realUri.replace(placeholderTag, secondaryUri) + + if queryString: + realUri += "?" + queryString + "&" + else: + realUri += "?" + realUri += "guid=" + str(uuid.uuid4()) + + return realUri + class ActivityTemplate: def __init__(self): self.devices = None # List of (Device Name, DeviceId) diff --git a/congruity/user_button_mapping.xsd b/congruity/user_button_mapping.xsd index 50d86d6..4a4c561 100644 --- a/congruity/user_button_mapping.xsd +++ b/congruity/user_button_mapping.xsd @@ -13,6 +13,7 @@ <element name="EventType" type="xsd:string"/> <element name="Id" type="xsd:string"/> <element name="Order" type="xsd:string"/> + <element name="ActionName" type="xsd:string"/> </sequence> </complexType> @@ -54,6 +55,10 @@ <element name="ButtonLongPressAction" type="ns15:AbstractButtonAction" nillable="true"/> <element name="ButtonState" type="xsd:string"/> <element name="FunctionGroupType" type="xsd:string"/> + <element name="ButtonKey" type="xsd:string"/> + <!-- Not seen non-null yet --> + <element name="ButtonImageKey" type="xsd:string" nillable="true"/> + <element name="ButtonImagePath" type="xsd:string" nillable="true"/> </sequence> </complexType> diff --git a/congruity/user_feature.xsd b/congruity/user_feature.xsd index db7a69e..7dd5d07 100644 --- a/congruity/user_feature.xsd +++ b/congruity/user_feature.xsd @@ -72,6 +72,7 @@ <element name="PressDuration" type="xsd:string"/> <element name="StateName" type="xsd:string" nillable="true"/> <element name="StateValue" type="xsd:string" nillable="true"/> + <element name="Attributes" type="xsd:string" nillable="true"/> <!-- I've never seen a non-nil one, so type is a guess --> </sequence> </complexType> diff --git a/mhfetch b/mhfetch new file mode 100755 index 0000000..a1b3886 --- /dev/null +++ b/mhfetch @@ -0,0 +1,311 @@ +#!/usr/bin/env python2 +import sys, os, re + +sys.path.append('/local/scratch/ijc/development/pvr/harmony/congruity/build/lib.linux-x86_64-2.7/congruity') + +from urllib2 import URLError + +sys.path.append('/usr/share/congruity') +from mhmanager import MHManager +from mhmanager import MHAccountDetails +from mhmanager import SaveActivityTemplate +from mhmanager import Secrets + +odir = "/home/ijc/development/pvr/lirc-remote-config/Harmony Ultimate One" +try: + import argparse +except: + use_local_wsdl = True + if '--use-global-wsdl' in sys.argv: + use_local_wsdl = False + if '--use-local-wsdl' in sys.argv: + use_local_wsdl = True + suds_debug = False + if '--suds-debug' in sys.argv: + suds_debug = True +else: + parser = argparse.ArgumentParser(description='Manage Logitech Harmony Remotes.') + parser.add_argument('-d', '--suds-debug', help='output SOAP messages', + action='store_true') + parser.add_argument('-l', '--use-local-wsdl', help='use local wsdl file', + action='store_true') + args = parser.parse_args() + suds_debug = args.suds_debug + use_local_wsdl = args.use_local_wsdl + +if not use_local_wsdl: + print >>sys.stderr, "Forcing local WSDL" + use_local_wsdl = True + +mhMgr = MHManager(use_local_wsdl, suds_debug) + +import libconcord + +username, password = "i...@hellion.org.uk", "«password» +#from threading import Condition +#username, password = None, None +# +#secrets = Secrets() +#cv = Condition() +#def OnUserFetched(_username, _password): +# print "Got username+password" +# username = _username +# password = _password +# cv.acquire() +# cv.notify() +# cv.release() +# +#cv.acquire() +#try: +# secrets.fetchUser(OnUserFetched) +# print "Waiting for login details" +# cv.wait(3) +#except e: +# print "Waiting for login details: %s" % e +#cv.release() +#if username is None or password is None: +# print "No credentials" +# sys.exit(1) +try: + print "Logging in" + loginResult = mhMgr.Login(username, password) +except URLError as e: + loginResult = e.reason + +if loginResult is True: + print "Login successful" +else: + if loginResult is None: + msg = 'You appear to have used a members.harmonyremote.com ' \ + 'account. Please create a myharmony.com account or ' \ + 'login with an existing one.' + elif loginResult is False: + msg = 'Login failed. Username or password incorrect.' + else: + msg = 'Login failed. %s' % loginResult + print(msg) + os.exit(1) + +print "Found Remotes:" +remotes = mhMgr.GetRemotes() +products = {} +for remote in remotes: + product = mhMgr.GetProduct(remote.SkinId) + products[remote] = product + print " - %s" % product.DisplayName +print +if len(remotes) != 1: + print "Unexpected number of remotes" + +remote = remotes[0] +product = products[remote] + +print "Selected remote: %s" % product.DisplayName +print + +devices = mhMgr.GetDevices(remote.Id) +print "Devices:" +for d in devices: + print " - %s - %s" % (d.ParentDeviceManufacturer, d.ParentDeviceModel) + if d.ParentDeviceManufacturer == "Microsoft" and d.ParentDeviceModel == "Windows Media Center": + device = d +print + +def buttonMapToXml(n, mythsource, jumpcmd, fn, bm): + mythContexts = { + "Recorded TV": "TV Playback", + "Music": "Music", + "Movies": "TV Playback", + "Videos": "Video", + "Pictures": "Gallery", + "Live TV": "TV Playback", + } + f = open(os.path.join(odir,fn), "w") + if mythsource is not None: + mythcontext = mythContexts.get(mythsource, None) + if mythcontext is not None: + if jumpcmd is not None: + f.write("<!-- \t<jump source=\"%s\" command=\"%s\" /> -->\n" % (mythsource, jumpcmd)) + else: + f.write("<!-- \t<jump source=\"%s\" /> -->\n" % (mythsource)) + f.write("\t<activity name=\"%s\" mythsource=\"%s\" mythcontext=\"%s\">\n" % (n, mythsource, mythcontext)) + else: + f.write("\t<activity name=\"%s\" mythsource=\"%s\">\n" % (n, mythsource)) + else: + f.write("\t<activity name=\"%s\">\n" % n) + + nrcustom = 1 + for b in bm.Buttons[0]: + #f.write("<!-- %s -->\n" % b) + try: + b.ButtonAction.ActionName # If present then is a submenu... + continue + except: + pass + + name = None + soft = "" + try: + b.TextOnRemote + #name = "Soft %02d: %s" % (nrcustom, b.TextOnRemote) + name = b.TextOnRemote + #name = "Soft %d" % (nrcustom) + soft = " soft=\"true\"" + nrcustom += 1 + except: + pass + try: + name = b.ButtonKey + except: + pass + + #Cannot detect gestures? + + if name is None: + f.write("<!-- %s -->\n" % b) + continue + + f.write("\t\t<mapping button=\"%s\"" % (name)) + try: + f.write(" command=\"%s\"" % b.ButtonAction.CommandName) + except: + pass + try: + f.write(" long=\"%s\"" % b.ButtonLongPressAction.CommandName) + except: + pass + try: + f.write(" double=\"%s\"" % b.ButtonDoublePressAction.CommandName) + except: + pass + f.write(soft) + f.write(" />\n") + #print >>f, "%s: Press:%s Dbl:%s Long:%s" % (name, + # b.ButtonAction and b.ButtonAction.CommandName or "<None>", + # b.ButtonDoublePressAction and b.ButtonDoublePressAction.CommandName or "<None>", + # b.ButtonLongPressAction and b.ButtonLongPressAction.CommandName or "<None>") + f.write("\t</activity>\n") + f.close() + +print "Device: %s (%s - %s)" % (device.Name, device.ParentDeviceManufacturer, device.ParentDeviceModel) +print + +ubm = mhMgr.GetUserButtonMap(device.Id) +f = open(os.path.join(odir, "%s.%s.map" % (device.Name, "Device")), "w") +print >>f, ubm +f.close() + +buttonMapToXml(device.Name + " Device Mode", None, None, "%s.%s.xml" % (device.Name, "Device"), ubm) + + +f = open(os.path.join(odir, "%s.%s.xml" % (device.Name, "Command")), "w") +f.write("<!-- %s Commands -->\n" % device.Name) +ms30bit = re.compile("\(\)\(0[xX]3FF0(.*)\)\(\)") +def parseKeyCode(kc): + g,p,sc,n = kc.split(":") + if g == "G" and p == "Microsoft 30 Bit" and n == "3": + r = ms30bit.match(sc) + if r is not None: + return r.group(1) + return None +cs = mhMgr.GetCommands(device.Id) +cs.sort(key=lambda x: x.KeyCode) +for c in cs: + sc = parseKeyCode(c.KeyCode) + if sc is not None: + #f.write("\t<command name=\"%s\"\tscancode=\"0x0000%s\"\t/>\n" % (c.Name, sc.lower())) + f.write("\t<command scancode=\"0x0000%s\"\tname=\"%s\"\t/>\n" % (sc.lower(), c.Name)) + else: + f.write("\t<!-- %s: %s /-->\n" % (c.Name,c.KeyCode)) +f.close() + + +activities = mhMgr.GetActivities(remote.Id) +print "Activities:" +for activity in activities: + f = open(os.path.join(odir, "%s.%s.act" % (device.Name, activity.Name)), "w") + print >>f, activity + f.close() + + print " - %s" % activity.Name + #_, devices = mhMgr.GetActivityRolesAndDevices(remote.Id, activity.Type) + #for d in devices: + # print " - %s - %s (%x)" % (d.ParentDeviceManufacturer, d.ParentDeviceModel, d.Id.Value) + + ubm = mhMgr.GetActivityButtonMap(device.Id, activity.Id) + f = open(os.path.join(odir, "%s.%s.map" % (device.Name, activity.Name)), "w") + print >>f, ubm + f.close() + i = mhMgr.GetDeviceInput(activity, device.Id) or None + js = [j.SelectedInput.Name for j in activity.Roles[0] if j.DeviceId.Value == device.Id.Value] + if len(js) > 0: + j = js[0] + else: + j = None + buttonMapToXml(activity.Name, i, j, "%s.%s.xml" % (device.Name, activity.Name), ubm) +print + +#activity = activities[0] +#print "Selected activity: %s" % activity.Name + +#template = mhMgr.GetActivityTemplate(remote.Id,activity) +#print activity.Name, template +#def interact(): +# import code +# code.InteractiveConsole(locals=globals()).interact() +#interact() + +#roles, devices = mhMgr.GetActivityRolesAndDevices(remote.Id, activity.Type) +#print "Devices:" +#for d in devices: +# print " - %s - %s" % (d.ParentDeviceManufacturer, d.ParentDeviceModel) +# if d.ParentDeviceManufacturer == "Microsoft" and d.ParentDeviceModel == "Windows Media Center": +# device = d +#print +# +#print "Device: %s - %s" % (device.ParentDeviceManufacturer, device.ParentDeviceModel) + +#ubm = mhMgr.GetActivityButtonMap(device.Id, activity.Id) +#ubm = mhMgr.GetAllUserButtonMap(device.Id, activity.Id) +#f = open("%s.%s.map" % (device.Name, "Device"), "w") +#print >>f, ubm[0] +#f.close() +#f = open("%s.%s.map" % (device.Name, activity.Name), "w") +#print >>f, ubm[1] +#f.close() + +#print +#for b in ubm[1].Buttons[0]: +# name = None +# try: +# name = b.TextOnRemote +# except: +# pass +# try: +# name = b.ButtonKey +# except: +# pass +# +# #if isinstance(b, suds.sudsobject.SoftRemoteButton): +# # name = b.TextOnRemote +# #elif isinstance(b, suds.sudsobject.HardRemoteButton): +# # name = b.ButtonKey +# #elif isinstance(b, suds.sudsobject.AbstraceRemoteButton): +# # name = b.ButtonKey +# #else: +# # name = type(b) +# print "%s: Press:%s Dbl:%s Long:%s" % (name, +# b.ButtonAction and b.ButtonAction.CommandName or "<None>", +# b.ButtonDoublePressAction and b.ButtonDoublePressAction.CommandName or "<None>", +# b.ButtonLongPressAction and b.ButtonLongPressAction.CommandName or "<None>") + +#t = mhMgr.GetActivityTemplate(remote.Id, activity.Type) #YYY b:Attributes +#ubm = mhMgr.GetUserButtonMap(device.Id) #YYY a:ActionName +#ubm = mhMgr.GetUserButtonMap(device.Id, activity) +#bm = mhMgr.GetButtonMap(device.Id) +#cs = mhMgr.GetCommands(device.Id) +# mhMgr.GetRemoteCanvas(remote.SkinId) +# mhMgr.GetRemoteCanvas(remote.Surfaces[0][0].SkinId) +# mhMgr.GetConfig(remote, "foo") #XXX +# mhMgr.GetUserFeatures(device.Id) #XXX b:Attributes +# mhMgr.GetPowerFeature(device.Id) #XXX b:Attributes diff --git a/mhprogram b/mhprogram new file mode 100755 index 0000000..c349303 --- /dev/null +++ b/mhprogram @@ -0,0 +1,100 @@ +#!/usr/bin/env python2 +import sys, os, re + +from urllib2 import URLError + +sys.path.append('/usr/share/congruity') +from mhmanager import MHManager +from mhmanager import MHAccountDetails +from mhmanager import SaveActivityTemplate +from mhmanager import Secrets + +try: + import argparse +except: + use_local_wsdl = True + if '--use-global-wsdl' in sys.argv: + use_local_wsdl = False + if '--use-local-wsdl' in sys.argv: + use_local_wsdl = True + suds_debug = False + if '--suds-debug' in sys.argv: + suds_debug = True +else: + parser = argparse.ArgumentParser(description='Manage Logitech Harmony Remotes.') + parser.add_argument('-d', '--suds-debug', help='output SOAP messages', + action='store_true') + parser.add_argument('-l', '--use-local-wsdl', help='use local wsdl file', + action='store_true') + args = parser.parse_args() + suds_debug = args.suds_debug + use_local_wsdl = args.use_local_wsdl + +mhMgr = MHManager(use_local_wsdl, suds_debug) + +import libconcord + +username, password = "i...@hellion.org.uk", "«password»" +try: + print "Logging in" + loginResult = mhMgr.Login(username, password) +except URLError as e: + loginResult = e.reason + +if loginResult is True: + print "Login successful" +else: + if loginResult is None: + msg = 'You appear to have used a members.harmonyremote.com ' \ + 'account. Please create a myharmony.com account or ' \ + 'login with an existing one.' + elif loginResult is False: + msg = 'Login failed. Username or password incorrect.' + else: + msg = 'Login failed. %s' % loginResult + print(msg) + os.exit(1) + +print "Found Remotes:" +remotes = mhMgr.GetRemotes() +products = {} +for remote in remotes: + product = mhMgr.GetProduct(remote.SkinId) + products[remote] = product + print " - %s" % product.DisplayName +print +if len(remotes) != 1: + print "Unexpected number of remotes" + +remote = remotes[0] +product = products[remote] + +print "Selected remote: %s" % product.DisplayName +print + +devices = mhMgr.GetDevices(remote.Id) +print "Devices:" +for d in devices: + print " - %s - %s" % (d.ParentDeviceManufacturer, d.ParentDeviceModel) + if d.ParentDeviceManufacturer == "Microsoft" and d.ParentDeviceModel == "Windows Media Center": + device = d +print + + +print "Device: %s (%s - %s)" % (device.Name, device.ParentDeviceManufacturer, device.ParentDeviceModel) +print + +#r = mhMgr.UpdateIRCommandKC("BAE", "G:Microsoft 30 Bit:()(0x3FF07BAE)():3", device.Id) +#r = mhMgr.UpdateIRCommandKC("AFE", "G:Microsoft 30 Bit:()(0x3FF07AFE)():3", device.Id) +#r = mhMgr.UpdateIRCommandKC("AFF", "G:Microsoft 30 Bit:()(0x3FF07AFF)():3", device.Id) +#r = mhMgr.UpdateIRCommandKC("C00", "G:Microsoft 30 Bit:()(0x3FF07C00)():3", device.Id) +#r = mhMgr.UpdateIRCommandKC("C01", "G:Microsoft 30 Bit:()(0x3FF07C01)():3", device.Id) + +#for code in range(0xb00,0xb10): +#for code in range(0xb10,0xb20)+range(0xba0,0xbb0): +for code in range(0xb00,0xb02)+range(0xc01,0xc40): + n=hex(code).upper().lstrip("0X") + c=hex(0x3ff07000 + code).upper().lstrip("0X") + c="G:Microsoft 30 Bit:()(0x"+c+")():3" + t=mhMgr.UpdateIRCommandKC(n, c, device.Id) + print n,c,"Result",t ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot _______________________________________________ concordance-devel mailing list concordance-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/concordance-devel