gshock-api 2.0.2__tar.gz → 2.0.4__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.
Files changed (51) hide show
  1. gshock_api-2.0.4/PKG-INFO +120 -0
  2. gshock_api-2.0.4/README.rst +107 -0
  3. {gshock_api-2.0.2 → gshock_api-2.0.4}/pyproject.toml +1 -1
  4. {gshock_api-2.0.2 → gshock_api-2.0.4}/setup.py +1 -1
  5. gshock_api-2.0.4/src/gshock_api/app_notification.py +55 -0
  6. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/casio_constants.py +1 -0
  7. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/connection.py +38 -4
  8. gshock_api-2.0.4/src/gshock_api/device.py +6 -0
  9. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/gshock_api.py +10 -9
  10. gshock_api-2.0.4/src/gshock_api/iolib/app_notification_io.py +170 -0
  11. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/button_pressed_io.py +0 -1
  12. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/dst_watch_state_io.py +0 -1
  13. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/error_io.py +1 -1
  14. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/message_dispatcher.py +0 -1
  15. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/scanner.py +7 -8
  16. gshock_api-2.0.4/src/gshock_api.egg-info/PKG-INFO +120 -0
  17. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api.egg-info/SOURCES.txt +3 -1
  18. gshock_api-2.0.2/PKG-INFO +0 -100
  19. gshock_api-2.0.2/README.rst +0 -87
  20. gshock_api-2.0.2/src/gshock_api/core.py +0 -4
  21. gshock_api-2.0.2/src/gshock_api.egg-info/PKG-INFO +0 -100
  22. {gshock_api-2.0.2 → gshock_api-2.0.4}/LICENSE.txt +0 -0
  23. {gshock_api-2.0.2 → gshock_api-2.0.4}/setup.cfg +0 -0
  24. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/__init__.py +0 -0
  25. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/alarms.py +0 -0
  26. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/cancelable_result.py +0 -0
  27. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/configurator.py +0 -0
  28. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/event.py +0 -0
  29. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/__init__.py +0 -0
  30. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/alarms_io.py +0 -0
  31. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/app_info_io.py +0 -0
  32. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/dst_for_world_cities_io.py +0 -0
  33. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/events_io.py +0 -0
  34. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/settings_io.py +0 -0
  35. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/time_adjustement_io.py +0 -0
  36. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/time_io.py +0 -0
  37. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/timer_io.py +0 -0
  38. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/unknown_io.py +0 -0
  39. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/watch_condition_io.py +0 -0
  40. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/watch_name_io.py +0 -0
  41. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/iolib/world_cities_io.py +0 -0
  42. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/logger.py +0 -0
  43. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/mailsener.py +0 -0
  44. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/settings.py +0 -0
  45. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/utils.py +0 -0
  46. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api/watch_info.py +0 -0
  47. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api.egg-info/dependency_links.txt +0 -0
  48. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api.egg-info/not-zip-safe +0 -0
  49. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api.egg-info/requires.txt +0 -0
  50. {gshock_api-2.0.2 → gshock_api-2.0.4}/src/gshock_api.egg-info/top_level.txt +0 -0
  51. {gshock_api-2.0.2 → gshock_api-2.0.4}/tests/test_code.py +0 -0
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: gshock_api
3
+ Version: 2.0.4
4
+ Summary: Pythgon API for GShock Watches using BLE
5
+ Home-page: https://github.com/pyscaffold/pyscaffold/
6
+ Author: Ivo Zivkov
7
+ Author-email: Ivo Zivkov <izivkov@gmail.com>
8
+ Platform: any
9
+ Description-Content-Type: text/x-rst
10
+ License-File: LICENSE.txt
11
+ Requires-Dist: requests
12
+ Dynamic: license-file
13
+
14
+ gshock_api
15
+ ================
16
+
17
+ Announcement
18
+ ============
19
+
20
+ The library now supports sending application-level notifications to watches that support it, namely:
21
+
22
+ * DW-H5600
23
+ * GBD-H2000
24
+ * GBD-200
25
+ * GBD-100
26
+ * GBD-800
27
+ * GBD-100BAR
28
+ * GBX-100
29
+
30
+ The new API is **send_app_notification** function.
31
+ See the `examples/api_test.py <https://github.com/izivkov/gshock_api/blob/main/src/examples/api_tests.py#L453-L456>`_ file for usage examples.
32
+
33
+ Only the **DW-H5600** watch has been tested. If you have any of the watches supporting notifications, please let me know your findings.
34
+
35
+
36
+ Overview
37
+ ========
38
+ This is a **Python API library** for G-Shock watches that support Bluetooth Low Energy (BLE) communication.
39
+
40
+ G(M)W-5600, G(M)W-5000, GA-B2100, GA-B001-1AER, GST-B500, GST-B200, MSG-B100,
41
+ G-B001, GBD-H1000 (Partial support), MRG-B5000, GCW-B5000, GG-B100, ABL-100WE,
42
+ Edifice ECB-30, ECB-10, ECB-20, most Edifice watches, most Protrek models.
43
+
44
+ It can perform the following tasks:
45
+
46
+ - Set watch's time
47
+ - Set Home Time (Home City)
48
+ - Set Alarms
49
+ - Set Reminders
50
+ - Set watch's settings.
51
+ - Get watch's name
52
+ - Get watch's battery level
53
+ - Get Watch's temperature
54
+ - Get/Set watch's Timer
55
+ - Send notifications to watch (supported models)
56
+
57
+ Dependencies
58
+ ============
59
+
60
+ This project requires the following Python packages:
61
+ .. code-block:: sh
62
+
63
+ pytz
64
+ bleak
65
+
66
+
67
+ So you can install them using the following command:
68
+ .. code-block:: sh
69
+
70
+ pip3 install -r requirements.txt
71
+
72
+
73
+ To understand how to use the library, please refer to the **`src/examples`** folder.
74
+
75
+ Virtual Environment Setup
76
+ =========================
77
+
78
+ It is recommended that you create a virtual environment to run the tests:
79
+
80
+ 1. Create a virtual environment:
81
+
82
+ .. code-block:: sh
83
+
84
+ # Create a virtual environment
85
+ python3 -m venv venv
86
+
87
+ # Activate it (Mac/Linux)
88
+ source venv/bin/activate
89
+
90
+ # Install dependencies
91
+ pip3 install -e .
92
+
93
+ 2. Run the tests:
94
+
95
+ .. code-block:: sh
96
+
97
+ python3 src/examples/api_tests.py [--multi-watch]
98
+
99
+ The optional **`--multi-watch`** parameter forces the library to scan for watches every time it tries to connect to a watch. If not provided,
100
+ the library will try to connect to the last connected watch only. If you have multiple watches, you should use this parameter.
101
+
102
+ The optional **`--multi-watch`** parameter forces the library to scan for watches every time it tries to connect to a watch. If not provided,
103
+ the library will try to connect to the last connected watch only. If you have multiple watches, you should use this parameter.
104
+
105
+
106
+ Installing the library for your project:
107
+ ========================================
108
+
109
+ .. code-block:: sh
110
+
111
+ pip3 install gshock-api
112
+
113
+ See `this project <https://github.com/izivkov/GShockTimeServer>`_ using this library to run a time server for G-Shock watches.
114
+
115
+ See also `this blog <https://digitalsober.wordpress.com/2025/05/05/g-shock-watch-integration-with-sxmo/>`_ for using the library in the `SXMO <https://sxmo.org/>`_ mobile environment.
116
+
117
+ Troubleshooting:
118
+ ================
119
+ If your watch cannot connect, and the
120
+ **`--multi-watch`** parameter is not used, remove the **`config.ini`** file and try again.
@@ -0,0 +1,107 @@
1
+ gshock_api
2
+ ================
3
+
4
+ Announcement
5
+ ============
6
+
7
+ The library now supports sending application-level notifications to watches that support it, namely:
8
+
9
+ * DW-H5600
10
+ * GBD-H2000
11
+ * GBD-200
12
+ * GBD-100
13
+ * GBD-800
14
+ * GBD-100BAR
15
+ * GBX-100
16
+
17
+ The new API is **send_app_notification** function.
18
+ See the `examples/api_test.py <https://github.com/izivkov/gshock_api/blob/main/src/examples/api_tests.py#L453-L456>`_ file for usage examples.
19
+
20
+ Only the **DW-H5600** watch has been tested. If you have any of the watches supporting notifications, please let me know your findings.
21
+
22
+
23
+ Overview
24
+ ========
25
+ This is a **Python API library** for G-Shock watches that support Bluetooth Low Energy (BLE) communication.
26
+
27
+ G(M)W-5600, G(M)W-5000, GA-B2100, GA-B001-1AER, GST-B500, GST-B200, MSG-B100,
28
+ G-B001, GBD-H1000 (Partial support), MRG-B5000, GCW-B5000, GG-B100, ABL-100WE,
29
+ Edifice ECB-30, ECB-10, ECB-20, most Edifice watches, most Protrek models.
30
+
31
+ It can perform the following tasks:
32
+
33
+ - Set watch's time
34
+ - Set Home Time (Home City)
35
+ - Set Alarms
36
+ - Set Reminders
37
+ - Set watch's settings.
38
+ - Get watch's name
39
+ - Get watch's battery level
40
+ - Get Watch's temperature
41
+ - Get/Set watch's Timer
42
+ - Send notifications to watch (supported models)
43
+
44
+ Dependencies
45
+ ============
46
+
47
+ This project requires the following Python packages:
48
+ .. code-block:: sh
49
+
50
+ pytz
51
+ bleak
52
+
53
+
54
+ So you can install them using the following command:
55
+ .. code-block:: sh
56
+
57
+ pip3 install -r requirements.txt
58
+
59
+
60
+ To understand how to use the library, please refer to the **`src/examples`** folder.
61
+
62
+ Virtual Environment Setup
63
+ =========================
64
+
65
+ It is recommended that you create a virtual environment to run the tests:
66
+
67
+ 1. Create a virtual environment:
68
+
69
+ .. code-block:: sh
70
+
71
+ # Create a virtual environment
72
+ python3 -m venv venv
73
+
74
+ # Activate it (Mac/Linux)
75
+ source venv/bin/activate
76
+
77
+ # Install dependencies
78
+ pip3 install -e .
79
+
80
+ 2. Run the tests:
81
+
82
+ .. code-block:: sh
83
+
84
+ python3 src/examples/api_tests.py [--multi-watch]
85
+
86
+ The optional **`--multi-watch`** parameter forces the library to scan for watches every time it tries to connect to a watch. If not provided,
87
+ the library will try to connect to the last connected watch only. If you have multiple watches, you should use this parameter.
88
+
89
+ The optional **`--multi-watch`** parameter forces the library to scan for watches every time it tries to connect to a watch. If not provided,
90
+ the library will try to connect to the last connected watch only. If you have multiple watches, you should use this parameter.
91
+
92
+
93
+ Installing the library for your project:
94
+ ========================================
95
+
96
+ .. code-block:: sh
97
+
98
+ pip3 install gshock-api
99
+
100
+ See `this project <https://github.com/izivkov/GShockTimeServer>`_ using this library to run a time server for G-Shock watches.
101
+
102
+ See also `this blog <https://digitalsober.wordpress.com/2025/05/05/g-shock-watch-integration-with-sxmo/>`_ for using the library in the `SXMO <https://sxmo.org/>`_ mobile environment.
103
+
104
+ Troubleshooting:
105
+ ================
106
+ If your watch cannot connect, and the
107
+ **`--multi-watch`** parameter is not used, remove the **`config.ini`** file and try again.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gshock_api" # Change to your project name
7
- version = "2.0.2"
7
+ version = "2.0.4"
8
8
  description = "Pythgon API for GShock Watches using BLE"
9
9
  readme = "README.rst"
10
10
  authors = [{ name = "Ivo Zivkov", email = "izivkov@gmail.com" }]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='gshock_api',
5
- version='2.0.2',
5
+ version='2.0.4',
6
6
  package_dir={'': 'src'},
7
7
  packages=find_packages(where='src'),
8
8
  install_requires=[
@@ -0,0 +1,55 @@
1
+ from enum import Enum
2
+ from dataclasses import dataclass
3
+
4
+ class NotificationType(Enum):
5
+ GENERIC = 0
6
+ PHONE_CALL_URGENT = 1
7
+ PHONE_CALL = 2
8
+ EMAIL = 3
9
+ MESSAGE = 4
10
+ CALENDAR = 5
11
+ EMAIL_SMS = 6
12
+
13
+ @dataclass
14
+ class AppNotification:
15
+
16
+ type: NotificationType
17
+ timestamp: str
18
+ app: str
19
+ title: str
20
+ text: str
21
+ short_text: str = ""
22
+
23
+ def __post_init__(self):
24
+ max_length_text = 193
25
+ max_length_short_text = 40
26
+ max_combined = 206 # Example combined max in bytes
27
+
28
+ # Truncate individual fields by UTF-8 byte length
29
+ text_bytes = self.text.encode("utf-8")
30
+ if len(text_bytes) > max_length_text:
31
+ self.text = text_bytes[:max_length_text].decode("utf-8", errors="ignore")
32
+
33
+ short_text_bytes = self.short_text.encode("utf-8")
34
+ if len(short_text_bytes) > max_length_short_text:
35
+ self.short_text = short_text_bytes[:max_length_short_text].decode("utf-8", errors="ignore")
36
+
37
+ # Now check combined UTF-8 byte length
38
+ text_bytes = self.text.encode("utf-8")
39
+ short_text_bytes = self.short_text.encode("utf-8")
40
+ total_len = len(text_bytes) + len(short_text_bytes)
41
+ if total_len > max_combined:
42
+ # Only shorten text, not short_text
43
+ allowed_text_bytes = max(0, max_combined - len(short_text_bytes))
44
+ self.text = text_bytes[:allowed_text_bytes].decode("utf-8", errors="ignore")
45
+
46
+ def to_dict(self) -> dict:
47
+ return {
48
+ "type": self.type,
49
+ "timestamp": self.timestamp,
50
+ "app": self.app,
51
+ "title": self.title,
52
+ "text": self.text,
53
+ "short_text": self.short_text,
54
+ }
55
+
@@ -6,6 +6,7 @@ class CasioConstants:
6
6
  "26eb002c-b012-49a8-b1f8-394fb2032b0f"
7
7
  )
8
8
  CASIO_ALL_FEATURES_CHARACTERISTIC_UUID = "26eb002d-b012-49a8-b1f8-394fb2032b0f"
9
+ CASIO_NOTIFICATION_CHARACTERISTIC_UUID = "26eb0030-b012-49a8-b1f8-394fb2032b0f"
9
10
  CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID = "26eb0023-b012-49a8-b1f8-394fb2032b0f"
10
11
  CASIO_CONVOY_CHARACTERISTIC_UUID = "26eb0024-b012-49a8-b1f8-394fb2032b0f"
11
12
  SERIAL_NUMBER_STRING = "00002a25-0000-1000-8000-00805f9b34fb"
@@ -5,26 +5,43 @@ from gshock_api.casio_constants import CasioConstants
5
5
  from gshock_api import message_dispatcher
6
6
  from gshock_api.utils import to_casio_cmd
7
7
  from gshock_api.logger import logger
8
+ from gshock_api.watch_info import watch_info
9
+ from gshock_api.device import Device
8
10
 
9
11
  class Connection:
10
- def __init__(self, device):
12
+ def __init__(self, device: Device):
11
13
  self.handles_map = self.init_handles_map()
12
14
  self.device = device
13
- self.client = BleakClient(device)
15
+ self.client = BleakClient(device.address)
16
+ self.characteristics_map = {}
17
+
14
18
 
15
19
  def notification_handler(
16
20
  self, characteristic: BleakGATTCharacteristic, data: bytearray
17
21
  ):
18
22
  message_dispatcher.MessageDispatcher.on_received(data)
19
23
 
24
+ async def init_characteristics_map(self):
25
+ """
26
+ Prints all services and characteristics of the connected BLE device.
27
+ """
28
+ services = await self.client.get_services()
29
+ for service in services:
30
+ for char in service.characteristics:
31
+ self.characteristics_map[char.uuid] = char.uuid # Store in map
32
+
20
33
  async def connect(self):
21
34
  try:
22
35
  await self.client.connect()
36
+ await self.init_characteristics_map()
23
37
  await self.client.start_notify(
24
38
  CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID,
25
39
  self.notification_handler,
26
40
  )
41
+
42
+ watch_info.set_name_and_model(self.device.name)
27
43
  return True
44
+
28
45
  except Exception as e:
29
46
  logger.debug(f"Cannot connect: {e}")
30
47
  return False
@@ -32,10 +49,27 @@ class Connection:
32
49
  async def disconnect(self):
33
50
  await self.client.disconnect()
34
51
 
52
+ def is_service_supported(self, handle):
53
+ uuid = self.handles_map.get(handle)
54
+ supported = (uuid not in self.characteristics_map)
55
+ print(f"write, service with handle {handle} is supported: {supported}")
56
+ return supported
57
+
35
58
  async def write(self, handle, data):
36
59
  try:
60
+ uuid = self.handles_map.get(handle)
61
+ if (uuid not in self.characteristics_map):
62
+ logger.error(
63
+ "write failed: handle {} not in characteristics map".format(handle)
64
+ )
65
+ if (handle == 13):
66
+ logger.error(
67
+ "Your watch does not suppot notifications..."
68
+ )
69
+ return
70
+
37
71
  await self.client.write_gatt_char(
38
- self.handles_map[handle], to_casio_cmd(data)
72
+ uuid, to_casio_cmd(data)
39
73
  )
40
74
  except Exception as e:
41
75
  logger.debug("write failed with exception: {}".format(e))
@@ -54,6 +88,7 @@ class Connection:
54
88
  0x0C
55
89
  ] = CasioConstants.CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID
56
90
  handles_map[0x0E] = CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID
91
+ handles_map[0x0D] = CasioConstants.CASIO_NOTIFICATION_CHARACTERISTIC_UUID
57
92
  handles_map[0x11] = CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID
58
93
  handles_map[0x14] = CasioConstants.CASIO_CONVOY_CHARACTERISTIC_UUID
59
94
  handles_map[0xFF] = CasioConstants.SERIAL_NUMBER_STRING
@@ -61,5 +96,4 @@ class Connection:
61
96
  return handles_map
62
97
 
63
98
  async def sendMessage(self, message):
64
- # await callWriter(self, message)
65
99
  await message_dispatcher.MessageDispatcher.send_to_watch(message)
@@ -0,0 +1,6 @@
1
+ from dataclasses import dataclass
2
+
3
+ @dataclass
4
+ class Device:
5
+ name: str
6
+ address: str
@@ -1,24 +1,20 @@
1
- import asyncio
2
1
  import logging
3
2
  import json
4
3
  import time
5
4
 
6
- # from data_watcher import data_watcher
7
5
  from gshock_api.iolib.dst_watch_state_io import DtsState
8
6
  from gshock_api.iolib.button_pressed_io import WatchButton
7
+ from gshock_api.iolib.app_notification_io import AppNotificationIO
8
+
9
9
  from gshock_api import message_dispatcher
10
10
  from gshock_api.utils import (
11
- to_ascii_string,
12
11
  to_hex_string,
13
- to_int_array,
14
12
  to_compact_string,
15
- clean_str,
16
13
  )
17
14
  from gshock_api.alarms import alarms_inst
18
15
  from gshock_api.event import Event
19
16
  from gshock_api.watch_info import watch_info
20
17
 
21
-
22
18
  class GshockAPI:
23
19
  """
24
20
  This class contains all the API functions. This should the the main interface to the
@@ -163,6 +159,9 @@ class GshockAPI:
163
159
  for item in array_of_dst_watch_state[: watch_info.dstCount]:
164
160
  await self.read_and_write(item["function"], item["state"])
165
161
 
162
+ async def send_app_notification (self, hex_str):
163
+ await self.connection.write(0xD, hex_str)
164
+
166
165
  async def read_write_dst_for_world_cities(self):
167
166
  array_of_get_dst_for_world_cities = [
168
167
  {"function": self.get_dst_for_world_cities, "city_number": 0},
@@ -451,6 +450,8 @@ class GshockAPI:
451
450
  result = await message_dispatcher.AppInfoIO.request(self.connection)
452
451
  return await result
453
452
 
454
- # def subscribe(self, subject_name, on_next) -> None:
455
- # data_watcher.add_subject(subject_name)
456
- # data_watcher.subscribe("GshockAPI", subject_name, on_next)
453
+ async def send_app_notification (self, notification):
454
+ encoded_buffer = AppNotificationIO.encode_notification_packet(notification)
455
+ encrypted_buffer = AppNotificationIO.xor_encode_buffer(encoded_buffer)
456
+ await self.connection.write(0xD, encrypted_buffer)
457
+
@@ -0,0 +1,170 @@
1
+ from gshock_api.app_notification import AppNotification, NotificationType
2
+
3
+ class AppNotificationIO:
4
+ def xor_decode_buffer(buffer: str, key: int = 255) -> bytes:
5
+ """
6
+ Decodes a hex-encoded buffer using XOR with the given key.
7
+
8
+ Args:
9
+ buffer (str): The hex-encoded buffer as a string.
10
+ key (int): The XOR key to decode the buffer.
11
+
12
+ Returns:
13
+ bytes: The XOR-decoded bytes.
14
+ """
15
+ buffer_bytes = bytes.fromhex(buffer)
16
+ decoded_bytes = bytes(b ^ key for b in buffer_bytes)
17
+ return decoded_bytes
18
+
19
+ def xor_encode_buffer(decoded_bytes: bytes, key: int = 255) -> str:
20
+ """
21
+ Encodes a buffer using XOR with the given key.
22
+
23
+ Args:
24
+ decoded_bytes (bytes): The original decoded bytes to encode.
25
+ key (int): The XOR key to encode the buffer.
26
+
27
+ Returns:
28
+ str: The XOR-encoded buffer as a hex string.
29
+ """
30
+ encoded_bytes = bytes(b ^ key for b in decoded_bytes)
31
+ return encoded_bytes.hex()
32
+
33
+ def read_length_prefixed_string(buf: bytes, offset: int) -> tuple[str, int]:
34
+ if offset + 2 > len(buf):
35
+ raise ValueError("Not enough data to read length prefix")
36
+
37
+ length = buf[offset]
38
+ if buf[offset + 1] != 0x00:
39
+ raise ValueError("Expected null second byte in length prefix")
40
+
41
+ start = offset + 2
42
+ end = start + length
43
+ if end > len(buf):
44
+ raise ValueError("String length exceeds buffer")
45
+
46
+ string = buf[start:end].decode("utf-8")
47
+ return string, end
48
+
49
+ def decode_notification_packet(buf: bytes) -> AppNotification:
50
+ """
51
+ Decodes a G-Shock calendar notification buffer into an AppNotification object.
52
+ """
53
+ offset = 0
54
+
55
+ if len(buf) < 6:
56
+ raise ValueError("Buffer too short")
57
+
58
+ # Read 6-byte header (skip or store if needed)
59
+ offset = 6
60
+
61
+ # Read 1-byte type
62
+ notif_type = buf[offset]
63
+ notif_type_enum = NotificationType(notif_type)
64
+ offset += 1
65
+
66
+ # Read 15-byte ASCII timestamp
67
+ timestamp_raw = buf[offset:offset + 15].decode("ascii")
68
+ offset += 15
69
+
70
+ # Read app name (length-prefixed UTF-8 string)
71
+ app, offset = AppNotificationIO.read_length_prefixed_string(buf, offset)
72
+
73
+ # Read title (length-prefixed UTF-8 string)
74
+ title, offset = AppNotificationIO.read_length_prefixed_string(buf, offset)
75
+
76
+ # Read empty string (length-prefixed UTF-8 string, skip)
77
+ _, offset = AppNotificationIO.read_length_prefixed_string(buf, offset)
78
+
79
+ # Read text (length-prefixed UTF-8 string)
80
+ text, offset = AppNotificationIO.read_length_prefixed_string(buf, offset)
81
+
82
+ # Construct and return AppNotification object
83
+ notification = AppNotification(
84
+ type = notif_type_enum,
85
+ timestamp=timestamp_raw,
86
+ app=app,
87
+ title=title,
88
+ text=text
89
+ )
90
+ return notification
91
+
92
+
93
+ def write_length_prefixed_string(text: str) -> bytes:
94
+ """
95
+ Encodes a string as a length-prefixed UTF-8 byte sequence for G-Shock BLE notifications.
96
+
97
+ Structure:
98
+ ----------
99
+ [Length: 1 byte][0x00][UTF-8 bytes of string]
100
+
101
+ - Length: Number of bytes in the UTF-8 encoded string (max 255).
102
+ - 0x00: Separator byte (always 0x00).
103
+ - UTF-8 bytes: The actual string data.
104
+
105
+ Args:
106
+ text (str): The string to encode.
107
+
108
+ Returns:
109
+ bytes: The encoded length-prefixed UTF-8 byte sequence.
110
+
111
+ Raises:
112
+ ValueError: If the encoded string is longer than 255 bytes.
113
+ """
114
+
115
+ encoded = text.encode("utf-8")
116
+ if len(encoded) > 255:
117
+ raise ValueError("Encoded string too long")
118
+ return bytes([len(encoded), 0x00]) + encoded
119
+
120
+ def encode_notification_packet(data: AppNotification) -> bytes:
121
+ """
122
+ Encodes an AppNotification object into a binary buffer for G-Shock BLE notifications.
123
+
124
+ Message Structure:
125
+ ------------------
126
+ The encoded message has the following structure (all fields are concatenated in order):
127
+
128
+ [Header: 6 bytes] +
129
+ [Type: 1 byte] +
130
+ [Timestamp: 15 bytes, ASCII] +
131
+ [App Name: length-prefixed UTF-8 string] +
132
+ [Title: length-prefixed UTF-8 string] +
133
+ [Short Text: length-prefixed UTF-8 string] +
134
+ [Text: length-prefixed UTF-8 string]
135
+
136
+ - Header: Fixed 6-byte value (hex "000000000001").
137
+ - Type: 1 byte, value from NotificationType enum.
138
+ - Timestamp: 15 ASCII characters.
139
+ - App Name, Title, Short Text, Text: Each is encoded as [length (1 byte)][0x00][UTF-8 bytes].
140
+ - The length is the number of bytes in the UTF-8 encoded string (max 255).
141
+ - The second byte is always 0x00 as a separator.
142
+
143
+ Args:
144
+ data (AppNotification): The notification to encode.
145
+
146
+ Returns:
147
+ bytes: The encoded binary buffer.
148
+
149
+ Raises:
150
+ TypeError: If data is not an AppNotification instance.
151
+ ValueError: If any string field is too long to encode.
152
+
153
+ Example:
154
+ >>> notif = AppNotification(type=NotificationType.CALENDAR, timestamp="20250516T233000", app="Calendar", title="Meeting", text="Discuss project")
155
+ >>> buf = AppNotificationIO.encode_notification_packet(notif)
156
+ """
157
+
158
+ if not isinstance(data, AppNotification):
159
+ raise TypeError("data must be an AppNotification instance")
160
+
161
+ header = "000000000001"
162
+ result = bytearray()
163
+ result += bytes.fromhex(header)
164
+ result.append(data.type.value)
165
+ result += data.timestamp.encode("ascii")
166
+ result += AppNotificationIO.write_length_prefixed_string(data.app)
167
+ result += AppNotificationIO.write_length_prefixed_string(data.title)
168
+ result += AppNotificationIO.write_length_prefixed_string(data.short_text) # Empty string for the separator
169
+ result += AppNotificationIO.write_length_prefixed_string(data.text)
170
+ return bytes(result)
@@ -8,7 +8,6 @@ from gshock_api.casio_constants import CasioConstants
8
8
 
9
9
  CHARACTERISTICS = CasioConstants.CHARACTERISTICS
10
10
 
11
-
12
11
  class WatchButton(IntEnum):
13
12
  UPPER_LEFT = 1
14
13
  LOWER_LEFT = 2
@@ -14,7 +14,6 @@ class DtsState(IntEnum):
14
14
  TWO = 2
15
15
  FOUR = 4
16
16
 
17
-
18
17
  class DstWatchStateIO:
19
18
  result: CancelableResult = None
20
19
  connection = None
@@ -3,5 +3,5 @@ from gshock_api.logger import logger
3
3
 
4
4
  class ErrorIO:
5
5
  @staticmethod
6
- async def on_received(message):
6
+ def on_received(message):
7
7
  logger.info(f"ErrorIO onReceived: {message}")
@@ -75,7 +75,6 @@ class MessageDispatcher:
75
75
  logger.info(f"Found key: {MessageDispatcher.data_received_messages[key]}")
76
76
  MessageDispatcher.data_received_messages[key](data)
77
77
 
78
-
79
78
  # Usage example
80
79
  if __name__ == "__main__":
81
80
  # Simulated messages
@@ -4,12 +4,12 @@ from bleak import BleakScanner
4
4
  from gshock_api.configurator import conf
5
5
  from gshock_api.watch_info import watch_info
6
6
  from gshock_api.logger import logger
7
-
7
+ from gshock_api.device import Device
8
8
 
9
9
  class Scanner:
10
10
  CASIO_SERVICE_UUID = "00001804-0000-1000-8000-00805f9b34fb"
11
11
 
12
- async def scan(self, device_address=None):
12
+ async def scan(self, device_address=None) -> Device:
13
13
  scanner = BleakScanner()
14
14
  logger.info("Scanning for devices...")
15
15
 
@@ -22,10 +22,9 @@ class Scanner:
22
22
  logger.info(f"device: {device}")
23
23
  if device is None:
24
24
  continue
25
-
26
- # watch_info.set_name(device.name)
27
- # watch_info.set_address(device.address)
28
- watch_info.set_name_and_model(device.name)
25
+
26
+ # This is now set after connection in Connection class
27
+ # watch_info.set_name_and_model(device.name)
29
28
 
30
29
  conf.put("device.address", device.address)
31
30
  conf.put("device.name", device.name)
@@ -35,8 +34,8 @@ class Scanner:
35
34
  device = await scanner.find_device_by_address(
36
35
  device_address, sys.float_info.max
37
36
  )
37
+ watch_info.set_name_and_model(device.name)
38
38
 
39
- return device
40
-
39
+ return Device(device.name, device.address)
41
40
 
42
41
  scanner = Scanner()
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: gshock_api
3
+ Version: 2.0.4
4
+ Summary: Pythgon API for GShock Watches using BLE
5
+ Home-page: https://github.com/pyscaffold/pyscaffold/
6
+ Author: Ivo Zivkov
7
+ Author-email: Ivo Zivkov <izivkov@gmail.com>
8
+ Platform: any
9
+ Description-Content-Type: text/x-rst
10
+ License-File: LICENSE.txt
11
+ Requires-Dist: requests
12
+ Dynamic: license-file
13
+
14
+ gshock_api
15
+ ================
16
+
17
+ Announcement
18
+ ============
19
+
20
+ The library now supports sending application-level notifications to watches that support it, namely:
21
+
22
+ * DW-H5600
23
+ * GBD-H2000
24
+ * GBD-200
25
+ * GBD-100
26
+ * GBD-800
27
+ * GBD-100BAR
28
+ * GBX-100
29
+
30
+ The new API is **send_app_notification** function.
31
+ See the `examples/api_test.py <https://github.com/izivkov/gshock_api/blob/main/src/examples/api_tests.py#L453-L456>`_ file for usage examples.
32
+
33
+ Only the **DW-H5600** watch has been tested. If you have any of the watches supporting notifications, please let me know your findings.
34
+
35
+
36
+ Overview
37
+ ========
38
+ This is a **Python API library** for G-Shock watches that support Bluetooth Low Energy (BLE) communication.
39
+
40
+ G(M)W-5600, G(M)W-5000, GA-B2100, GA-B001-1AER, GST-B500, GST-B200, MSG-B100,
41
+ G-B001, GBD-H1000 (Partial support), MRG-B5000, GCW-B5000, GG-B100, ABL-100WE,
42
+ Edifice ECB-30, ECB-10, ECB-20, most Edifice watches, most Protrek models.
43
+
44
+ It can perform the following tasks:
45
+
46
+ - Set watch's time
47
+ - Set Home Time (Home City)
48
+ - Set Alarms
49
+ - Set Reminders
50
+ - Set watch's settings.
51
+ - Get watch's name
52
+ - Get watch's battery level
53
+ - Get Watch's temperature
54
+ - Get/Set watch's Timer
55
+ - Send notifications to watch (supported models)
56
+
57
+ Dependencies
58
+ ============
59
+
60
+ This project requires the following Python packages:
61
+ .. code-block:: sh
62
+
63
+ pytz
64
+ bleak
65
+
66
+
67
+ So you can install them using the following command:
68
+ .. code-block:: sh
69
+
70
+ pip3 install -r requirements.txt
71
+
72
+
73
+ To understand how to use the library, please refer to the **`src/examples`** folder.
74
+
75
+ Virtual Environment Setup
76
+ =========================
77
+
78
+ It is recommended that you create a virtual environment to run the tests:
79
+
80
+ 1. Create a virtual environment:
81
+
82
+ .. code-block:: sh
83
+
84
+ # Create a virtual environment
85
+ python3 -m venv venv
86
+
87
+ # Activate it (Mac/Linux)
88
+ source venv/bin/activate
89
+
90
+ # Install dependencies
91
+ pip3 install -e .
92
+
93
+ 2. Run the tests:
94
+
95
+ .. code-block:: sh
96
+
97
+ python3 src/examples/api_tests.py [--multi-watch]
98
+
99
+ The optional **`--multi-watch`** parameter forces the library to scan for watches every time it tries to connect to a watch. If not provided,
100
+ the library will try to connect to the last connected watch only. If you have multiple watches, you should use this parameter.
101
+
102
+ The optional **`--multi-watch`** parameter forces the library to scan for watches every time it tries to connect to a watch. If not provided,
103
+ the library will try to connect to the last connected watch only. If you have multiple watches, you should use this parameter.
104
+
105
+
106
+ Installing the library for your project:
107
+ ========================================
108
+
109
+ .. code-block:: sh
110
+
111
+ pip3 install gshock-api
112
+
113
+ See `this project <https://github.com/izivkov/GShockTimeServer>`_ using this library to run a time server for G-Shock watches.
114
+
115
+ See also `this blog <https://digitalsober.wordpress.com/2025/05/05/g-shock-watch-integration-with-sxmo/>`_ for using the library in the `SXMO <https://sxmo.org/>`_ mobile environment.
116
+
117
+ Troubleshooting:
118
+ ================
119
+ If your watch cannot connect, and the
120
+ **`--multi-watch`** parameter is not used, remove the **`config.ini`** file and try again.
@@ -5,11 +5,12 @@ setup.cfg
5
5
  setup.py
6
6
  src/gshock_api/__init__.py
7
7
  src/gshock_api/alarms.py
8
+ src/gshock_api/app_notification.py
8
9
  src/gshock_api/cancelable_result.py
9
10
  src/gshock_api/casio_constants.py
10
11
  src/gshock_api/configurator.py
11
12
  src/gshock_api/connection.py
12
- src/gshock_api/core.py
13
+ src/gshock_api/device.py
13
14
  src/gshock_api/event.py
14
15
  src/gshock_api/gshock_api.py
15
16
  src/gshock_api/logger.py
@@ -28,6 +29,7 @@ src/gshock_api.egg-info/top_level.txt
28
29
  src/gshock_api/iolib/__init__.py
29
30
  src/gshock_api/iolib/alarms_io.py
30
31
  src/gshock_api/iolib/app_info_io.py
32
+ src/gshock_api/iolib/app_notification_io.py
31
33
  src/gshock_api/iolib/button_pressed_io.py
32
34
  src/gshock_api/iolib/dst_for_world_cities_io.py
33
35
  src/gshock_api/iolib/dst_watch_state_io.py
gshock_api-2.0.2/PKG-INFO DELETED
@@ -1,100 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: gshock_api
3
- Version: 2.0.2
4
- Summary: Pythgon API for GShock Watches using BLE
5
- Home-page: https://github.com/pyscaffold/pyscaffold/
6
- Author: Ivo Zivkov
7
- Author-email: Ivo Zivkov <izivkov@gmail.com>
8
- Platform: any
9
- Description-Content-Type: text/x-rst
10
- License-File: LICENSE.txt
11
- Requires-Dist: requests
12
- Dynamic: license-file
13
-
14
- GShockTimeServer
15
- ================
16
-
17
- Overview
18
- ========
19
- GShockTimeServer allows you to set the correct time on your Casio G-Shock watches, including:
20
-
21
- - `B5600 <https://amzn.to/3Mt68Qb>`__
22
- - `B5000 <https://amzn.to/4194M13>`__
23
- - `B2100 <https://amzn.to/3MUDCGY>`__
24
-
25
- Additionally, this repository provides an API for developing applications for these watches.
26
- While still a work in progress, you can refer to the ``api_tests.py`` file for usage examples.
27
-
28
- Usage
29
- =====
30
- This app can run on any device with Python and Bluetooth capabilities—from a desktop to a Raspberry Pi Zero.
31
- It has been tested on Linux but should also work on Windows.
32
-
33
- .. image:: images/pizero.jpg
34
- :alt: Pi Zero Running the Server
35
- :align: center
36
-
37
- Setting the Time
38
- ----------------
39
- 1. Ensure the app is running on your device.
40
- 2. Short-press the **lower-right** button on your G-Shock watch to connect.
41
- 3. Once connected, the app will automatically set the correct time on your watch.
42
- 4. If **AUTO TIME ADJUSTMENT** is enabled, the watch will sync up to **four times daily**.
43
-
44
- Dependencies
45
- ============
46
-
47
- This project requires the following Python packages:
48
- ```
49
- pytz
50
- bleak
51
- ```
52
-
53
- So you can install them using the following command:
54
- ```
55
- pip3 install -r requirements.txt
56
- ```
57
-
58
- Virtual Environment Setup
59
- =========================
60
- To run the server in a virtual environment:
61
-
62
- 1. Create a virtual environment:
63
-
64
- .. code-block:: sh
65
-
66
- # Create a virtual environment
67
- python3 -m venv myenv
68
-
69
- # Activate it (Mac/Linux)
70
- source myenv/bin/activate
71
-
72
- # Install dependencies
73
- pip3 install -e .
74
-
75
- 2. Run the tests:
76
-
77
- .. code-block:: sh
78
-
79
- python3 src/examples/api_tests.py [--multi-watch]
80
-
81
- 3. Run the server:
82
-
83
- .. code-block:: sh
84
-
85
- python3 src/examples/gshock_server.py [--multi-watch]
86
-
87
- The optional **`--multi-watch`** parameter allows you to connect if you have multiple watches.
88
-
89
-
90
- Installing the library for your project:
91
- ========================================
92
-
93
- ```
94
- pip3 install gshock-api
95
- ```
96
-
97
- Troubleshooting:
98
- ================
99
- If your watch cannot connect, and the
100
- **`--multi-watch`** parameter is not used, remove the **`config.ini`** file and try again.
@@ -1,87 +0,0 @@
1
- GShockTimeServer
2
- ================
3
-
4
- Overview
5
- ========
6
- GShockTimeServer allows you to set the correct time on your Casio G-Shock watches, including:
7
-
8
- - `B5600 <https://amzn.to/3Mt68Qb>`__
9
- - `B5000 <https://amzn.to/4194M13>`__
10
- - `B2100 <https://amzn.to/3MUDCGY>`__
11
-
12
- Additionally, this repository provides an API for developing applications for these watches.
13
- While still a work in progress, you can refer to the ``api_tests.py`` file for usage examples.
14
-
15
- Usage
16
- =====
17
- This app can run on any device with Python and Bluetooth capabilities—from a desktop to a Raspberry Pi Zero.
18
- It has been tested on Linux but should also work on Windows.
19
-
20
- .. image:: images/pizero.jpg
21
- :alt: Pi Zero Running the Server
22
- :align: center
23
-
24
- Setting the Time
25
- ----------------
26
- 1. Ensure the app is running on your device.
27
- 2. Short-press the **lower-right** button on your G-Shock watch to connect.
28
- 3. Once connected, the app will automatically set the correct time on your watch.
29
- 4. If **AUTO TIME ADJUSTMENT** is enabled, the watch will sync up to **four times daily**.
30
-
31
- Dependencies
32
- ============
33
-
34
- This project requires the following Python packages:
35
- ```
36
- pytz
37
- bleak
38
- ```
39
-
40
- So you can install them using the following command:
41
- ```
42
- pip3 install -r requirements.txt
43
- ```
44
-
45
- Virtual Environment Setup
46
- =========================
47
- To run the server in a virtual environment:
48
-
49
- 1. Create a virtual environment:
50
-
51
- .. code-block:: sh
52
-
53
- # Create a virtual environment
54
- python3 -m venv myenv
55
-
56
- # Activate it (Mac/Linux)
57
- source myenv/bin/activate
58
-
59
- # Install dependencies
60
- pip3 install -e .
61
-
62
- 2. Run the tests:
63
-
64
- .. code-block:: sh
65
-
66
- python3 src/examples/api_tests.py [--multi-watch]
67
-
68
- 3. Run the server:
69
-
70
- .. code-block:: sh
71
-
72
- python3 src/examples/gshock_server.py [--multi-watch]
73
-
74
- The optional **`--multi-watch`** parameter allows you to connect if you have multiple watches.
75
-
76
-
77
- Installing the library for your project:
78
- ========================================
79
-
80
- ```
81
- pip3 install gshock-api
82
- ```
83
-
84
- Troubleshooting:
85
- ================
86
- If your watch cannot connect, and the
87
- **`--multi-watch`** parameter is not used, remove the **`config.ini`** file and try again.
@@ -1,4 +0,0 @@
1
- # filepath: gshock_api/src/gshock_api/core.py
2
- def example_function():
3
- return "Hello from gshock_api core!"
4
-
@@ -1,100 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: gshock_api
3
- Version: 2.0.2
4
- Summary: Pythgon API for GShock Watches using BLE
5
- Home-page: https://github.com/pyscaffold/pyscaffold/
6
- Author: Ivo Zivkov
7
- Author-email: Ivo Zivkov <izivkov@gmail.com>
8
- Platform: any
9
- Description-Content-Type: text/x-rst
10
- License-File: LICENSE.txt
11
- Requires-Dist: requests
12
- Dynamic: license-file
13
-
14
- GShockTimeServer
15
- ================
16
-
17
- Overview
18
- ========
19
- GShockTimeServer allows you to set the correct time on your Casio G-Shock watches, including:
20
-
21
- - `B5600 <https://amzn.to/3Mt68Qb>`__
22
- - `B5000 <https://amzn.to/4194M13>`__
23
- - `B2100 <https://amzn.to/3MUDCGY>`__
24
-
25
- Additionally, this repository provides an API for developing applications for these watches.
26
- While still a work in progress, you can refer to the ``api_tests.py`` file for usage examples.
27
-
28
- Usage
29
- =====
30
- This app can run on any device with Python and Bluetooth capabilities—from a desktop to a Raspberry Pi Zero.
31
- It has been tested on Linux but should also work on Windows.
32
-
33
- .. image:: images/pizero.jpg
34
- :alt: Pi Zero Running the Server
35
- :align: center
36
-
37
- Setting the Time
38
- ----------------
39
- 1. Ensure the app is running on your device.
40
- 2. Short-press the **lower-right** button on your G-Shock watch to connect.
41
- 3. Once connected, the app will automatically set the correct time on your watch.
42
- 4. If **AUTO TIME ADJUSTMENT** is enabled, the watch will sync up to **four times daily**.
43
-
44
- Dependencies
45
- ============
46
-
47
- This project requires the following Python packages:
48
- ```
49
- pytz
50
- bleak
51
- ```
52
-
53
- So you can install them using the following command:
54
- ```
55
- pip3 install -r requirements.txt
56
- ```
57
-
58
- Virtual Environment Setup
59
- =========================
60
- To run the server in a virtual environment:
61
-
62
- 1. Create a virtual environment:
63
-
64
- .. code-block:: sh
65
-
66
- # Create a virtual environment
67
- python3 -m venv myenv
68
-
69
- # Activate it (Mac/Linux)
70
- source myenv/bin/activate
71
-
72
- # Install dependencies
73
- pip3 install -e .
74
-
75
- 2. Run the tests:
76
-
77
- .. code-block:: sh
78
-
79
- python3 src/examples/api_tests.py [--multi-watch]
80
-
81
- 3. Run the server:
82
-
83
- .. code-block:: sh
84
-
85
- python3 src/examples/gshock_server.py [--multi-watch]
86
-
87
- The optional **`--multi-watch`** parameter allows you to connect if you have multiple watches.
88
-
89
-
90
- Installing the library for your project:
91
- ========================================
92
-
93
- ```
94
- pip3 install gshock-api
95
- ```
96
-
97
- Troubleshooting:
98
- ================
99
- If your watch cannot connect, and the
100
- **`--multi-watch`** parameter is not used, remove the **`config.ini`** file and try again.
File without changes
File without changes