def testGetSkoobotsByName(self): """ Test the getSkoobotsByName() method The method should return a list of (addr, name) tuples for all skoobots matching name """ setUpRegistry = SkoobotRegistry(self.tempPath) names = (self.skooName, self.skooDupName, "nobody", None) for name in names: with self.subTest(name=name): skoobots = setUpRegistry.getSkoobotsByName(name) if name == self.skooDupName: self.assertEqual(2, len(skoobots)) for skoobot in skoobots: self.assertEqual(self.skooDupName, skoobot[1]) # Make a list of just the addresses skooDupAddrs = [skoo[0] for skoo in skoobots] self.assertIn(self.skooDupAddr1, skooDupAddrs) self.assertIn(self.skooDupAddr2, skooDupAddrs) elif name == self.skooName: self.assertEqual(1, len(skoobots)) # There is only 1 skoobot, so test it skoobot = skoobots[0] self.assertEqual(self.skooName, skoobot[1]) self.assertEqual(self.skooAddr, skoobot[0]) else: self.assertEqual(0, len(skoobots))
def testGetSkoobotByAddress(self): """ Test the getSkoobotsByAddress() method The method should return a list of (addr, name) tupes for the skoobot matching addr, if any. Addresses are unique so there cannot be more than one. We verify uniqueness in the adding tests. """ registry = SkoobotRegistry(self.tempPath) addrs = (self.skooAddr, self.skooDupAddr1, self.skooDupAddr2, "nomatch", None) matchExpected = (self.skooAddr, self.skooDupAddr1, self.skooDupAddr2) for addr in addrs: expectedLen = 1 if addr in matchExpected else 0 with self.subTest(addr=addr, expectedLen=expectedLen): skoobots = registry.getSkoobotsByAddress(addr) self.assertEqual(expectedLen, len(skoobots)) if expectedLen == 1: # There is exactly 1 skoobot in the list, so use it. skoobot = skoobots[0] if addr == self.skooAddr: self.assertEqual(addr, skoobot[0]) self.assertEqual(self.skooName, skoobot[1]) else: self.assertEqual(addr, skoobot[0]) self.assertEqual(self.skooDupName, skoobot[1])
def testGetDefaultName(self): """ Test for method getDefaultName() Method gets the default name. """ registry = SkoobotRegistry(self.tempPath) self.assertEqual(self.skooName, registry.getDefaultName())
def __init__(self): self.transport = TransportBluepy() self.registry = SkoobotRegistry() # Table of characteristic name to uuid mappings. # The characteristic names used are the ones in the firmware. self.uuids = { "cmd": "00001525-1212-efde-1523-785feabcd123", "data": "00001524-1212-efde-1523-785feabcd123", "byte2": "00001526-1212-efde-1523-785feabcd123", "byte128": "00001527-1212-efde-1523-785feabcd123", "byte4": "00001528-1212-efde-1523-785feabcd123", } self.connectedSkoobot = None
def testBug11(self): """ Tests the resolution of bug #11 "Registry setDefault() does strange things if given a list of lists as a parameter" Check that it raises a TypeError when called with something other than String or None. It turns out that the error only triggers with tuples. """ registry = SkoobotRegistry(self.tempPath) with self.subTest("Valid arguments"): registry.setDefault(None) self.assertEqual(None, registry.getDefaultName()) registry.setDefault(self.skooName) self.assertEqual(self.skooName, registry.getDefaultName()) with self.subTest("Invalid arguments"): with self.assertRaises(TypeError): registry.setDefault(("test", ))
def testGenerateName(self): """ Tests for the generateName() method """ registry = SkoobotRegistry(self.tempPath) altSkooAddr = "aa:aa:aa:aa:aa:aa" altSkooName = "Alt" with self.subTest("Generate name from default list"): name = registry.generateName() self.assertIn(name, registry.skoobotNames) with self.subTest("Generate Alt name"): registry.skoobotNames = set([altSkooName]) name = registry.generateName() self.assertEqual(altSkooName, name) with self.subTest("Names all used"): registry.skoobotNames = set([altSkooName]) registry.addSkoobot(altSkooAddr) with self.assertRaises(KeyError): name = registry.generateName()
def testConstruct(self): """ Test construction with a non-existent file and the JSON file created during setup """ with self.subTest("Empty registry"): emptyRegistry = SkoobotRegistry("~/nonexistent.json") self.assertEqual(dict(), emptyRegistry.registry) self.assertEqual(True, emptyRegistry.valid) self.assertEqual(None, emptyRegistry.getDefaultName()) # Make sure that ~ in the filename was expanded self.assertNotIn("~", emptyRegistry.registryPath) with self.subTest("setUp() registry"): registry = SkoobotRegistry(self.tempPath) self.assertEqual(3, len(registry.registry)) self.assertEqual(True, registry.valid) self.assertEqual(self.skooName, registry.getDefaultName())
def scan(): transport = TransportBluepy() registry = SkoobotRegistry() rawDevices = transport.findRawDevices() skoobots = [] for device in rawDevices: scanList = device.getScanData() for scanItem in scanList: if scanItem[0] == 9 and scanItem[2] == "Skoobot": skoobots.append(device) for skoobot in skoobots: # print(transport.rawDeviceInfoStr(skoobot)) addr = skoobot.addr registry.addSkoobot(addr) name = registry.getSkoobotsByAddress(addr)[0][1] if registry.getDefaultName() == None: registry.setDefault(name) defaultText = " (default)" if registry.getDefaultName() == name else "" msg = "Added Skoobot {0:s} to registry with name {1:s}{2:s}" print(msg.format(addr, name, defaultText)) print("Saving to list of Skoobots to registry {0:s}".format( registry.registryPath)) registry.save() shutil.chown(registry.registryPath, os.getlogin())
class SkoobotController: """ Control API for Skoobots """ def __init__(self): self.transport = TransportBluepy() self.registry = SkoobotRegistry() # Table of characteristic name to uuid mappings. # The characteristic names used are the ones in the firmware. self.uuids = { "cmd": "00001525-1212-efde-1523-785feabcd123", "data": "00001524-1212-efde-1523-785feabcd123", "byte2": "00001526-1212-efde-1523-785feabcd123", "byte128": "00001527-1212-efde-1523-785feabcd123", "byte4": "00001528-1212-efde-1523-785feabcd123", } self.connectedSkoobot = None def connect(self, name=None, addr=None): """ Connect to the given Skoobot. If no Skoobot is given, connects to the default. Returns the address of the connected Skoobot if successful; None otherwise. """ addrList = [] if addr != None: if isinstance(addr, str): addrList.append(addr) else: raise TypeError("addr should be a string") else: if name == None: name = self.registry.getDefaultName() elif not isinstance(name, str): raise TypeError("name should be a string") if name != None: skoobots = self.registry.getSkoobotsByName(name) addrList = [bot[0] for bot in skoobots] else: raise RuntimeError("No default skoobot defined") if len(addrList) == 0: raise ValueError( "No Skoobot with name {0:s} found".format(name)) if self.connectedSkoobot != None: self.disconnect() for botAddr in addrList: try: self.transport.connect(botAddr) self.connectedSkoobot = botAddr break except BTLEException: pass return self.connectedSkoobot def disconnect(self): self.transport.disconnect() self.connectedSkoobot = None def sendCommand(self, data, waitForResponse=False): if self.connectedSkoobot == None: raise RuntimeError("BLE not connected") data = int(data) cmdBytes = data.to_bytes(1, byteorder="little") characteristics = self.transport.getRawCharacteristicsByUUID( self.uuids["cmd"]) if len(characteristics) == 0: raise RuntimeError("cmd characteristic not supported by firmware") cmd = characteristics[0] cmd.write(cmdBytes, waitForResponse) def readBytes(self, charName="data"): """ Read an array of bytes from the named characteristic returns a bytearray of the data """ if self.connectedSkoobot == None: raise RuntimeError("BLE not connected") characteristics = self.transport.getRawCharacteristicsByUUID( self.uuids[charName]) if len(characteristics) == 0: raise RuntimeError( "{0:s} characteristic not supported by firmware".format( charName)) charac = characteristics[0] dataBytes = charac.read() return dataBytes def readData(self, charName="data"): dataBytes = self.readBytes(charName) value = int.from_bytes(dataBytes, byteorder="little") return value def cmdRight(self): self.sendCommand(CMD_RIGHT, True) def cmdLeft(self): self.sendCommand(CMD_LEFT, True) def cmdForward(self): self.sendCommand(CMD_FORWARD, True) def cmdBackward(self): self.sendCommand(CMD_BACKWARD, True) def cmdStop(self): self.sendCommand(CMD_STOP, True) def cmdSleep(self): self.sendCommand(CMD_SLEEP, True) def cmdRoverMode(self): self.sendCommand(CMD_ROVER_MODE, True) def requestDistance(self): self.sendCommand(CMD_GET_DISTANCE, True) return self.readData() def requestAmbientLight(self): self.sendCommand(CMD_GET_AMBIENT, True) return self.readData("byte2")
def testBug8(self): """ Tests the resolution of bug #8 """ badDefaultName = "gremlin" registry = SkoobotRegistry(self.tempPath) with self.subTest("Setting bad default"): oldDefault = registry.getDefaultName() with self.assertRaises(ValueError): registry.setDefault(badDefaultName) self.assertEqual(oldDefault, registry.getDefaultName()) with self.subTest("Loading bad default"): self.registryDict["default"] = badDefaultName with open(self.tempPath, "w") as registryFile: json.dump(self.registryDict, registryFile, indent=4) registry.load() self.assertEqual(None, registry.getDefaultName()) with self.subTest("Loading good default"): self.registryDict["default"] = self.skooName with open(self.tempPath, "w") as registryFile: json.dump(self.registryDict, registryFile, indent=4) registry.load() self.assertEqual(self.skooName, registry.getDefaultName())
def testSave(self): """ Tests for the save() method Make sure that save() works, except when the registry is marked invalid. """ registry = SkoobotRegistry(self.tempPath) altSkooAddr = "aa:aa:aa:aa:aa:aa" altSkooName = "Alt" extraSkooAddr = "ee:ee:ee:ee:ee:ee" extraSkooName = "Extra" with self.subTest("Undo alterations"): registry.addSkoobot(altSkooAddr, altSkooName) registry.setDefault(altSkooAddr) self.assertEqual(4, len(registry.registry)) registry.load() self.assertEqual(3, len(registry.registry)) self.assertEqual(self.skooName, registry.getDefaultName()) with self.subTest("Alter and save"): registry.addSkoobot(altSkooAddr, altSkooName) registry.setDefault(altSkooAddr) self.assertEqual(4, len(registry.registry)) # Save the state with the AltSkootbot entry registry.save() registry.addSkoobot(extraSkooAddr, extraSkooName) registry.setDefault(extraSkooAddr) self.assertEqual(5, len(registry.registry)) self.assertEqual(extraSkooName, registry.getDefaultName()) # Restore to the save() state registry.load() self.assertEqual(4, len(registry.registry)) self.assertEqual(altSkooName, registry.getDefaultName()) with self.subTest("Don't save invalid"): registry.addSkoobot(extraSkooAddr, altSkooName) registry.setDefault(extraSkooAddr) self.assertEqual(5, len(registry.registry)) registry.valid = False # Fail to save the state with the Extra entry registry.save() # Restore to the previous save() state registry.load() self.assertEqual(4, len(registry.registry)) self.assertEqual(altSkooName, registry.getDefaultName())
def testLoad(self): """ Test for loading the registry. Most of this is already tested by the constructor tests, however, we need to check that a reload works and that a failed load sets the valid flag to false """ registry = SkoobotRegistry(self.tempPath) with self.subTest("Empty dict"): emptyDict = {} with open(self.tempPath, "w") as registryFile: json.dump(emptyDict, registryFile) self.assertEqual(3, len(registry.registry)) registry.load() self.assertEqual(0, len(registry.registry)) self.assertEqual(True, registry.valid) self.assertEqual(None, registry.getDefaultName()) with self.subTest("Invalid dict"): with open(self.tempPath, "w") as registryFile: registryFile.write("rubbish") registry.addSkoobot(self.skooAddr) self.assertEqual(True, registry.valid) with self.assertRaises(json.JSONDecodeError): registry.load() self.assertEqual(0, len(registry.registry)) self.assertEqual(False, registry.valid) self.assertEqual(None, registry.getDefaultName()) with self.subTest("Reload good dict"): with open(self.tempPath, "w") as registryFile: json.dump(self.registryDict, registryFile) self.assertEqual(0, len(registry.registry)) registry.load() self.assertEqual(3, len(registry.registry)) self.assertEqual(True, registry.valid) self.assertEqual(self.skooName, registry.getDefaultName())
def testSetDefault(self): """ Test for method setDefault() Method sets the default name. It takes one parameter, which is either the address or the name. """ registry = SkoobotRegistry(self.tempPath) registry.setDefault(self.skooDupName) self.assertEqual(self.skooDupName, registry.getDefaultName()) registry.setDefault(self.skooAddr) self.assertEqual(self.skooName, registry.getDefaultName()) registry.setDefault(self.skooDupAddr1) self.assertEqual(self.skooDupName, registry.getDefaultName())
def testAddSkoobot(self): """ Test addition of skoobots using the addSkoobot() method The method adds a skoobot to the registry using an address and an optional name. """ registry = SkoobotRegistry(self.tempPath) namedAddr = "ff:ff:ff:ff:ff:ff" namedName = "newSkoobot" unnamedAddr = "ff:ff:ff:ff:ff:fe" with self.subTest("Add named Skoobot"): registry.addSkoobot(namedAddr, namedName) self.assertEqual(4, len(registry.registry)) self.assertEqual(1, len(registry.getSkoobotsByAddress(namedAddr))) self.assertEqual(1, len(registry.getSkoobotsByName(namedName))) with self.subTest("Add unnamed Skoobot"): registry.addSkoobot(unnamedAddr) self.assertEqual(5, len(registry.registry)) skoobots = registry.getSkoobotsByAddress(unnamedAddr) self.assertEqual(1, len(skoobots)) self.assertIn(skoobots[0][1], registry.skoobotNames) with self.subTest("Add duplicate Skoobot"): # Bug #7: By default this replaces the existing # skoobot. If the replace=False parameter is set, # it raises a RuntimeError unless the parameters # are compatible with the existing entry. # # It is always true that it does not result in a # duplicate address. registry.addSkoobot(namedAddr, namedName) self.assertEqual(5, len(registry.registry)) registry.addSkoobot(namedAddr, replace=False) self.assertEqual(5, len(registry.registry)) with self.assertRaises(RuntimeError): registry.addSkoobot(unnamedAddr, namedName, replace=False) with self.subTest("Test invalid parameters"): with self.assertRaises(TypeError): registry.addSkoobot((namedAddr, namedName)) with self.assertRaises(TypeError): registry.addSkoobot(namedAddr, (namedAddr, namedName))