pycupra 0.0.11__py3-none-any.whl → 0.0.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycupra/__version__.py +1 -1
- pycupra/const.py +2 -0
- pycupra/dashboard.py +31 -0
- pycupra/vehicle.py +117 -7
- {pycupra-0.0.11.dist-info → pycupra-0.0.13.dist-info}/METADATA +1 -1
- pycupra-0.0.13.dist-info/RECORD +13 -0
- pycupra-0.0.11.dist-info/RECORD +0 -13
- {pycupra-0.0.11.dist-info → pycupra-0.0.13.dist-info}/WHEEL +0 -0
- {pycupra-0.0.11.dist-info → pycupra-0.0.13.dist-info}/licenses/LICENSE +0 -0
- {pycupra-0.0.11.dist-info → pycupra-0.0.13.dist-info}/top_level.txt +0 -0
pycupra/__version__.py
CHANGED
pycupra/const.py
CHANGED
@@ -125,6 +125,8 @@ AUTH_TOKENKEYS = 'https://identity.vwgroup.io/oidc/v1/keys'
|
|
125
125
|
# API endpoints
|
126
126
|
API_MBB_STATUSDATA = 'https://customer-profile.vwgroup.io/v3/customers/{self._user_id}/mbbStatusData'
|
127
127
|
API_PERSONAL_DATA= 'https://customer-profile.vwgroup.io/v3/customers/{self._user_id}/personalData'
|
128
|
+
#Other option for personal data is '{baseurl}/v1/users/{self._user_id}'
|
129
|
+
|
128
130
|
API_VEHICLES = '{APP_URI}/v2/users/{self._user_id}/garage/vehicles' # Garage info
|
129
131
|
API_MYCAR = '{baseurl}/v5/users/{self._user_id}/vehicles/{vin}/mycar' # Vehicle status report
|
130
132
|
API_CHARGING = '{baseurl}/v1/vehicles/{vin}/charging' # Vehicle charging information
|
pycupra/dashboard.py
CHANGED
@@ -933,6 +933,31 @@ class RequestResults(Sensor):
|
|
933
933
|
def attributes(self):
|
934
934
|
return dict(self.vehicle.request_results)
|
935
935
|
|
936
|
+
class ChargingState(BinarySensor):
|
937
|
+
def __init__(self):
|
938
|
+
super().__init__(attr="charging_state", name="Charging state", icon="mdi:battery-charging", device_class='power')
|
939
|
+
|
940
|
+
@property
|
941
|
+
def state(self):
|
942
|
+
return self.vehicle.charging_state
|
943
|
+
|
944
|
+
@property
|
945
|
+
def assumed_state(self):
|
946
|
+
return False
|
947
|
+
|
948
|
+
@property
|
949
|
+
def attributes(self):
|
950
|
+
attr = {}
|
951
|
+
state = self.vehicle.attrs.get('charging', {}).get('status', {}).get('state', '')
|
952
|
+
type = self.vehicle.attrs.get('charging', {}).get('status', {}).get('charging', {}).get('type', '')
|
953
|
+
mode = self.vehicle.attrs.get('charging', {}).get('status', {}).get('charging', {}).get('mode', '')
|
954
|
+
if state in {'charging', 'conservation'}:
|
955
|
+
attr['state']=state
|
956
|
+
if type != '':
|
957
|
+
attr['type']=type
|
958
|
+
if mode != '':
|
959
|
+
attr['mode']=mode
|
960
|
+
return attr
|
936
961
|
|
937
962
|
def create_instruments():
|
938
963
|
return [
|
@@ -960,6 +985,7 @@ def create_instruments():
|
|
960
985
|
DepartureProfile1(),
|
961
986
|
DepartureProfile2(),
|
962
987
|
DepartureProfile3(),
|
988
|
+
ChargingState(),
|
963
989
|
Sensor(
|
964
990
|
attr="distance",
|
965
991
|
name="Odometer",
|
@@ -1284,6 +1310,11 @@ def create_instruments():
|
|
1284
1310
|
name="Energy flow",
|
1285
1311
|
device_class="power"
|
1286
1312
|
),
|
1313
|
+
#BinarySensor(
|
1314
|
+
# attr="charging_state",
|
1315
|
+
# name="Charging state",
|
1316
|
+
# device_class="power"
|
1317
|
+
#),
|
1287
1318
|
BinarySensor(
|
1288
1319
|
attr="parking_light",
|
1289
1320
|
name="Parking light",
|
pycupra/vehicle.py
CHANGED
@@ -471,7 +471,7 @@ class Vehicle:
|
|
471
471
|
raise SeatInvalidRequestException('Departure timers are not supported.')
|
472
472
|
|
473
473
|
async def set_timer_schedule(self, id, schedule={}):
|
474
|
-
""" Set departure
|
474
|
+
""" Set departure timer schedule. """
|
475
475
|
data = {}
|
476
476
|
# Validate required user inputs
|
477
477
|
supported = "is_departure" + str(id) + "_supported"
|
@@ -518,7 +518,7 @@ class Vehicle:
|
|
518
518
|
if not 16 <= int(schedule.get("targetTemp", None)) <= 30:
|
519
519
|
raise SeatInvalidRequestException('Target temp must be integer value from 16 to 30')
|
520
520
|
else:
|
521
|
-
data['temp'] = schedule.get('targetTemp')
|
521
|
+
data['temp'] = int(schedule.get('targetTemp'))
|
522
522
|
raise SeatInvalidRequestException('Target temp (yet) not supported.')
|
523
523
|
|
524
524
|
# Validate charge target and current
|
@@ -666,6 +666,98 @@ class Vehicle:
|
|
666
666
|
self._requests['departuretimer'] = {'status': 'Exception'}
|
667
667
|
raise SeatException('Failed to set departure timer schedule')
|
668
668
|
|
669
|
+
async def set_departure_profile_schedule(self, id, schedule={}):
|
670
|
+
""" Set departure profile schedule. """
|
671
|
+
data = {}
|
672
|
+
# Validate required user inputs
|
673
|
+
supported = "is_departure_profile" + str(id) + "_supported"
|
674
|
+
if getattr(self, supported) is not True:
|
675
|
+
raise SeatConfigException(f'Departure profile id {id} is not supported for this vehicle.')
|
676
|
+
else:
|
677
|
+
_LOGGER.debug(f'Departure profile id {id} is supported')
|
678
|
+
if not schedule:
|
679
|
+
raise SeatInvalidRequestException('A schedule must be set.')
|
680
|
+
if not isinstance(schedule.get('enabled', ''), bool):
|
681
|
+
raise SeatInvalidRequestException('The enabled variable must be set to True or False.')
|
682
|
+
if not isinstance(schedule.get('recurring', ''), bool):
|
683
|
+
raise SeatInvalidRequestException('The recurring variable must be set to True or False.')
|
684
|
+
if not re.match('^[0-9]{2}:[0-9]{2}$', schedule.get('time', '')):
|
685
|
+
raise SeatInvalidRequestException('The time for departure must be set in 24h format HH:MM.')
|
686
|
+
|
687
|
+
# Validate optional inputs
|
688
|
+
if schedule.get('recurring', False):
|
689
|
+
if not re.match('^[yn]{7}$', schedule.get('days', '')):
|
690
|
+
raise SeatInvalidRequestException('For recurring schedules the days variable must be set to y/n mask (mon-sun with only wed enabled): nnynnnn.')
|
691
|
+
elif not schedule.get('recurring'):
|
692
|
+
if not re.match('^[0-9]{4}-[0-9]{2}-[0-9]{2}$', schedule.get('date', '')):
|
693
|
+
raise SeatInvalidRequestException('For single departure profile schedule the date variable must be set to YYYY-mm-dd.')
|
694
|
+
|
695
|
+
if self._relevantCapabilties.get('departureProfiles', {}).get('active', False):
|
696
|
+
# Check if profileIds is set and correct
|
697
|
+
if schedule.get('chargingProgramId', False):
|
698
|
+
# At the moment, only one charging program id is supported
|
699
|
+
chargingProgramId = int(schedule.get('chargingProgramId', False))
|
700
|
+
found = False
|
701
|
+
for chargingProgram in self.attrs.get('departureProfiles', {}).get('profileIds', []):
|
702
|
+
if chargingProgram.get('id',None) == chargingProgramId:
|
703
|
+
found = True
|
704
|
+
break
|
705
|
+
if not found:
|
706
|
+
raise SeatInvalidRequestException('The charging program id provided for the departure profile schedule is unknown.')
|
707
|
+
else:
|
708
|
+
profileIds = []
|
709
|
+
profileIds.append(chargingProgramId)
|
710
|
+
else:
|
711
|
+
raise SeatInvalidRequestException('No charging program id provided for departure profile schedule.')
|
712
|
+
|
713
|
+
newDepProfileSchedule = {}
|
714
|
+
# Prepare data and execute
|
715
|
+
newDepProfileSchedule['id'] = id
|
716
|
+
# Converting schedule to data map
|
717
|
+
if schedule.get("enabled",False):
|
718
|
+
newDepProfileSchedule['enabled']=True
|
719
|
+
else:
|
720
|
+
newDepProfileSchedule['enabled']=False
|
721
|
+
if schedule.get("recurring",False):
|
722
|
+
newDepProfileSchedule['recurringTimer']= {
|
723
|
+
"startTime": schedule.get('time',"00:00"),
|
724
|
+
"recurringOn":{""
|
725
|
+
"mondays":(schedule.get('days',"nnnnnnn")[0]=='y'),
|
726
|
+
"tuesdays":(schedule.get('days',"nnnnnnn")[1]=='y'),
|
727
|
+
"wednesdays":(schedule.get('days',"nnnnnnn")[2]=='y'),
|
728
|
+
"thursdays":(schedule.get('days',"nnnnnnn")[3]=='y'),
|
729
|
+
"fridays":(schedule.get('days',"nnnnnnn")[4]=='y'),
|
730
|
+
"saturdays":(schedule.get('days',"nnnnnnn")[5]=='y'),
|
731
|
+
"sundays":(schedule.get('days',"nnnnnnn")[6]=='y'),
|
732
|
+
}
|
733
|
+
}
|
734
|
+
else:
|
735
|
+
if self._relevantCapabilties.get('departureProfiles', {}).get('supportsSingleTimer', False):
|
736
|
+
startDateTime = datetime.fromisoformat(schedule.get('date',"2025-01-01")+'T'+schedule.get('time',"00:00"))
|
737
|
+
_LOGGER.info(f'startDateTime={startDateTime.isoformat()}')
|
738
|
+
newDepProfileSchedule['singleTimer']= {
|
739
|
+
"startDateTimeLocal": startDateTime.isoformat(),
|
740
|
+
}
|
741
|
+
else:
|
742
|
+
raise SeatInvalidRequestException('Vehicle does not support single timer.')
|
743
|
+
newDepProfileSchedule["profileIds"]= profileIds
|
744
|
+
|
745
|
+
# Now we have to substitute the current departure profile schedule with the given id by the new one
|
746
|
+
data= deepcopy(self.attrs.get('departureProfiles'))
|
747
|
+
if len(data.get('timers', []))<1:
|
748
|
+
raise SeatInvalidRequestException(f'No timers found in departure profile: {data}.')
|
749
|
+
idFound=False
|
750
|
+
for e in range(len(data.get('timers', []))):
|
751
|
+
if data['timers'][e].get('id',-1)==id:
|
752
|
+
data['timers'][e] = newDepProfileSchedule
|
753
|
+
idFound=True
|
754
|
+
if idFound:
|
755
|
+
return await self._set_departure_profiles(data, action='set')
|
756
|
+
raise SeatInvalidRequestException(f'Departure profile id {id} not found in {data.get('timers',[])}.')
|
757
|
+
else:
|
758
|
+
_LOGGER.info('Departure profiles are not supported.')
|
759
|
+
raise SeatInvalidRequestException('Departure profiles are not supported.')
|
760
|
+
|
669
761
|
async def set_departure_profile_active(self, id=1, action='off'):
|
670
762
|
""" Activate/deactivate departure profiles. """
|
671
763
|
data = {}
|
@@ -1552,18 +1644,36 @@ class Vehicle:
|
|
1552
1644
|
return True
|
1553
1645
|
|
1554
1646
|
@property
|
1555
|
-
def
|
1556
|
-
"""Return true if
|
1557
|
-
check = self.attrs.get('
|
1558
|
-
if check == '
|
1647
|
+
def charging_state(self):
|
1648
|
+
"""Return true if vehicle is charging."""
|
1649
|
+
check = self.attrs.get('charging', {}).get('status', {}).get('state', '')
|
1650
|
+
if check == 'charging':
|
1559
1651
|
return True
|
1560
1652
|
else:
|
1561
1653
|
return False
|
1562
1654
|
|
1655
|
+
@property
|
1656
|
+
def is_charging_state_supported(self):
|
1657
|
+
"""Charging state supported."""
|
1658
|
+
if self.attrs.get('charging', {}).get('status', {}).get('state', False):
|
1659
|
+
return True
|
1660
|
+
|
1661
|
+
@property
|
1662
|
+
def energy_flow(self):
|
1663
|
+
"""Return true if energy is flowing to (i.e. charging) or from (i.e. climating with battery power) the battery."""
|
1664
|
+
if self.charging_state:
|
1665
|
+
return True
|
1666
|
+
check = self.attrs.get('charging', {}).get('status', {}).get('state', '')
|
1667
|
+
if self.is_electric_climatisation_supported:
|
1668
|
+
if self.electric_climatisation and check not in {'charging', 'conservation'}:
|
1669
|
+
# electric climatisation is on and car is not charging or conserving power
|
1670
|
+
return True
|
1671
|
+
return False
|
1672
|
+
|
1563
1673
|
@property
|
1564
1674
|
def is_energy_flow_supported(self):
|
1565
1675
|
"""Energy flow supported."""
|
1566
|
-
if self.
|
1676
|
+
if self.is_charging_state_supported:
|
1567
1677
|
return True
|
1568
1678
|
|
1569
1679
|
# Vehicle location states
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pycupra
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.13
|
4
4
|
Summary: A library to read and send vehicle data via Cupra/Seat portal using the same API calls as the MyCupra/MySeat mobile app.
|
5
5
|
Home-page: https://github.com/WulfgarW/pycupra
|
6
6
|
Author: WulfgarW
|
@@ -0,0 +1,13 @@
|
|
1
|
+
pycupra/__init__.py,sha256=VPzUfKd5mBFD1UERNV61FbGHih5dQPupLgIfYtmIUi4,230
|
2
|
+
pycupra/__version__.py,sha256=TaTBX6INREWajim6xVOUYCFZYeyoJA-OFOU2qeaT5nk,208
|
3
|
+
pycupra/connection.py,sha256=_bTo1sBsUUDEf6Q8-u4H079KT_IN5sW5vlYI26ZB9mc,82423
|
4
|
+
pycupra/const.py,sha256=mgl29DcZz_J5hSzxknteu0ocDOXmQAgP0x17kvVSSi0,10234
|
5
|
+
pycupra/dashboard.py,sha256=FYheQ_U_mJ6BpgGq0auzcL-MsGOzSIlRo9vPvjf2pGQ,42434
|
6
|
+
pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
|
7
|
+
pycupra/utilities.py,sha256=cH4MiIzT2WlHgmnl_E7rR0R5LvCXfDNvirJolct50V8,2563
|
8
|
+
pycupra/vehicle.py,sha256=DTbUzd31lyXON77_6no1mCrw5uajx24K5qDKA70PL9M,132533
|
9
|
+
pycupra-0.0.13.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
10
|
+
pycupra-0.0.13.dist-info/METADATA,sha256=8xEMN1iXZC6Cc5sIOp_5dkzEpN9YXhdF_YoD07cFjLs,2579
|
11
|
+
pycupra-0.0.13.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
12
|
+
pycupra-0.0.13.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
|
13
|
+
pycupra-0.0.13.dist-info/RECORD,,
|
pycupra-0.0.11.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
pycupra/__init__.py,sha256=VPzUfKd5mBFD1UERNV61FbGHih5dQPupLgIfYtmIUi4,230
|
2
|
-
pycupra/__version__.py,sha256=akVZtK-CzC3P-mjo2trC9vYwm42-vUYO2HYPgdI5UWw,208
|
3
|
-
pycupra/connection.py,sha256=_bTo1sBsUUDEf6Q8-u4H079KT_IN5sW5vlYI26ZB9mc,82423
|
4
|
-
pycupra/const.py,sha256=VEYH8TUsJGJwBwloaajwoElYd0qxE7oesvoagvDdE-4,10161
|
5
|
-
pycupra/dashboard.py,sha256=MTvOcxAco6Okk0jJKPeqC-VgvYtKtZb2jm2muUQeu1c,41323
|
6
|
-
pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
|
7
|
-
pycupra/utilities.py,sha256=cH4MiIzT2WlHgmnl_E7rR0R5LvCXfDNvirJolct50V8,2563
|
8
|
-
pycupra/vehicle.py,sha256=nCHqcWtsbq3OHeAKSIUUqA89IBUbEWnxgD-nn2zjUSI,126311
|
9
|
-
pycupra-0.0.11.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
10
|
-
pycupra-0.0.11.dist-info/METADATA,sha256=OJSAgLb6Fe5jb3Mq100MCm1ZZYMw7p2VznRghnSFYr4,2579
|
11
|
-
pycupra-0.0.11.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
12
|
-
pycupra-0.0.11.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
|
13
|
-
pycupra-0.0.11.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|