diff --git a/esp32/frozen/Pybytes/_pybytes_constants.py b/esp32/frozen/Pybytes/_pybytes_constants.py index bd2117fef5..8ae4058530 100644 --- a/esp32/frozen/Pybytes/_pybytes_constants.py +++ b/esp32/frozen/Pybytes/_pybytes_constants.py @@ -69,11 +69,12 @@ class constants: __TYPE_OTA = 0x05 __TYPE_FCOTA = 0x06 __TYPE_PONG = 0x07 - __TYPE_PYMESH = 0x0D - __TYPE_PYBYTES = 0x0E - __TYPE_RELEASE_INFO = 0x0B __TYPE_RELEASE_DEPLOY = 0x0A + __TYPE_RELEASE_INFO = 0x0B __TYPE_DEVICE_NETWORK_DEPLOY = 0x0C + __TYPE_PYMESH = 0x0D + __TYPE_PYBYTES = 0x0E + __TYPE_ML = 0x0F __PYBYTES_PROTOCOL = ">B%ds" __PYBYTES_PROTOCOL_PING = ">B" __PYBYTES_INTERNAL_PROTOCOL = ">BBH" @@ -90,6 +91,8 @@ class constants: __COMMAND_ANALOG_WRITE = 4 __COMMAND_CUSTOM_METHOD = 5 __COMMAND_CUSTOM_LOCATION = 6 + __COMMAND_START_SAMPLE = 7 + __COMMAND_DEPLOY_MODEL = 8 __FCOTA_COMMAND_HIERARCHY_ACQUISITION = 0x00 __FCOTA_COMMAND_FILE_ACQUISITION = 0x01 @@ -105,6 +108,10 @@ class constants: __DEVICE_TYPE_LOPY_4 = 0x04 __DEVICE_TYPE_UNKNOWN = 0x05 + __FWTYPE_DEFAULT = 0x00 + __FWTYPE_PYMESH = 0x01 + __FWTYPE_PYGATE = 0x02 + # {"ssid":"%s", "mac_addr":"%s", "channel":"%s", "power":"%s"} __WIFI_NETWORK_FORMAT = ">6sBb" diff --git a/esp32/frozen/Pybytes/_pybytes_library.py b/esp32/frozen/Pybytes/_pybytes_library.py index e3ac7898ff..78ee000abc 100644 --- a/esp32/frozen/Pybytes/_pybytes_library.py +++ b/esp32/frozen/Pybytes/_pybytes_library.py @@ -86,9 +86,18 @@ def pack_info_message(self, releaseVersion=None): body.append((release >> 8) & 0xFF) body.append(release & 0xFF) - if releaseVersion is not None: - body.append((releaseVersion >> 8) & 0xFF) - body.append(releaseVersion & 0xFF) + if releaseVersion is None: + releaseVersion = 0 + + body.append((releaseVersion >> 8) & 0xFF) + body.append(releaseVersion & 0xFF) + + if hasattr(os.uname(), 'pymesh'): + body.append(constants.__FWTYPE_PYMESH) + elif hasattr(os.uname(), 'pygate'): + body.append(constants.__FWTYPE_PYGATE) + else: + body.append(constants.__FWTYPE_DEFAULT) return self.__pack_message(constants.__TYPE_INFO, body) diff --git a/esp32/frozen/Pybytes/_pybytes_machine_learning.py b/esp32/frozen/Pybytes/_pybytes_machine_learning.py new file mode 100644 index 0000000000..5101895a60 --- /dev/null +++ b/esp32/frozen/Pybytes/_pybytes_machine_learning.py @@ -0,0 +1,167 @@ +''' +Copyright (c) 2020, Pycom Limited. +This software is licensed under the GNU GPL version 3 or any +later version, with permitted additional terms. For more information +see the Pycom Licence v1.0 document supplied with this file, or +available at https://www.pycom.io/opensource/licensing +''' + +import math +import json + +try: + from pybytes_debug import print_debug +except: + from _pybytes_debug import print_debug + +try: + import urequest +except: + import _urequest as urequest + +try: + from pybytes_constants import constants +except: + from _pybytes_constants import constants + +import pycom + +try: + from LIS2HH12 import * +except: + print_debug(5, "LIS2HH12 not imported") + +# 20 seconds, max window in time for recording +MAX_LEN_MSEC = const(20000) + +# 350Hz, max frequency +MAX_FREQ_HZ = const(350) + + +class MlFeatures(): + def __init__(self, pybytes_protocol=None, parameters=None): + if parameters is not None: + self.__length = parameters["length"] + self.__label = parameters["label"] + self.__sampleName = parameters["sampleName"] + self.__type = parameters["type"] + self.__device = parameters["device"] + self.__model = parameters["model"] + self.__mlSample = parameters["mlSample"] + self.__frequency = parameters["frequency"] + self.__pybytes_protocol = pybytes_protocol + self.__data = [] + + def _debug_hack(self, pybytes): + self.__pybytes = pybytes + + def start_sampling(self, pin): + # here define the required libraries + try: + from pysense import Pysense + except: + print_debug(5, "pysense not imported") + + try: + from pytrack import Pytrack + except: + print_debug(5, "pytrack not imported") + + lib = False + try: + py = Pysense() + lib = True + except NameError: + print_debug(5, "Pysense not defined") + + if not lib: + try: + py = Pytrack() + except NameError: + print_debug(5, "Check if Pysense/Pytrack libraries are loaded") + return + + try: + li = LIS2HH12(py) + except NameError: + print_debug(5, "LIS2HH12 library are not loaded") + return + li.set_odr(ODR_400_HZ) + + # make the max record length to 20 seconds + self.__length = min(MAX_LEN_MSEC, self.__length) + + # make the max frequency to 350Hz + self.__frequency = min(MAX_FREQ_HZ, self.__frequency) + + # compute time interval between 2 consecutive samples + delta_t_us = int(1000000.0 / self.__frequency) + # compute the number of samples to be acquisition + samples_num = math.ceil(self.__length * self.__frequency / 1000) + 1 + + pycom.heartbeat(False) + pycom.rgbled(0x7f7f00) + time.sleep(0.5) + + self.__data = [] + index = 0 + print("Start acquisition data for %d msec, freq %d Hz" % (self.__length, self.__frequency)) + + next_ts = time.ticks_us() + ts_orig = next_ts + while True: + while time.ticks_diff(next_ts, time.ticks_us()) > 0: + pass + acc = li.acceleration() + ts = next_ts + self.__data.append((ts - ts_orig, acc)) + next_ts = ts + delta_t_us + index += 1 + if index >= samples_num: + break # done + + print("Done acquisition %d samples, real freq %.1f Hz" % (index, index / (self.__length / 1000))) + self._parse_data(pin) + + def _send_data(self, data, pin, acc, ts): + if self.__pybytes_protocol is not None: + if self.__type == 2: + self.__label = self.__sampleName + self.__pybytes_protocol.send_pybytes_custom_method_values(pin, [ + data], + 'sample/{}/{}/{}/{}/{}'.format(self.__label, self.__type, self.__model, self.__device, self.__mlSample)) + else: + self.__pybytes.send_signal(pin & 0xFF, str((int(ts / 1000), acc))) + + def _parse_data(self, pin): + print("_parse_data, %d samples" % len(self.__data)) + pycom.rgbled(0x8d05f5) + data = ['{"data": "ml"}'] + for (ts, acc) in self.__data: + data.append('{' + '"data": [{},{},{}], "ms": {}'.format(acc[0], acc[1], acc[2], int(ts / 1000)) + '}') + if len(data) > 25: + self._send_data(data, pin, acc, ts) + data = ['{"data": "ml"}'] + self._send_data(data, pin, acc, ts) + pycom.heartbeat(True) + + def deploy_model(self, modelId, silent=False): + try: + file = '/flash/model_definition.json' + modelDefinition = {} + url = '{}://{}/ml/{}'.format( + constants.__DEFAULT_PYCONFIG_PROTOCOL, + constants.__DEFAULT_PYCONFIG_DOMAIN, + modelId + ) + print_debug(2, '{}'.format(url)) + result = urequest.get(url, headers={'content-type': 'application/json'}) + modelDefinition = json.loads(result.content.decode()) + print_debug(2, 'modelDefinition: {}'.format(modelDefinition)) + f = open(file, 'w') + f.write(json.dumps(modelDefinition).encode('utf-8')) + f.close() + print_debug(2, "Model definition written to {}".format(file)) + except Exception as e: + if not silent: + print_debug(2, "Exception: {}".format(e)) diff --git a/esp32/frozen/Pybytes/_pybytes_protocol.py b/esp32/frozen/Pybytes/_pybytes_protocol.py index 385d8c6f9c..f8817a5f6a 100644 --- a/esp32/frozen/Pybytes/_pybytes_protocol.py +++ b/esp32/frozen/Pybytes/_pybytes_protocol.py @@ -31,6 +31,11 @@ except: from _pybytes_pymesh_config import PybytesPymeshConfig +try: + from pybytes_machine_learning import MlFeatures +except: + from _pybytes_machine_learning import MlFeatures + try: from pybytes_config_reader import PybytesConfigReader except: @@ -281,10 +286,10 @@ def __process_recv_message(self, message): splittedBody = bodyString.split(',') if (len(splittedBody) >= 2): path = splittedBody[0] - print_debug(2, path[len(path)-7:len(path)]) - if (path[len(path)-7:len(path)] != '.pymakr'): + print_debug(2, path[len(path) - 7:len(path)]) + if (path[len(path) - 7:len(path)] != '.pymakr'): self.send_fcota_ping('updating file...') - newContent = bodyString[len(path)+1:len(body)] + newContent = bodyString[len(path) + 1:len(body)] if (self.__FCOTA.update_file_content(path, newContent) is True): # noqa size = self.__FCOTA.get_file_size(path) self.send_fcota_file(newContent, path, size) @@ -319,7 +324,18 @@ def __process_recv_message(self, message): if (len(body) > 3): value = body[2] << 8 | body[3] - if (command == constants.__COMMAND_PIN_MODE): + if (command == constants.__COMMAND_START_SAMPLE): + parameters = ujson.loads(body[2: len(body)].decode("utf-8")) + sampling = MlFeatures(self, parameters=parameters) + sampling.start_sampling(pin=parameters["pin"]) + self.send_ota_response(result=2, topic='sample') + elif (command == constants.__COMMAND_DEPLOY_MODEL): + parameters = ujson.loads(body[2: len(body)].decode("utf-8")) + sampling = MlFeatures() + sampling.deploy_model(modelId=parameters["modelId"]) + self.send_ota_response(result=2, topic='deploymlmodel') + + elif (command == constants.__COMMAND_PIN_MODE): pass elif (command == constants.__COMMAND_DIGITAL_READ): diff --git a/esp32/frozen/Pybytes/_pybytes_pymesh_config.py b/esp32/frozen/Pybytes/_pybytes_pymesh_config.py index 699e16c11e..116f797925 100644 --- a/esp32/frozen/Pybytes/_pybytes_pymesh_config.py +++ b/esp32/frozen/Pybytes/_pybytes_pymesh_config.py @@ -53,13 +53,6 @@ def pymesh_init(self): # initialize Pymesh self.__pymesh = Pymesh(self.__pymesh_config, self.pymesh_new_message_cb) - while not self.__pymesh.is_connected(): - print(self.__pymesh.status_str()) - time.sleep(3) - - # send message to the Node having MAC address 5 - self.__pymesh.send_mess(2, "Hello World") - print("Done Pymesh init, forever loop, exit/stop with Ctrl+C multiple times") self.__pymesh_br_enabled = False if self.__pymesh_config.get("br_ena", False): @@ -69,8 +62,6 @@ def pymesh_init(self): print_debug(99, "Set as border router") self.__pymesh.br_set(PymeshConfig.BR_PRIORITY_NORM, self.pymesh_new_br_message_cb) - self.__pybytes.send_signal(1, str(self.__pymesh.mac()) + " : " + str(time.time()) + "s, " + str(pycom.get_free_heap())) - print_debug(99, "Send to Pyb,", pycom.get_free_heap()) else: # not connected anymore to pybytes if self.__pymesh_br_enabled: self.__pymesh_br_enabled = False @@ -88,8 +79,13 @@ def unpack_pymesh_message(self, signal_number, value): pyb_ip = '1:2:3::' + hex(pyb_port)[2:] pkt_start = self.__pack_tocken_prefix + self.__pack_tocken_sep + deviceID + self.__pack_tocken_sep - self.__pymesh.send_mess_external(pyb_ip, pyb_port, pkt_start + monitoringData) - self.__pymesh.send_mess_external(pyb_ip, pyb_port, pkt_start + value) + # send data to the port equal with signal_number + self.__pymesh.send_mess_external(pyb_ip, signal_number, pkt_start + value) + + time.sleep(3) # shouldn't send too fast to BR + + # hardcode monitoring data to be sent on signal #2 + self.__pymesh.send_mess_external(pyb_ip, 2, pkt_start + monitoringData) def pymesh_new_message_cb(self, rcv_ip, rcv_port, rcv_data): ''' callback triggered when a new packet arrived ''' @@ -122,9 +118,11 @@ def pymesh_new_br_message_cb(self, rcv_ip, rcv_port, rcv_data, dest_ip, dest_por if len(x) > 2: token = x[1] rcv_data = rcv_data[len(self.__pack_tocken_prefix) + len(token) + len(self.__pack_tocken_sep):] - pkt = 'BR %d B from %s (%s), to %s ( %d): %s' % (len(rcv_data), token, rcv_ip, dest_ip, dest_port, str(rcv_data)) - print_debug(99, 'Pymesh node packet: {} '.format(pkt)) - self.__pybytes.send_node_signal(1, str(rcv_data.decode()).replace("#", ""), token.decode()) + + # send data to Pybytes only if it's coded properly + pkt = 'BR %d B from %s (%s), to %s ( %d): %s' % (len(rcv_data), token, rcv_ip, dest_ip, dest_port, str(rcv_data)) + print_debug(99, 'Pymesh node packet: {} '.format(pkt)) + self.__pybytes.send_node_signal(dest_port & 0xFF, str(rcv_data.decode()).replace("#", ""), token.decode()) return def get_config(self, token, silent=False): diff --git a/esp32/pycom_version.h b/esp32/pycom_version.h index 1a6333107d..4675e70422 100644 --- a/esp32/pycom_version.h +++ b/esp32/pycom_version.h @@ -10,14 +10,14 @@ #ifndef VERSION_H_ #define VERSION_H_ -#define SW_VERSION_NUMBER "1.20.2.r0" +#define SW_VERSION_NUMBER "1.20.2.r1" #define LORAWAN_VERSION_NUMBER "1.0.2" #define SIGFOX_VERSION_NUMBER "1.0.1" #if (VARIANT == PYBYTES) -#define PYBYTES_VERSION_NUMBER "1.5.1" +#define PYBYTES_VERSION_NUMBER "1.6.0" #endif #endif /* VERSION_H_ */