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

Reply via email to