EnigmaIOT  0.9.8
Secure sensor and gateway platform based on ESP8266 and ESP32
EnigmaIoTUpdate.py
Go to the documentation of this file.
1 import base64
2 import paho.mqtt.client as mqtt
3 import time
4 import hashlib
5 import argparse
6 import os
7 import json
8 
9 # EnigmaIoTUpdate -f <file.bin> -d <address> -t <basetopic> -u <mqttuser> -P <mqttpass> -s <mqttserver>
10 # -p <mqttport> <-s> -D <speed>
11 
12 args = None
13 sleepyNode = True
14 resultTopic = "/result/#"
15 sleepSetTopic = "/set/sleeptime"
16 sleepResultTopic = "/result/sleeptime"
17 otaSetTopic = "/set/ota"
18 otaResultTopic = "/result/ota"
19 otaOutOfSequenceError = "OTA out of sequence error"
20 otaOK = "OTA finished OK"
21 # otaLength = 0
22 otaFinished = False
23 idx = 0
24 
25 OTA_OUT_OF_SEQUENCE = 4
26 OTA_FINISHED = 6
27 
28 
29 def on_connect(client, userdata, flags, rc):
30  global args
31 
32  if rc == 0:
33  print("Connected with result code " + str(rc))
34  mqtt.Client.connected_flag = True
35  else:
36  print("Error connecting. Code =" + str(rc))
37  return
38 
39  sleep_topic = args.baseTopic + "/" + args.address + resultTopic
40  client.subscribe(sleep_topic)
41  print("Subscribed")
42 
43 
44 def on_message(client, userdata, msg):
45  global sleepyNode
46  global idx, otaFinished
47 
48  payload = json.loads(msg.payload)
49 
50  if msg.topic.find(sleepResultTopic) >= 0 and payload['sleeptime'] == 0:
51  sleepyNode = False
52  print(msg.topic + " " + str(msg.payload))
53 
54  # payload = msg.payload.decode('utf-8')
55 
56  if msg.topic.find(otaResultTopic) >= 0:
57 
58  if payload['status'] == OTA_OUT_OF_SEQUENCE:
59  print(payload['last_chunk'], end='')
60  idx = int(payload['last_chunk'])
61 
62  elif payload['status'] == OTA_FINISHED:
63  print(" OTA Finished ", end='')
64  otaFinished = True
65 
66 
67 def main():
68  global args
69  global sleepyNode
70  global otaFinished
71 
72  opt = argparse.ArgumentParser(description='This program allows updating EnigmaIOT node over the air using'
73  'MQTT messages.')
74  opt.add_argument("-f", "--file",
75  type=str,
76  dest="filename",
77  default="program.bin",
78  help="File to program into device")
79  opt.add_argument("-d", "--daddress",
80  type=str,
81  dest="address",
82  help="Device address")
83  opt.add_argument("-t", "--topic",
84  type=str,
85  dest="baseTopic",
86  default="enigmaiot",
87  help="Base topic for MQTT messages")
88  opt.add_argument("-u", "--user",
89  type=str,
90  dest="mqttUser",
91  default="",
92  help="MQTT server username")
93  opt.add_argument("-P", "--password",
94  type=str,
95  dest="mqttPass",
96  default="",
97  help="MQTT server user password")
98  opt.add_argument("-S", "--server",
99  type=str,
100  dest="mqttServer",
101  default="127.0.0.1",
102  help="MQTT server address or name")
103  opt.add_argument("-p", "--port",
104  type=int,
105  dest="mqttPort",
106  default=1883,
107  help="MQTT server port")
108  opt.add_argument("-s", "--secure",
109  action="store_true",
110  dest="mqttSecure",
111  help="Use secure TLS in MQTT connection. Normally you should use port 8883")
112  opt.add_argument("--unsecure",
113  action="store_false",
114  dest="mqttSecure",
115  default=False,
116  help="Use secure plain TCP in MQTT connection. Normally you should use port 1883")
117  opt.add_argument("-D", "--speed",
118  type=str,
119  dest="otaSpeed",
120  default="fast",
121  help="OTA update speed profile: 'fast', 'medium' or 'slow' Throttle this down in case of"
122  "problems with OTA update. Default: %default")
123 
124  # (options, args) = opt.parse_args()
125  args = opt.parse_args()
126 
127  if not args.address:
128  opt.error('Destination address not supplied')
129 
130  # print(options)
131 
132  ota_topic = args.baseTopic + "/" + args.address + otaSetTopic
133  mqttclientname = "EnigmaIoTUpdate"
134 
135  ota_length = os.stat(args.filename).st_size
136 
137  delay_options = {"fast": 0.02, "medium": 0.06, "slow": 0.18}
138  packet_delay = delay_options.get(args.otaSpeed, 0.07)
139 
140  with open(args.filename, "rb") as binary_file:
141  chunked_file = []
142  encoded_string = []
143  n = 212 # Max 215 - 2. Divisible by 4 => 212
144 
145  for chunk in iter(lambda: binary_file.read(n), b""):
146  chunked_file.append(chunk)
147  for chunk in chunked_file:
148  encoded_string.append(base64.b64encode(bytes(chunk)).decode('ascii'))
149  # chunked_string = [encoded_string[i:i+n] for i in range(0, len(encoded_string), n)]
150  binary_file.seek(0);
151  hash_md5 = hashlib.md5()
152  for chunk in iter(lambda: binary_file.read(4096), b""):
153  hash_md5.update(chunk)
154 
155  # print(hash_md5.hexdigest())
156  binary_file.close()
157 
158  mqtt.Client.connected_flag = False
159  client = mqtt.Client(mqttclientname, True)
160  client.username_pw_set(username=args.mqttUser, password=args.mqttPass)
161  if args.mqttSecure:
162  client.tls_set()
163  client.on_connect = on_connect
164  client.on_message = on_message
165 
166  client.connect(host=args.mqttServer, port=args.mqttPort)
167  while not client.connected_flag: # wait in loop
168  print("Connecting to MQTT server")
169  client.loop()
170  time.sleep(1)
171 
172  # client.loop_start()
173  sleep_topic = args.baseTopic + "/" + args.address + sleepSetTopic
174  client.publish(sleep_topic, "0")
175 
176  while sleepyNode:
177  print("Waiting for non sleepy confirmation")
178  client.loop()
179  time.sleep(1)
180 
181  print("Sending hash: " + hash_md5.hexdigest())
182  md5_str = hash_md5.hexdigest()
183 
184  # msg 0, file size, number of chunks, md5 checksum
185  print("Sending %d bytes in %d chunks" % (ota_length,len(encoded_string)))
186  client.publish(ota_topic, "0," + str(ota_length) + "," + str(len(encoded_string)) + "," + md5_str)
187 
188  # for i in range(0, len(chunked_string), 1):
189  print("Sending file: " + args.filename)
190  global idx
191 
192  # remove to simulate lost message
193  # error = False
194 
195  while idx < len(encoded_string):
196  client.loop()
197  time.sleep(packet_delay)
198  # time.sleep(0.2)
199  # if i not in range(10,13):
200  i = idx + 1
201  client.publish(ota_topic, str(i) + "," + encoded_string[idx])
202  idx = idx + 1
203 
204  # remove to simulate lost message
205  # if idx == 100 and not error:
206  # error = True
207  # idx = idx + 1
208 
209  if i % 2 == 0:
210  print(".", end='')
211  if i % 160 == 0:
212  print(" %.f%%" % (i / len(encoded_string) * 100))
213  if i == len(encoded_string):
214  for i in range(0, 40):
215  client.loop()
216  time.sleep(0.5)
217  if otaFinished:
218  print(" OTA OK ", end='')
219  break
220 
221  print("100%")
222  # time.sleep(5)
223  client.disconnect()
224 
225 
226 if __name__ == '__main__':
227  main()
EnigmaIoTUpdate.on_message
def on_message(client, userdata, msg)
Definition: EnigmaIoTUpdate.py:44
EnigmaIoTUpdate.on_connect
def on_connect(client, userdata, flags, rc)
Definition: EnigmaIoTUpdate.py:29
EnigmaIoTUpdate.main
def main()
Definition: EnigmaIoTUpdate.py:67