EnigmaIOT  0.9.8
Secure sensor and gateway platform based on ESP8266 and ESP32
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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