#!/usr/bin/python
# -*- coding: utf-8 -*-
import re
from binascii import hexlify, unhexlify
from struct import pack, unpack
from math import cos, pi
from sys import argv
class AddressItem(object):
def __init__(self, value = None):
if value == None:
self.set_default()
elif self.is_field(value):
self.parse(value)
elif isinstance(value, AddressItem):
self.copy(value)
else:
self.source = None
if not self.set_value(value):
raise TypeError(u"Invalid parameters to " +
self.__class__.__name__ + u": " +
repr(value))
def set_default(self):
# Called when None is passed
self.source = None
return self
def is_field(self, value):
# Optional Override
return type(value) == unicode
def set_value(self, value):
# Override with type check and value set
return None
def parse(self, value):
# Override with parser
self.source = unicode(value)
return self
def unparse(self):
# Override with unparser
return str(self)
def copy(self, other):
# Override with value copying
self.source = other.source
return self
def __str__(self):
return unicode(self).encode('utf-8', 'replace')
def __unicode__(self):
return unicode(other.source)
def __cmp__(self, other):
if isinstance(other, AddressItem):
return cmp(self.source, other.source)
else:
return self.do_compare(other)
def do_compare(self, other):
# Ignore comparison
return 1
class AddressDegrees(AddressItem):
def __init__(self, d, m=0, s=0):
if type(d) == int:
super(AddressDegrees, self).__init__((d, m, s))
else:
super(AddressDegrees, self).__init__(d)
def set_default(self):
self.d, self.m, self.s = [ 0, 0, 0 ]
return super(AddressDegrees, self).set_default()
def set_value(self, value):
if type(value) == tuple and len(value) == 3:
self.d, self.m, self.s = value
self.d = self.restrict_to_domain(self.d)
return self
elif type(value) == float:
return self.parse_degrees(value)
def parse(self, value):
super(AddressDegrees, self).parse(value)
return self.parse_degrees(float(value))
def split_negative(self, value):
neg = value < 0
if neg:
value = -value
return (neg, value)
def is_negative(self):
return (self.d < 0, self.d)
def round_seconds(self):
self.s = int(self.s + .5)
if self.s == 60:
self.s = 0
self.m += 1
if self.m == 60:
self.m = 0
self.d += self.d/abs(self.d)
def parse_degrees(self, value):
value = self.restrict_to_domain(value)
neg, value = self.split_negative(value)
self.d = int(value)
value -= self.d
value = value*60
self.m = int(value)
self.s = (value - self.m)*60
#self.round_seconds()
if neg:
self.d = -self.d
return self
def restrict_to_domain(self, value):
return value % 360
def __float__(self):
neg, d = self.is_negative()
if neg:
m, s = -self.m, -self.s
else:
m, s = self.m, self.s
return d + m/60. + s/3600.
def unparse(self):
return '{:.6f}'.format(round(float(self), 6))
def copy(self, other):
self.d, self.m, self.s = other.d, other.m, other.s
return super(AddressDegrees, self).copy(other)
def __str__(self):
return '{:d}*{:02d}\'{:02.4f}"'.format(self.d, self.m,
self.s)
def __unicode__(self):
return u'{:d}\u00B0{:02d}\'{:02.4f}"'.format(self.d, self.m,
self.s)
def __cmp__(self, other):
if isinstance(other, AddressDegrees) or type(other) == float:
r = cmp(float(self), float(other))
if r:
return r
return super(AddressDegrees, self).__cmp__(other)
def do_compare(self, other):
return type(other) != float
def __iadd__(self, other):
self.parse_degrees(float(self) + other)
return self
def __isub__(self, other):
self.parse_degrees(float(self) - other)
return self
def __imul__(self, other):
self.parse_degrees(float(self) * other)
return self
def __idiv__(self, other):
self.parse_degrees(float(self) / other)
return self
def __itruediv__(self, other):
self.parse_degrees(__truediv__(float(self), other))
return self
def __ifloordiv__(self, other):
self.parse_degrees(float(self) // other)
return self
def __imod__(self, other):
self.parse_degrees(float(self) % other)
return self
def __ipow__(self, other, modulo = 360):
self.parse_degrees(pow(float(self), other, modulo))
return self
def __ilshift__(self, other):
self.parse_degrees(float(self) << other)
return self
def __irshift__(self, other):
self.parse_degrees(float(self) >> other)
return self
def __iand__(self, other):
self.parse_degrees(float(self) & other)
return self
def __ixor__(self, other):
self.parse_degrees(float(self) ^ other)
return self
def __ior__(self, other):
self.parse_degrees(float(self) | other)
return self
class AddressDirection(AddressDegrees):
def set_default(self):
self.unset = True
return super(AddressDirection, self).set_default()
def set_value(self, value):
if type(value) == int:
value = value / 10.
self.unset = False
return super(AddressDirection, self).set_value(value)
def parse(self, value):
self.unset = value == u'-1'
if self.unset:
value = u'0'
super(AddressDirection, self).parse(value)
return self.parse_degrees(float(value) / 10.)
def unparse(self):
if self.unset:
return '-1'
else:
return str(int(round(float(self) * 10)))
def copy(self, other):
self.unset = other.unset
return super(AddressDirection, self).copy(other)
def __str__(self):
if self.unset:
return u'None'
else:
return super(AddressDirection, self).__str__()
def __unicode__(self):
if self.unset:
return u'None'
else:
return super(AddressDirection, self).__unicode__()
class AddressDegreeCoordinates(AddressDegrees):
COUNTERCLOCKWISE = u'\u00B0'
CLOCKWISE = u'-\u00B0'
MAX = 360
def __init__(self, d, h=None, m=0, s=0):
if type(d) == int:
super(AddressDegreeCoordinates,
self).__init__((d, h, m, s))
else:
super(AddressDegreeCoordinates,
self).__init__(d)
def set_default(self):
self.h = self.COUNTERCLOCKWISE
return super(AddressDegreeCoordinates, self).set_default()
def set_value(self, value):
if type(value) == tuple and len(value) == 4:
self.d, self.h, self.m, self.s = value
if self.h == None:
# Get h from sign of d
if self.d < 0:
self.h = self.CLOCKWISE
self.d = -self.d
else:
self.h = self.COUNTERCLOCKWISE
self.d = self.restrict_to_domain(self.d)
return self
elif type(value) == float:
return self.parse_degrees(value)
def restrict_to_domain(self, value):
# Put value in range given by ±self.MAX
return (value + self.MAX) % (2 * self.MAX) - self.MAX
def split_negative(self, value):
neg, value = super(AddressDegreeCoordinates,
self).split_negative(value)
if neg:
self.h = self.CLOCKWISE
else:
self.h = self.COUNTERCLOCKWISE
# Treat number as positive as sign is a separate entity
return (False, value)
def is_negative(self):
# Integrate Sign and Value
neg = self.h == self.CLOCKWISE
if neg:
return (neg, -self.d)
else:
return (neg, self.d)
def copy(self, other):
self.h = other.h
return super(AddressDegreeCoordinates, self).copy(other)
def __unicode__(self):
# Note: Will raise an exception if self.h is unicode
# Prefer Unicode conversion
return u'{:d}{}{:02d}\'{:02.4f}"'.format(self.d, self.h, self.m,
self.s)
def __str__(self):
return unicode(self).encode('utf-8', 'replace')
def __ipow__(self, other, modulo = MAX * 2):
self.parse_degrees(pow(float(self), other, modulo))
return self
class AddressLongitude(AddressDegreeCoordinates):
COUNTERCLOCKWISE = u'E'
CLOCKWISE = u'W'
MAX = 180
class AddressLatitude(AddressDegreeCoordinates):
COUNTERCLOCKWISE = u'N'
CLOCKWISE = u'S'
MAX = 90
class AddressItemValue(AddressItem):
def set_default(self):
self.value = None
return super(AddressItemValue, self).set_default()
def set_value(self, value):
self.value = value
return self
def copy(self, other):
self.value = other.value
return super(AddressItemValue, self).copy(other)
def unparse(self):
return str(self.value)
def __str__(self):
return unicode(self).encode('utf-8', 'replace')
def __unicode__(self):
return unicode(self.value)
def __cmp__(self, other):
if isinstance(other, AddressItem):
r = cmp(self.value, other.value)
if r:
return r
elif type(self.value) == type(other):
r = cmp(self.value, other)
if r:
return r
return super(AddressItemValue, self).__cmp__(other)
def do_compare(self, other):
return type(other) != type(self.value)
def __iadd__(self, other):
self.value += other
return self
def __isub__(self, other):
self.value -= other
return self
def __imul__(self, other):
self.value *= other
return self
def __idiv__(self, other):
self.value /= other
return self
def __itruediv__(self, other):
self.value.__itruediv__(other)
return self
def __ifloordiv__(self, other):
self.value //= other
return self
def __imod__(self, other):
self.value %= other
return self
def __ipow__(self, other, modulo = None):
self.value = pow(self.value, other, modulo)
return self
def __ilshift__(self, other):
self.value <<= other
return self
def __irshift__(self, other):
self.value >>= other
return self
def __iand__(self, other):
self.value &= other
return self
def __ixor__(self, other):
self.value ^= other
return self
def __ior__(self, other):
self.value |= other
return self
class AddressEnumerated(AddressItemValue):
names = { }
def set_default(self):
super(AddressEnumerated, self).set_default()
if self.names:
self.value = min(self.names.keys())
return self
def is_field(self, value):
return super(AddressEnumerated, self).is_field(value) and \
value not in self.names.values()
def set_value(self, value):
if type(value) == int:
if value not in self.names.keys():
print u"Warning: Invalid " + self.__class__.__name__ + \
u": " + unicode(value)
return super(AddressEnumerated, self).set_value(value)
elif value in self.names.values():
return self.set_from_string(value)
def parse(self, value):
super(AddressEnumerated, self).parse(value)
v = int(value)
if v in self.names.keys():
self.value = v
else:
raise ValueError(self.__class__.__name__ + u" out of range: "
+ repr(value))
def unparse(self):
if self.value not in self.names.keys():
print u"Warning: Invalid " + self.__class__.__name__ + \
u": " + unicode(value)
return super(AddressEnumerated, self).unparse()
def set_from_string(self, value):
for k, v in self.names.iteritems():
if v == value:
self.value = k
return self
# Name not found
raise NameError(u"Invalid Enumeration String: " + unicode(value))
def __str__(self):
if self.value in self.names.keys():
return self.names[self.value]
else:
return super(AddressEnumerated, self).__str__()
def __unicode__(self):
if self.value in self.names.keys():
return self.names[self.value]
else:
return super(AddressEnumerated, self).__unicode__()
def __int__(self):
return int(self.value)
class AddressGroup(AddressEnumerated):
NONE = 0
FAMILY = 1
FRIENDS = 2
BUSINESS = 3
RESTAURANT = 4
LEISURE = 5
SHOPPING = 6
ACCOMODATIONS = 7
OTHER = 8
names = {
NONE: u'None',
FAMILY: u'Family',
FRIENDS: u'Friends',
BUSINESS: u'Business',
RESTAURANT: u'Restaurant',
LEISURE: u'Leisure',
SHOPPING: u'Shopping',
ACCOMODATIONS: u'Accomodations',
OTHER: u'Other'
}
class AddressChargerType(AddressEnumerated):
# TODO: Can the Charger Type be unset in a standard Address Book?
UNSET = None
NONE = 0
L2 = 1
CHAdeMO = 2
names = {
UNSET: u'Unset',
NONE: u'None',
L2: u'L2',
CHAdeMO: u'CHAdeMO'
}
def set_default(self):
super(AddressChargerType, self).set_default()
self.value = None
return self
def set_value(self, value):
if value == None:
# Skip Parent base class call
AddressItem.set_default(self)
self.value = None
return self
else:
return super(AddressChargerType, self).set_value(value)
def parse(self, value):
if value == u'':
# Skip Parent base class call
AddressItem.parse(self, value)
self.value = None
return self
else:
return super(AddressChargerType, self).parse(value)
def unparse(self):
if self.value == None:
return ''
else:
return super(AddressChargerType, self).unparse()
class AddressIcon(AddressEnumerated):
Default_Marker = 0
Flag_Blue = 1
Flag_Green = 2
Flag_Yellow = 3
Flag_Orange = 4
DFlag_Blue = 5
DFlag_Green = 6
DFlag_Yellow = 7
DFlag_Orange = 8
Pin_Blue = 9
Pin_Green = 10
Pin_Yellow = 11
Pin_Orange = 12
Exclamation = 13
DExclamation = 14
Musical_Note = 15
Home_Blue = 16
Home_Red = 17
Apartment_Red = 18
Apartment_Yellow = 19
Office_Silver = 20
Office_Gold = 21
Pagoda = 22
Church = 23
EVSE_Block = 24
Stadium = 25
Hot_Springs = 26
Still_Frame_Camera = 27
Video_Camera = 28
Binoculars = 29
Food = 30
Tea = 31
Fine_Dining = 32
Gift_Bag = 33
Gift_Box = 34
Golf = 35
Baseball = 36
Sea_Cruise = 37
Horse = 38
Tennis = 39
Camping = 40
Fishing = 41
Biking = 42
Puppy = 43
EVSE_Round = 44
names = {
Default_Marker: u'Default Marker',
Flag_Blue: u'Blue Flag',
Flag_Green: u'Green Flag',
Flag_Yellow: u'Yellow Flag',
Flag_Orange: u'Orange Flag',
DFlag_Blue: u'Double Blue Flags',
DFlag_Green: u'Double Green Flags',
DFlag_Yellow: u'Double Yellow Flags',
DFlag_Orange: u'Double Orange Flags',
Pin_Blue: u'Blue Pin',
Pin_Green: u'Green Pin',
Pin_Yellow: u'Yellow Pin',
Pin_Orange: u'Orange Pin',
Exclamation: u'Exclamation Point',
DExclamation: u'Double Exclamation Point',
Musical_Note: u'Musical Note',
Home_Blue: u'Blue House',
Home_Red: u'Red House',
Apartment_Red: u'Red Apartment',
Apartment_Yellow: u'Yellow Apartment',
Office_Silver: u'Silver Office',
Office_Gold: u'Gold Office',
Pagoda: u'Pagoda',
Church: u'Church',
EVSE_Block: u'CHAdeMO',
Stadium: u'Stadium',
Hot_Springs: u'Hot Springs',
Still_Frame_Camera: u'Still-Frame Camera',
Video_Camera: u'Video Camera',
Binoculars: u'Binoculars',
Food: u'Food',
Tea: u'Tea',
Fine_Dining: u'Fine Dining',
Gift_Bag: u'Gift Bag',
Gift_Box: u'Gift Box',
Golf: u'Golf',
Baseball: u'Baseball',
Sea_Cruise: u'Sea Cruise',
Horse: u'Horse',
Tennis: u'Tennis',
Camping: u'Camping',
Fishing: u'Fishing',
Biking: u'Biking',
Puppy: u'Puppy',
EVSE_Round: u'Level 2 EVSE'
}
class AddressTone(AddressEnumerated):
No_Tone = -1
Chime = 780
Melody = 781
Sound = 782
Dog = 783
Cat = 784
Wave = 785
Church_Bell = 786
Music_Box_1 = 787
Music_Box_2 = 792
Marimba = 788
Trumpet = 789
Horn = 790
Scratch = 791
names = {
No_Tone: u'No Tone',
Chime: u'Chime',
Melody: u'Melody',
Sound: u'Sound',
Dog: u'Dog',
Cat: u'Cat',
Wave: u'Wave',
Church_Bell: u'Church Bell',
Music_Box_1: u'Music Box 1',
Music_Box_2: u'Music Box 2',
Marimba: u'Marimba',
Trumpet: u'Trumpet',
Horn: u'Horn',
Scratch: u'Scratch'
}
class AddressDistance(AddressEnumerated):
Not_Set = 3000
Fifty_Meters = 450
One_Hundred_Meters = 900
Three_Hundred_Meters = 3001
Five_Hundred_Meters = 4500
names = {
Not_Set: u'None Set (300m)',
Fifty_Meters: u'50m',
One_Hundred_Meters: u'100m',
Three_Hundred_Meters: u'300m',
Five_Hundred_Meters: u'500m'
}
english_names = {
Not_Set: u'None Set (900ft)',
Fifty_Meters: u'150ft',
One_Hundred_Meters: u'300ft',
Three_Hundred_Meters: u'1000ft',
Five_Hundred_Meters: u'1500ft'
}
meters = {
Not_Set: None,
Fifty_Meters: 50,
One_Hundred_Meters: 100,
Three_Hundred_Meters: 300,
Five_Hundred_Meters: 500
}
feet = {
Not_Set: None,
Fifty_Meters: 150,
One_Hundred_Meters: 300,
Three_Hundred_Meters: 1000,
Five_Hundred_Meters: 1500
}
def __init__(self, value, english = False):
self.english = english
super(AddressDistance, self).__init__(value)
def set_default(self):
super(AddressDistance, self).set_default()
self.value = self.Not_Set
return self
def is_field(self, value):
return super(AddressDistance, self).is_field(value) and \
value not in self.english_names.values()
def set_value(self, value):
if value == None or value == u'None Set':
return super(AddressDistance, self).set_value(self.Not_Set)
elif value in self.english_names.values():
return self.set_from_english_string(value)
else:
return super(AddressDistance, self).set_value(value)
def copy(self, other):
self.english = other.english
return super(AddressDistance, self).copy(other)
def in_meters(self):
if self.value in self.meters.keys():
return self.meters[self.value]
else:
return self.value // 9
def in_feet(self):
if self.value in self.feet.keys():
return self.feet[self.value]
else:
return self.value // 3
def __int__(self):
if self.english:
v = self.in_feet()
if v == None:
return self.feet[self.Three_Hundred_Meters]
else:
return v
else:
v = self.in_meters()
if v == None:
return self.meters[self.Three_Hundred_Meters]
else:
return v
def set_from_meters(self, value):
for k, v in self.meters.iteritems():
if v == value:
self.value = k
self.english = False
return self
def set_from_feet(self, value):
for k, v in self.feet.iteritems():
if v == value:
self.value = k
self.english = True
return self
def set_english(self, value):
if type(value) == bool:
self.english = value
return self
def set_from_string(self, value):
if super(AddressDistance, self).set_from_string(value):
self.english = False
return self
def set_from_english_string(self, value):
for k, v in self.english_names.iteritems():
if v == value:
self.value = k
self.english = True
return self
def english_str(self):
if self.value in self.english_names.keys():
return self.english_names[self.value]
else:
return super(AddressDistance, self).__str__()
def english_unicode(self):
if self.value in self.english_names.keys():
return self.english_names[self.value]
else:
return super(AddressDistance, self).__unicode__()
def __str__(self):
if self.english:
return self.english_str()
else:
return super(AddressDistance, self).__str__()
def __unicode__(self):
if self.english:
return self.english_unicode()
else:
return super(AddressDistance, self).__unicode__()
class AddressRegion(AddressEnumerated):
North_America = 32
Europe = 48
names = {
North_America: u'North America',
Europe: u'Europe',
}
class AddressString(AddressItemValue):
hex_pattern = re.compile(ur'^(?:[0-9A-F][0-9A-F])*00$')
def __init__(self, value, literal=False):
self.literal = literal
super(AddressString, self).__init__(value)
def set_default(self):
super(AddressString, self).set_default()
self.value = u''
return self
def is_field(self, value):
# Optional Override
return super(AddressString, self).is_field(value) and \
self.hex_pattern.match(value) and not self.literal
def set_value(self, value):
return super(AddressString, self).set_value(str(value))
def parse(self, value):
super(AddressString, self).parse(str(value))
if not self.hex_pattern.match(value):
raise ValueError(u"Format of text string invalid: " +
repr(hexbytes))
self.value = unhexlify(value[:-2]).decode('utf-8')
def unparse(self):
return hexlify(self.value).upper() + '00'
class AddressIntRange(AddressItemValue):
MIN = 0
MAX = 1
def set_default(self):
super(AddressIntRange, self).set_default()
self.value = self.MIN
return self
def set_value(self, value):
if type(value) == int or type(value) == long or value == None:
if value < self.MIN or value > self.MAX:
print u"Warning: Invalid " + self.__class__.__name__ + \
u": " + unicode(value)
return super(AddressIntRange, self).set_value(value)
def parse(self, value):
super(AddressIntRange, self).parse(value)
v = int(value)
if v >= self.MIN and v <= self.MAX:
self.value = v
else:
raise ValueError(self.__class__.__name__ +
u" out of range: " + repr(value))
def unparse(self):
if self.value < self.MIN or self.value > self.MAX:
print u"Warning: Invalid " + self.__class__.__name__ + \
u": " + unicode(value)
return super(AddressIntRange, self).unparse()
def __int__(self):
return self.value
def __long__(self):
return self.value
def __unicode__(self):
return unicode(self.value)
def __str__(self):
return unicode(self).encode('utf-8', 'replace')
class AddressGridIndex(AddressIntRange):
# Grid Data Notes
# -1659656333 = 38N48'45" 77W01'52"
# -1659656334 = 38N48'45" 77W03'45"
# Thus, one grid point here represents about 113 arcseconds about
# 1540m
# math.cos(latitude)*92.5/3*113 to get approximate arcsecond length
# -1551855391(69,490) = 47N20'18" 122W13'12"
# -1551855392(1882, 556) = 47N20'20" 122W13'16"
# North/South here are noise represented by AFAICT 66/2048ths of a
# unit and 2 arc-seconds and thus about 61⅔m
# East/West represents 4 arcseconds and thus about 120.6m and maybe
# 234/2048 ticks of grid data; this makes about 1,056m per grid point
# North American Grid Limits:
# 79N59'59" by 172W00'00", -1141671936(0, 2046) = (0x7800, 0xbbf3)
# 79N59'59" by 52W00'00", -1141656589(2046, 2045) = (0xb3f3, 0xbbf3)
# 0N00'00" by 172W00'00", -2147452928(0, 0) = (0x7800, 0x8000)
# 0N00'00" by 52W00'00", -2147437581(2046, 0) = (0xb3f3, 0x8000)
# Fully Qualified with x, y:
# (0x3c00000, 0x5df9ffe), (0x59f9ffe, 0x5df9ffd),
# (0x3c00000, 0x4000000), (0x59f9ffe, 0x4000000)
# 120 Longitudinal Degrees, 80 Latitudinal Degrees
# Round to:
# (0x3c00000, 0x5e00000), (0x5a00000, 0x5e00000),
# (0x3c00000, 0x4000000), (0x5a00000, 0x4000000)
# 0x1e00000 units of Longitude, 0x1e00000 units of Latitude
# 1:2**18 (262,144) is the ratio from Longitude to Grid Unit (slope)
# 1:393,216 is the ratio from Latitude to Grid Unit (slope)
# Sign of Longitude-slope is +1 because Longitude is negative
# Sign of Latitude-slope is +1
# When Longitude is 172W, grid x is 0x3c00000, thus 0x6700000 at 0E
# When Latitude is 0N, grid y is 0x4000000, which is our intercept
# Horizontal Grid = Longitude*2**18 + 0x6700000
# Vertical Grid = Latitude*393216 + 0x4000000
# Longitude = (HGrid - 0x6700000)/2**18
# Latitude = (VGrid - 0x4000000)/393216
# New, less-agressive Rounding
# (0x3c00000, 0x5dfa000), (0x59fa000, 0x5dfa000),
# (0x3c00000, 0x4000000), (0x59fa000, 0x4000000)
# 0x1dfa000 units of Longitude, 0x1dfa000 units of Latitude
# 1:261939.2 is the ratio from Longitude to Grid Unit (slope)
# 1:392908.8 is the ratio from Latitude to Grid Unit (slope)
# Signs are the same as before
# When Longitude is 172W, grid x is 0x3c00000, thus 0x66f7666.6...6
# Which is exactly 107968102.4
# Again, Latitude at 0N is 0x4000000 or 67108864
# Horizontal Grid = Longitude*261939.2 + 107968102.4
# Vertical Grid = Latitude*392908.8 + 67108864.
# Longitude = (HGrid - 107968102.4)/261939.2
# Latitude = (VGrid - 67108864.)/392908.8
#
# Back to basics:
# 52W00'00" 79N59'59" 0xbbf4b3f4
# 172W00'00" 79N59'59" 0xbbf47800
# 52W00'00" 0N00'00" 0x8000b3f4
# 172W00'00" 0N00'00" 0x80007800
# 15348 units over 432000 longitude seconds, 28' per unit
# 15348 units over 288000-1 latitude seconds, 19' per unit
#
# More observations from Potomac
# - - 0x9d13a775 Invalid
# - - 0x9d13a774 Invalid
# 77W01'52" 38N48'45" 0x9d13a773
# 77W03'45" 38N48'45" 0x9d13a772 113'
# 77W05'37" 38N48'45" 0x9d13a771 112'
#
#
# - - 0x9d15a773 Invalid
# - - 0x9d14a773 Invalid
# 77W01'52" 38N48'45" 0x9d13a773
# 77W01'52" 38N47'29" 0x9d12a773 76'
# 77W01'52" 38N46'14" 0x9d11a773 75'
#
# 0x80002000 is a mask for the always on bits
# 0x400C000C is a mask for the never used bits
# This would explain the 4:1 ratio of data points
# Consider x & y is actually:
# x = ((self.unsigned & 0xFFF0) >> 2) + (self.unsigned & 0x3)
# y = ((self.unsigned & 0xFFF00000) >> 18) +
# ((self.unsigned & 0x30000) >> 16)
#
# And units of Longitude/Latitude is now 25-bit, not 27, so consider
# the new max and mins of:
# (0x0f00000, 0x177fffe), (0x167fffe, 0x177fffd)
# (0x0f00000, 0x1000000), (0x167fffe, 0x1000000)
# Or rounding off the 11-bit data:
# (0x0f00000, 0x1780000), (0x1680000, 0x1780000)
# (0x0f00000, 0x1000000), (0x1680000, 0x1000000)
# Or a span of 0x780000 (7,864,320) ticks
# Longitude be 0x10000 (65,536) ticks per degree
# Latitude be 0x18000 (98,304) ticks per degree
MIN = -1<<31
MAX = (1<<31) - 1
@property
def x(self):
"""Get the Width/Longitude/Horizontal component of the Grid Data"""
half = self.unsigned & 0xFFFF
return (half >> 2) + (half & 0x3)
@x.setter
def x(self, other):
u = self.unsigned
u &= 0xFFFF0000
u |= (other << 2) & 0xFFF0
u |= other & 0x3
self.unsigned = u
return self
@property
def y(self):
"""Get the Length/Latitude/Vertical component of the Grid Data"""
half = (self.unsigned >> 16) & 0xFFFF
return (half >> 2) + (half & 0x3)
@y.setter
def y(self, other):
u = self.unsigned
u &= 0x0000FFFF
u |= (other << 18) & 0xFFF00000
u |= (other << 16) & 0x30000
self.unsigned = u
return self
@property
def unsigned(self):
other = self.value & 0x7fffffff
if self.value < 0:
other += 0x80000000
return other
#return unpack('L', pack('l', self.value))[0]
@unsigned.setter
def unsigned(self, other):
if other & 0x80000000:
self.value = -(-other & 0x7FFFFFFF)
else:
self.value = other & 0x7FFFFFFF
#self.value = unpack('l', pack('L', 0xFFFFFFFF00000000 + other))[0]
return self
class AddressGridOffset(AddressIntRange):
# Max observed is 2046, and min 0, so these are likely correct
# 11-bit unsigned number.
MIN = 0
MAX = 2047
class AddressZero(AddressIntRange):
MIN = 0
MAX = 0
class AddressNegativeOne(AddressIntRange):
MIN = -1
MAX = -1
class AddressNegativeOneBase16(AddressIntRange):
MIN = 65535
MAX = 65535
class AddressNegativeOneBase32(AddressIntRange):
MIN = 4294967295L
MAX = 4294967295L
class AddressBoolean(AddressItemValue):
def set_default(self):
super(AddressBoolean, self).set_default()
self.value = False
return self
def set_value(self, value):
if type(value) == bool:
return super(AddressBoolean, self).set_value(value)
def parse(self, value):
super(AddressBoolean, self).parse(value)
if value == u'0':
self.value = False
elif value == u'1':
self.value = True
else:
raise ValueError(self.__class__.__name__ +
u" out of range: " + repr(value))
def unparse(self):
if self.value == True:
return '1'
elif self.value == False:
return '0'
else:
print u"Warning: Invalid " + self.__class__.__name__ + \
u": " + unicode(value)
return super(AddressBoolean, self).unparse()
class AddressEmpty(AddressItem):
def set_default(self):
self.value = None
return super(AddressEmpty, self).set_default()
# No set_value because the only valid value is None
def copy(self, other):
self.value = other.value
return super(AddressEmpty, self).copy(other)
def parse(self, value):
super(AddressEmpty, self).parse(value)
if value == u'':
self.value = None
else:
raise ValueError(self.__class__.__name__ +
u" out of range: " + repr(value))
def unparse(self):
if self.value == None:
return ''
else:
print u"Warning: Invalid " + self.__class__.__name__ + \
u": " + unicode(value)
return unicode(self.value)
def __str__(self):
return unicode(self).encode('utf-8', 'replace')
def __unicode__(self):
return unicode(self.value)
def __cmp__(self, other):
if isinstance(other, AddressEmpty):
r = cmp(self.value, other.value)
if r:
return r
elif other == None:
r = cmp(self.value, other)
if r:
return r
return super(AddressEmpty, self).__cmp__(other)
def do_compare(self, other):
return other != None
class Address(object):
field_names = [ u'latitude', u'longitude', u'group', u'phone',
u'name', u'field_6', u'street', u'number',
u'map_data_grid', u'map_data_x', u'map_data_y',
u'charger_type', u'field_13', u'field_14',
u'field_15', u'field_16', u'public', u'field_18',
u'field_19', u'show_icon', u'icon_id', u'play_tone',
u'tone_id', u'distance', u'direction', u'field_26',
u'field_27', u'field_28' ]
sort_order = field_names # Change to set the sort order
unparsed_string = re.compile(ur'^(?:[^,]*,){' +
unicode(len(field_names) - 1) + ur'}[^,]*$')
def __init__(self, *rest, **named):
if len(rest) == 1 and isinstance(rest[0], Address):
self.copy(rest[0])
else:
# Initialize defaults
for k in self.field_names:
setattr(self, k, None)
if len(rest) == 1 and type(rest[0]) in [ unicode, str ] and \
self.unparsed_string.match(rest[0]):
self.parse(unicode(rest[0]))
elif len(rest) == 1 and type(rest[0]) in (list, tuple):
self.parse_list(rest[0])
elif rest:
# Fields listed as parameter list
self.parse_list(rest)
# Keyword overrides
for k, v in named.iteritems():
if k in self.field_names + [u'icon', u'tone']:
setattr(self, k, v)
elif k == u'position':
self.set_position(v[0], v[1])
else:
raise NameError(self.__class__.__name__ +
u" Invalid attribute: " + repr(k))
def parse(self, entries):
return self.parse_list(entries.split(u','))
def parse_list(self, entries):
if len(entries) < len(self.field_names):
print u"Warning: Truncated Parameter List (Expected " + \
unicode(len(self.field_names)) + u"): " + repr(entries)
print u" Values which remain default: " + \
repr(self.field_names[len(entries):])
elif len(entries) > len(self.field_names):
print u"Warning: Too many parameters (Expected " + \
unicode(len(self.field_names)) + u"): " + repr(entries)
print u" The following values are ignored: " + \
repr(entries[len(self.field_names):])
for k, v in zip(self.field_names, entries):
setattr(self, k, unicode(v))
return self
def unparse(self):
return ','.join(map(lambda x: getattr(self, x).unparse(),
self.field_names))
def copy(self, other):
for k in self.field_names:
setattr(self, k, getattr(other, k))
return self
def __str__(self):
return unicode(self).encode('utf-8', 'replace')
def __unicode__(self):
u = u''
for i in self.field_names:
u += i + u'\t' + unicode(getattr(self, i)) + u'\n'
u += self.get_url() + u'\n'
return u
def __cmp__(self, other):
if isinstance(other, Address):
for k in self.sort_order:
r = cmp(getattr(self, k), getattr(other, k))
if r:
return r
return 0
return 1
def set_position(self, longitude, latitude):
"""Set the Longitude, Latitude and Map Data"""
# TODO: 90 degrees becomes negative
self.grid = (longitude, latitude)
self.degrees = (longitude, latitude)
return self
def get_url(self):
"""Return a URL that will link to a map (in Google) with the
given location pinned."""
template = 'https://www.google.com/maps/search/?api=1&query='
template += '{:.7f},{:.7f}'
return template.format(*self.grid[::-1])
x_slope = 0x10000
y_slope = 0x18000
x_basis = 0x0f00000
y_basis = 0x1000000
@property
def x(self):
"""The Map Grid X Component"""
return (self.map_data_grid.x << 11) + int(self.map_data_x)
@x.setter
def x(self, other):
hi, low = divmod(other, 2048)
hi &= 0x3FFF # Strip off high bits
self.map_data_grid.x = hi
self.map_data_x = low
return self
@property
def y(self):
"""The Map Grid Y Component"""
return (self.map_data_grid.y << 11) + int(self.map_data_y)
@y.setter
def y(self, other):
hi, low = divmod(other, 2048)
hi &= 0x3FFF # Strip off high bits
self.map_data_grid.y = hi
self.map_data_y = low
return self
@property
def grid(self):
"""The position of this location in terms of floats representing longitude and latitude based on the Grid Data"""
return ((float(self.x) - self.x_basis)/self.x_slope - 172,
(float(self.y) - self.y_basis)/self.y_slope)
@grid.setter
def grid(self, other):
o, a = other
x = int((float(o) + 172)*self.x_slope + self.x_basis)
y = int(float(a)*self.y_slope + self.y_basis)
self.x = x
self.y = y
return self
@property
def degrees(self):
"""The Longitude and Latitude of this Address as a tuple"""
return (self.longitude, self.latitude)
@degrees.setter
def degrees(self, other):
self.longitude, self.latitude = other
return self
@property
def grid_degrees(self):
"""Return the position of this location in terms of longitude and latitude based on the Grid Data"""
o, a = self.grid
return (AddressLongitude(o), AddressLatitude(a))
@grid_degrees.setter
def grid_degrees(self, other):
self.grid = other
return self
# Required
@property
def latitude(self):
"""Latitude of the Address"""
return self._latitude
@latitude.setter
def latitude(self, value):
self._latitude = AddressLatitude(value)
# Required
@property
def longitude(self):
"""Longitude of the Address"""
return self._longitude
@longitude.setter
def longitude(self, value):
self._longitude = AddressLongitude(value)
# 0 to 8
@property
def group(self):
"""Group of the Address"""
return self._group
@group.setter
def group(self, value):
self._group = AddressGroup(value)
# Ascii (can be blank)
@property
def phone(self):
"""Phone Number for the Address"""
return self._phone
@phone.setter
def phone(self, value):
self._phone = AddressString(value)
# Ascii (can be blank)
@property
def name(self):
"""Name for the Address"""
return self._name
@name.setter
def name(self, value):
self._name = AddressString(value)
# Empty String
@property
def field_6(self):
"""Unidentified Field #6 for the Address"""
return self._field_6
@field_6.setter
def field_6(self, value):
self._field_6 = AddressString(value)
# Ascii (can be blank)
@property
def street(self):
"""Street Name of the Address"""
return self._street
@street.setter
def street(self, value):
self._street = AddressString(value)
# Ascii (can be blank)
@property
def number(self):
"""House Number of the Address"""
return self._number
@number.setter
def number(self, value):
self._number = AddressString(value)
# Signed 32-bit (?)
@property
def map_data_grid(self):
"""32-Bit Grid Location for the Address"""
return self._map_data_grid
@map_data_grid.setter
def map_data_grid(self, value):
self._map_data_grid = AddressGridIndex(value)
# 0–2047 (?)
@property
def map_data_x(self):
"""11-Bit Grid X-Offset for the Address"""
return self._map_data_x
@map_data_x.setter
def map_data_x(self, value):
self._map_data_x = AddressGridOffset(value)
# 0–2047 (?)
@property
def map_data_y(self):
"""11-Bit Grid Y-Offset for the Address"""
return self._map_data_y
@map_data_y.setter
def map_data_y(self, value):
self._map_data_y = AddressGridOffset(value)
# None (0), 1 or 2
@property
def charger_type(self):
"""Type of EVSE, if any, at the Address"""
return self._charger_type
@charger_type.setter
def charger_type(self, value):
self._charger_type = AddressChargerType(value)
# None
@property
def field_13(self):
"""Unidentified Field #13 for the Address"""
return self._field_13
@field_13.setter
def field_13(self, value):
self._field_13 = AddressEmpty(value)
# None
@property
def field_14(self):
"""Unidentified Field #14 for the Address"""
return self._field_14
@field_14.setter
def field_14(self, value):
self._field_14 = AddressEmpty(value)
# None
@property
def field_15(self):
"""Unidentified Field #15 for the Address"""
return self._field_15
@field_15.setter
def field_15(self, value):
self._field_15 = AddressEmpty(value)
# 0
@property
def field_16(self):
"""Unidentified Field #16 for the Address"""
return self._field_16
@field_16.setter
def field_16(self, value):
self._field_16 = AddressZero(value)
# True or False (1 or 0), both
@property
def public(self):
"""True IFF this address is Public"""
return self._public
@public.setter
def public(self, value):
self._public = AddressBoolean(value)
# -1
@property
def field_18(self):
"""Unidentified Field #18 for the Address"""
return self._field_18
@field_18.setter
def field_18(self, value):
self._field_18 = AddressNegativeOne(value)
# 0
@property
def field_19(self):
"""Unidentified Field #19 for the Address"""
return self._field_19
@field_19.setter
def field_19(self, value):
self._field_19 = AddressZero(value)
# 0 or 1 as Bool
@property
def show_icon(self):
"""True IFF an Icon should be displayed for this Address"""
return self._show_icon
@show_icon.setter
def show_icon(self, value):
self._show_icon = AddressBoolean(value)
# Max seen is 44
@property
def icon_id(self):
"""The ID of the Icon for the Address"""
return self._icon_id
@icon_id.setter
def icon_id(self, value):
self._icon_id = AddressIcon(value)
# Icon set with automatic show if not None
@property
def icon(self):
"""The ID of the Icon for the Address or None for no Icon."""
if self.show_icon:
return self.icon_id
else:
return None
@icon.setter
def icon(self, value):
"""IFF null turns off icons for this address otherwise sets the
icon to value."""
if value == None:
self.icon_id = None
self.show_icon = False
else:
self.icon_id = value
self.show_icon = True
# 0 or 1 as Bool
@property
def play_tone(self):
"""True IFF a Tone should be Played when near this Address"""
return self._play_tone
@play_tone.setter
def play_tone(self, value):
self._play_tone = AddressBoolean(value)
# -1 or 780-792
@property
def tone_id(self):
"""The ID of the Tone for the Address"""
return self._tone_id
@tone_id.setter
def tone_id(self, value):
self._tone_id = AddressTone(value)
# Tone set with automatic play set if not None
@property
def tone(self):
"""The ID of the Tone for the Address or None for no Tone."""
if self.play_tone:
return self.tone_id
else:
return None
@tone.setter
def tone(self, value):
"""IFF null turns off tones for this address otherwise sets the
tone to value."""
if value == None:
self.tone_id = None
self.play_tone = False
else:
self.tone_id = value
self.play_tone = True
# 450, 900, 3000, 3001, 4500 is 50, 100, None, 300, 500
# respectively; note, 3001 means manually set 300m, 3000 is the
# default and is evaluated as None
@property
def distance(self):
"""The Distance from the Address at which the Tone will play"""
return self._distance
@distance.setter
def distance(self, value):
self._distance = AddressDistance(value)
# Degrees times 10 in 2 degree increments via UI
@property
def direction(self):
"""The Compass Direction for the Address"""
return self._direction
@direction.setter
def direction(self, value):
self._direction = AddressDirection(value)
# -1[32]
@property
def field_26(self):
"""Unidentified Field #26 for the Address"""
return self._field_26
@field_26.setter
def field_26(self, value):
self._field_26 = AddressNegativeOneBase32(value)
# -1[16]
@property
def field_27(self):
"""Unidentified Field #27 for the Address"""
return self._field_27
@field_27.setter
def field_27(self, value):
self._field_27 = AddressNegativeOneBase16(value)
# -1[16]
@property
def field_28(self):
"""Unidentified Field #28 for the Address"""
return self._field_28
@field_28.setter
def field_28(self, value):
self._field_28 = AddressNegativeOneBase16(value)
class AddressOne(AddressIntRange):
MIN = 1
MAX = 1
class AddressHeaderIs2(AddressIntRange):
MIN = 2
MAX = 2
class AddressHeaderIs16(AddressIntRange):
MIN = 16
MAX = 16
class AddressHeaderIs31(AddressIntRange):
MIN = 31
MAX = 31
class AddressHeaderIs0x101(AddressIntRange):
MIN = 257
MAX = 257
class AddressHeaderIs1024(AddressIntRange):
MIN = 1024
MAX = 1024
class AddressBookMaximumEntries(AddressEnumerated):
ADDRESS_BOOK = 200
HOME = 1
names = {
ADDRESS_BOOK: u'Address Book (200)',
HOME: u'Home (1)'
}
class AddressBookType(AddressEnumerated):
ADDRESS_BOOK = 16
HOME = 17
names = {
ADDRESS_BOOK: u'Address Book',
HOME: u'Home'
}
class AddressBook(object):
hdr_pack = '<' + 'HBBLHHHHHBB' + 'L' * 6
hdr_null = '\00' * 212
field_names = [ u'field_1h', u'field_1la', u'field_1lb', u'field_2',
u'field_3h', u'field_3l', u'region', u'field_4l',
u'field_5h', u'book_type', u'field_5lb', u'field_6',
u'field_7', u'field_8', u'field_9', u'entries',
u'max_entries'
]
# Bind count to a temporary when parsing or copying
parse_names = map(lambda x: u'_tmp_cnt' if x == u'entries' else x,
field_names)
sort_order = filter(lambda x: x != u'entries', field_names)
def __init__(self, *unnamed, **named):
entries = None
if len(unnamed) == 1 and isinstance(unnamed[0], AddressBook):
self.copy(unnamed[0])
else:
# Initialize defaults
for k in self.parse_names:
setattr(self, k, None)
del self._tmp_cnt
# Set default to book
self.make_book()
self._addresses = []
if len(unnamed) == 1 and type(unnamed[0]) in [str, unicode]:
self.parse(unnamed[0])
else:
if len(unnamed) == 1 and \
type(unnamed[0]) in (list, tuple):
unnamed = unnamed[0]
# Fields
fields = unnamed[:len(self.field_names)]
for k, v in zip(self.parse_names, fields):
setattr(self, k, v)
if hasattr(self, u'_tmp_cnt'):
entries = self._tmp_cnt
del self._tmp_cnt
# Addresses
addresses = unnamed[len(self.field_names):]
if len(addresses) == 1 and \
type(addresses) in (list, tuple):
addresses = addresses[0]
self._addresses = map(Address, addresses)
# Keyword Overrides
for k, v in named.iteritems():
if k == u'addresses':
if type(v) in (list, tuple):
self._addresses.extend(map(Address, v))
else:
self._addresses.append(Address(v))
elif k in self.sort_order or k == u'home':
setattr(self, k, v)
else:
raise KeyError(u"Error: Invalid Keyword Argument: " +
repr(k) + u" with value " + repr(v))
# Finally, check if count was provided and incorrect
if entries != None and len(self) != entries:
raise ValueError(u"Error: Count field doesn't match " +
u"number of addresses: " + unicode(len(self)) +
u" addresses; " + unicode(entries) +
u" indicated.")
def __enter__(self):
return self
def __exit__(self, thetype, value, traceback):
pass
def parse(self, book):
if len(book) < 256:
raise ValueError(u"Address Book missing header.")
header, fields = book[:256], book[256:]
if header[44:] != self.hdr_null:
header_bytes = map(ord, header)
raise ValueError(u"Non-Zero High Address Book" +
repr(header_bytes[44:]))
# Don't bind count, that just needs varification
for k, v in zip(self.parse_names,
unpack(self.hdr_pack, header[:44])):
setattr(self, k, v)
entries = self._tmp_cnt
del self._tmp_cnt
# Read Addresses
fields = fields.split(u"\r\n")
if fields[-1] == u'':
# Drop blank line
fields = fields[:-1]
else:
print u"Warning: Address Book didn't terminate with CR LF"
self._addresses = map(Address, fields)
# Verify Count
if entries != len(self):
raise ValueError(u"Error: Count field doesn't match " +
u"number of addresses: " + unicode(len(self)) +
u" addresses; " + unicode(entries) +
u" indicated.")
return self
def unparse(self):
s = pack(self.hdr_pack, *map(lambda k: int(getattr(self, k)),
self.field_names))
s += self.hdr_null
s += '\r\n'.join(map(lambda x: x.unparse(), self._addresses))
s += '\r\n' # Blank line
return s
def copy(self, other):
for k in self.field_names:
if k != u'entries':
setattr(self, k, getattr(other, k))
self._addresses = map(Address, other._addresses)
return self
def make_home(self):
self.book_type = AddressBookType.HOME
self.max_entries = AddressBookMaximumEntries.HOME
return self
def make_book(self):
self.book_type = AddressBookType.ADDRESS_BOOK
self.max_entries = AddressBookMaximumEntries.ADDRESS_BOOK
return self
def compare_mapping(self):
s = u''
for i in self:
x, y = i.grid
s += unicode(i.name) + u'\t'
s += unicode(i.longitude) + u'\t'
s += unicode(AddressLongitude(x)) + u'\t'
s += unicode((float(i.longitude) - float(x))*3600) + u'\t'
s += unicode(i.latitude) + u'\t'
s += unicode(AddressLatitude(y)) + u'\t'
s += unicode((float(i.latitude) - float(y))*3600) + u'\t'
s += hex(unpack('I', pack('i', int(i.map_data_grid)))[0])
s += u'\t'
s += hex(int(i.map_data_x)) + u'\t'
s += hex(int(i.map_data_y)) + u'\t'
s += unicode(cos(float(i.latitude)/180*pi)) + u'\n'
return s
def compare2(self):
s = u''
for i in self:
x, y = i.grid
s += unicode(i.longitude) + u'\t'
s += unicode(x) + u'\t'
s += unicode(i.latitude) + u'\t'
s += unicode(y) + u'\n'
return s
@property
def home(self):
"""True IFF this Address Book stores just the Home Address"""
return self.book_type == AddressBookType.HOME and \
self.max_entries == AddressBookMaximumEntries.HOME
@home.setter
def home(self, value):
if value:
return self.make_home()
else:
return self.make_book()
def compare_headers(self, other):
if isinstance(other, AddressBook):
for k in self.sort_order:
r = cmp(getattr(self, k), getattr(other, k))
if r:
return r
return 0
return 1
def is_same_booktype(self, other):
return self.compare_headers(other) == 0
def __str__(self):
return unicode(self).encode('utf-8', 'replace')
def __unicode__(self):
u = u''
for i in self.field_names:
u += i + u'\t' + unicode(getattr(self, i)) + u'\n'
u += u'\n'.join(map(lambda x: u'\t' +
unicode(x).replace(u'\n', u'\n\t'),
list(self)))
return u
def __cmp__(self, other):
if isinstance(other, AddressBook):
r = self.compare_headers(other)
if r:
return r
else:
return cmp(self._addresses, other._addresses)
else:
return 1
def __list__(self):
return self._addresses
def __len__(self):
return len(self._addresses)
def __getitem__(self, index):
return self._addresses.__getitem__(index)
def __setitem__(self, index, value):
if type(value) in (tuple, list):
return self._addresses.__setitem__(index,
map(Address, value))
else:
return self._addresses.__setitem__(index, Address(value))
def __delitem__(self, index):
return self._addresses.__delitem__(index)
def __iter__(self):
return self._addresses.__iter__()
def __reversed__(self):
return self._addresses.__reversed__()
def __contains__(self, other):
return self._addresses.__contains__(other)
def append(self, addr):
return self._addresses.append(Address(addr))
def extend(self, addrs):
return self._addresses.extend(map(Address, addrs))
def insert(self, index, value):
if type(value) in (tuple, list):
return self._addresses.insert(index, map(Address, value))
else:
return self._addresses.insert(index, Address(value))
def remove(self, addr):
return self._addresses.remove(Address(addr))
def pop(self, index = None):
return self._addresses.pop(index)
def index(self, addr):
return self._addresses.index(Address(addr))
def count(self, addr):
return self._addresses.count(Address(addr))
def sort(self):
return self._addresses.sort()
def reverse(self):
return self._addresses.reverse()
def __add__(self, other):
if (isinstance(other, AddressBook) and
self.is_same_booktype(other)) or \
type(other) in (list, tuple):
dup = AddressBook(self)
dup += other
return dup
else:
raise TypeError(u"Error: Can't concatinate incompatible " +
u"Address Books.")
__radd__ = __add__
def __iadd__(self, other):
if (isinstance(other, AddressBook) and
self.is_same_booktype(other)) or \
type(other) in (list, tuple):
# Copy Addresses, not references
self.extend(map(Address, list(other)))
return self
else:
raise TypeError(u"Error: Can't concatinate incompatible " +
u"Address Books.")
def __mul__(self, entries):
dup = AddressBook(self)
dup *= entries
return dup
__rmul__ = __mul__
def __imul__(self, entries):
# Implement like Add to force Address Copy
if type(entries) in (int, long):
if entries < 1:
del self._addresses
else:
dup = AddressBook(self)
for i in xrange(entries - 1):
self += dup
return self
else:
raise TypeError(u"can't multiply sequence by non-int of " +
u"type " + repr(type(entries)))
@property
def field_1h(self):
"""Unidentified High Word for Field #1 for the Address Book"""
return self._field_1h
@field_1h.setter
def field_1h(self, value):
self._field_1h = AddressHeaderIs2(value)
@property
def field_1la(self):
"""Unidentified Byte A of Low Word for Field #1 for the Address Book"""
return self._field_1la
@field_1la.setter
def field_1la(self, value):
self._field_1la = AddressOne(value)
@property
def field_1lb(self):
"""Unidentified Byte B of Low Word for Field #1 for the Address Book"""
return self._field_1lb
@field_1lb.setter
def field_1lb(self, value):
self._field_1lb = AddressOne(value)
@property
def field_2(self):
"""Unidentified Field #2 for the Address Book"""
return self._field_2
@field_2.setter
def field_2(self, value):
self._field_2 = AddressZero(value)
@property
def field_3h(self):
"""Unidentified High Word for Field #3 for the Address Book"""
return self._field_3h
@field_3h.setter
def field_3h(self, value):
self._field_3h = AddressHeaderIs16(value)
@property
def field_3l(self):
"""Unidentified Low Word for Field #3 for the Address Book"""
return self._field_3l
@field_3l.setter
def field_3l(self, value):
self._field_3l = AddressOne(value)
@property
def region(self):
"""Map Global Region for the Address Book"""
return self._region
@region.setter
def region(self, value):
self._region = AddressRegion(value)
@property
def field_4l(self):
"""Unidentified Low Word for Field #4 for the Address Book"""
return self._field_4l
@field_4l.setter
def field_4l(self, value):
self._field_4l = AddressHeaderIs16(value)
@property
def field_5h(self):
"""Unidentified High Word for Field #5 for the Address Book"""
return self._field_5h
@field_5h.setter
def field_5h(self, value):
self._field_5h = AddressZero(value)
@property
def book_type(self):
"""The type of this Address Book"""
return self._book_type
@book_type.setter
def book_type(self, value):
self._book_type = AddressBookType(value)
@property
def field_5lb(self):
"""Unidentified Byte B of Low Word for Field #5 for the Address Book"""
return self._field_5lb
@field_5lb.setter
def field_5lb(self, value):
self._field_5lb = AddressHeaderIs16(value)
@property
def field_6(self):
"""Unidentified Field #6 for the Address Book"""
return self._field_6
@field_6.setter
def field_6(self, value):
self._field_6 = AddressZero(value)
@property
def field_7(self):
"""Unidentified Field #7 for the Address Book"""
return self._field_7
@field_7.setter
def field_7(self, value):
self._field_7 = AddressZero(value)
@property
def field_8(self):
"""Unidentified Field #8 for the Address Book"""
return self._field_8
@field_8.setter
def field_8(self, value):
self._field_8 = AddressHeaderIs31(value)
@property
def field_9(self):
"""Unidentified Field #9 for the Address Book"""
return self._field_9
@field_9.setter
def field_9(self, value):
self._field_9 = AddressHeaderIs1024(value)
# Set in the old way because this one is read-only
entries = property(__len__, None, None,
u"Number of Addresses in the Address Book")
@property
def max_entries(self):
"""Maximum Number of Entries for the Address Book"""
return self._max_entries
@max_entries.setter
def max_entries(self, value):
self._max_entries = AddressBookMaximumEntries(value)
def kml(self, start=None, stop=None, step=1):
"""kml([[[start,] stop], step]) -> A kml file containing the selected
points from this address book.
Points are selected via start, stop and step variables. These variables
have the same meaning as in the range builtin. If no parameters are
given, the entire Address Book is iterated over."""
header = u"""
Nissan LEAF Address Book
Entries
"""
footer = u"""
normal
#icon-22-nodesc-normal
highlight
#icon-22-nodesc-highlight
"""
marker = u"""
{1}
#icon-22-nodesc
{0.grid[0]},{0.grid[1]},0.0
"""
output = header
if start == None and stop == None:
stop = len(self)
start = 0
elif start == None:
start = 0
elif stop == None:
stop = start
start = 0
for i in range(start, stop, step):
name = unicode(self._addresses[i].name).replace(u'&',
u'&'). \
replace(u'<', u'<').replace(u'>', u'>')
print name
output += marker.format(self._addresses[i], name)
output += footer
return output.encode('utf-8', 'xmlcharrefreplace')
if __name__ == u'__main__':
if len(argv) > 1:
with file(argv[1]) as f:
with AddressBook(f.read()) as a:
if len(argv) > 2:
for addr in argv[2:]:
try:
i = int(addr)
print(a[i])
except ValueError:
pass
else:
print(a)