easycoder 260110.1__py2.py3-none-any.whl → 260118.3__py2.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.
Potentially problematic release.
This version of easycoder might be problematic. Click here for more details.
- easycoder/__init__.py +2 -1
- easycoder/ec_classes.py +11 -2
- easycoder/ec_compiler.py +1 -0
- easycoder/ec_core.py +100 -50
- easycoder/ec_gclasses.py +36 -9
- easycoder/ec_graphics.py +65 -12
- easycoder/ec_mqtt.py +435 -0
- easycoder/ec_program.py +30 -13
- easycoder/ec_value.py +4 -3
- easycoder/pre/ec_core.py +8 -9
- easycoder/pre/ec_program.py +1 -1
- easycoder/pre/ec_value.py +1 -1
- {easycoder-260110.1.dist-info → easycoder-260118.3.dist-info}/METADATA +2 -1
- {easycoder-260110.1.dist-info → easycoder-260118.3.dist-info}/RECORD +17 -16
- {easycoder-260110.1.dist-info → easycoder-260118.3.dist-info}/WHEEL +0 -0
- {easycoder-260110.1.dist-info → easycoder-260118.3.dist-info}/entry_points.txt +0 -0
- {easycoder-260110.1.dist-info → easycoder-260118.3.dist-info}/licenses/LICENSE +0 -0
easycoder/ec_mqtt.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
from easycoder import Handler, ECObject, ECValue, RuntimeError
|
|
2
|
+
import paho.mqtt.client as mqtt
|
|
3
|
+
import time
|
|
4
|
+
import threading
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
#############################################################################
|
|
8
|
+
# MQTT client class
|
|
9
|
+
class MQTTClient():
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__()
|
|
12
|
+
|
|
13
|
+
def create(self, program, clientID, broker, port, topics):
|
|
14
|
+
self.program = program
|
|
15
|
+
self.clientID = clientID
|
|
16
|
+
self.broker = broker
|
|
17
|
+
self.port = port
|
|
18
|
+
self.topics = topics
|
|
19
|
+
self.onConnectPC = None
|
|
20
|
+
self.onMessagePC = None
|
|
21
|
+
self.timeout = False
|
|
22
|
+
self.messages = {}
|
|
23
|
+
self.chunked_messages = {} # Store incoming chunked messages {topic: {part_num: data}}
|
|
24
|
+
self.confirmation_lock = threading.Lock()
|
|
25
|
+
self.chunk_size = 1024 # Default chunk size
|
|
26
|
+
self.last_send_time = None # Time taken for last message transmission (seconds)
|
|
27
|
+
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=self.clientID) # type: ignore
|
|
28
|
+
|
|
29
|
+
# Setup callbacks
|
|
30
|
+
self.client.on_connect = self.on_connect
|
|
31
|
+
self.client.on_message = self.on_message
|
|
32
|
+
|
|
33
|
+
def on_connect(self, client, userdata, flags, reason_code, properties):
|
|
34
|
+
print(f"Client {self.clientID} connected")
|
|
35
|
+
for item in self.topics:
|
|
36
|
+
topic = self.program.getObject(self.program.getVariable(item))
|
|
37
|
+
self.client.subscribe(topic.getName(), qos=topic.getQoS())
|
|
38
|
+
print(f"Subscribed to topic: {topic.getName()} with QoS {topic.getQoS()}")
|
|
39
|
+
|
|
40
|
+
if self.onConnectPC is not None:
|
|
41
|
+
self.program.run(self.onConnectPC)
|
|
42
|
+
self.program.flushCB()
|
|
43
|
+
|
|
44
|
+
def on_message(self, client, userdata, msg):
|
|
45
|
+
payload = msg.payload.decode('utf-8')
|
|
46
|
+
topic = msg.topic
|
|
47
|
+
|
|
48
|
+
# Check if this is a chunked message (format: "!part!<n> <total><data>")
|
|
49
|
+
if payload.startswith('!part!'):
|
|
50
|
+
# Extract: "!part!<n> <total><data>"
|
|
51
|
+
header_end = payload.find(' ', 6) # Find space after part number
|
|
52
|
+
if header_end > 6:
|
|
53
|
+
try:
|
|
54
|
+
part_num = int(payload[6:header_end]) # Extract part number
|
|
55
|
+
# Find next space to get total_chunks
|
|
56
|
+
total_end = payload.find(' ', header_end + 1)
|
|
57
|
+
if total_end > header_end:
|
|
58
|
+
total_chunks = int(payload[header_end + 1:total_end])
|
|
59
|
+
data = payload[total_end + 1:] # Rest is data
|
|
60
|
+
|
|
61
|
+
# Initialize chunked message storage if this is part 0
|
|
62
|
+
if part_num == 0:
|
|
63
|
+
self.chunked_messages[topic] = {}
|
|
64
|
+
|
|
65
|
+
# Store this chunk with its part number
|
|
66
|
+
if topic in self.chunked_messages:
|
|
67
|
+
self.chunked_messages[topic][part_num] = data
|
|
68
|
+
print(f"Received chunk {part_num}/{total_chunks - 1} on topic {topic}")
|
|
69
|
+
except (ValueError, IndexError):
|
|
70
|
+
pass
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
elif payload.startswith('!last!'):
|
|
74
|
+
# Final chunk: "!last!<total><data>"
|
|
75
|
+
try:
|
|
76
|
+
# Find where the total ends and data begins (first space after !last!)
|
|
77
|
+
space_pos = payload.find(' ', 6)
|
|
78
|
+
if space_pos > 6:
|
|
79
|
+
total_chunks = int(payload[6:space_pos])
|
|
80
|
+
data = payload[space_pos + 1:] # Rest is data
|
|
81
|
+
|
|
82
|
+
# Initialize topic storage if not present (single chunk case)
|
|
83
|
+
if topic not in self.chunked_messages:
|
|
84
|
+
self.chunked_messages[topic] = {}
|
|
85
|
+
|
|
86
|
+
# Store the last chunk
|
|
87
|
+
self.chunked_messages[topic][total_chunks - 1] = data
|
|
88
|
+
|
|
89
|
+
# Verify all chunks are present
|
|
90
|
+
expected_parts = set(range(total_chunks))
|
|
91
|
+
received_parts = set(self.chunked_messages[topic].keys())
|
|
92
|
+
|
|
93
|
+
if expected_parts == received_parts:
|
|
94
|
+
# All chunks received - assemble complete message
|
|
95
|
+
message_parts = [self.chunked_messages[topic][i] for i in sorted(self.chunked_messages[topic].keys())]
|
|
96
|
+
complete_message = ''.join(message_parts)
|
|
97
|
+
del self.chunked_messages[topic]
|
|
98
|
+
|
|
99
|
+
# Confirmation is now handled at the EasyCoder level
|
|
100
|
+
# print(f"All chunks received for topic {topic} ({len(complete_message)} bytes total).")
|
|
101
|
+
self.message = {"topic": topic, "payload": complete_message}
|
|
102
|
+
|
|
103
|
+
if self.onMessagePC is not None:
|
|
104
|
+
# print(f'Run from PC {self.onMessagePC}')
|
|
105
|
+
self.program.run(self.onMessagePC)
|
|
106
|
+
self.program.flushCB()
|
|
107
|
+
else:
|
|
108
|
+
missing = expected_parts - received_parts
|
|
109
|
+
print(f"Warning: Missing chunks {missing} for topic {topic}")
|
|
110
|
+
except (ValueError, IndexError):
|
|
111
|
+
pass
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
def getMessageTopic(self):
|
|
115
|
+
return self.message.topic # type: ignore
|
|
116
|
+
|
|
117
|
+
def getReceivedMessage(self):
|
|
118
|
+
return self.message
|
|
119
|
+
|
|
120
|
+
def onMessage(self, pc):
|
|
121
|
+
self.onMessagePC = pc
|
|
122
|
+
|
|
123
|
+
def sendMessage(self, topic, message, qos, chunk_size=0):
|
|
124
|
+
"""Send a message, optionally chunked if chunk_size > 0
|
|
125
|
+
|
|
126
|
+
Stores transmission time in self.last_send_time (seconds)
|
|
127
|
+
"""
|
|
128
|
+
send_start = time.time()
|
|
129
|
+
|
|
130
|
+
# Send message in chunks using selected strategy
|
|
131
|
+
message_len = len(message)
|
|
132
|
+
num_chunks = (message_len + chunk_size - 1) // chunk_size
|
|
133
|
+
|
|
134
|
+
# print(f'Sending message ({message_len} bytes) in {num_chunks} chunks of size {chunk_size} to topic {topic} with QoS {qos}')
|
|
135
|
+
|
|
136
|
+
self._send_rapid_fire(topic, message, qos, chunk_size, num_chunks)
|
|
137
|
+
|
|
138
|
+
self.last_send_time = time.time() - send_start
|
|
139
|
+
print(f'Message transmission complete in {self.last_send_time:.3f} seconds')
|
|
140
|
+
|
|
141
|
+
def _send_rapid_fire(self, topic, message, qos, chunk_size, num_chunks):
|
|
142
|
+
"""Send all chunks as rapidly as possible, wait for single final confirmation"""
|
|
143
|
+
# print(f"Sending all {num_chunks} chunks as fast as possible...")
|
|
144
|
+
|
|
145
|
+
# Send all chunks rapidly
|
|
146
|
+
for i in range(num_chunks):
|
|
147
|
+
start = i * chunk_size
|
|
148
|
+
end = min(start + chunk_size, len(message))
|
|
149
|
+
chunk_data = message[start:end]
|
|
150
|
+
|
|
151
|
+
# Prepare chunk with header: "!part!<n> <total><data>" or "!last!<total><data>"
|
|
152
|
+
if i == num_chunks - 1:
|
|
153
|
+
chunk_msg = f"!last!{num_chunks} {chunk_data}"
|
|
154
|
+
else:
|
|
155
|
+
chunk_msg = f"!part!{i} {num_chunks} {chunk_data}"
|
|
156
|
+
|
|
157
|
+
# Send without waiting
|
|
158
|
+
self.client.publish(topic, chunk_msg, qos=qos)
|
|
159
|
+
# print(f"Sent chunk {i}/{num_chunks - 1} to topic {topic} with QoS {qos}: {chunk_msg}")
|
|
160
|
+
# No waiting here; confirmations are handled in EasyCoder using sender identity
|
|
161
|
+
|
|
162
|
+
# Start the MQTT client loop
|
|
163
|
+
def run(self):
|
|
164
|
+
self.client.connect(self.broker, int(self.port), 60)
|
|
165
|
+
self.client.loop_start()
|
|
166
|
+
|
|
167
|
+
###############################################################################
|
|
168
|
+
# An MQTT topic
|
|
169
|
+
class ECTopic(ECObject):
|
|
170
|
+
def __init__(self):
|
|
171
|
+
super().__init__()
|
|
172
|
+
|
|
173
|
+
def getName(self):
|
|
174
|
+
v = self.getValue()
|
|
175
|
+
if v is None:
|
|
176
|
+
return ""
|
|
177
|
+
if v is None:
|
|
178
|
+
return ""
|
|
179
|
+
return v['name']
|
|
180
|
+
|
|
181
|
+
def getQoS(self):
|
|
182
|
+
v = self.getValue()
|
|
183
|
+
if v is None:
|
|
184
|
+
return 0
|
|
185
|
+
if v is None:
|
|
186
|
+
return 0
|
|
187
|
+
return int(v['qos'])
|
|
188
|
+
|
|
189
|
+
def textify(self):
|
|
190
|
+
v = self.getValue()
|
|
191
|
+
if v is None:
|
|
192
|
+
return ""
|
|
193
|
+
return f'{{"name": "{v["name"]}", "qos": {v["qos"]}}}'
|
|
194
|
+
|
|
195
|
+
###############################################################################
|
|
196
|
+
###############################################################################
|
|
197
|
+
# The MQTT compiler and runtime handlers
|
|
198
|
+
class MQTT(Handler):
|
|
199
|
+
|
|
200
|
+
def __init__(self, compiler):
|
|
201
|
+
Handler.__init__(self, compiler)
|
|
202
|
+
self.spoke = None
|
|
203
|
+
|
|
204
|
+
def getName(self):
|
|
205
|
+
return 'mqtt'
|
|
206
|
+
|
|
207
|
+
#############################################################################
|
|
208
|
+
# Keyword handlers
|
|
209
|
+
|
|
210
|
+
# init {topic} name {name} qos {qos}
|
|
211
|
+
def k_init(self, command):
|
|
212
|
+
if self.nextIsSymbol():
|
|
213
|
+
record = self.getSymbolRecord()
|
|
214
|
+
self.checkObjectType(record, ECTopic)
|
|
215
|
+
command['topic'] = record['name']
|
|
216
|
+
self.skip('name')
|
|
217
|
+
name =self.nextValue()
|
|
218
|
+
command['name'] = name
|
|
219
|
+
self.skip('qos')
|
|
220
|
+
command['qos'] = self.nextValue()
|
|
221
|
+
self.add(command)
|
|
222
|
+
return True
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
def r_init(self, command):
|
|
226
|
+
record = self.getVariable(command['topic'])
|
|
227
|
+
topic = ECTopic()
|
|
228
|
+
value = {}
|
|
229
|
+
value['name'] = self.textify(command['name'])
|
|
230
|
+
value['qos'] = int(self.textify(command['qos']))
|
|
231
|
+
topic.setValue(value)
|
|
232
|
+
record['object'] = topic
|
|
233
|
+
return self.nextPC()
|
|
234
|
+
|
|
235
|
+
# mqtt id {clientID} broker {broker} port {port} topics {topic} [and {topic} ...]
|
|
236
|
+
def k_mqtt(self, command):
|
|
237
|
+
while True:
|
|
238
|
+
token = self.peek()
|
|
239
|
+
if token == 'id':
|
|
240
|
+
self.nextToken()
|
|
241
|
+
command['clientID'] = self.nextValue()
|
|
242
|
+
elif token == 'broker':
|
|
243
|
+
self.nextToken()
|
|
244
|
+
command['broker'] = self.nextValue()
|
|
245
|
+
elif token == 'port':
|
|
246
|
+
self.nextToken()
|
|
247
|
+
command['port'] = self.nextValue()
|
|
248
|
+
elif token == 'topics':
|
|
249
|
+
self.nextToken()
|
|
250
|
+
topics = []
|
|
251
|
+
while self.nextIsSymbol():
|
|
252
|
+
record = self.getSymbolRecord()
|
|
253
|
+
self.checkObjectType(record, ECTopic())
|
|
254
|
+
topics.append(record['name'])
|
|
255
|
+
if self.peek() == 'and': self.nextToken()
|
|
256
|
+
else:break
|
|
257
|
+
command['topics'] = topics
|
|
258
|
+
else:
|
|
259
|
+
self.add(command)
|
|
260
|
+
return True
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
def r_mqtt(self, command):
|
|
264
|
+
if hasattr(self.program, 'mqttClient'):
|
|
265
|
+
raise RuntimeError(self.program, 'MQQT client already defined')
|
|
266
|
+
clientID = self.textify(command['clientID'])
|
|
267
|
+
broker = self.textify(command['broker'])
|
|
268
|
+
port = self.textify(command['port'])
|
|
269
|
+
topics = command['topics']
|
|
270
|
+
client = MQTTClient()
|
|
271
|
+
client.create(self.program, clientID, broker, port, topics)
|
|
272
|
+
client.run()
|
|
273
|
+
self.program.mqttClient = client
|
|
274
|
+
return self.nextPC()
|
|
275
|
+
|
|
276
|
+
# on mqtt message {action}
|
|
277
|
+
def k_on(self, command):
|
|
278
|
+
token = self.peek()
|
|
279
|
+
if token == 'mqtt':
|
|
280
|
+
self.nextToken()
|
|
281
|
+
event = self.nextToken()
|
|
282
|
+
if event in ['connect', 'message']:
|
|
283
|
+
command['event'] = event
|
|
284
|
+
self.nextToken()
|
|
285
|
+
command['goto'] = 0
|
|
286
|
+
self.add(command)
|
|
287
|
+
cmd = {}
|
|
288
|
+
cmd['domain'] = 'core'
|
|
289
|
+
cmd['lino'] = command['lino']
|
|
290
|
+
cmd['keyword'] = 'gotoPC'
|
|
291
|
+
cmd['goto'] = 0
|
|
292
|
+
cmd['debug'] = False
|
|
293
|
+
self.add(cmd)
|
|
294
|
+
# Add the action and a 'stop'
|
|
295
|
+
self.compileOne()
|
|
296
|
+
cmd = {}
|
|
297
|
+
cmd['domain'] = 'core'
|
|
298
|
+
cmd['lino'] = command['lino']
|
|
299
|
+
cmd['keyword'] = 'stop'
|
|
300
|
+
cmd['debug'] = False
|
|
301
|
+
self.add(cmd)
|
|
302
|
+
# Fixup the link
|
|
303
|
+
command['goto'] = self.getCodeSize()
|
|
304
|
+
return True
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
def r_on(self, command):
|
|
308
|
+
event = command['event']
|
|
309
|
+
if event == 'connect':
|
|
310
|
+
self.program.mqttClient.onConnectPC = self.nextPC()+1
|
|
311
|
+
elif event == 'message':
|
|
312
|
+
self.program.mqttClient.onMessagePC = self.nextPC()+1
|
|
313
|
+
return command['goto']
|
|
314
|
+
|
|
315
|
+
# send {message} to {topic}
|
|
316
|
+
def k_send(self, command):
|
|
317
|
+
if self.nextIs('mqtt'):
|
|
318
|
+
command['message'] = self.nextValue()
|
|
319
|
+
self.skip('to')
|
|
320
|
+
if self.nextIsSymbol():
|
|
321
|
+
record = self.getSymbolRecord()
|
|
322
|
+
self.checkObjectType(record, MQTTClient)
|
|
323
|
+
command['to'] = record['name']
|
|
324
|
+
token = self.peek()
|
|
325
|
+
if token == 'with':
|
|
326
|
+
self.nextToken()
|
|
327
|
+
while True:
|
|
328
|
+
token = self.nextToken()
|
|
329
|
+
if token == 'qos':
|
|
330
|
+
command['qos'] = self.nextValue()
|
|
331
|
+
if self.peek() == 'and':
|
|
332
|
+
self.nextToken()
|
|
333
|
+
else:
|
|
334
|
+
break
|
|
335
|
+
self.add(command)
|
|
336
|
+
return True
|
|
337
|
+
return False
|
|
338
|
+
|
|
339
|
+
def r_send(self, command):
|
|
340
|
+
if not hasattr(self.program, 'mqttClient'):
|
|
341
|
+
raise RuntimeError(self.program, 'No MQTT client defined')
|
|
342
|
+
topic = self.getObject(self.getVariable(command['to']))
|
|
343
|
+
message = self.textify(command['message'])
|
|
344
|
+
|
|
345
|
+
# Validate that outgoing message is valid JSON with required 'sender' and 'action' fields
|
|
346
|
+
try:
|
|
347
|
+
payload_dict = json.loads(message)
|
|
348
|
+
if not isinstance(payload_dict, dict):
|
|
349
|
+
raise RuntimeError(self.program, f'MQTT message must be a JSON object, got {type(payload_dict).__name__}')
|
|
350
|
+
|
|
351
|
+
# sender can be a string (topic name) or a dict with 'name' and 'qos' keys
|
|
352
|
+
if 'sender' not in payload_dict:
|
|
353
|
+
raise RuntimeError(self.program, 'MQTT message must contain "sender" field')
|
|
354
|
+
sender_val = payload_dict.get('sender')
|
|
355
|
+
if isinstance(sender_val, str):
|
|
356
|
+
pass # sender is a string (topic name), valid
|
|
357
|
+
elif isinstance(sender_val, dict):
|
|
358
|
+
if 'name' not in sender_val or not isinstance(sender_val.get('name'), str):
|
|
359
|
+
raise RuntimeError(self.program, 'MQTT message "sender" dict must contain "name" field as string')
|
|
360
|
+
else:
|
|
361
|
+
raise RuntimeError(self.program, f'MQTT message "sender" must be a string or dict, got {type(sender_val).__name__}')
|
|
362
|
+
|
|
363
|
+
# action must be a string
|
|
364
|
+
if 'action' not in payload_dict or not isinstance(payload_dict.get('action'), str):
|
|
365
|
+
raise RuntimeError(self.program, 'MQTT message must contain "action" field as string')
|
|
366
|
+
except json.JSONDecodeError as e:
|
|
367
|
+
raise RuntimeError(self.program, f'MQTT message must be valid JSON: {e}')
|
|
368
|
+
|
|
369
|
+
if 'qos' in command:
|
|
370
|
+
qos = int(self.textify(command['qos']))
|
|
371
|
+
else:
|
|
372
|
+
qos = topic.qos if hasattr(topic, 'qos') else 1
|
|
373
|
+
self.program.mqttClient.sendMessage(topic.getName(), message, qos, chunk_size=1024)
|
|
374
|
+
if self.program.mqttClient.timeout:
|
|
375
|
+
return 0
|
|
376
|
+
return self.nextPC()
|
|
377
|
+
|
|
378
|
+
# Declare a topic variable
|
|
379
|
+
def k_topic(self, command):
|
|
380
|
+
self.compiler.addValueType()
|
|
381
|
+
return self.compileVariable(command, 'ECTopic')
|
|
382
|
+
|
|
383
|
+
def r_topic(self, command):
|
|
384
|
+
return self.nextPC()
|
|
385
|
+
|
|
386
|
+
#############################################################################
|
|
387
|
+
# Compile a value in this domain
|
|
388
|
+
def compileValue(self):
|
|
389
|
+
token = self.getToken()
|
|
390
|
+
if token == 'the':
|
|
391
|
+
token = self.nextToken()
|
|
392
|
+
if self.isSymbol():
|
|
393
|
+
record = self.getSymbolRecord()
|
|
394
|
+
object = self.getObject(record)
|
|
395
|
+
if isinstance(object, ECTopic):
|
|
396
|
+
return ECValue(domain=self.getName(), type='topic', content=record['name'])
|
|
397
|
+
else: return None
|
|
398
|
+
else:
|
|
399
|
+
if token == 'mqtt':
|
|
400
|
+
token = self.nextToken()
|
|
401
|
+
if token == 'message':
|
|
402
|
+
return ECValue(domain=self.getName(), type='mqtt', content=token)
|
|
403
|
+
# else:
|
|
404
|
+
# return self.getValue()
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
#############################################################################
|
|
408
|
+
# Modify a value or leave it unchanged.
|
|
409
|
+
def modifyValue(self, value):
|
|
410
|
+
return value
|
|
411
|
+
|
|
412
|
+
#############################################################################
|
|
413
|
+
# Value handlers
|
|
414
|
+
|
|
415
|
+
def v_message(self, v):
|
|
416
|
+
return self.program.mqttClient.message
|
|
417
|
+
|
|
418
|
+
def v_mqtt(self, v):
|
|
419
|
+
content = v.getContent()
|
|
420
|
+
if content == 'message':
|
|
421
|
+
return self.program.mqttClient.message
|
|
422
|
+
return None
|
|
423
|
+
|
|
424
|
+
def v_topic(self, v):
|
|
425
|
+
topic = self.getObject(self.getVariable(self.textify(v.getContent())))
|
|
426
|
+
return f'{{"name": "{topic.getName()}", "qos": {topic.getQoS()}}}'
|
|
427
|
+
|
|
428
|
+
#############################################################################
|
|
429
|
+
# Compile a condition
|
|
430
|
+
def compileCondition(self):
|
|
431
|
+
condition = {}
|
|
432
|
+
return condition
|
|
433
|
+
|
|
434
|
+
#############################################################################
|
|
435
|
+
# Condition handlers
|
easycoder/ec_program.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import time, sys, json
|
|
1
|
+
import time, sys, json, traceback
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from collections import deque
|
|
4
|
+
|
|
4
5
|
from .ec_classes import (
|
|
5
6
|
Script,
|
|
6
7
|
Token,
|
|
@@ -60,6 +61,7 @@ class Program:
|
|
|
60
61
|
self.value = self.compiler.value
|
|
61
62
|
self.condition = self.compiler.condition
|
|
62
63
|
self.graphics = None
|
|
64
|
+
self.mqtt = None
|
|
63
65
|
self.psutil = None
|
|
64
66
|
self.useClass(Core)
|
|
65
67
|
self.ticker = 0
|
|
@@ -93,7 +95,7 @@ class Program:
|
|
|
93
95
|
f'{round((finishCompile - startCompile) * 1000)} ms')
|
|
94
96
|
for name in self.symbols.keys():
|
|
95
97
|
record = self.code[self.symbols[name]]
|
|
96
|
-
if name[-1] != ':' and not 'used'
|
|
98
|
+
if name[-1] != ':' and not record['used']:
|
|
97
99
|
print(f'Variable "{name}" not used')
|
|
98
100
|
else:
|
|
99
101
|
print(f'Run {self.name}')
|
|
@@ -119,6 +121,15 @@ class Program:
|
|
|
119
121
|
self.useClass(Graphics)
|
|
120
122
|
return True
|
|
121
123
|
|
|
124
|
+
# Use the MQTT module
|
|
125
|
+
def useMQTT(self):
|
|
126
|
+
if self.mqtt == None:
|
|
127
|
+
print('Loading MQTT module')
|
|
128
|
+
from .ec_mqtt import MQTT
|
|
129
|
+
self.mqtt = MQTT
|
|
130
|
+
self.useClass(MQTT)
|
|
131
|
+
return True
|
|
132
|
+
|
|
122
133
|
# Use the psutil module
|
|
123
134
|
def usePSUtil(self):
|
|
124
135
|
if self.psutil == None:
|
|
@@ -243,7 +254,7 @@ class Program:
|
|
|
243
254
|
# Test if an item is a string or a number
|
|
244
255
|
def getItemType(self, value):
|
|
245
256
|
return int if isinstance(value, int) else str
|
|
246
|
-
|
|
257
|
+
|
|
247
258
|
# Get the value of an item that may be an ECValue or a raw value. Return as an ECValue
|
|
248
259
|
def getValueOf(self, item):
|
|
249
260
|
value = ECValue()
|
|
@@ -261,6 +272,15 @@ class Program:
|
|
|
261
272
|
elif varType == 'dict': value.setValue(type=dict, content=item)
|
|
262
273
|
else: value.setValue(type=None, content=None)
|
|
263
274
|
return value
|
|
275
|
+
|
|
276
|
+
# Get the value of an item from its domain handler
|
|
277
|
+
def textifyInDomain(self, value):
|
|
278
|
+
domainName = value.getDomain() # type: ignore
|
|
279
|
+
if domainName == None: domainName = 'core'
|
|
280
|
+
domain = self.domainIndex[domainName]
|
|
281
|
+
handler = domain.valueHandler(value.getType()) # type: ignore
|
|
282
|
+
result = handler(value) if handler else None
|
|
283
|
+
return result
|
|
264
284
|
|
|
265
285
|
# Runtime function to evaluate an ECObject or ECValue. Returns another ECValue
|
|
266
286
|
# This function may be called recursively by value handlers.
|
|
@@ -289,7 +309,7 @@ class Program:
|
|
|
289
309
|
|
|
290
310
|
elif valType == 'symbol': # type: ignore
|
|
291
311
|
# If it's a symbol, get its value
|
|
292
|
-
record = self.getVariable(value.
|
|
312
|
+
record = self.getVariable(value.getName()) # type: ignore
|
|
293
313
|
if not 'object' in record: return None # type: ignore
|
|
294
314
|
variable = self.getObject(record) # type: ignore
|
|
295
315
|
result = variable.getValue() # type: ignore
|
|
@@ -316,12 +336,7 @@ class Program:
|
|
|
316
336
|
result.setValue(type=str, content=content)
|
|
317
337
|
|
|
318
338
|
else:
|
|
319
|
-
|
|
320
|
-
domainName = value.getDomain() # type: ignore
|
|
321
|
-
if domainName == None: domainName = 'core'
|
|
322
|
-
domain = self.domainIndex[domainName]
|
|
323
|
-
handler = domain.valueHandler(value.getType()) # type: ignore
|
|
324
|
-
if handler: result = handler(value)
|
|
339
|
+
result = self.textifyInDomain(value)
|
|
325
340
|
|
|
326
341
|
return result
|
|
327
342
|
|
|
@@ -334,7 +349,7 @@ class Program:
|
|
|
334
349
|
if isinstance(value, dict):
|
|
335
350
|
value = value['object']
|
|
336
351
|
if isinstance(value, ECObject):
|
|
337
|
-
value = value.
|
|
352
|
+
value = value.textify() # type: ignore
|
|
338
353
|
if isinstance(value, ECValue): # type: ignore
|
|
339
354
|
v = self.evaluate(value) # type: ignore
|
|
340
355
|
else:
|
|
@@ -344,6 +359,8 @@ class Program:
|
|
|
344
359
|
if v.getType() == 'object':
|
|
345
360
|
return value.getContent() # type: ignore
|
|
346
361
|
return v.getContent()
|
|
362
|
+
elif isinstance(v, ECObject):
|
|
363
|
+
return v.textify() # type: ignore
|
|
347
364
|
if isinstance(v, (dict, list)):
|
|
348
365
|
return json.dumps(v)
|
|
349
366
|
return v
|
|
@@ -363,7 +380,6 @@ class Program:
|
|
|
363
380
|
value = object.getValue() # type: ignore
|
|
364
381
|
if value is None:
|
|
365
382
|
raise NoValueRuntimeError(self, f'Symbol "{record["name"]}" has no value')
|
|
366
|
-
# copy = deepcopy(value)
|
|
367
383
|
copy = ECValue(domain=value.getDomain(),type=value.getType(),content=deepcopy(value.getContent()))
|
|
368
384
|
return copy
|
|
369
385
|
|
|
@@ -478,7 +494,8 @@ class Program:
|
|
|
478
494
|
pass # Place a breakpoint here for a debugger to catch
|
|
479
495
|
self.pc = handler(command)
|
|
480
496
|
except Exception as e:
|
|
481
|
-
|
|
497
|
+
tb = traceback.format_exc()
|
|
498
|
+
raise RuntimeError(self, f'Error during execution of {domainName}:{keyword}: {str(e)}\n\nTraceback:\n{tb}')
|
|
482
499
|
# Deal with 'exit'
|
|
483
500
|
if self.pc == -1:
|
|
484
501
|
queue = deque()
|
easycoder/ec_value.py
CHANGED
|
@@ -22,11 +22,11 @@ class Value:
|
|
|
22
22
|
|
|
23
23
|
value = ECValue()
|
|
24
24
|
|
|
25
|
-
if token == 'true':
|
|
25
|
+
if token.lower() == 'true':
|
|
26
26
|
value.setValue(bool, True)
|
|
27
27
|
return value
|
|
28
28
|
|
|
29
|
-
if token == 'false':
|
|
29
|
+
if token.lower() == 'false':
|
|
30
30
|
value.setValue(bool, False)
|
|
31
31
|
return value
|
|
32
32
|
|
|
@@ -96,8 +96,9 @@ class Value:
|
|
|
96
96
|
self.compiler.warning(f'ec_value.compileValue: Cannot get the value of "{token}"')
|
|
97
97
|
return None
|
|
98
98
|
if item.getType() == 'symbol':
|
|
99
|
-
object = self.compiler.getSymbolRecord(item.
|
|
99
|
+
object = self.compiler.getSymbolRecord(item.getName())['object']
|
|
100
100
|
if not object.hasRuntimeValue(): return None
|
|
101
|
+
item.setContent(object.name)
|
|
101
102
|
|
|
102
103
|
if self.peek() == 'cat':
|
|
103
104
|
self.nextToken() # consume 'cat'
|
easycoder/pre/ec_core.py
CHANGED
|
@@ -69,7 +69,7 @@ class Core(Handler):
|
|
|
69
69
|
if not isinstance(self.getObject(record), ECVariable): return False
|
|
70
70
|
# If 'giving' comes next, this variable is the second value
|
|
71
71
|
if self.peek() == 'giving':
|
|
72
|
-
v2 = ECValue(type='symbol',
|
|
72
|
+
v2 = ECValue(type='symbol', name=record['name'])
|
|
73
73
|
command['value2'] = v2
|
|
74
74
|
self.nextToken()
|
|
75
75
|
# Now get the target variable
|
|
@@ -1655,8 +1655,7 @@ class Core(Handler):
|
|
|
1655
1655
|
self.checkObjectType(record, ECObject)
|
|
1656
1656
|
# If 'giving' comes next, this variable is the second value
|
|
1657
1657
|
if self.peek() == 'giving':
|
|
1658
|
-
v2 = ECValue(type='symbol')
|
|
1659
|
-
v2.setContent(record['name'])
|
|
1658
|
+
v2 = ECValue(type='symbol', name=record['name'])
|
|
1660
1659
|
command['value2'] = v2
|
|
1661
1660
|
self.nextToken()
|
|
1662
1661
|
# Now get the target variable
|
|
@@ -1912,7 +1911,7 @@ class Core(Handler):
|
|
|
1912
1911
|
value = ECValue()
|
|
1913
1912
|
token = self.getToken()
|
|
1914
1913
|
if self.isSymbol():
|
|
1915
|
-
value.setValue(type='symbol',
|
|
1914
|
+
value.setValue(type='symbol', name=token)
|
|
1916
1915
|
return value
|
|
1917
1916
|
|
|
1918
1917
|
value.setType(token)
|
|
@@ -1946,13 +1945,13 @@ class Core(Handler):
|
|
|
1946
1945
|
value.format = None
|
|
1947
1946
|
return value
|
|
1948
1947
|
|
|
1949
|
-
if token == '
|
|
1948
|
+
if token == 'item':
|
|
1950
1949
|
value.index = self.nextValue()
|
|
1951
1950
|
if self.nextToken() == 'of':
|
|
1952
1951
|
if self.nextIsSymbol():
|
|
1953
1952
|
record = self.getSymbolRecord()
|
|
1954
1953
|
self.checkObjectType(record['object'], ECList)
|
|
1955
|
-
value.target = ECValue(type='symbol',
|
|
1954
|
+
value.target = ECValue(type='symbol', name=record['name'])
|
|
1956
1955
|
return value
|
|
1957
1956
|
return None
|
|
1958
1957
|
|
|
@@ -2214,10 +2213,10 @@ class Core(Handler):
|
|
|
2214
2213
|
value = v
|
|
2215
2214
|
return value
|
|
2216
2215
|
|
|
2217
|
-
def
|
|
2216
|
+
def v_item(self, v):
|
|
2218
2217
|
index = self.textify(v.index)
|
|
2219
2218
|
targetName = v.target
|
|
2220
|
-
target = self.getVariable(targetName.
|
|
2219
|
+
target = self.getVariable(targetName.getName())
|
|
2221
2220
|
variable = self.getObject(target)
|
|
2222
2221
|
self.checkObjectType(variable, ECList)
|
|
2223
2222
|
content = variable.getContent()
|
|
@@ -2656,7 +2655,7 @@ class Core(Handler):
|
|
|
2656
2655
|
|
|
2657
2656
|
def c_empty(self, condition):
|
|
2658
2657
|
if condition.value1.getType() == 'symbol':
|
|
2659
|
-
record = self.getVariable(condition.value1.
|
|
2658
|
+
record = self.getVariable(condition.value1.name)
|
|
2660
2659
|
variable = self.getObject(record)
|
|
2661
2660
|
if isinstance(variable, (ECList, ECDictionary)):
|
|
2662
2661
|
comparison = variable.isEmpty()
|
easycoder/pre/ec_program.py
CHANGED
|
@@ -289,7 +289,7 @@ class Program:
|
|
|
289
289
|
|
|
290
290
|
elif valType == 'symbol': # type: ignore
|
|
291
291
|
# If it's a symbol, get its value
|
|
292
|
-
record = self.getVariable(value.
|
|
292
|
+
record = self.getVariable(value.getName()) # type: ignore
|
|
293
293
|
if not 'object' in record: return None # type: ignore
|
|
294
294
|
variable = self.getObject(record) # type: ignore
|
|
295
295
|
result = variable.getValue() # type: ignore
|
easycoder/pre/ec_value.py
CHANGED
|
@@ -96,7 +96,7 @@ class Value:
|
|
|
96
96
|
self.compiler.warning(f'ec_value.compileValue: Cannot get the value of "{token}"')
|
|
97
97
|
return None
|
|
98
98
|
if item.getType() == 'symbol':
|
|
99
|
-
object = self.compiler.getSymbolRecord(item.
|
|
99
|
+
object = self.compiler.getSymbolRecord(item.getName())['object']
|
|
100
100
|
if not object.hasRuntimeValue(): return None
|
|
101
101
|
|
|
102
102
|
if self.peek() == 'cat':
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easycoder
|
|
3
|
-
Version:
|
|
3
|
+
Version: 260118.3
|
|
4
4
|
Summary: Rapid scripting in English
|
|
5
5
|
Keywords: compiler,scripting,prototyping,programming,coding,python,low code,hypertalk,computer language,learn to code
|
|
6
6
|
Author-email: Graham Trott <gtanyware@gmail.com>
|
|
@@ -12,6 +12,7 @@ Requires-Dist: requests
|
|
|
12
12
|
Requires-Dist: psutil
|
|
13
13
|
Requires-Dist: paramiko
|
|
14
14
|
Requires-Dist: pyside6
|
|
15
|
+
Requires-Dist: paho-mqtt
|
|
15
16
|
Project-URL: Home, https://github.com/easycoder/easycoder-py
|
|
16
17
|
|
|
17
18
|
# Introduction
|