gshock-api 2.0.24__tar.gz → 2.0.26__tar.gz
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.
- {gshock_api-2.0.24/src/gshock_api.egg-info → gshock_api-2.0.26}/PKG-INFO +1 -1
- {gshock_api-2.0.24 → gshock_api-2.0.26}/pyproject.toml +1 -1
- {gshock_api-2.0.24 → gshock_api-2.0.26}/setup.py +1 -1
- gshock_api-2.0.26/src/gshock_api/always_connected_watch_filter.py +40 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/connection.py +5 -5
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/gshock_api.py +3 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/dst_watch_state_io.py +0 -1
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/scanner.py +3 -5
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/watch_info.py +96 -45
- {gshock_api-2.0.24 → gshock_api-2.0.26/src/gshock_api.egg-info}/PKG-INFO +1 -1
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api.egg-info/SOURCES.txt +1 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/LICENSE.txt +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/README.rst +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/setup.cfg +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/__init__.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/alarms.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/app_notification.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/cancelable_result.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/casio_constants.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/event.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/exceptions.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/__init__.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/alarms_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/app_info_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/app_notification_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/button_pressed_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/dst_for_world_cities_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/error_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/events_io.py +2 -2
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/settings_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/time_adjustement_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/time_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/timer_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/unknown_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/watch_condition_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/watch_name_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/iolib/world_cities_io.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/logger.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/mailsener.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/message_dispatcher.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/settings.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api/utils.py +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api.egg-info/dependency_links.txt +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api.egg-info/not-zip-safe +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api.egg-info/requires.txt +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/src/gshock_api.egg-info/top_level.txt +0 -0
- {gshock_api-2.0.24 → gshock_api-2.0.26}/tests/test_code.py +0 -0
|
@@ -5,7 +5,7 @@ requires = ["setuptools>=61.0"]
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "gshock_api" # Change to your project name
|
|
8
|
-
version = "2.0.
|
|
8
|
+
version = "2.0.26"
|
|
9
9
|
description = "Pythgon API for GShock Watches using BLE"
|
|
10
10
|
readme = "README.rst"
|
|
11
11
|
authors = [{ name = "Ivo Zivkov", email = "izivkov@gmail.com" }]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from gshock_api.watch_info import watch_info
|
|
3
|
+
|
|
4
|
+
class AlwaysConnectedWatchFilter:
|
|
5
|
+
"""
|
|
6
|
+
For always-connected watches, limit the connection frequency to once every 6 hours.
|
|
7
|
+
Otherwise, they may block other watches from connecting.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.last_connected_times = {}
|
|
12
|
+
|
|
13
|
+
def connection_filter(self, watch_name):
|
|
14
|
+
watch = watch_info.lookup_watch_info(watch_name)
|
|
15
|
+
|
|
16
|
+
if not watch["alwaysConnected"]:
|
|
17
|
+
# not always connected - allow...
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
last_time = self.last_connected_times.get(watch_name)
|
|
21
|
+
now = time.time()
|
|
22
|
+
|
|
23
|
+
if last_time is None:
|
|
24
|
+
# connected for the first time - allow...
|
|
25
|
+
self.update_connection_time(watch_name=watch_name)
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
elapsed = now - last_time
|
|
29
|
+
if elapsed > 6 * 3600:
|
|
30
|
+
# last connected more than 6 hours ago - allow...
|
|
31
|
+
self.update_connection_time(watch_name=watch_name)
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
# last connected less than 6 hours ago - deny...
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
def update_connection_time(self, watch_name):
|
|
38
|
+
self.last_connected_times[watch_name.strip()] = time.time()
|
|
39
|
+
|
|
40
|
+
always_connected_watch_filter = AlwaysConnectedWatchFilter()
|
|
@@ -33,15 +33,13 @@ class Connection:
|
|
|
33
33
|
for char in service.characteristics:
|
|
34
34
|
self.characteristics_map[char.uuid] = char.uuid # Store in map
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
async def connect(self, excluded_watches: list[str] | None = None) -> bool:
|
|
36
|
+
async def connect(self, watch_filter=None) -> bool:
|
|
39
37
|
try:
|
|
40
38
|
# Scan for device if address not provided
|
|
41
39
|
if self.address == None:
|
|
42
40
|
device = await scanner.scan(
|
|
43
41
|
device_address=self.address if self.address else None,
|
|
44
|
-
|
|
42
|
+
watch_filter=watch_filter
|
|
45
43
|
)
|
|
46
44
|
if device is None:
|
|
47
45
|
logger.info("No G-Shock device found or name matches excluded watches.")
|
|
@@ -90,8 +88,10 @@ class Connection:
|
|
|
90
88
|
)
|
|
91
89
|
return
|
|
92
90
|
|
|
91
|
+
responseType = True if handle == 0x0E else False
|
|
92
|
+
|
|
93
93
|
await self.client.write_gatt_char(
|
|
94
|
-
uuid, to_casio_cmd(data)
|
|
94
|
+
uuid, to_casio_cmd(data), response=responseType
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
except Exception as e:
|
|
@@ -455,3 +455,6 @@ class GshockAPI:
|
|
|
455
455
|
encrypted_buffer = AppNotificationIO.xor_encode_buffer(encoded_buffer)
|
|
456
456
|
await self.connection.write(0xD, encrypted_buffer)
|
|
457
457
|
|
|
458
|
+
# Note: replace all logging calls with 'print' or minimal logger.
|
|
459
|
+
# Ensure your async calls and message_dispatcher methods are ported or stubbed for MicroPython.
|
|
460
|
+
|
|
@@ -17,11 +17,10 @@ class Scanner:
|
|
|
17
17
|
async def scan(
|
|
18
18
|
self,
|
|
19
19
|
device_address: str | None = None,
|
|
20
|
-
|
|
20
|
+
watch_filter = None,
|
|
21
21
|
max_retries: int = 60, # Optional: to prevent infinite loops
|
|
22
22
|
) -> BLEDevice | None:
|
|
23
23
|
scanner = BleakScanner()
|
|
24
|
-
excluded_watches = excluded_watches or []
|
|
25
24
|
|
|
26
25
|
retries = 0
|
|
27
26
|
device = None
|
|
@@ -39,9 +38,8 @@ class Scanner:
|
|
|
39
38
|
return False
|
|
40
39
|
|
|
41
40
|
is_casio = parts[0].lower() == "casio"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return is_casio and not is_excluded
|
|
41
|
+
passed = is_casio and (watch_filter is None or watch_filter(device.name))
|
|
42
|
+
return passed
|
|
45
43
|
|
|
46
44
|
device = await scanner.find_device_by_filter(
|
|
47
45
|
casio_filter,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
1
|
from gshock_api.logger import logger
|
|
3
2
|
|
|
4
|
-
class WATCH_MODEL
|
|
3
|
+
class WATCH_MODEL:
|
|
5
4
|
GA = 1
|
|
6
5
|
GW = 2
|
|
7
6
|
DW = 3
|
|
@@ -17,6 +16,7 @@ class WATCH_MODEL(Enum):
|
|
|
17
16
|
GB = 13
|
|
18
17
|
GM = 14
|
|
19
18
|
ABL = 15
|
|
19
|
+
DW_H = 16
|
|
20
20
|
UNKNOWN = 20
|
|
21
21
|
|
|
22
22
|
class WatchInfo:
|
|
@@ -167,6 +167,7 @@ class WatchInfo:
|
|
|
167
167
|
"longLightDuration": "3s",
|
|
168
168
|
"worldCities": False,
|
|
169
169
|
"temperature": False,
|
|
170
|
+
"alwaysConnected": True,
|
|
170
171
|
},
|
|
171
172
|
{
|
|
172
173
|
"model": WATCH_MODEL.ECB,
|
|
@@ -185,6 +186,23 @@ class WatchInfo:
|
|
|
185
186
|
"hasPowerSavingMode": False,
|
|
186
187
|
"hasDnD": True
|
|
187
188
|
},
|
|
189
|
+
{
|
|
190
|
+
"model": WATCH_MODEL.DW_H,
|
|
191
|
+
"worldCitiesCount": 2,
|
|
192
|
+
"dstCount": 1,
|
|
193
|
+
"alarmCount": 5,
|
|
194
|
+
"hasAutoLight": True,
|
|
195
|
+
"hasReminders": False,
|
|
196
|
+
"shortLightDuration": "1.5s",
|
|
197
|
+
"longLightDuration": "3s",
|
|
198
|
+
"worldCities": True,
|
|
199
|
+
"temperature": False,
|
|
200
|
+
"hasBatteryLevel": False,
|
|
201
|
+
"alwaysConnected": True,
|
|
202
|
+
"findButtonUserDefined": True,
|
|
203
|
+
"hasPowerSavingMode": False,
|
|
204
|
+
"hasDnD": True
|
|
205
|
+
},
|
|
188
206
|
{
|
|
189
207
|
"model": WATCH_MODEL.UNKNOWN,
|
|
190
208
|
"worldCitiesCount": 2,
|
|
@@ -201,32 +219,63 @@ class WatchInfo:
|
|
|
201
219
|
self.model_map = {entry["model"]: entry for entry in self.models}
|
|
202
220
|
|
|
203
221
|
def set_name_and_model(self, name):
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
222
|
+
details = self._resolve_watch_details(name)
|
|
223
|
+
if not details:
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# Persist fields
|
|
227
|
+
self.name = details["name"]
|
|
228
|
+
self.shortName = details["shortName"]
|
|
229
|
+
self.model = details["model"]
|
|
230
|
+
self.hasReminders = details["hasReminders"]
|
|
231
|
+
self.hasAutoLight = details["hasAutoLight"]
|
|
232
|
+
self.alarmCount = details["alarmCount"]
|
|
233
|
+
self.worldCitiesCount = details["worldCitiesCount"]
|
|
234
|
+
self.dstCount = details["dstCount"]
|
|
235
|
+
self.shortLightDuration = details["shortLightDuration"]
|
|
236
|
+
self.longLightDuration = details["longLightDuration"]
|
|
237
|
+
self.weekLanguageSupported = details["weekLanguageSupported"]
|
|
238
|
+
self.worldCities = details["worldCities"]
|
|
239
|
+
self.temperature = details["temperature"]
|
|
240
|
+
self.batteryLevelLowerLimit= details["batteryLevelLowerLimit"]
|
|
241
|
+
self.batteryLevelUpperLimit= details["batteryLevelUpperLimit"]
|
|
242
|
+
self.alwaysConnected = details["alwaysConnected"]
|
|
243
|
+
self.findButtonUserDefined = details["findButtonUserDefined"]
|
|
244
|
+
self.hasPowerSavingMode = details["hasPowerSavingMode"]
|
|
245
|
+
self.hasDnD = details["hasDnD"]
|
|
246
|
+
self.hasBatteryLevel = details["hasBatteryLevel"]
|
|
247
|
+
self.hasWorldCities = details["hasWorldCities"]
|
|
207
248
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
249
|
+
def lookup_watch_info(self, name):
|
|
250
|
+
# Public non-destructive lookup
|
|
251
|
+
return self._resolve_watch_details(name)
|
|
211
252
|
|
|
212
|
-
|
|
213
|
-
|
|
253
|
+
def _resolve_watch_details(self, name):
|
|
254
|
+
# Internal method for lookup logic
|
|
255
|
+
shortName = None
|
|
256
|
+
model = WATCH_MODEL.UNKNOWN
|
|
257
|
+
|
|
258
|
+
parts = name.split(" ")
|
|
259
|
+
if len(parts) > 1:
|
|
260
|
+
shortName = parts[1] # instead of `parts`
|
|
261
|
+
if not shortName:
|
|
262
|
+
return None # Could return a dict of defaults or None
|
|
214
263
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
self.model = WATCH_MODEL.GST
|
|
264
|
+
if shortName in {"ECB-10", "ECB-20", "ECB-30"}:
|
|
265
|
+
model = WATCH_MODEL.ECB
|
|
266
|
+
elif shortName.startswith("ABL"):
|
|
267
|
+
model = WATCH_MODEL.ABL
|
|
268
|
+
elif shortName.startswith("GST"):
|
|
269
|
+
model = WATCH_MODEL.GST
|
|
222
270
|
else:
|
|
223
|
-
# Ordered prefix-to-model mapping (longer prefixes first)
|
|
224
271
|
prefix_map = [
|
|
225
272
|
("MSG", WATCH_MODEL.MSG),
|
|
226
273
|
("GPR", WATCH_MODEL.GPR),
|
|
274
|
+
("GM-B2100", WATCH_MODEL.GA),
|
|
227
275
|
("GBM", WATCH_MODEL.GA),
|
|
228
276
|
("GBD", WATCH_MODEL.GBD),
|
|
229
277
|
("GMW", WATCH_MODEL.GMW),
|
|
278
|
+
("DW-H", WATCH_MODEL.DW_H),
|
|
230
279
|
("DW", WATCH_MODEL.DW),
|
|
231
280
|
("GA", WATCH_MODEL.GA),
|
|
232
281
|
("GB", WATCH_MODEL.GB),
|
|
@@ -235,35 +284,37 @@ class WatchInfo:
|
|
|
235
284
|
("MRG", WATCH_MODEL.MRG),
|
|
236
285
|
("ABL", WATCH_MODEL.ABL),
|
|
237
286
|
]
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
self.model = model
|
|
287
|
+
for prefix, m in prefix_map:
|
|
288
|
+
if shortName.startswith(prefix):
|
|
289
|
+
model = m
|
|
242
290
|
break
|
|
243
291
|
|
|
244
|
-
model_info = self.model_map.get(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
292
|
+
model_info = self.model_map.get(model, {})
|
|
293
|
+
computed = {
|
|
294
|
+
"name": name,
|
|
295
|
+
"shortName": shortName,
|
|
296
|
+
"model": model,
|
|
297
|
+
# Use model_info for each property
|
|
298
|
+
"hasReminders": model_info.get("hasReminders", False),
|
|
299
|
+
"hasAutoLight": model_info.get("hasAutoLight", False),
|
|
300
|
+
"alarmCount": model_info.get("alarmCount", 0),
|
|
301
|
+
"worldCitiesCount": model_info.get("worldCitiesCount", 0),
|
|
302
|
+
"dstCount": model_info.get("dstCount", 0),
|
|
303
|
+
"shortLightDuration": model_info.get("shortLightDuration", ""),
|
|
304
|
+
"longLightDuration": model_info.get("longLightDuration", ""),
|
|
305
|
+
"weekLanguageSupported": model_info.get("weekLanguageSupported", True),
|
|
306
|
+
"worldCities": model_info.get("worldCities", True),
|
|
307
|
+
"temperature": model_info.get("temperature", True),
|
|
308
|
+
"batteryLevelLowerLimit": model_info.get("batteryLevelLowerLimit", 15),
|
|
309
|
+
"batteryLevelUpperLimit": model_info.get("batteryLevelUpperLimit", 20),
|
|
310
|
+
"alwaysConnected": model_info.get("alwaysConnected", False),
|
|
311
|
+
"findButtonUserDefined": model_info.get("findButtonUserDefined", False),
|
|
312
|
+
"hasPowerSavingMode": model_info.get("hasPowerSavingMode", False),
|
|
313
|
+
"hasDnD": model_info.get("hasDnD", False),
|
|
314
|
+
"hasBatteryLevel": model_info.get("hasBatteryLevel", False),
|
|
315
|
+
"hasWorldCities": model_info.get("hasWorldCities", True),
|
|
316
|
+
}
|
|
317
|
+
return computed
|
|
267
318
|
|
|
268
319
|
def set_address(self, address):
|
|
269
320
|
self.address = address
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -17,7 +17,6 @@ from gshock_api.utils import (
|
|
|
17
17
|
|
|
18
18
|
CHARACTERISTICS = CasioConstants.CHARACTERISTICS
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
class ReminderMasks:
|
|
22
21
|
YEARLY_MASK = 0b00001000
|
|
23
22
|
MONTHLY_MASK = 0b00010000
|
|
@@ -33,7 +32,6 @@ class ReminderMasks:
|
|
|
33
32
|
|
|
34
33
|
ENABLED_MASK = 0b00000001
|
|
35
34
|
|
|
36
|
-
|
|
37
35
|
class EventsIO:
|
|
38
36
|
result: CancelableResult = None
|
|
39
37
|
connection = None
|
|
@@ -184,6 +182,7 @@ class EventsIO:
|
|
|
184
182
|
title_byte_arr += bytearray([index + 1])
|
|
185
183
|
title_byte_arr += title
|
|
186
184
|
title_byte_arr_to_send = to_compact_string(to_hex_string(title_byte_arr))
|
|
185
|
+
|
|
187
186
|
await EventsIO.connection.write(0x000E, title_byte_arr_to_send)
|
|
188
187
|
|
|
189
188
|
reminder_time_byte_arr = bytearray([])
|
|
@@ -195,6 +194,7 @@ class EventsIO:
|
|
|
195
194
|
reminder_time_byte_arr_to_send = to_compact_string(
|
|
196
195
|
to_hex_string(bytearray(reminder_time_byte_arr))
|
|
197
196
|
)
|
|
197
|
+
|
|
198
198
|
await EventsIO.connection.write(0x000E, reminder_time_byte_arr_to_send)
|
|
199
199
|
|
|
200
200
|
@staticmethod
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|