├── .gitignore ├── README.md ├── examples ├── color.py ├── context.py └── test.py └── infinity ├── __init__.py └── infinity.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Disney Infinity USB Base library 2 | ================================ 3 | 4 | Python. Requires hidapi. 5 | 6 | Tested with the PS3/4 and Wii U base. I think the XBox bases are different and so likely won't work. 7 | 8 | ```python 9 | 10 | from infinity import InfinityBase 11 | 12 | base = InfinityBase() 13 | 14 | # this will be run whenever a figure or disk is added/removed 15 | base.onTagsChanged = lambda: print("Tags added or removed.") 16 | 17 | base.connect() 18 | 19 | # get all the figures and disks on the base 20 | base.getAllTags(print) 21 | 22 | base.setColor(1, 200, 0, 0) 23 | 24 | base.setColor(2, 0, 56, 0) 25 | 26 | base.fadeColor(3, 0, 0, 200) 27 | 28 | time.sleep(3) 29 | 30 | base.flashColor(3, 0, 0, 200) 31 | 32 | while True: 33 | pass 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /examples/color.py: -------------------------------------------------------------------------------- 1 | from context import infinity 2 | import time 3 | from infinity import InfinityBase 4 | 5 | base = InfinityBase() 6 | base.connect() 7 | 8 | # these tag ids will be different to yours... 9 | LUKE = [0, 4, 125, 103, 226, 124, 67, 128] 10 | IRON_MAN = [0, 4, 131, 130, 242, 59, 53, 128] 11 | RAPUNZEL = [0, 4, 130, 247, 2, 147, 47, 128] 12 | SPIDERMAN = [0, 4, 60, 154, 234, 58, 53, 128] 13 | JACK_SPARROW = [0, 4, 66, 176, 106, 125, 47, 128] 14 | MERIDA = [0, 4, 166, 54, 122, 150, 51, 128] 15 | 16 | YELLOW = [255, 92, 0] 17 | GLOW = [10, 10, 10] 18 | RED = [255, 0, 0] 19 | LIGHT_BLUE = [40, 40, 255] 20 | PINK = [255, 40, 40] 21 | PURPLE = [220, 30, 30] 22 | OFF = [0, 0, 0] 23 | 24 | def setColors(base_to_tag): 25 | for b in [1,2,3]: 26 | if b in base_to_tag: 27 | people = base_to_tag[b] 28 | if SPIDERMAN in people: 29 | base.setColor(b, *RED) 30 | elif IRON_MAN in people: 31 | base.setColor(b, *YELLOW) 32 | elif RAPUNZEL in people: 33 | base.setColor(b, *PINK) 34 | elif LUKE in people: 35 | base.setColor(b, *LIGHT_BLUE) 36 | elif JACK_SPARROW in people: 37 | base.setColor(b, *PURPLE) 38 | else: 39 | base.setColor(b, *GLOW) 40 | else: 41 | base.setColor(b, *OFF) 42 | 43 | def updateColors(): 44 | base.getAllTags(setColors) 45 | 46 | base.onTagsChanged = updateColors 47 | 48 | updateColors() 49 | 50 | while True: 51 | pass 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/context.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.abspath('..')) 5 | 6 | import infinity 7 | -------------------------------------------------------------------------------- /examples/test.py: -------------------------------------------------------------------------------- 1 | from context import infinity 2 | 3 | from infinity import InfinityBase 4 | 5 | import time 6 | 7 | def futurePrint(s): 8 | print(s) 9 | 10 | base = InfinityBase() 11 | 12 | base.onTagsChanged = lambda: futurePrint("Tags added or removed.") 13 | 14 | base.connect() 15 | 16 | base.getAllTags(futurePrint) 17 | 18 | base.setColor(1, 200, 0, 0) 19 | 20 | base.setColor(2, 0, 56, 0) 21 | 22 | base.fadeColor(3, 0, 0, 200) 23 | 24 | time.sleep(3) 25 | 26 | base.flashColor(3, 0, 0, 200) 27 | 28 | print("Try adding and removing figures and discs to/from the base. CTRL-C to quit") 29 | while True: 30 | pass 31 | 32 | -------------------------------------------------------------------------------- /infinity/__init__.py: -------------------------------------------------------------------------------- 1 | from infinity import InfinityBase 2 | -------------------------------------------------------------------------------- /infinity/infinity.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from collections import defaultdict 3 | import hidapi 4 | 5 | class InfinityComms(threading.Thread): 6 | def __init__(self): 7 | threading.Thread.__init__(self) 8 | self.device = self.initBase() 9 | self.finish = False 10 | self.pending_requests = {} 11 | self.message_number = 0 12 | self.observers = [] 13 | 14 | def initBase(self): 15 | hidapi.hid_init() 16 | device = hidapi.hid_open(0x0e6f, 0x0129) 17 | hidapi.hid_set_nonblocking(device, False) 18 | return device 19 | 20 | def run(self): 21 | while not self.finish: 22 | line = hidapi.hid_read_timeout(self.device,32,3000) 23 | 24 | if not len(line): 25 | continue 26 | 27 | fields = [c for c in line] 28 | if fields[0] == 0xaa: 29 | length = fields[1] 30 | message_id = fields[2] 31 | if message_id in self.pending_requests: 32 | deferred = self.pending_requests[message_id] 33 | deferred.resolve(fields[3:length+2]) 34 | del self.pending_requests[message_id] 35 | else: 36 | self.unknown_message(line) 37 | elif fields[0] == 0xab: 38 | self.notifyObservers() 39 | else: 40 | self.unknown_message(line) 41 | 42 | def addObserver(self, object): 43 | self.observers.append(object) 44 | 45 | def notifyObservers(self): 46 | for obs in self.observers: 47 | obs.tagsUpdated() 48 | 49 | def unknown_message(self, fields): 50 | print("UNKNOWN MESSAGE RECEIVED ", fields) 51 | 52 | def next_message_number(self): 53 | self.message_number = (self.message_number + 1) % 256 54 | return self.message_number 55 | 56 | def send_message(self, command, data = []): 57 | message_id, message = self.construct_message(command, data) 58 | result = Deferred() 59 | self.pending_requests[message_id] = result 60 | hidapi.hid_write(self.device, message) 61 | return Promise(result) 62 | 63 | def construct_message(self, command, data): 64 | message_id = self.next_message_number() 65 | command_body = [command, message_id] + data 66 | command_length = len(command_body) 67 | command_bytes = [0x00, 0xff, command_length] + command_body 68 | message = [0x00] * 33 69 | checksum = 0 70 | for (index, byte) in enumerate(command_bytes): 71 | message[index] = byte 72 | checksum = checksum + byte 73 | message[len(command_bytes)] = checksum & 0xff 74 | return (message_id, map(chr, message)) 75 | 76 | 77 | class Deferred(object): 78 | def __init__(self): 79 | self.event = threading.Event() 80 | self.rejected = False 81 | self.result = None 82 | 83 | def resolve(self, value): 84 | self.rejected = False 85 | self.result = value 86 | self.event.set() 87 | 88 | def wait(self): 89 | while not self.event.is_set(): 90 | self.event.wait(3) 91 | 92 | 93 | class Promise(object): 94 | def __init__(self, deferred): 95 | self.deferred = deferred 96 | 97 | def then(self, success, failure=None): 98 | def task(): 99 | try: 100 | self.deferred.wait() 101 | result = self.deferred.result 102 | success(result) 103 | except Exception as ex: 104 | if failure: 105 | failure(ex) 106 | else: 107 | print(ex.message) 108 | threading.Thread(target=task).start() 109 | return self 110 | 111 | def wait(self): 112 | self.deferred.wait() 113 | 114 | 115 | class InfinityBase(object): 116 | def __init__(self): 117 | self.comms = InfinityComms() 118 | self.comms.addObserver(self) 119 | self.onTagsChanged = None 120 | 121 | def connect(self): 122 | self.comms.daemon = True 123 | self.comms.start() 124 | self.activate() 125 | 126 | def disconnect(self): 127 | self.comms.finish = True 128 | 129 | def activate(self): 130 | activate_message = [0x28,0x63,0x29,0x20,0x44, 131 | 0x69,0x73,0x6e,0x65,0x79, 132 | 0x20,0x32,0x30,0x31,0x33] 133 | self.comms.send_message(0x80, activate_message) 134 | 135 | def tagsUpdated(self): 136 | if self.onTagsChanged: 137 | self.onTagsChanged() 138 | 139 | def getAllTags(self, then): 140 | def queryAllTags(idx): 141 | if len(idx) == 0: 142 | then(dict()) 143 | numberToGet = [0] * len(idx) 144 | tagByPlatform = defaultdict(list) 145 | for (platform, tagIdx) in idx: 146 | def fileTag(platform): 147 | def inner(tag): 148 | tagByPlatform[platform].append(tag) 149 | numberToGet.pop() 150 | if len(numberToGet) == 0: 151 | then(dict(tagByPlatform)) 152 | return inner 153 | self.getTag(tagIdx, fileTag(platform)) 154 | self.getTagIdx(queryAllTags) 155 | 156 | def getTagIdx(self, then): 157 | def parseIndex(bytes): 158 | values = [ ((byte & 0xF0) >> 4, byte & 0x0F ) for byte in bytes if byte != 0x09] 159 | then(values) 160 | self.comms.send_message(0xa1).then(parseIndex) 161 | 162 | def getTag(self, idx, then): 163 | self.comms.send_message(0xb4, [idx]).then(then) 164 | 165 | def setColor(self, platform, r, g, b): 166 | self.comms.send_message(0x90, [platform, r, g, b]) 167 | 168 | def fadeColor(self, platform, r, g, b): 169 | self.comms.send_message(0x92, [platform, 0x10, 0x02, r, g, b]) 170 | 171 | def flashColor(self, platform, r, g, b): 172 | self.comms.send_message(0x93, [platform, 0x02, 0x02, 0x06, r, g, b]) 173 | 174 | if __name__ == '__main__': 175 | import time 176 | 177 | def futurePrint(s): 178 | print(s) 179 | 180 | base = InfinityBase() 181 | 182 | base.onTagsChanged = lambda: futurePrint("Tags added or removed.") 183 | 184 | base.connect() 185 | 186 | base.getAllTags(futurePrint) 187 | 188 | base.setColor(1, 200, 0, 0) 189 | 190 | base.setColor(2, 0, 56, 0) 191 | 192 | base.fadeColor(3, 0, 0, 200) 193 | 194 | time.sleep(3) 195 | 196 | base.flashColor(3, 0, 0, 200) 197 | 198 | print("Try adding and removing figures and discs to/from the base. CTRL-C to quit") 199 | while True: 200 | pass 201 | 202 | --------------------------------------------------------------------------------