spelliot 1.0.0__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.
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: spelliot
3
+ Version: 1.0.0
4
+ Summary: Spell IoT Cloud Library for Python and Raspberry Pi
5
+ Author: Karthickraja (Petals Automation)
6
+ Author-email: petalsautomationembedded@gmail.com
7
+ Maintainer: Petals Automation
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Dist: websocket-client
11
+ Requires-Dist: requests
12
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: classifier
15
+ Dynamic: maintainer
16
+ Dynamic: requires-dist
17
+ Dynamic: summary
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,19 @@
1
+ from setuptools import setup
2
+
3
+ setup(
4
+ name='spelliot',
5
+ version='1.0.0',
6
+ description='Spell IoT Cloud Library for Python and Raspberry Pi',
7
+ author='Karthickraja (Petals Automation)',
8
+ maintainer='Petals Automation',
9
+ author_email='petalsautomationembedded@gmail.com',
10
+ py_modules=['spell_iot'], # This tells pip to include spell_iot.py
11
+ install_requires=[
12
+ 'websocket-client',
13
+ 'requests'
14
+ ],
15
+ classifiers=[
16
+ 'Programming Language :: Python :: 3',
17
+ 'Operating System :: OS Independent',
18
+ ],
19
+ )
@@ -0,0 +1,208 @@
1
+ import websocket
2
+ import json
3
+ import time
4
+ import threading
5
+ import urllib.parse
6
+
7
+ class Spell_IoT:
8
+ def __init__(self):
9
+ self.ssid = ""
10
+ self.password = ""
11
+ self.device_token = ""
12
+ self.callbacks = {}
13
+ self.last_values = {}
14
+ self.last_rgb = {}
15
+ self.ws = None
16
+ self.ws_url = "wss://api.spelliot.com:443/ws-mobile"
17
+ self._connected = False
18
+
19
+ def begin(self, token, ssid="", password=""):
20
+ # ssid and password are not strictly needed on Raspberry Pi
21
+ # as it connects to the network via OS, but kept for compatibility with C++.
22
+ self.ssid = ssid
23
+ self.password = password
24
+ self.device_token = token
25
+
26
+ # Start the status thread
27
+ self._status_thread = threading.Thread(target=self._status_thread_func)
28
+ self._status_thread.daemon = True
29
+ self._status_thread.start()
30
+
31
+ def registerPin(self, pin, callback):
32
+ self.callbacks[pin.upper()] = callback
33
+
34
+ # --- READ FUNCTIONS ---
35
+ def read(self, pin):
36
+ return self.last_values.get(pin.upper(), "")
37
+
38
+ def readInt(self, pin):
39
+ val = self.read(pin)
40
+ try:
41
+ return int(val)
42
+ except ValueError:
43
+ return 0
44
+
45
+ def readBool(self, pin):
46
+ return self.read(pin) == "1"
47
+
48
+ def readRGB(self, pin):
49
+ # Returns a tuple (R, G, B)
50
+ return self.last_rgb.get(pin.upper(), (0, 0, 0))
51
+
52
+ # --- WRITE FUNCTIONS ---
53
+ def urlEncode(self, value):
54
+ return urllib.parse.quote(str(value))
55
+
56
+ def writeAck(self, pin, value):
57
+ if not self._connected or not self.ws:
58
+ return False
59
+
60
+ pin = pin.upper()
61
+ encodedValue = self.urlEncode(value)
62
+ fm = "0.0.0" # Firmware version equivalent
63
+ msg = (
64
+ "SEND\n"
65
+ f"destination:/app/device/{self.device_token}\n\n"
66
+ f"{pin}={encodedValue}&version={fm}\0"
67
+ )
68
+ self.ws.send(msg)
69
+ print(msg)
70
+ print(f"Data Sent -> Pin: {pin} | Value: {value}")
71
+ return True
72
+
73
+ def write(self, pin, value):
74
+ # Wrapper for writeAck just like in C++
75
+ return self.writeAck(pin, value)
76
+
77
+ def writeInternal(self, pin, value):
78
+ return self.writeAck(pin, value)
79
+
80
+ def writeRawWS(self, pin, value):
81
+ if self.ws and self._connected:
82
+ self.ws.send(str(value))
83
+
84
+ def Status(self):
85
+ return self._connected
86
+
87
+ def _status_thread_func(self):
88
+ while True:
89
+ if self._connected:
90
+ self.write("status", "Online")
91
+ time.sleep(10)
92
+
93
+ # --- SKYLINK DOCKER MEMORY (Stub for Pi) ---
94
+ def storeMemoryString(self, keyss, values):
95
+ print(f"[Docker Memory] Saving {keyss} = {values}")
96
+
97
+ def storeMemoryInt(self, keyss, values):
98
+ print(f"[Docker Memory] Saving {keyss} = {values}")
99
+
100
+ # --- OTA UPDATES (Stub for Pi) ---
101
+ def updates(self, url):
102
+ print(f"[SpellIoT Air Update] Update requested from URL: {url}")
103
+ print("Note: Air Updates (OTA) work differently on Raspberry Pi than ESP32.")
104
+ print("You can implement Python file downloading and script restarting here.")
105
+
106
+ # --- INTERNAL HELPERS ---
107
+ def _hex_to_rgb(self, hex_str):
108
+ hex_str = hex_str.lstrip('#')
109
+ if len(hex_str) != 6:
110
+ return (0, 0, 0)
111
+ try:
112
+ return tuple(int(hex_str[i:i+2], 16) for i in (0, 2, 4))
113
+ except:
114
+ return (0, 0, 0)
115
+
116
+ # --- WEBSOCKET EVENT HANDLERS ---
117
+ def _on_message(self, ws, message):
118
+ if message.startswith("CONNECTED"):
119
+ print("Spell-IoT Cloud CONNECTED")
120
+ self._connected = True
121
+ subscribe_frame = (
122
+ "SUBSCRIBE\n"
123
+ "id:sub-0\n"
124
+ "ack:auto\n"
125
+ f"destination:/topic/device/{self.device_token}\n\n\0"
126
+ )
127
+ ws.send(subscribe_frame)
128
+
129
+ elif message.startswith("MESSAGE"):
130
+ try:
131
+ parts = message.split("\n\n")
132
+ if len(parts) > 1:
133
+ body = parts[1].strip('\0')
134
+
135
+ # Prevent JSON decode error on empty STOMP frames (like heartbeat responses)
136
+ if not body.strip():
137
+ return
138
+
139
+ data = json.loads(body)
140
+ pin = data.get("pin", "").strip().upper()
141
+ val = data.get("value", "").strip()
142
+
143
+ print(f"Data Received <- Pin: {pin} | Value: {val}")
144
+
145
+ if pin == "AIR":
146
+ print("SpellIoT Air Update!")
147
+ self.updates(val)
148
+ else:
149
+ # Store cache
150
+ self.last_values[pin] = val
151
+ if val.startswith("#"):
152
+ self.last_rgb[pin] = self._hex_to_rgb(val)
153
+
154
+ # Call user callback if registered
155
+ if pin in self.callbacks:
156
+ self.callbacks[pin](val)
157
+
158
+ except Exception as e:
159
+ # Suppress the known empty body JSON error completely
160
+ if "Expecting value: line 1 column 1" not in str(e):
161
+ print(f"WS Error parsing: {e}")
162
+
163
+ def _on_error(self, ws, error):
164
+ print(f"WS Error: {error}")
165
+
166
+ def _on_close(self, ws, close_status_code, close_msg):
167
+ print("WS Disconnected!")
168
+ self._connected = False
169
+
170
+ def _on_open(self, ws):
171
+ connect_frame = (
172
+ "CONNECT\n"
173
+ "accept-version:1.2\n"
174
+ "host:api.spelliot.com\n"
175
+ "heart-beat:10000,0000\n\n\0"
176
+ )
177
+ ws.send(connect_frame)
178
+
179
+ def _run_ws(self):
180
+ while True:
181
+ self.ws = websocket.WebSocketApp(self.ws_url,
182
+ on_open=self._on_open,
183
+ on_message=self._on_message,
184
+ on_error=self._on_error,
185
+ on_close=self._on_close)
186
+ self.ws.run_forever()
187
+ print("Reconnecting in 5 seconds...")
188
+ time.sleep(5)
189
+
190
+ # --- LOOP FUNCTIONS ---
191
+ def autoRun(self):
192
+ """Starts the websocket client in a background thread (same as loop_start)"""
193
+ self.loop_start()
194
+
195
+ def loop_start(self):
196
+ """Starts the websocket client in a background thread"""
197
+ thread = threading.Thread(target=self._run_ws)
198
+ thread.daemon = True
199
+ thread.start()
200
+
201
+ def loop_forever(self):
202
+ """Starts the websocket client and blocks forever"""
203
+ self._run_ws()
204
+
205
+ def loop(self):
206
+ """Equivalent to C++ loop(), does nothing in Python since thread is already running"""
207
+ pass
208
+
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: spelliot
3
+ Version: 1.0.0
4
+ Summary: Spell IoT Cloud Library for Python and Raspberry Pi
5
+ Author: Karthickraja (Petals Automation)
6
+ Author-email: petalsautomationembedded@gmail.com
7
+ Maintainer: Petals Automation
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Dist: websocket-client
11
+ Requires-Dist: requests
12
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: classifier
15
+ Dynamic: maintainer
16
+ Dynamic: requires-dist
17
+ Dynamic: summary
@@ -0,0 +1,7 @@
1
+ setup.py
2
+ spell_iot.py
3
+ spelliot.egg-info/PKG-INFO
4
+ spelliot.egg-info/SOURCES.txt
5
+ spelliot.egg-info/dependency_links.txt
6
+ spelliot.egg-info/requires.txt
7
+ spelliot.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ websocket-client
2
+ requests
@@ -0,0 +1 @@
1
+ spell_iot