From a3c4fc270d54881d77e1d196836f45029fd24f7b Mon Sep 17 00:00:00 2001
From: Anton Tetov <anton@tetov.se>
Date: Mon, 13 Jun 2022 15:50:39 +0200
Subject: [PATCH] Installable structure

---
 .flake8                                       |   3 +
 README.md                                     |  23 +-
 environment.yml                               |   4 +-
 pyproject.toml                                |   3 +
 .../farmbot_yolo}/__init__.py                 |   0
 {farmbot_yolo => src/farmbot_yolo}/client.py  | 490 +++++++++---------
 {farmbot_yolo => src/farmbot_yolo}/darknet.py |   0
 {farmbot_yolo => src/farmbot_yolo}/detect.py  |   0
 .../farmbot_yolo}/download.py                 |   2 +-
 {farmbot_yolo => src/farmbot_yolo}/gripper.py |  10 +-
 .../farmbot_yolo}/location.py                 |   0
 {farmbot_yolo => src/farmbot_yolo}/main.py    |   0
 {farmbot_yolo => src/farmbot_yolo}/move.py    |   0
 .../farmbot_yolo}/read_credentials.py         |   0
 .../farmbot_yolo}/request_token.py            |   0
 {farmbot_yolo => src/farmbot_yolo}/try.py     |   0
 16 files changed, 273 insertions(+), 262 deletions(-)
 create mode 100644 .flake8
 rename {farmbot_yolo => src/farmbot_yolo}/__init__.py (100%)
 rename {farmbot_yolo => src/farmbot_yolo}/client.py (97%)
 rename {farmbot_yolo => src/farmbot_yolo}/darknet.py (100%)
 rename {farmbot_yolo => src/farmbot_yolo}/detect.py (100%)
 rename {farmbot_yolo => src/farmbot_yolo}/download.py (98%)
 rename {farmbot_yolo => src/farmbot_yolo}/gripper.py (76%)
 rename {farmbot_yolo => src/farmbot_yolo}/location.py (100%)
 rename {farmbot_yolo => src/farmbot_yolo}/main.py (100%)
 rename {farmbot_yolo => src/farmbot_yolo}/move.py (100%)
 rename {farmbot_yolo => src/farmbot_yolo}/read_credentials.py (100%)
 rename {farmbot_yolo => src/farmbot_yolo}/request_token.py (100%)
 rename {farmbot_yolo => src/farmbot_yolo}/try.py (100%)

diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..8dd399a
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length = 88
+extend-ignore = E203
diff --git a/README.md b/README.md
index ed1529e..f41979e 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 
 https://pjreddie.com/darknet/yolo/
 
-## YOLO, implemented by Darknet
+## YOLO, implemented using Darknet
 
 Darknet is a deep learning framework created by the author of YOLO. It is
 written in C and provides necessary Python APIs for object detection.
@@ -59,11 +59,10 @@ system does not perfectly align with the planting bed's corner.
 The camera coordinate can be transformed to the global one via a transition and
 a rotation. From the picture, we can easily write the formula
 
-## Install, Compile
+## Setup
 
-1. `git clone git@gitlab.control.lth.se:alexp/farmbot`
+1. `git clone git@gitlab.control.lth.se:robotlab/farmbot_yolo`
 1. Initialize submodules: `git submodules init && git submodules update`.
-1. Change `LIBSO=0` to `LIBSO=1` in `darknet/Makefile`.
 1. `make` to compile
 1. Install anaconda using [official
    installer](https://www.anaconda.com/products/distribution) and
@@ -75,6 +74,8 @@ a rotation. From the picture, we can easily write the formula
 
 ### Compile Darknet
 
+**Already done in the above steps**
+
 Check the
 [instruction](https://github.com/AlexeyAB/darknet#how-to-compile-on-linux-using-make)
 for how to use `Make` to compile on Linux.
@@ -95,32 +96,32 @@ for how to use the web app to set zeros.
 
 The software has three main modules:
 
-1. `move.py`: drive FarmBot to move, take photos, and open/close the gripper
-2. `detection.py`: run darknet as a dynamic library for detecting, output
+1. `move`: drive FarmBot to move, take photos, and open/close the gripper
+2. `detection`: run darknet as a dynamic library for detecting, output
    bounding boxes
-3. `location.py`: input bounding boxes, transfer to real-world coordinate
+3. `location`: input bounding boxes, transfer to real-world coordinate
 
-We also provide `main.py` as a warpper for all the modules above. By running it,
+We also provide `main.py` as a wrapper for all the modules above. By running it,
 you can make Farmbot automatically conduct the whole process. The three modules
 can also be run separately, mostly for debugging purpose.
 
 First go to `farmbot_yolo` and `conda activate <env>` to run the following scripts.
 `<env>` is the same as the one you created in _Install, Compile_
 
-### Move Famrbot, take photos, and open/close the gripper
+### Move Farmbot, take photos, and open/close the gripper
 
 ### YOLO detection
 
 All the arguments for file path are set to default.
 
 ```
-python ./detect.py --dont_show --ext_output --save_labels --input ../img --weights ../weights/yolov3-vattenhallen_best.weights  --config_file ../cfg/yolov3-vattenhallen-test.cfg --data_file ../data/vattenhallen.data
+python farmbot_yolo.detect --dont_show --ext_output --save_labels --input ../img --weights ../weights/yolov3-vattenhallen_best.weights  --config_file ../cfg/yolov3-vattenhallen-test.cfg --data_file ../data/vattenhallen.data
 ```
 
 ### Calculate location
 
 ```
-python location.py -v -cam ../static/camera_no_distortion.mat -loc ../img/locations/ -a ../img/annotations -o ../static/distance.txt -l ../log/location.log
+python farmbot_yolo.location -v -cam ../static/camera_no_distortion.mat -loc ../img/locations/ -a ../img/annotations -o ../static/distance.txt -l ../log/location.log
 ```
 
 All the arguments has default values, which means they can be all omitted if you
diff --git a/environment.yml b/environment.yml
index dea26e0..b8ba919 100644
--- a/environment.yml
+++ b/environment.yml
@@ -1,9 +1,10 @@
-name: false
+name: farm-dev
 channels:
   - conda-forge
 dependencies:
   - python==3.8
   - black
+  - isort
   - mypy
   - flake8
   - opencv
@@ -12,4 +13,3 @@ dependencies:
   - scipy
   - pandas
   - requests
-prefix: /local/home/tetov/miniforge3/envs/farm-dev
diff --git a/pyproject.toml b/pyproject.toml
index 270af2b..d4221ab 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,3 +26,6 @@ dependencies = [
 
 [project.optional-dependencies]
 dev = ["black", "mypy", "flake8", "isort"]
+
+[tool.isort]
+profile = "black"
diff --git a/farmbot_yolo/__init__.py b/src/farmbot_yolo/__init__.py
similarity index 100%
rename from farmbot_yolo/__init__.py
rename to src/farmbot_yolo/__init__.py
diff --git a/farmbot_yolo/client.py b/src/farmbot_yolo/client.py
similarity index 97%
rename from farmbot_yolo/client.py
rename to src/farmbot_yolo/client.py
index 296fcc4..bac752a 100755
--- a/farmbot_yolo/client.py
+++ b/src/farmbot_yolo/client.py
@@ -1,245 +1,245 @@
-"""Communicate with the server at the manufacturer.
-
-Not useful for the local drive script
-"""
-import paho.mqtt.client as mqtt
-import json
-import time
-from uuid import uuid4  # 通用唯一标识符 ( Universally Unique Identifier )
-import logging  # 日志模块
-
-# values over max (and under min) will be clipped
-MAX_X = 2400
-MAX_Y = 1200
-MAX_Z = 469  # TODO test this one!
-
-
-def coord(x, y, z):
-    return {"kind": "coordinate", "args": {"x": x, "y": y, "z": z}}  # 返回json 嵌套对象
-
-
-def move_request(x, y, z):
-    return {
-        "kind": "rpc_request",  # 返回 json对象,对象内含数组
-        "args": {"label": ""},
-        "body": [
-            {
-                "kind": "move_absolute",
-                "args": {
-                    "location": coord(x, y, z),
-                    "offset": coord(0, 0, 0),
-                    "speed": 100,
-                },
-            }
-        ],
-    }
-
-
-def read_pin_request(pin_number, pin_mode="digital"):
-    modes = {"digital": 0, "analog": 1}
-
-    if pin_mode != "digital":
-        raise NotImplementedError()
-
-    return {
-        "kind": "rpc_request",
-        "args": {"label": ""},
-        "body": [
-            {
-                "kind": "read_pin",
-                "args": {
-                    "label": "pin" + str(pin_number),
-                    "pin_mode": modes[pin_mode] or (modes["digital"]),
-                    "pin_number": pin_number,
-                },
-            }
-        ],
-    }
-
-
-def write_pin_request(pin_number, pin_value, pin_mode="digital"):
-    modes = {"digital": 0, "analog": 1}
-
-    if pin_mode != "digital":
-        raise NotImplementedError()
-
-    return {
-        "kind": "rpc_request",
-        "args": {"label": ""},
-        "body": [
-            {
-                "kind": "write_pin",
-                "args": {
-                    "pin_mode": modes[pin_mode] or (modes["digital"]),
-                    "pin_number": pin_number,
-                    "pin_value": pin_value,
-                },
-            }
-        ],
-    }
-
-
-def toggle_pin_request(pin_number):
-    return {
-        "kind": "rpc_request",
-        "args": {"label": ""},
-        "body": [
-            {
-                "kind": "toggle_pin",
-                "args": {
-                    "pin_number": pin_number,
-                },
-            }
-        ],
-    }
-
-
-def take_photo_request():
-    return {
-        "kind": "rpc_request",
-        "args": {"label": ""},  # label空着是为了在blocking_request中填上uuid,唯一识别码
-        "body": [{"kind": "take_photo", "args": {}}],
-    }
-
-
-def clip(v, min_v, max_v):
-    if v < min_v:
-        return min_v
-    if v > max_v:
-        return max_v
-    return v
-
-
-class FarmbotClient(object):
-    def __init__(self, device_id, token):
-
-        self.device_id = device_id
-        self.client = mqtt.Client()  # 类元素继承了另一个对象
-        self.client.username_pw_set(self.device_id, token)  # 传入 用户名和密码
-        self.client.on_connect = self._on_connect  # ???
-        self.client.on_message = self._on_message
-
-        logging.basicConfig(
-            level=logging.DEBUG,
-            format="%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s",
-            filename="farmbot_client.log",
-            filemode="a",
-        )
-        console = logging.StreamHandler()
-        console.setLevel(logging.INFO)
-        console.setFormatter(logging.Formatter("%(asctime)s\t%(message)s"))
-        logging.getLogger("").addHandler(console)
-
-        self.connected = False
-        self.client.connect(
-            "clever-octopus.rmq.cloudamqp.com", 1883, 60
-        )  # 前面的url要运行按README.md中request_token.py 后面俩是TCP Port, Websocket Port
-        self.client.loop_start()
-        # 初始化函数里就会连接到服务器上,所以每次实例化一个新的client时,就已经连上了
-
-    def shutdown(self):
-        self.client.disconnect()
-        self.client.loop_stop()
-
-    def move(self, x, y, z):
-        x = clip(x, 0, MAX_X)
-        y = clip(y, 0, MAX_Y)
-        z = clip(z, 0, MAX_Z)
-        status_ok = self._blocking_request(move_request(x, y, z))  # 发请求
-        logging.info(
-            "MOVE (%s,%s,%s) [%s]", x, y, z, status_ok
-        )  # 存日志,包括执行了什么“move x y z +返回值 ”
-
-    def take_photo(self):
-        # TODO: is this enough? it's issue a request for the photo, but is the actual capture async?
-        status_ok = self._blocking_request(take_photo_request())
-        logging.info("TAKE_PHOTO [%s]", status_ok)
-
-    def read_pin(self, pin_number, pin_mode="digital"):
-        status_ok = self._blocking_request(
-            read_pin_request(pin_number, pin_mode=pin_mode)
-        )
-        logging.info(f"READ PIN (pin_number: {pin_number}) [{status_ok}]")
-
-    def write_pin(self, pin_number, pin_value, pin_mode="digital"):
-        status_ok = self._blocking_request(
-            write_pin_request(pin_number, pin_value, pin_mode=pin_mode)
-        )
-        logging.info(
-            f"WRITE PIN (pin_number: {pin_number}, pin_value: {pin_value}) [{status_ok}]"
-        )
-
-    def toggle_pin(self, pin_number):
-        status_ok = self._blocking_request(toggle_pin_request(pin_number))
-        logging.info(f"TOGGLE PIN (pin_number: {pin_number}) [{status_ok}]")
-
-    def _blocking_request(self, request, retries_remaining=3):
-        if retries_remaining == 0:
-            logging.error(
-                "< blocking request [%s] OUT OF RETRIES", request
-            )  # 尝试3次,然后在日志中记录错误
-            return False
-
-        self._wait_for_connection()  # 在哪定义的?
-
-        # assign a new uuid for this attempt
-        self.pending_uuid = str(uuid4())
-        request["args"]["label"] = self.pending_uuid  # 接收move_request函数的json对象
-        logging.debug("> blocking request [%s] retries=%d", request, retries_remaining)
-
-        # send request off 发送请求
-        self.rpc_status = None
-        self.client.publish(
-            "bot/" + self.device_id + "/from_clients", json.dumps(request)
-        )
-
-        # wait for response
-        timeout_counter = 600  # ~1min 等待1s
-        while self.rpc_status is None:  # 这个self.rpc_status 是应答的flag
-            time.sleep(0.1)
-            timeout_counter -= 1
-            if timeout_counter == 0:
-                logging.warn("< blocking request TIMEOUT [%s]", request)  # 时间到了,无应答
-                return self._blocking_request(request, retries_remaining - 1)
-        self.pending_uuid = None
-
-        # if it's ok, we're done!
-        if self.rpc_status == "rpc_ok":
-            logging.debug("< blocking request OK [%s]", request)
-            return True
-
-        # if it's not ok, wait a bit and retry
-        if self.rpc_status == "rpc_error":
-            logging.warn("< blocking request ERROR [%s]", request)
-            time.sleep(1)
-            return self._blocking_request(request, retries_remaining - 1)
-
-        # unexpected state (???)
-        msg = "unexpected rpc_status [%s]" % self.rpc_status
-        logging.error(msg)
-        raise Exception(msg)
-
-    def _wait_for_connection(self):
-        # TODO: better way to do all this async event driven rather than with polling :/
-        timeout_counter = 600  # ~1min
-        while not self.connected:  # 用一个self.connected判断连上了没有,若没连上,等待
-            time.sleep(0.1)
-            timeout_counter -= 1
-            if timeout_counter == 0:
-                raise Exception("unable to connect")
-
-    def _on_connect(self, client, userdata, flags, rc):
-        logging.debug("> _on_connect")
-        self.client.subscribe("bot/" + self.device_id + "/from_device")
-        self.connected = True
-        logging.debug("< _on_connect")
-
-    def _on_message(self, client, userdata, msg):
-        resp = json.loads(msg.payload.decode())
-        if resp["args"]["label"] != "ping":
-            logging.debug("> _on_message [%s] [%s]", msg.topic, resp)
-        if (
-            msg.topic.endswith("/from_device")
-            and resp["args"]["label"] == self.pending_uuid
-        ):
-            self.rpc_status = resp["kind"]
+"""Communicate with the server at the manufacturer.
+
+Not useful for the local drive script
+"""
+import paho.mqtt.client as mqtt
+import json
+import time
+from uuid import uuid4  # 通用唯一标识符 ( Universally Unique Identifier )
+import logging  # 日志模块
+
+# values over max (and under min) will be clipped
+MAX_X = 2400
+MAX_Y = 1200
+MAX_Z = 469  # TODO test this one!
+
+
+def coord(x, y, z):
+    return {"kind": "coordinate", "args": {"x": x, "y": y, "z": z}}  # 返回json 嵌套对象
+
+
+def move_request(x, y, z):
+    return {
+        "kind": "rpc_request",  # 返回 json对象,对象内含数组
+        "args": {"label": ""},
+        "body": [
+            {
+                "kind": "move_absolute",
+                "args": {
+                    "location": coord(x, y, z),
+                    "offset": coord(0, 0, 0),
+                    "speed": 100,
+                },
+            }
+        ],
+    }
+
+
+def read_pin_request(pin_number, pin_mode="digital"):
+    modes = {"digital": 0, "analog": 1}
+
+    if pin_mode != "digital":
+        raise NotImplementedError()
+
+    return {
+        "kind": "rpc_request",
+        "args": {"label": ""},
+        "body": [
+            {
+                "kind": "read_pin",
+                "args": {
+                    "label": "pin" + str(pin_number),
+                    "pin_mode": modes[pin_mode] or (modes["digital"]),
+                    "pin_number": pin_number,
+                },
+            }
+        ],
+    }
+
+
+def write_pin_request(pin_number, pin_value, pin_mode="digital"):
+    modes = {"digital": 0, "analog": 1}
+
+    if pin_mode != "digital":
+        raise NotImplementedError()
+
+    return {
+        "kind": "rpc_request",
+        "args": {"label": ""},
+        "body": [
+            {
+                "kind": "write_pin",
+                "args": {
+                    "pin_mode": modes[pin_mode] or (modes["digital"]),
+                    "pin_number": pin_number,
+                    "pin_value": pin_value,
+                },
+            }
+        ],
+    }
+
+
+def toggle_pin_request(pin_number):
+    return {
+        "kind": "rpc_request",
+        "args": {"label": ""},
+        "body": [
+            {
+                "kind": "toggle_pin",
+                "args": {
+                    "pin_number": pin_number,
+                },
+            }
+        ],
+    }
+
+
+def take_photo_request():
+    return {
+        "kind": "rpc_request",
+        "args": {"label": ""},  # label空着是为了在blocking_request中填上uuid,唯一识别码
+        "body": [{"kind": "take_photo", "args": {}}],
+    }
+
+
+def clip(v, min_v, max_v):
+    if v < min_v:
+        return min_v
+    if v > max_v:
+        return max_v
+    return v
+
+
+class FarmbotClient(object):
+    def __init__(self, device_id, token):
+
+        self.device_id = device_id
+        self.client = mqtt.Client()  # 类元素继承了另一个对象
+        self.client.username_pw_set(self.device_id, token)  # 传入 用户名和密码
+        self.client.on_connect = self._on_connect  # ???
+        self.client.on_message = self._on_message
+
+        logging.basicConfig(
+            level=logging.DEBUG,
+            format="%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s",
+            filename="farmbot_client.log",
+            filemode="a",
+        )
+        console = logging.StreamHandler()
+        console.setLevel(logging.INFO)
+        console.setFormatter(logging.Formatter("%(asctime)s\t%(message)s"))
+        logging.getLogger("").addHandler(console)
+
+        self.connected = False
+        self.client.connect(
+            "clever-octopus.rmq.cloudamqp.com", 1883, 60
+        )  # 前面的url要运行按README.md中request_token.py 后面俩是TCP Port, Websocket Port
+        self.client.loop_start()
+        # 初始化函数里就会连接到服务器上,所以每次实例化一个新的client时,就已经连上了
+
+    def shutdown(self):
+        self.client.disconnect()
+        self.client.loop_stop()
+
+    def move(self, x, y, z):
+        x = clip(x, 0, MAX_X)
+        y = clip(y, 0, MAX_Y)
+        z = clip(z, 0, MAX_Z)
+        status_ok = self._blocking_request(move_request(x, y, z))  # 发请求
+        logging.info(
+            "MOVE (%s,%s,%s) [%s]", x, y, z, status_ok
+        )  # 存日志,包括执行了什么“move x y z +返回值 ”
+
+    def take_photo(self):
+        # TODO: is this enough? it's issue a request for the photo, but is the actual capture async?
+        status_ok = self._blocking_request(take_photo_request())
+        logging.info("TAKE_PHOTO [%s]", status_ok)
+
+    def read_pin(self, pin_number, pin_mode="digital"):
+        status_ok = self._blocking_request(
+            read_pin_request(pin_number, pin_mode=pin_mode)
+        )
+        logging.info(f"READ PIN (pin_number: {pin_number}) [{status_ok}]")
+
+    def write_pin(self, pin_number, pin_value, pin_mode="digital"):
+        status_ok = self._blocking_request(
+            write_pin_request(pin_number, pin_value, pin_mode=pin_mode)
+        )
+        logging.info(
+            f"WRITE PIN (pin_number: {pin_number}, pin_value: {pin_value}) [{status_ok}]"
+        )
+
+    def toggle_pin(self, pin_number):
+        status_ok = self._blocking_request(toggle_pin_request(pin_number))
+        logging.info(f"TOGGLE PIN (pin_number: {pin_number}) [{status_ok}]")
+
+    def _blocking_request(self, request, retries_remaining=3):
+        if retries_remaining == 0:
+            logging.error(
+                "< blocking request [%s] OUT OF RETRIES", request
+            )  # 尝试3次,然后在日志中记录错误
+            return False
+
+        self._wait_for_connection()  # 在哪定义的?
+
+        # assign a new uuid for this attempt
+        self.pending_uuid = str(uuid4())
+        request["args"]["label"] = self.pending_uuid  # 接收move_request函数的json对象
+        logging.debug("> blocking request [%s] retries=%d", request, retries_remaining)
+
+        # send request off 发送请求
+        self.rpc_status = None
+        self.client.publish(
+            "bot/" + self.device_id + "/from_clients", json.dumps(request)
+        )
+
+        # wait for response
+        timeout_counter = 600  # ~1min 等待1s
+        while self.rpc_status is None:  # 这个self.rpc_status 是应答的flag
+            time.sleep(0.1)
+            timeout_counter -= 1
+            if timeout_counter == 0:
+                logging.warn("< blocking request TIMEOUT [%s]", request)  # 时间到了,无应答
+                return self._blocking_request(request, retries_remaining - 1)
+        self.pending_uuid = None
+
+        # if it's ok, we're done!
+        if self.rpc_status == "rpc_ok":
+            logging.debug("< blocking request OK [%s]", request)
+            return True
+
+        # if it's not ok, wait a bit and retry
+        if self.rpc_status == "rpc_error":
+            logging.warn("< blocking request ERROR [%s]", request)
+            time.sleep(1)
+            return self._blocking_request(request, retries_remaining - 1)
+
+        # unexpected state (???)
+        msg = "unexpected rpc_status [%s]" % self.rpc_status
+        logging.error(msg)
+        raise Exception(msg)
+
+    def _wait_for_connection(self):
+        # TODO: better way to do all this async event driven rather than with polling :/
+        timeout_counter = 600  # ~1min
+        while not self.connected:  # 用一个self.connected判断连上了没有,若没连上,等待
+            time.sleep(0.1)
+            timeout_counter -= 1
+            if timeout_counter == 0:
+                raise Exception("unable to connect")
+
+    def _on_connect(self, client, userdata, flags, rc):
+        logging.debug("> _on_connect")
+        self.client.subscribe("bot/" + self.device_id + "/from_device")
+        self.connected = True
+        logging.debug("< _on_connect")
+
+    def _on_message(self, client, userdata, msg):
+        resp = json.loads(msg.payload.decode())
+        if resp["args"]["label"] != "ping":
+            logging.debug("> _on_message [%s] [%s]", msg.topic, resp)
+        if (
+            msg.topic.endswith("/from_device")
+            and resp["args"]["label"] == self.pending_uuid
+        ):
+            self.rpc_status = resp["kind"]
diff --git a/farmbot_yolo/darknet.py b/src/farmbot_yolo/darknet.py
similarity index 100%
rename from farmbot_yolo/darknet.py
rename to src/farmbot_yolo/darknet.py
diff --git a/farmbot_yolo/detect.py b/src/farmbot_yolo/detect.py
similarity index 100%
rename from farmbot_yolo/detect.py
rename to src/farmbot_yolo/detect.py
diff --git a/farmbot_yolo/download.py b/src/farmbot_yolo/download.py
similarity index 98%
rename from farmbot_yolo/download.py
rename to src/farmbot_yolo/download.py
index 67caace..fa732ab 100644
--- a/farmbot_yolo/download.py
+++ b/src/farmbot_yolo/download.py
@@ -6,7 +6,7 @@ from typing import List
 
 import requests
 
-import utils.creds as creds
+from farmbot_yolo import creds
 
 
 # note: download only returns 100 at a time!
diff --git a/farmbot_yolo/gripper.py b/src/farmbot_yolo/gripper.py
similarity index 76%
rename from farmbot_yolo/gripper.py
rename to src/farmbot_yolo/gripper.py
index 27b7221..b5f8fef 100755
--- a/farmbot_yolo/gripper.py
+++ b/src/farmbot_yolo/gripper.py
@@ -2,14 +2,15 @@
 
 TODO: Integrate with FarmbotClient or FarmbotYoloClient
 """
-from utils.client import FarmbotClient
-from utils.creds import device_id
-from utils.creds import token
+from farmbot_yolo.client import FarmbotClient
+from farmbot_yolo.creds import device_id
+from farmbot_yolo.creds import token
 
 GRIPPER_PIN = 12
 GRIPPER_OPEN_STATE = 0
 GRIPPER_CLOSED_STATE = 1
 
+
 def gripper_open():
     """Manipulate gripper by setting pin to 1."""
     client = FarmbotClient(device_id, token)
@@ -22,3 +23,6 @@ def gripper_close():
     client = FarmbotClient(device_id, token)
     client.write_pin(GRIPPER_PIN, GRIPPER_CLOSED_STATE, pin_mode="digital")
     client.shutdown()
+
+
+# TODO: Add argumentparser and ifnamemain function
diff --git a/farmbot_yolo/location.py b/src/farmbot_yolo/location.py
similarity index 100%
rename from farmbot_yolo/location.py
rename to src/farmbot_yolo/location.py
diff --git a/farmbot_yolo/main.py b/src/farmbot_yolo/main.py
similarity index 100%
rename from farmbot_yolo/main.py
rename to src/farmbot_yolo/main.py
diff --git a/farmbot_yolo/move.py b/src/farmbot_yolo/move.py
similarity index 100%
rename from farmbot_yolo/move.py
rename to src/farmbot_yolo/move.py
diff --git a/farmbot_yolo/read_credentials.py b/src/farmbot_yolo/read_credentials.py
similarity index 100%
rename from farmbot_yolo/read_credentials.py
rename to src/farmbot_yolo/read_credentials.py
diff --git a/farmbot_yolo/request_token.py b/src/farmbot_yolo/request_token.py
similarity index 100%
rename from farmbot_yolo/request_token.py
rename to src/farmbot_yolo/request_token.py
diff --git a/farmbot_yolo/try.py b/src/farmbot_yolo/try.py
similarity index 100%
rename from farmbot_yolo/try.py
rename to src/farmbot_yolo/try.py
-- 
GitLab