diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000..0cfdcb6a
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,39 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master, develop ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '39 7 * * 6'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'python' ]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index db992258..7b85a714 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -4,21 +4,15 @@ name: Coverage (Coveralls)
on: [push, pull_request]
jobs:
- # Runs coverage and upload it
coverage:
name: Coverage Test
- # The type of runner that the job will run on
runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Set up Python environment
uses: actions/setup-python@v2
with:
python-version: 3.6
- # Runs a set of commands using the runners shell
- name: Install dependencies
run: |
python -m pip install --upgrade pip
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 982b8598..e3b9e93d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,6 +8,9 @@ on:
jobs:
deploy:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
steps:
- uses: actions/checkout@v2
@@ -69,6 +72,17 @@ jobs:
run: |
python setup.py sdist bdist_wheel
twine upload --skip-existing dist/*
+ - name: login-to-ghcr
+ uses: docker/login-action@v1
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.LUXROBO_TOKEN }}
+ - name: build-and-push-docker-image
+ uses: docker/build-push-action@v2
+ with:
+ push: true
+ tags: ghcr.io/luxrobo/pymodi:${{ steps.checkout_branch.outputs.version }}
- name: send-release-email
uses: dawidd6/action-send-mail@v2.2.0
with:
diff --git a/AUTHORS.md b/AUTHORS.md
index 930e754d..6201de94 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -11,7 +11,9 @@ Maintainer
Committer
---------
-- Jinsung Ha (jinsung@luxrobo.com)
+- Jinsoo Heo (koriel@luxrobo.com)
+- Jinsung Ha (jsung5381@naver.com)
+- Sebeom Lee (moses97@gmail.com)
Contributors
------------
@@ -22,3 +24,6 @@ Contributors
- Sebeom Lee (moses97@gmail.com)
- Doyeon Kim (archdo0909@gmail.com)
- Seokyoung Woo (wsy8029@gmail.com)
+- Seungyoon Lee (syoon624@gmail.com)
+- Jemin Yeon (yjm9507@yonsei.ac.kr)
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..b3ba8143
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.6-slim-buster
+COPY . /usr/src/app
+WORKDIR /usr/src/app
+RUN python setup.py install --user
+WORKDIR /workspace
+
+LABEL org.opencontainers.image.source="https://github.com/luxrobo/pymodi"
diff --git a/HISTORY.md b/HISTORY.md
index 924e6da7..a29b9c7d 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -104,7 +104,7 @@ History
2. Improve BLE connection stability in Linux Platform
3. Implement module usage manual (i.e. quick-start)
4. Implement module inspection functionality (i.e. diagnose)
-5. Refactor GUI debugger design, fix initialization issue on macOS
+5. Refactor GUI debugger design, fix initialization issue on macOS
1.1.0 (2020-12-17)
--
@@ -112,3 +112,11 @@ History
2. Implement network base (STM32) firmware updater
3. Implement GD32 firmware updater
4. Implement [VirtualMODI](https://github.com/luxrobo/virtual-modi) integration code
+
+1.2.0 (2022-02-16)
+--
+1. Detach GUI Firmware Updater from PyMODI to individual repo
+2. Implement web client method for MODI json connection
+3. Package PyMODI into a dockerfile via GHCR
+4. Fix minor bug in BLE connection
+
diff --git a/README.md b/README.md
index a4547a16..54f4b174 100644
--- a/README.md
+++ b/README.md
@@ -33,12 +33,6 @@ PyMODI provides a control of modular electronics, [MODI](https://modi.luxrobo.co
* Update of MODI firmware consisting of both ESP32 and STM32 modules
* Direct manipulation of MODI network module using [MODI Play](https://play.google.com/store/apps/details?id=com.luxrobo.modiplay&hl=en_US)
-UML Diagram
------------
-
-
-
-
Build Status
------------
|master|develop|
@@ -80,7 +74,7 @@ python --version
Install the latest PyMODI if you haven't installed it yet:
```commandline
-python -m pip install -U pymodi --user
+python -m pip install pymodi --user --upgrade
```
You can also install PyMODI at develop branch (containing latest changes but it can be unstable) with:
@@ -181,11 +175,6 @@ To update MODI modules (all modules but the network module),
$ python -m modi --update_modules
```
-To update firmware in GUI mode (all three commands above are available)
-```commandline
-$ python -m modi --update_in_gui
-```
-
To diagnose MODI modules (helpful to find existing malfunctioning modules),
```commandline
$ python -m modi --inspect
diff --git a/docs/_static/img/logo.png b/docs/_static/img/logo.png
index a46118a7..b6fb827a 100644
Binary files a/docs/_static/img/logo.png and b/docs/_static/img/logo.png differ
diff --git a/docs/_static/img/logo2.jpg b/docs/_static/img/logo2.jpg
deleted file mode 100644
index d7f7f8c6..00000000
Binary files a/docs/_static/img/logo2.jpg and /dev/null differ
diff --git a/docs/_static/img/logo3.jpg b/docs/_static/img/logo3.jpg
deleted file mode 100644
index 08838e7e..00000000
Binary files a/docs/_static/img/logo3.jpg and /dev/null differ
diff --git a/modi/__main__.py b/modi/__main__.py
index 50d4448c..21255201 100644
--- a/modi/__main__.py
+++ b/modi/__main__.py
@@ -7,10 +7,10 @@
import modi
-from modi.util.debugger import Debugger
-from modi.util.usage_instructor import UsageInstructor
-from modi.util.tutor import Tutor
-from modi.util.inspector import Inspector
+from modi.util.debugging_util import Debugger
+from modi.util.usage_util import UsageInstructor
+from modi.util.tutorial_util import Tutor
+from modi.util.inspection_util import Inspector
from modi.util.firmware_updater import STM32FirmwareUpdater
from modi.util.firmware_updater import ESP32FirmwareUpdater
@@ -50,7 +50,7 @@ def check_option(*options):
try:
# all commands should be defined here in advance
opts, args = getopt(
- sys.argv[1:], "tamhvpdinubgxy",
+ sys.argv[1:], "tamhvpdinubxy",
[
"tutorial",
"initialize",
@@ -63,7 +63,6 @@ def check_option(*options):
"update_network",
"usage",
"update_network_base",
- "update_in_gui",
"update_modules_gd",
"update_network_base_gd",
]
@@ -200,12 +199,3 @@ def check_option(*options):
usage = UsageInstructor()
usage.run_usage_manual()
os._exit(0)
-
- # Run GUI MODI Firmware Updater
- if check_option('-g', '--update_in_gui'):
- from PyQt5 import QtWidgets
- from modi.util.gui_firmware_updater import Form
- app = QtWidgets.QApplication(sys.argv)
- w = Form()
- sys.exit(app.exec())
- os._exit(0)
diff --git a/modi/about.py b/modi/about.py
index 678ab3fc..e61630c3 100644
--- a/modi/about.py
+++ b/modi/about.py
@@ -1,5 +1,5 @@
__title__ = "pymodi"
-__version__ = "1.1.0"
+__version__ = "1.2.0"
__author__ = "LUXROBO"
__email__ = "tech@luxrobo.com"
__description__ = "Python API for controlling modular electronics, MODI."
diff --git a/modi/assets/image/button.png b/modi/assets/image/button.png
deleted file mode 100644
index b23aebd6..00000000
Binary files a/modi/assets/image/button.png and /dev/null differ
diff --git a/modi/assets/image/dial.png b/modi/assets/image/dial.png
deleted file mode 100644
index a954fc5b..00000000
Binary files a/modi/assets/image/dial.png and /dev/null differ
diff --git a/modi/assets/image/display.png b/modi/assets/image/display.png
deleted file mode 100644
index a5263aee..00000000
Binary files a/modi/assets/image/display.png and /dev/null differ
diff --git a/modi/assets/image/env.png b/modi/assets/image/env.png
deleted file mode 100644
index ec6fd228..00000000
Binary files a/modi/assets/image/env.png and /dev/null differ
diff --git a/modi/assets/image/gyro.png b/modi/assets/image/gyro.png
deleted file mode 100644
index 19e03096..00000000
Binary files a/modi/assets/image/gyro.png and /dev/null differ
diff --git a/modi/assets/image/ir.png b/modi/assets/image/ir.png
deleted file mode 100644
index 92dbe5cd..00000000
Binary files a/modi/assets/image/ir.png and /dev/null differ
diff --git a/modi/assets/image/led.png b/modi/assets/image/led.png
deleted file mode 100644
index 8e5667d5..00000000
Binary files a/modi/assets/image/led.png and /dev/null differ
diff --git a/modi/assets/image/mic.png b/modi/assets/image/mic.png
deleted file mode 100644
index a06174dd..00000000
Binary files a/modi/assets/image/mic.png and /dev/null differ
diff --git a/modi/assets/image/motor.png b/modi/assets/image/motor.png
deleted file mode 100644
index 885a18a0..00000000
Binary files a/modi/assets/image/motor.png and /dev/null differ
diff --git a/modi/assets/image/network.png b/modi/assets/image/network.png
deleted file mode 100644
index 25eb4520..00000000
Binary files a/modi/assets/image/network.png and /dev/null differ
diff --git a/modi/assets/image/speaker.png b/modi/assets/image/speaker.png
deleted file mode 100644
index 94b7d402..00000000
Binary files a/modi/assets/image/speaker.png and /dev/null differ
diff --git a/modi/assets/image/ultrasonic.png b/modi/assets/image/ultrasonic.png
deleted file mode 100644
index 3f74da09..00000000
Binary files a/modi/assets/image/ultrasonic.png and /dev/null differ
diff --git a/modi/assets/modi_firmware_updater.ui b/modi/assets/modi_firmware_updater.ui
deleted file mode 100644
index 671f3591..00000000
--- a/modi/assets/modi_firmware_updater.ui
+++ /dev/null
@@ -1,165 +0,0 @@
-
-
- MODI_FIRMWARE_UPDATER
-
-
-
- 0
- 0
- 1141
- 338
-
-
-
- Dialog
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 550
- 0
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 10000
- 16777215
-
-
-
-
- 28
- 75
- true
- false
-
-
-
- Qt::StrongFocus
-
-
- 네트워크 모듈 업데이트
-
-
-
- 64
- 64
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 10000
- 16777215
-
-
-
-
- 28
- 75
- true
- false
-
-
-
- Qt::NoFocus
-
-
- 모듈 초기화
-
-
-
- 64
- 64
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 10000
- 16777215
-
-
-
-
- 28
- 75
- true
- false
-
-
-
- Qt::NoFocus
-
-
- 네트워크 모듈 초기화
-
-
-
- 64
- 64
-
-
-
-
- -
-
-
- 버튼 텍스트 한/영 전환
-
-
-
-
-
-
-
-
-
-
-
-
- slot1()
- slot2()
- slot3()
-
-
diff --git a/modi/modi.py b/modi/modi.py
index 2a95589f..ebd0ca98 100644
--- a/modi/modi.py
+++ b/modi/modi.py
@@ -9,9 +9,9 @@
from modi._exe_thrd import ExeThrd
from modi.util.connection_util import is_network_module_connected, is_on_pi
-from modi.util.miscellaneous import ModuleList
-from modi.util.stranger import check_complete
-from modi.util.topology_manager import TopologyManager
+from modi.util.miscellaneous_util import ModuleList
+from modi.util.strange_util import check_complete
+from modi.util.topology_util import TopologyManager
from modi.util.firmware_updater import STM32FirmwareUpdater
from modi.util.firmware_updater import ESP32FirmwareUpdater
@@ -19,6 +19,19 @@
class MODI:
+ network_uuids = {}
+
+ def __call__(cls, *args, **kwargs):
+ network_uuid = kwargs.get('network_uuid')
+ conn_type = kwargs.get('conn_type')
+ if conn_type != 'ble':
+ return super(MODI, cls).__call__(*args, **kwargs)
+ if not network_uuid:
+ raise ValueError('Should input a valid network uuid!')
+ if network_uuid not in cls.network_uuids:
+ cls.network_uuids[network_uuid] = \
+ super(MODI, cls).__call__(*args, **kwargs)
+ return cls.network_uuids[network_uuid]
def __init__(
self, modi_version=1, conn_type="", verbose=False, port=None,
@@ -48,9 +61,10 @@ def __init__(
init_time = time.time()
while not self._topology_manager.is_topology_complete():
time.sleep(0.1)
- if time.time() - init_time > 5:
- print("MODI init timeout over. "
- "Check your module connection.")
+ if time.time() - init_time > 3:
+ print(
+ 'MODI init timeout over. Check your module connection.'
+ )
break
check_complete(self)
print("MODI modules are initialized!")
@@ -106,9 +120,8 @@ def is_not_checked(module):
bad_modules.append(module)
return bad_modules
- @staticmethod
def __init_task(
- conn_type, verbose, port, network_uuid,
+ self, conn_type, verbose, port, network_uuid,
):
if not conn_type:
is_can = not is_network_module_connected() and is_on_pi()
@@ -116,19 +129,24 @@ def __init_task(
if conn_type == 'ser':
return im('modi.task.ser_task').SerTask(verbose, port)
- elif conn_type == 'can':
- return im('modi.task.can_task').CanTask(verbose)
+ elif conn_type == 'soc':
+ return im('modi.task.soc_task').SocTask(verbose, port)
elif conn_type == 'vir':
return im('modi.task.vir_task').VirTask(verbose, port)
+ elif conn_type == 'can':
+ return im('modi.task.can_task').CanTask(verbose)
elif conn_type == 'ble':
+ if not network_uuid:
+ raise ValueError('Network UUID not specified!')
+ self.network_uuids[network_uuid] = self
mod_path = {
- 'win32': 'modi.task.ble_task.ble_task_win',
+ 'win32': 'modi.task.ble_task.ble_task_mac',
'linux': 'modi.task.ble_task.ble_task_rpi',
'darwin': 'modi.task.ble_task.ble_task_mac',
}.get(sys.platform)
return im(mod_path).BleTask(verbose, network_uuid)
else:
- raise ValueError(f'Invalid conn mode {conn_type}')
+ raise ValueError(f'Invalid conn mode: {conn_type}')
def open(self):
atexit.register(self.close)
diff --git a/modi/task/ble_task/ble_task_mac.py b/modi/task/ble_task/ble_task_mac.py
index c7314aa7..12ad92e8 100644
--- a/modi/task/ble_task/ble_task_mac.py
+++ b/modi/task/ble_task/ble_task_mac.py
@@ -1,81 +1,82 @@
+import sys
import json
-import base64
-import asyncio
import time
+import base64
+import asyncio as aio
+import nest_asyncio as nest_aio
+
from typing import Optional
from queue import Queue
from threading import Thread
-from bleak import discover, BleakClient, BleakError
-from bleak.backends.corebluetooth import client as mac_client
+from bleak import BleakClient, BleakError, BleakScanner
from modi.task.conn_task import ConnTask
from modi.util.connection_util import MODIConnectionError
-from modi.util.miscellaneous import ask_modi_device
-class BleTask(ConnTask):
+nest_aio.apply()
+
+class BleTask(ConnTask):
CHAR_UUID = '00008421-0000-1000-8000-00805f9b34fb'
def __init__(self, verbose=False, uuid=None):
super().__init__(verbose=verbose)
- print("Initiating ble_task connection...")
- self._loop = asyncio.get_event_loop()
- self.__uuid = uuid
+ self.modi_name = f'MODI_{uuid.upper()}'
+ print(f'Initiating ble_task connection with {self.modi_name}')
+ self._loop = aio.get_event_loop()
self._recv_q = Queue()
self._send_q = Queue()
self.__close_event = False
- self.__get_service = \
- mac_client.BleakClientCoreBluetooth.get_services
- mac_client.BleakClientCoreBluetooth.get_services = self.mac_get_service
+
+ if sys.platform == 'darwin':
+ from bleak.backends.corebluetooth import client as mac_client
+ self.__get_service = \
+ mac_client.BleakClientCoreBluetooth.get_services
+ mac_client.BleakClientCoreBluetooth.get_services = \
+ self.mac_get_service
@staticmethod
- # The 'self' parameter of this function is necessary
async def mac_get_service(client):
return None
- async def _list_modi_devices(self):
- devices = await discover(timeout=1)
- modi_devies = []
- for d in devices:
- if 'MODI' in d.name:
- modi_devies.append(d)
- if not self.__uuid:
- self.__uuid = ask_modi_device(
- [d.name.upper() for d in modi_devies])
- for d in modi_devies:
- if self.__uuid in d.name.upper():
- return d
- return None
+ def match_device(self, device, _):
+ return device.name == self.modi_name
async def __connect(self, address):
- client = BleakClient(address, timeout=1)
- await client.connect(timeout=1)
- await asyncio.sleep(1)
- await self.__get_service(client)
+ client = BleakClient(
+ address, disconnected_callback=self.handle_disconnected, timeout=2
+ )
+ await client.connect(timeout=2)
+ await aio.sleep(1)
+ if sys.platform == 'darwin':
+ await self.__get_service(client)
return client
def __run_loop(self):
- asyncio.set_event_loop(self._loop)
- tasks = asyncio.gather(self.__send_handler(), self.__watch_notify())
+ aio.set_event_loop(self._loop)
+ tasks = aio.gather(self.__send_handler(), self.__watch_notify())
self._loop.run_until_complete(tasks)
async def __watch_notify(self):
await self._bus.start_notify(self.CHAR_UUID, self.__recv_handler)
while True:
- await asyncio.sleep(0.001)
+ await aio.sleep(0.001)
if self.__close_event:
break
async def __send_handler(self):
while True:
if self._send_q.empty():
- await asyncio.sleep(0.001)
+ await aio.sleep(0.001)
else:
- await self._bus.write_gatt_char(
- self.CHAR_UUID, self._send_q.get()
- )
+ try:
+ await self._bus.write_gatt_char(
+ self.CHAR_UUID, self._send_q.get()
+ )
+ except BleakError:
+ self.__close_event = True
if self.__close_event:
break
@@ -83,8 +84,10 @@ def __recv_handler(self, _, data):
self._recv_q.put(self.__parse_ble_msg(data))
def open_conn(self):
- loop = asyncio.get_event_loop()
- modi_device = loop.run_until_complete(self._list_modi_devices())
+ loop = aio.get_event_loop()
+ modi_device = loop.run_until_complete(
+ BleakScanner.find_device_by_filter(self.match_device)
+ )
if modi_device:
self._bus = self._loop.run_until_complete(
self.__connect(modi_device.address)
@@ -92,8 +95,10 @@ def open_conn(self):
Thread(target=self.__run_loop, daemon=True).start()
print(f"Connected to {modi_device.name}")
else:
- raise MODIConnectionError(f"Network module of {self.__uuid}"
- f" not found!")
+ raise MODIConnectionError(
+ f"Network module of {self.modi_name} not found!"
+ 'Perhaps, the module is already paired with your device?'
+ )
async def __close_client(self):
try:
@@ -108,6 +113,10 @@ def close_conn(self):
while self._loop.is_running():
time.sleep(0.1)
self._loop.run_until_complete(self.__close_client())
+ self._loop.close()
+
+ def handle_disconnected(self, _):
+ print('Device is being properly disconnected...')
def recv(self) -> Optional[str]:
if self._recv_q.empty():
diff --git a/modi/task/ble_task/ble_task_rpi.py b/modi/task/ble_task/ble_task_rpi.py
index 6771e774..ef839144 100644
--- a/modi/task/ble_task/ble_task_rpi.py
+++ b/modi/task/ble_task/ble_task_rpi.py
@@ -9,7 +9,7 @@
from threading import Thread
from modi.task.conn_task import ConnTask
-from modi.util.miscellaneous import ask_modi_device
+from modi.util.miscellaneous_util import ask_modi_device
class BleTask(ConnTask):
diff --git a/modi/task/exe_task.py b/modi/task/exe_task.py
index ab339a30..c7e24081 100644
--- a/modi/task/exe_task.py
+++ b/modi/task/exe_task.py
@@ -4,8 +4,8 @@
from modi.module.module import Module, BROADCAST_ID
from modi.module.setup_module.battery import Battery
-from modi.util.miscellaneous import get_module_from_name
-from modi.util.miscellaneous import get_module_type_from_uuid
+from modi.util.miscellaneous_util import get_module_from_name
+from modi.util.miscellaneous_util import get_module_type_from_uuid
from modi.util.message_util import unpack_data, decode_data, parse_message
diff --git a/modi/task/soc_task.py b/modi/task/soc_task.py
new file mode 100644
index 00000000..7bb28ec9
--- /dev/null
+++ b/modi/task/soc_task.py
@@ -0,0 +1,43 @@
+
+
+from websocket import create_connection
+
+from modi.task.conn_task import ConnTask
+
+
+class SocTask(ConnTask):
+ """
+ SocTask implements a websocket client.
+ Thus, PyMODI works as a websocket client, listening to a MODI Client.
+ However, it's possible for PyMODI client to connect to a MODI relay server.
+ A connection between a server and a client must be one-to-one.
+ """
+
+ def __init__(self, verbose=False, port=None):
+ super().__init__(verbose=verbose)
+ print('Initiating soc_task connection...')
+ DEFAULT_SOC_PORT = 8765
+ self.port = port if port else DEFAULT_SOC_PORT
+
+ def open_conn(self):
+ self._bus = create_connection(f'ws://localhost:{self.port}')
+
+ def close_conn(self):
+ self._bus.close()
+
+ def recv(self):
+ json_pkt = self._bus.recv()
+ if self.verbose:
+ print(f'recv: {json_pkt}')
+ return json_pkt
+
+ @ConnTask.wait
+ def send(self, pkt):
+ self._bus.send(pkt.encode())
+ if self.verbose:
+ print(f'send: {pkt}')
+
+ def send_nowait(self, pkt):
+ self._bus.send(pkt.encode())
+ if self.verbose:
+ print(f'send: {pkt}')
diff --git a/modi/util/debugger.py b/modi/util/debugging_util.py
similarity index 100%
rename from modi/util/debugger.py
rename to modi/util/debugging_util.py
diff --git a/modi/util/firmware_updater.py b/modi/util/firmware_updater.py
index b85e5974..4adb43fa 100644
--- a/modi/util/firmware_updater.py
+++ b/modi/util/firmware_updater.py
@@ -20,7 +20,7 @@
from modi.module.module import Module
from modi.util.message_util import unpack_data, decode_message, parse_message
from modi.util.connection_util import list_modi_ports, is_on_pi
-from modi.util.miscellaneous import get_module_type_from_uuid
+from modi.util.miscellaneous_util import get_module_type_from_uuid
class STM32FirmwareUpdater:
@@ -58,6 +58,12 @@ def __init__(
self.request_network_id()
+ def __del__(self):
+ try:
+ self.close()
+ except Exception as e:
+ print('Magic del is called with an exception:', e)
+
def set_ui(self, ui):
self.ui = ui
@@ -99,6 +105,7 @@ def update_module_firmware(self, update_network_base=False):
f'Sending a request to update firmware of network '
f'({self.network_id})'
)
+ if not self.update_in_progress:
self.request_to_update_firmware(
self.network_id, is_network=True
)
@@ -121,12 +128,42 @@ def __open_conn(self):
else:
return im('modi.task.ser_task').SerTask()
- def reinitialize_serial_connection(self):
+ def _reconnect_serial_connection(self, modi_num):
+ while True:
+ time.sleep(0.1)
+ disconnect = False
+ if not list_modi_ports():
+ disconnect = True
+ if disconnect:
+ if modi_num == list_modi_ports():
+ self.__reinitialize_serial_connection()
+ break
+
+ def reinitialize_serial_connection(self, mode=1):
+ if self.ui and self.update_network_base and mode == 2:
+ modi_num = len(list_modi_ports())
+ if self.ui.is_english:
+ self.ui.update_network_stm32.setText(
+ "Reconnect network module and "
+ "click the button again please."
+ )
+ else:
+ self.ui.update_network_stm32.setText(
+ "네트워크 모듈을 재연결 후 버튼을 다시 눌러주십시오."
+ )
+ th.Thread(
+ target=self._reconnect_serial_connection,
+ args=(modi_num,),
+ daemon=True
+ ).start()
+ else:
+ self.__reinitialize_serial_connection()
+
+ def __reinitialize_serial_connection(self):
print('Temporally disconnecting the serial connection...')
self.close()
-
- print('Re-init serial connection for the update, in 2 seconds...')
time.sleep(2)
+ print('Re-init serial connection for the update, in 2 seconds...')
self.__conn = self.__open_conn()
self.__conn.open_conn()
self.__running = True
@@ -151,7 +188,7 @@ def request_to_update_firmware(self, module_id, is_network=False) -> None:
module_id, 4, Module.PNP_OFF
)
self.__conn.send_nowait(firmware_update_message)
- self.reinitialize_serial_connection()
+ self.reinitialize_serial_connection(2)
else:
firmware_update_message = self.__set_module_state(
module_id, Module.UPDATE_FIRMWARE, Module.PNP_OFF
@@ -219,13 +256,13 @@ def __update_firmware(self, module_id: int, module_type: str) -> None:
root_path = (
'https://download.luxrobo.com/modi-network-os'
)
- zip_path = path.join(root_path, 'network.zip')
+ zip_path = root_path + '/network.zip'
bin_path = 'network.bin'
else:
root_path = (
'https://download.luxrobo.com/modi-skeleton-mobile'
)
- zip_path = path.join(root_path, 'skeleton.zip')
+ zip_path = root_path + '/skeleton.zip'
bin_path = (
path.join(f'skeleton/{module_type.lower()}.bin')
if module_type != 'env' else
@@ -259,6 +296,30 @@ def __update_firmware(self, module_id: int, module_type: str) -> None:
page_offset = 0 if not self.update_network_base else 0x8800
for page_begin in range(bin_begin, bin_end + 1, page_size):
progress = 100 * page_begin // bin_end
+
+ if self.ui:
+ if self.update_network_base:
+ if self.ui.is_english:
+ self.ui.update_network_stm32.setText(
+ f"Network STM32 update is in progress. "
+ f"({progress}%)"
+ )
+ else:
+ self.ui.update_network_stm32.setText(
+ f"네트워크 모듈 초기화가 진행중입니다. "
+ f"({progress}%)"
+ )
+ else:
+ if self.ui.is_english:
+ self.ui.update_stm32_modules.setText(
+ f"STM32 modules update is in progress. "
+ f"({progress}%)"
+ )
+ else:
+ self.ui.update_stm32_modules.setText(
+ f"모듈 초기화가 진행중입니다. ({progress}%)"
+ )
+
print(
f"\rUpdating {module_type} ({module_id}) "
f"{self.__progress_bar(page_begin, bin_end)} "
@@ -313,13 +374,13 @@ def __update_firmware(self, module_id: int, module_type: str) -> None:
# Get version info from version_path, using appropriate methods
version_info, version_file = None, 'version.txt'
if self.ui:
- version_path = path.join(root_path, version_file)
+ version_path = root_path + '/' + version_file
for line in ur.urlopen(version_path, timeout=5):
version_info = line.decode('utf-8').lstrip('v')
else:
if self.update_network_base:
version_file = 'base_' + version_file
- version_path = path.join(root_path, version_file)
+ version_path = root_path + '/' + version_file
with open(version_path) as version_file:
version_info = version_file.readline().lstrip('v').rstrip('\n')
version_digits = [int(digit) for digit in version_info.split('.')]
@@ -359,13 +420,45 @@ def __update_firmware(self, module_id: int, module_type: str) -> None:
print("Reboot message has been sent to all connected modules")
self.reset_state()
if self.update_network_base:
- self.reinitialize_serial_connection()
+ self.reinitialize_serial_connection(1)
time.sleep(0.5)
time.sleep(1)
self.update_in_progress = False
self.update_event.set()
+ if self.ui:
+ if self.update_network_base:
+ self.ui.update_stm32_modules.setStyleSheet(
+ f'border-image: url({self.ui.active_path})'
+ )
+ self.ui.update_stm32_modules.setEnabled(True)
+ if self.ui.is_english:
+ self.ui.update_network_stm32.setText(
+ "Update Network STM32"
+ )
+ else:
+ self.ui.update_network_stm32.setText(
+ "네트워크 모듈 초기화"
+ )
+ else:
+ self.ui.update_network_stm32.setStyleSheet(
+ f'border-image: url({self.ui.active_path})'
+ )
+ self.ui.update_network_stm32.setEnabled(True)
+ if self.ui.is_english:
+ self.ui.update_stm32_modules.setText(
+ "Update STM32 Modules."
+ )
+ else:
+ self.ui.update_stm32_modules.setText(
+ "모듈 초기화"
+ )
+ self.ui.update_network_esp32.setStyleSheet(
+ f'border-image: url({self.ui.active_path})'
+ )
+ self.ui.update_network_esp32.setEnabled(True)
+
@staticmethod
def __delay(span):
init_time = time.perf_counter()
@@ -698,6 +791,24 @@ def update_firmware(self, force=False):
self.flushOutput()
self.close()
+ if self.ui:
+ self.ui.update_stm32_modules.setStyleSheet(
+ f'border-image: url({self.ui.active_path})'
+ )
+ self.ui.update_stm32_modules.setEnabled(True)
+ self.ui.update_network_stm32.setStyleSheet(
+ f'border-image: url({self.ui.active_path})'
+ )
+ self.ui.update_network_stm32.setEnabled(True)
+ if self.ui.is_english:
+ self.ui.update_network_esp32.setText(
+ "Update Network ESP32"
+ )
+ else:
+ self.ui.update_network_esp32.setText(
+ "네트워크 모듈 업데이트"
+ )
+
def __device_ready(self):
print("Redirecting connection to esp device...")
self.write(b'{"c":43,"s":0,"d":4095,"b":"AA==","l":1}')
@@ -971,6 +1082,15 @@ def __write_binary_firmware(self, binary_firmware: bytes, manager):
)
if manager:
manager.quit()
+ if self.ui:
+ if self.ui.is_english:
+ self.ui.update_network_esp32.setText(
+ "Network ESP32 update is in progress. (100%)"
+ )
+ else:
+ self.ui.update_network_esp32.setText(
+ "네트워크 모듈 업데이트가 진행중입니다. (100%)"
+ )
print(f"\r{self.__progress_bar(1, 1)}")
print("Firmware Upload Complete")
@@ -986,6 +1106,17 @@ def __write_chunk(self, chunk, curr_seq, total_seq, manager):
for seq, block in enumerate(block_queue):
if manager:
manager.status = self.__progress_bar(curr_seq + seq, total_seq)
+ if self.ui:
+ if self.ui.is_english:
+ self.ui.update_network_esp32.setText(
+ f"Network ESP32 update is in progress. "
+ f"({int((curr_seq+seq)/total_seq*100)}%)"
+ )
+ else:
+ self.ui.update_network_esp32.setText(
+ f"네트워크 모듈 업데이트가 진행중입니다. "
+ f"({int((curr_seq+seq)/total_seq*100)}%)"
+ )
print(
f'\r{self.__progress_bar(curr_seq + seq, total_seq)}', end=''
)
diff --git a/modi/util/gui_firmware_updater.py b/modi/util/gui_firmware_updater.py
deleted file mode 100644
index 2ab61898..00000000
--- a/modi/util/gui_firmware_updater.py
+++ /dev/null
@@ -1,228 +0,0 @@
-import os
-import sys
-import time
-import logging
-
-import threading as th
-
-
-from PyQt5 import uic
-from PyQt5 import QtWidgets, QtCore, QtGui
-from PyQt5.QtCore import QObject, pyqtSignal
-
-from modi.util.firmware_updater import STM32FirmwareUpdater
-from modi.util.firmware_updater import ESP32FirmwareUpdater
-
-
-class StdoutRedirect(QObject):
- printOccur = pyqtSignal(str, str, name='print')
-
- def __init__(self):
- QObject.__init__(self, None)
- self.daemon = True
- self.sysstdout = sys.stdout.write
- self.sysstderr = sys.stderr.write
- self.logger = None
-
- def stop(self):
- sys.stdout.write = self.sysstdout
- sys.stderr.write = self.sysstderr
-
- def start(self):
- sys.stdout.write = self.write
- sys.stderr.write = lambda msg: self.write(msg, color='red')
-
- def write(self, s, color="black"):
- sys.stdout.flush()
- self.printOccur.emit(s, color)
- if self.logger and not self.__is_redundant_line(s):
- self.logger.info(s)
-
- @staticmethod
- def __is_redundant_line(line):
- if line.startswith('\rUpdating'):
- return True
-
- if line.startswith('\rFirmware Upload: ['):
- return True
-
- if len(line) < 3:
- return True
-
- return False
-
-
-class Form(QtWidgets.QDialog):
- """
- GUI Form of MODI Firmware Updater
- """
-
- def __init__(self, installer=False):
- self.logger = self.__init_logger()
- QtWidgets.QDialog.__init__(self)
- if installer:
- ui_path = os.path.dirname(__file__).replace(
- 'util', 'modi_firmware_updater.ui'
- )
- else:
- ui_path = (
- os.path.join(
- os.path.dirname(__file__),
- '..', 'assets', 'modi_firmware_updater.ui'
- )
- )
- self.ui = uic.loadUi(ui_path)
- self.ui.setWindowTitle('MODI Firmware Updater')
- self.setFixedSize(self.size())
- self.ui.show()
-
- # Redirect stdout to text browser (i.e. console in our UI)
- self.stdout = StdoutRedirect()
- self.stdout.start()
- self.stdout.printOccur.connect(
- lambda line: self.__append_text_line(line)
- )
- self.stdout.logger = self.logger
-
- # Init variable to check if the program is in installation mode
- self.ui.installation = installer
-
- # Connect up the buttons
- self.ui.update_network_esp32.clicked.connect(self.update_network_esp32)
- self.ui.update_stm32_modules.clicked.connect(self.update_stm32_modules)
- self.ui.update_network_stm32.clicked.connect(self.update_network_stm32)
- self.ui.translate_button.clicked.connect(self.translate_button_text)
-
- self.buttons = [
- self.ui.update_network_esp32,
- self.ui.update_stm32_modules,
- self.ui.update_network_stm32,
- self.ui.translate_button,
- ]
-
- # Disable the first button to be focused when UI is loaded
- self.ui.update_network_esp32.setAutoDefault(False)
- self.ui.update_network_esp32.setDefault(False)
-
- # Print init status
- time_now_str = time.strftime('[%Y/%m/%d@%X]', time.localtime())
- print(time_now_str + ' GUI MODI Firmware Updater has been started!')
-
- # Set up field variables
- self.firmware_updater = None
- self.button_in_english = False
-
- #
- # Main methods
- #
- def update_network_esp32(self):
- if self.firmware_updater and self.firmware_updater.update_in_progress:
- return
- self.ui.console.clear()
- print(
- 'ESP32 Firmware Updater has been initialized for esp update!'
- )
- esp32_updater = ESP32FirmwareUpdater()
- esp32_updater.set_ui(self.ui)
- th.Thread(target=esp32_updater.update_firmware, daemon=True).start()
- self.firmware_updater = esp32_updater
-
- def update_stm32_modules(self):
- if self.firmware_updater and self.firmware_updater.update_in_progress:
- return
- self.ui.console.clear()
- print(
- 'STM32 Firmware Updater has been initialized for module update!'
- )
- stm32_updater = STM32FirmwareUpdater()
- stm32_updater.set_ui(self.ui)
- th.Thread(
- target=stm32_updater.update_module_firmware, daemon=True
- ).start()
- self.firmware_updater = stm32_updater
-
- def update_network_stm32(self):
- if self.firmware_updater and self.firmware_updater.update_in_progress:
- return
- self.ui.console.clear()
- print(
- 'STM32 Firmware Updater has been initialized for base update!'
- )
- stm32_updater = STM32FirmwareUpdater()
- stm32_updater.set_ui(self.ui)
- th.Thread(
- target=stm32_updater.update_module_firmware,
- args=(True,),
- daemon=True
- ).start()
- self.firmware_updater = stm32_updater
-
- def translate_button_text(self):
- button_en = [
- 'Update Network ESP32',
- 'Update STM32 Modules',
- 'Update Network STM32',
- 'Translate Button Text To Korean',
- ]
- button_kr = [
- '네트워크 모듈 업데이트',
- '모듈 초기화',
- '네트워크 모듈 초기화',
- '버튼 텍스트를 영어로 변경',
- ]
- appropriate_translation = \
- button_kr if self.button_in_english else button_en
- self.button_in_english = not self.button_in_english
- for i, button in enumerate(self.buttons):
- button.setText(appropriate_translation[i])
-
- #
- # Helper functions
- #
- @staticmethod
- def __init_logger():
- logger = logging.getLogger('GUI MODI Firmware Updater Logger')
- logger.setLevel(logging.DEBUG)
-
- formatter = logging.Formatter(
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
- )
- file_handler = logging.FileHandler('gmfu.log')
- file_handler.setLevel(logging.DEBUG)
- file_handler.setFormatter(formatter)
-
- logger.addHandler(file_handler)
- return logger
-
- def __append_text_line(self, line):
- self.ui.console.moveCursor(
- QtGui.QTextCursor.End, QtGui.QTextCursor.MoveAnchor
- )
- self.ui.console.moveCursor(
- QtGui.QTextCursor.StartOfLine, QtGui.QTextCursor.MoveAnchor
- )
- self.ui.console.moveCursor(
- QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor
- )
-
- # Remove new line character if current line represents update_progress
- if self.__is_update_progress_line(line):
- self.ui.console.textCursor().removeSelectedText()
- self.ui.console.textCursor().deletePreviousChar()
-
- # Display user text input
- self.ui.console.moveCursor(QtGui.QTextCursor.End)
- self.ui.console.insertPlainText(line)
- QtWidgets.QApplication.processEvents(
- QtCore.QEventLoop.ExcludeUserInputEvents
- )
-
- @staticmethod
- def __is_update_progress_line(line):
- if line.startswith('\rUpdating'):
- return True
-
- if line.startswith('\rFirmware Upload: ['):
- return True
-
- return False
diff --git a/modi/util/inspector.py b/modi/util/inspection_util.py
similarity index 100%
rename from modi/util/inspector.py
rename to modi/util/inspection_util.py
diff --git a/modi/util/miscellaneous.py b/modi/util/miscellaneous_util.py
similarity index 94%
rename from modi/util/miscellaneous.py
rename to modi/util/miscellaneous_util.py
index 145045c2..2346c1f7 100644
--- a/modi/util/miscellaneous.py
+++ b/modi/util/miscellaneous_util.py
@@ -43,6 +43,11 @@ def get_module_from_name(module_type: str):
def ask_modi_device(devices):
+ if not devices:
+ raise ValueError(
+ 'No MODI network module(s) available!\n'
+ 'The network module that you\'re trying to connect, may in use.'
+ )
for idx, dev in enumerate(devices):
print(f"<{idx}>: {dev}")
i = input("Choose your device index (ex: 0) : ")
diff --git a/modi/util/stranger.py b/modi/util/strange_util.py
similarity index 100%
rename from modi/util/stranger.py
rename to modi/util/strange_util.py
diff --git a/modi/util/topology_manager.py b/modi/util/topology_util.py
similarity index 99%
rename from modi/util/topology_manager.py
rename to modi/util/topology_util.py
index b7de95e2..b0bc2a04 100644
--- a/modi/util/topology_manager.py
+++ b/modi/util/topology_util.py
@@ -1,7 +1,7 @@
import time
from typing import Dict, List, Tuple
-from modi.util.miscellaneous import ModuleList
+from modi.util.miscellaneous_util import ModuleList
class TopologyManager:
diff --git a/modi/util/tutor.py b/modi/util/tutorial_util.py
similarity index 100%
rename from modi/util/tutor.py
rename to modi/util/tutorial_util.py
diff --git a/modi/util/usage_instructor.py b/modi/util/usage_util.py
similarity index 100%
rename from modi/util/usage_instructor.py
rename to modi/util/usage_util.py
diff --git a/requirements-dev.txt b/requirements-dev.txt
index cb7bb097..4b167e08 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,7 +1,6 @@
-pip
-flake8
-black
-tox
-coverage
-pre-commit
-bumpversion
+flake8==4.0.1
+black==21.10b0
+tox==3.24.4
+coverage==6.1.1
+pre-commit==2.15.0
+bumpversion==0.6.0
diff --git a/requirements.txt b/requirements.txt
index b81824a5..76afbe61 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,13 +1,14 @@
# common
-pyserial
-pyqt5
+pyserial==3.5
+nest-asyncio==1.5.4
+websocket-client==1.2.3
# windows
-bleak==0.8.0; sys_platform == 'win32'
+bleak==0.13.0; sys_platform == 'win32'
# mac
tkmacosx; sys_platform == 'darwin'
-bleak==0.8.0; sys_platform == 'darwin'
+bleak==0.13.0; sys_platform == 'darwin'
# linux
python-can==3.3.4; sys_platform == 'linux'
diff --git a/setup.cfg b/setup.cfg
index 7e74acfe..f553df5f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 1.1.0
+current_version = 1.2.0
commit = True
tag = False
diff --git a/tests/module/input_module/test_button.py b/tests/module/input_module/test_button.py
index 2c87737f..996a84ab 100644
--- a/tests/module/input_module/test_button.py
+++ b/tests/module/input_module/test_button.py
@@ -2,7 +2,7 @@
from modi.module.input_module.button import Button
from modi.util.message_util import parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestButton(unittest.TestCase):
diff --git a/tests/module/input_module/test_dial.py b/tests/module/input_module/test_dial.py
index 741429ee..aff42131 100644
--- a/tests/module/input_module/test_dial.py
+++ b/tests/module/input_module/test_dial.py
@@ -2,7 +2,7 @@
from modi.module.input_module.dial import Dial
from modi.util.message_util import parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestDial(unittest.TestCase):
diff --git a/tests/module/input_module/test_env.py b/tests/module/input_module/test_env.py
index a261caa9..316133d8 100644
--- a/tests/module/input_module/test_env.py
+++ b/tests/module/input_module/test_env.py
@@ -2,7 +2,7 @@
from modi.module.input_module.env import Env
from modi.util.message_util import parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestEnv(unittest.TestCase):
diff --git a/tests/module/input_module/test_gyro.py b/tests/module/input_module/test_gyro.py
index 736fea90..58d4458b 100644
--- a/tests/module/input_module/test_gyro.py
+++ b/tests/module/input_module/test_gyro.py
@@ -2,7 +2,7 @@
from modi.module.input_module.gyro import Gyro
from modi.util.message_util import parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestGyro(unittest.TestCase):
diff --git a/tests/module/input_module/test_ir.py b/tests/module/input_module/test_ir.py
index 2c17324f..f0d80f9c 100644
--- a/tests/module/input_module/test_ir.py
+++ b/tests/module/input_module/test_ir.py
@@ -2,7 +2,7 @@
from modi.module.input_module.ir import Ir
from modi.util.message_util import parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestIr(unittest.TestCase):
diff --git a/tests/module/input_module/test_mic.py b/tests/module/input_module/test_mic.py
index af34b374..cce3064c 100644
--- a/tests/module/input_module/test_mic.py
+++ b/tests/module/input_module/test_mic.py
@@ -3,7 +3,7 @@
from modi.module.input_module.mic import Mic
from modi.util.message_util import parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestMic(unittest.TestCase):
diff --git a/tests/module/input_module/test_ultrasonic.py b/tests/module/input_module/test_ultrasonic.py
index 623ab1c8..7cab36ad 100644
--- a/tests/module/input_module/test_ultrasonic.py
+++ b/tests/module/input_module/test_ultrasonic.py
@@ -2,7 +2,7 @@
from modi.module.input_module.ultrasonic import Ultrasonic
from modi.util.message_util import parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestUltrasonic(unittest.TestCase):
diff --git a/tests/module/output_module/test_display.py b/tests/module/output_module/test_display.py
index 0a15e494..6ab6eb53 100644
--- a/tests/module/output_module/test_display.py
+++ b/tests/module/output_module/test_display.py
@@ -2,7 +2,7 @@
from modi.module.output_module.display import Display
from modi.util.message_util import parse_data, parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestDisplay(unittest.TestCase):
diff --git a/tests/module/output_module/test_led.py b/tests/module/output_module/test_led.py
index 833003cf..39000ae9 100644
--- a/tests/module/output_module/test_led.py
+++ b/tests/module/output_module/test_led.py
@@ -2,7 +2,7 @@
from modi.module.output_module.led import Led
from modi.util.message_util import parse_data, parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestLed(unittest.TestCase):
diff --git a/tests/module/output_module/test_motor.py b/tests/module/output_module/test_motor.py
index 631fdc42..d401300f 100644
--- a/tests/module/output_module/test_motor.py
+++ b/tests/module/output_module/test_motor.py
@@ -2,7 +2,7 @@
from modi.module.output_module.motor import Motor
from modi.util.message_util import parse_data, parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestMotor(unittest.TestCase):
diff --git a/tests/module/output_module/test_speaker.py b/tests/module/output_module/test_speaker.py
index 94a5cfaf..68b8e2bc 100644
--- a/tests/module/output_module/test_speaker.py
+++ b/tests/module/output_module/test_speaker.py
@@ -2,7 +2,7 @@
from modi.module.output_module.speaker import Speaker
from modi.util.message_util import parse_data, parse_message
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
class TestSpeaker(unittest.TestCase):
diff --git a/tests/task/test_exe_task.py b/tests/task/test_exe_task.py
index 5c8ff704..2b7c954f 100644
--- a/tests/task/test_exe_task.py
+++ b/tests/task/test_exe_task.py
@@ -1,6 +1,6 @@
import unittest
-from modi.util.miscellaneous import MockConn
+from modi.util.miscellaneous_util import MockConn
from modi.task.exe_task import ExeTask