diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5b050f278139b6ef62ec43f038ad79c8c3882d9f
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,22 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.3.0
+    hooks:
+      - id: trailing-whitespace
+      - id: end-of-file-fixer
+      - id: check-yaml
+      - id: check-added-large-files
+  - repo: https://github.com/psf/black
+    rev: 22.6.0
+    hooks:
+      - id: black
+  - repo: https://github.com/PyCQA/isort
+    rev: 5.10.1
+    hooks:
+      - id: isort
+  - repo: https://github.com/PyCQA/flake8
+    rev: 4.0.1
+    hooks:
+      - id: flake8
diff --git a/pyproject.toml b/pyproject.toml
index a94d26e536f661a1ff4823056be02756669bb68f..d07d56603168c00dcc9366956da18a095af90267 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,13 +7,9 @@ name = "farmbot_yolo"
 description = "farmbot object identification using YOLO/Darknet"
 version = "0.1.0"
 readme = "README.md"
-license = {text = "TODO"}
-authors = [
-    { name = "Ziliang Xiong", email = "13718722639leo@gmail.com" }
-]
-classifiers = [
-    "Programming Language :: Python :: 3",
-]
+license = { text = "TODO" }
+authors = [{ name = "Ziliang Xiong", email = "13718722639leo@gmail.com" }]
+classifiers = ["Programming Language :: Python :: 3"]
 requires-python = ">=3.7"
 dependencies = [
     "requests",
diff --git a/src/farmbot_yolo/client.py b/src/farmbot_yolo/client.py
index bac752a19d03878719beb79d852d23bfb24a2748..a3f2df7ddb0c618cc17cc0c6c53cc3f88c410f56 100644
--- a/src/farmbot_yolo/client.py
+++ b/src/farmbot_yolo/client.py
@@ -2,17 +2,20 @@
 
 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  # 日志模块
+from logging import getLogger
+from uuid import uuid4
+
+import paho.mqtt.client as mqtt
 
 # values over max (and under min) will be clipped
 MAX_X = 2400
 MAX_Y = 1200
 MAX_Z = 469  # TODO test this one!
 
+log = getLogger(__name__)
+
 
 def coord(x, y, z):
     return {"kind": "coordinate", "args": {"x": x, "y": y, "z": z}}  # 返回json 嵌套对象
@@ -119,17 +122,6 @@ class FarmbotClient(object):
         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
@@ -146,36 +138,35 @@ class FarmbotClient(object):
         y = clip(y, 0, MAX_Y)
         z = clip(z, 0, MAX_Z)
         status_ok = self._blocking_request(move_request(x, y, z))  # 发请求
-        logging.info(
+        log.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?
+        # 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)
+        log.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}]")
+        log.info(f"READ PIN (pin: {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}]"
-        )
+        log.info(f"WRITE PIN (pin: {pin_number}, 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}]")
+        log.info(f"TOGGLE PIN (pin: {pin_number}) [{status_ok}]")
 
     def _blocking_request(self, request, retries_remaining=3):
         if retries_remaining == 0:
-            logging.error(
+            log.error(
                 "< blocking request [%s] OUT OF RETRIES", request
             )  # 尝试3次,然后在日志中记录错误
             return False
@@ -185,7 +176,7 @@ class FarmbotClient(object):
         # 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)
+        log.debug("> blocking request [%s] retries=%d", request, retries_remaining)
 
         # send request off 发送请求
         self.rpc_status = None
@@ -199,24 +190,24 @@ class FarmbotClient(object):
             time.sleep(0.1)
             timeout_counter -= 1
             if timeout_counter == 0:
-                logging.warn("< blocking request TIMEOUT [%s]", request)  # 时间到了,无应答
+                log.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)
+            log.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)
+            log.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)
+        log.error(msg)
         raise Exception(msg)
 
     def _wait_for_connection(self):
@@ -229,15 +220,15 @@ class FarmbotClient(object):
                 raise Exception("unable to connect")
 
     def _on_connect(self, client, userdata, flags, rc):
-        logging.debug("> _on_connect")
+        log.debug("> _on_connect")
         self.client.subscribe("bot/" + self.device_id + "/from_device")
         self.connected = True
-        logging.debug("< _on_connect")
+        log.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)
+            log.debug("> _on_message [%s] [%s]", msg.topic, resp)
         if (
             msg.topic.endswith("/from_device")
             and resp["args"]["label"] == self.pending_uuid
diff --git a/src/farmbot_yolo/detect.py b/src/farmbot_yolo/detect.py
index 252b251970e1002589318ef58ebe96bc362de10e..a9dfe6415df152bd9a280d88f60661d222c2e78f 100644
--- a/src/farmbot_yolo/detect.py
+++ b/src/farmbot_yolo/detect.py
@@ -1,20 +1,18 @@
-"""
-load images taken by the camera, return bounding boxes
+"""Detect objects using trained model.
 
-customized based on Alexy/darknet_images.py
+Based on `darknet/darknet_images.py
+https://github.com/AlexeyAB/darknet/blob/master/darknet_images.py`.
 """
 import json
 import random
 import time
-from argparse import ArgumentParser
-from argparse import Namespace
+from argparse import ArgumentParser, Namespace
 from pathlib import Path
 
 import cv2
-from darknet import darknet
 
-from farmbot_yolo import REPO
-from farmbot_yolo import TMPDIR
+from darknet import darknet
+from farmbot_yolo import REPO, TMPDIR
 
 
 def check_arguments_errors(args):
@@ -107,7 +105,7 @@ def convert2relative(image, bbox):
 def save_annotations(original_size, name, image, detections, class_names):
     """
     Files saved with image_name.txt and relative coordinates
-    oringinal_size is Ziliang's improvement
+    original_size is Ziliang's improvement
     """
     height, width, _ = original_size
 
diff --git a/src/farmbot_yolo/download.py b/src/farmbot_yolo/download.py
index 28cec20cfae9831abade8d55784120044c6be41c..9b69b6f17e9e74b5fd05172e646a17306524cb29 100644
--- a/src/farmbot_yolo/download.py
+++ b/src/farmbot_yolo/download.py
@@ -2,23 +2,39 @@
 import argparse
 import json
 import os
+from logging import getLogger
 from pathlib import Path
-from typing import List
+from typing import Dict, List, Tuple
 
 import requests
 
 from farmbot_yolo import TMPDIR
-from farmbot_yolo import creds
+from farmbot_yolo.read_credentials import read_credentials
 
-IMG_FILE_SUFFIX = ".jpg"
+log = getLogger(__name__)
 
-REQUEST_HEADERS = {
-    "Authorization": "Bearer " + creds.token,
-    "content-type": "application/json",
-}
+IMG_FILE_SUFFIX = "jpg"
 
 
-def download_images(directory: os.PathLike, delete_after=False) -> List[str]:
+ENDPOINT_URL = "https://my.farmbot.io/api/images"
+
+
+def get_auth_header() -> Dict[str, str]:
+    creds = read_credentials()
+    return {"Authorization": f"Bearer {creds['token']}"}
+
+
+def get_img_metadata() -> Dict:
+    res = requests.get(ENDPOINT_URL, headers=get_auth_header())
+    img_metadata_list = res.json()
+
+    log.debug(f"Response: {img_metadata_list}")
+    log.debug(f"Response length: {len(img_metadata_list)}")
+
+    return img_metadata_list
+
+
+def download_images(directory: os.PathLike) -> List[Tuple[Path, Path]]:
     """Download all images on server, optionally deleting after download.
 
     Parameters
@@ -26,49 +42,31 @@ def download_images(directory: os.PathLike, delete_after=False) -> List[str]:
     directory
         directory to store images in
 
-    Raises
-    ------
-    RuntimeError
-        If the server gives a bad response (not 200).
-
     Returns
     -------
-    list of str
-        List of filepaths with downloaded images
+        List of filepaths with downloaded images and metadata files
     """
-    response = requests.get("https://my.farmbot.io/api/images", headers=REQUEST_HEADERS)
-    json_response = response.json()
-
-    if response.status_code != 200:
-        raise RuntimeError(f"Got status code {response.status_code}.")
+    img_metadata_list = get_img_metadata()
 
-    print(json_response)
-    print(f"Got a response containing {len(json_response)}")
-
-    if len(json_response) < 1:
-        return []
-
-    created_files = []
-
-    for img_dict in json_response:
-        if "placehold.it" in img_dict["attachment_url"]:
-            print("IGNORE! placeholder", img_dict["id"])
-            continue
+    downloaded_files = []
 
+    for img_dict in img_metadata_list:
         server_path: str = img_dict["meta"]["name"]
 
         if not server_path.startswith("/tmp/images"):
-            print("meta name does not start with /tmp/images")
+            # TODO: Why?
+            log.info("meta name does not start with /tmp/images, skipping")
             continue
 
-        filename = Path(server_path).stem + IMG_FILE_SUFFIX
+        filename = f"{Path(server_path).stem}_{img_dict['id']}.{IMG_FILE_SUFFIX}"
+
         filepath = Path(directory) / filename
 
-        print(">", filepath)
+        log.debug(f"Will download to: {filepath}")
 
         # download image from google storage and save locally
         if filepath.exists():
-            print("File exists, skipping")
+            log.warning(f"File {filepath.absolute()} exists, skipping")
             continue
 
         img_req = requests.get(img_dict["attachment_url"], allow_redirects=True)
@@ -76,7 +74,7 @@ def download_images(directory: os.PathLike, delete_after=False) -> List[str]:
         with filepath.open(mode="wb") as fp:
             fp.write(img_req.content)
 
-        img_metadata_dict = {
+        saved_metadata = {
             "farmbot_metadata": {
                 "id": img_dict["id"],
                 "location": {
@@ -87,20 +85,25 @@ def download_images(directory: os.PathLike, delete_after=False) -> List[str]:
             }
         }
 
-        metadata_filepath = filepath.with_suffix(".json")
-        with metadata_filepath.open(mode="w") as fp:
-            json.dump(img_metadata_dict, fp)
+        saved_metadata_filepath = filepath.with_suffix(".json")
+        with saved_metadata_filepath.open(mode="w") as fp:
+            json.dump(saved_metadata, fp)
 
-        # post delete from cloud storage
-        if delete_after:
-            requests.delete(
-                f"https://my.farmbot.io/api/images/{img_dict['id']}",
-                headers=REQUEST_HEADERS,
-            )
+        downloaded_files.append((filepath, saved_metadata_filepath))
 
-        created_files.append((filepath, metadata_filepath))
+    return downloaded_files
 
-    return created_files
+
+def delete_image(id: str):
+    requests.delete(f"{ENDPOINT_URL}/{id}", headers=get_auth_header())
+
+
+def delete_all_on_server() -> List[str]:
+    img_metadata_list = get_img_metadata()
+    ids = [data["id"] for data in img_metadata_list]
+
+    for id in ids:
+        delete_image(id)
 
 
 if __name__ == "__main__":
@@ -111,8 +114,11 @@ if __name__ == "__main__":
         help="Directory to download images too.",
         default=TMPDIR,
     )
-    parser.add_argument("-d", "--delete", action="store_true")
+    parser.add_argument("-d", "--delete-all", action="store_true")
 
     args = parser.parse_args()
 
-    download_images(args.download_dir, delete_after=args.delete)
+    download_images(args.download_dir)
+
+    if args.delete_all:
+        delete_all_on_server()
diff --git a/src/farmbot_yolo/gripper.py b/src/farmbot_yolo/gripper.py
index b5f8fef872ed3e44b9aa95a7364980ea00de8c0e..3e0011f5e3de5d864f9e416f84f0a63667579e15 100644
--- a/src/farmbot_yolo/gripper.py
+++ b/src/farmbot_yolo/gripper.py
@@ -2,27 +2,44 @@
 
 TODO: Integrate with FarmbotClient or FarmbotYoloClient
 """
+from argparse import ArgumentParser
+from typing import Dict
+
 from farmbot_yolo.client import FarmbotClient
-from farmbot_yolo.creds import device_id
-from farmbot_yolo.creds import token
+from farmbot_yolo.read_credentials import read_credentials
 
 GRIPPER_PIN = 12
 GRIPPER_OPEN_STATE = 0
 GRIPPER_CLOSED_STATE = 1
 
 
-def gripper_open():
+def gripper_open(creds: Dict[str, str]):
     """Manipulate gripper by setting pin to 1."""
-    client = FarmbotClient(device_id, token)
+    client = FarmbotClient(creds["device_id"], creds["token"])
     client.write_pin(GRIPPER_PIN, GRIPPER_OPEN_STATE, pin_mode="digital")
     client.shutdown()
 
 
-def gripper_close():
+def gripper_close(creds: Dict[str, str]):
     """Manipulate gripper by setting pin to 0."""
-    client = FarmbotClient(device_id, token)
+    client = FarmbotClient(creds["device_id"], creds["token"])
     client.write_pin(GRIPPER_PIN, GRIPPER_CLOSED_STATE, pin_mode="digital")
     client.shutdown()
 
 
-# TODO: Add argumentparser and ifnamemain function
+if __name__ == "__main__":
+    parser = ArgumentParser("Manipulate gripper.")
+    parser.add_argument("--open", action="store_true")
+    parser.add_argument("--close", action="store_true")
+
+    args = parser.parse_args()
+
+    creds = read_credentials
+
+    if args.open:
+        gripper_open(creds)
+    elif args.close:
+        gripper_close(creds)
+    else:
+        parser.print_help()
+        exit(1)
diff --git a/src/farmbot_yolo/location.py b/src/farmbot_yolo/location.py
index 2f9e1ef6345f096847879b85f74a92dfed217749..41cbef233f178441c7c54e425ab4d38815acd3f1 100644
--- a/src/farmbot_yolo/location.py
+++ b/src/farmbot_yolo/location.py
@@ -1,7 +1,8 @@
 """Convert coordinates local to photos to global coordinates.
-This script loads intrinsci camera matrix, which has been determined by
+
+This script loads intrinsic camera matrix, which has been determined by
 calibration using MATLAB. It reads YOLO's bounding boxes and calculate their
-global 2D coordinate on the planting bed accodring to coordinate transform.
+global 2D coordinate on the planting bed according to coordinate transform.
 """
 
 import json
@@ -17,21 +18,15 @@ from scipy.io import loadmat
 from farmbot_yolo import LOGDIR, REPO, TMPDIR
 
 """Logger for log file"""
-_LOG = getLogger(__name__)
+log = getLogger(__name__)
 
-"""Type alias"""
+"""Type aliases"""
 CameraPosition = Tuple[float, float, float]
 BoundingBox = Tuple[float, float, float, float]
 KMatrix = ndarray(shape=(3, 3))
 
-"""Allowed input and output file formats."""
-_CAM_EXTENSIONS = "mat"
-_ANNOTATION_EXTENSIONS = "txt"
-_LOCATIONS_EXTENSIONS = "txt"
-_OFFSET_EXTENSIONS = "txt"
-
-
-"""Constant sweeping height"""
+# Constant sweeping height
+# TODO: Expose to users
 SWEEP_Z = 575  # change according to the setting, z=-100, height is 57.5cm
 
 
@@ -50,32 +45,14 @@ def read_offsets(offset_path: Path) -> Tuple[Tuple[float, float]]:
         distance of the camera centroid to z axis of Farmbot (dx, dy) &
         distance of the gripper centroid to z axis of Farmbot (dx, dy)
     """
-    if not offset_path.is_file():
-        _LOG.error("{} is not a file or does not exist".format(offset_path))
-        return None
-
-    if not offset_path.suffix.lstrip(".") in _OFFSET_EXTENSIONS:
-        _LOG.error(
-            "{} must have an legal\
-             extension: {}".format(
-                offset_path, _OFFSET_EXTENSIONS
-            )
-        )
-        return None
-
-    try:
-        with open(offset_path, "r") as f:
-            offsets = f.readlines()
-    except IOError:
-        _LOG.error("Unable to open input file {}.".format(offset_path))
-        return None
-
-    cam_offset = (int(offsets[1]), int(offsets[2]))
-    gripper_offset = (int(offsets[4]), int(offsets[5]))
-    _LOG.info(
-        "Load the gripper offset\n{}\n and the camera offset \n{}".format(
-            gripper_offset, cam_offset
-        )
+    with offset_path.open(mode="r") as fp:
+        offsets = fp.readlines()
+
+    cam_offset = (float(offsets[1]), float(offsets[2]))
+    gripper_offset = (float(offsets[4]), float(offsets[5]))
+
+    log.info(
+        "Load the gripper offset: {gripper_offset} and the camera offset {cam_offset}"
     )
     return cam_offset, gripper_offset
 
@@ -93,28 +70,26 @@ def load_cam_matrix(cam_path: Path) -> Optional[ndarray]:
     intrinsic_matrix
         K matrix of the camera
     """
-    if not cam_path.suffix.lstrip(".") == _CAM_EXTENSIONS:
-        _LOG.error("{} has an illegal extension".format(cam_path))
-        return None
-
-    try:
-        data = loadmat(cam_path)
-    except FileNotFoundError:
-        _LOG.error(" No such file")
-        return None
+    with cam_path.open(mode="r") as fp:
+        data = loadmat(fp)
 
     intrinsic_matrix = data["camera_no_distortion"][0, 0][11]
-    _LOG.info("Load intrinsic_matrix of the camera \n{}".format(intrinsic_matrix))
+    log.info(f"Loaded intrinsic_matrix of the camera \n{intrinsic_matrix}")
     return intrinsic_matrix
 
 
 def cam_coordinate(pixel_x: int, pixel_y: int, cam_matrix) -> Tuple[float, float]:
-    """
-    Project one object's pixel coordinate into  the camera coordinate system
+    """Project one object's pixel coordinate into  the camera coordinate system.
 
+    Parameters
+    ----------
     Input: detection: a bounding box <x, y, w, h>
-           inner_matrix: matrix K that contains focal length and other inner parameters
-    Output: object's centroid location in camera coordinate
+    cam_matrix
+        Matrix K that contains focal length and other inner parameters.
+
+    Returns
+    -------
+        object's bounding box centroid coordinate in the camera coordinate system.
     """
     normalized_coordinate = dot(
         inv(cam_matrix.transpose()),
@@ -123,11 +98,8 @@ def cam_coordinate(pixel_x: int, pixel_y: int, cam_matrix) -> Tuple[float, float
     camera_coordinate = squeeze(normalized_coordinate)
     ratio = float(SWEEP_Z / camera_coordinate[2])
     local_position = (ratio * camera_coordinate[0], ratio * camera_coordinate[1])
-    _LOG.debug(
-        "Transfer from pixel coordinate to Camera coordinate. \n {}".format(
-            local_position
-        )
-    )
+    log.debug(f"Transfer from pixel coordinate to Camera coordinate. {local_position}")
+
     return local_position
 
 
@@ -159,25 +131,23 @@ def global_coordinate(
     x_camera, y_camera = coords_camera
     x_encoders, y_encoders = coords_encoders
 
+    # TODO: Expose to user
     x_delta_camera_gripper = 150
     y_delta_camera_gripper = 15
 
-    # TODO: Double check
+    # TODO: Test more
     x_global = x_camera + x_encoders + x_delta_camera_gripper
     y_global = y_camera + y_encoders + y_delta_camera_gripper
-    #          411        300          45          0
 
     return x_global, y_global
 
 
 def cal_location(args: Namespace) -> ndarray:
-    """
-    main function for this script
-    """
+    """Main function for this script."""
     cam_offset, gripper_offset = read_offsets(args.offset)
     K_matrix = load_cam_matrix(args.camera_matrix)
 
-    _LOG.info("Global coordinate calculation begins.")
+    log.info("Global coordinate calculation begins.")
 
     img_metadata_files = args.input_dir.glob("*.json")
 
@@ -192,7 +162,7 @@ def cal_location(args: Namespace) -> ndarray:
         x_encoders = farmbot_metadata["location"]["x"]
         y_encoders = farmbot_metadata["location"]["y"]
 
-        _LOG.debug(f"Loaded detection annotations: {annotations}")
+        log.debug(f"Loaded detection annotations: {annotations}")
 
         updated_annotations = []
         for annotation in annotations:
diff --git a/src/farmbot_yolo/main.py b/src/farmbot_yolo/main.py
index e1871366e771e3105c2c619fff147866cdb20678..7bd0bdc1a640ddecc9227461425ca5427f2c2c8b 100644
--- a/src/farmbot_yolo/main.py
+++ b/src/farmbot_yolo/main.py
@@ -1,23 +1,23 @@
-"""
-Author: Ziliang
-The main script of the project, it calls scripts for movement, detection, and coordinate calculation.
+"""Demo script.
 
+Calls scripts for movement, detection, and coordinate calculation.
 """
 from argparse import ArgumentParser, Namespace
-from logging import StringTemplateStyle
-from os import listdir, remove
-from os.path import join
+from logging import getLogger
 from pathlib import Path
-from typing import Tuple
+
 from numpy import sqrt
 from pandas import DataFrame
-from gripper import gripper_close, gripper_open
 
-from farmbot_yolo.move import *
-from farmbot_yolo.detect import *
-from farmbot_yolo.location import *
+from farmbot_yolo import TMPDIR
+from farmbot_yolo.detect import detect
+from farmbot_yolo.download import download_images
+from farmbot_yolo.gripper import gripper_close, gripper_open
+from farmbot_yolo.location import cal_location
+from farmbot_yolo.move import simple_move
+from farmbot_yolo.scan_bed import scan_bed
 
-_LOG = getLogger(__name__)
+log = getLogger(__name__)
 
 GRIP_Z = 468  # measure!
 SCAN_Z = 0
@@ -32,7 +32,8 @@ def remove_overlap(table_coordinate: DataFrame, tolerance=50.00) -> DataFrame:
     , delete the one with lower probability
 
     Choose a reasonable tolerance!!
-    :param table_coordinate: pandas dataframe that each row corresponds to a target [class, x, y, confidence]
+    :param table_coordinate: pandas data frame that each row corresponds to a
+        target [class, x, y, confidence]
     :param tolerance: a distance threshold
     """
     num_coordinates, num_col = table_coordinate.shape
@@ -52,34 +53,21 @@ def remove_overlap(table_coordinate: DataFrame, tolerance=50.00) -> DataFrame:
     return table_coordinate
 
 
-def remove_temp(path: Path) -> None:
-    """
-    Clean temporary files, i.e., photos, location.txt, annotations
-    """
-    for filename in listdir(path):
-        file = Path(join(path, filename))
-        if file.is_file():
-            remove(file)
-    return
-
-
 def main(args: Namespace):
-    # clean temporary files
-    remove_temp(args.input)
-    remove_temp(args.locations)
-    remove_temp(args.annotations)
     # start from the origin
     simple_move(ORIGIN_X, ORIGIN_Y, ORIGIN_Z)
-    _LOG.info("Go back to the origin")
+    log.info("Go back to the origin")
     # scan
-    scan(args.photo, args.locations, flag=False)
-    _LOG.info("Scan the planting bed")
+    log.info("Scan the planting bed")
+    scan_bed()
+    log.info("Downloading images")
+    download_images(TMPDIR)
     # detect
     detect(args)
-    _LOG.info("Detection is done")
+    log.info("Detection is done")
     # calculate locations
     list_global_coordinate = cal_location(args)
-    _LOG.info("Global coordinate calculation is done.")
+    log.info("Global coordinate calculation is done.")
     # choose class
     table_global_coordinate = DataFrame(
         list_global_coordinate, columns=["class", "x", "y", "confidence"]
@@ -90,10 +78,10 @@ def main(args: Namespace):
     goal_class = table_global_coordinate[
         table_global_coordinate["class"] == args.category
     ]
-    _LOG.info("Choose {}".format(args.category))
-    # if there is no desiered class of plants
+    log.info("Choose {}".format(args.category))
+    # if there is no desired class of plants
     if goal_class.empty:
-        _LOG.info("There is no {}".format(args.category))
+        log.info("There is no {}".format(args.category))
     # move and grip
     num_goals, num_col = goal_class.shape
     for i in range(num_goals):
@@ -102,7 +90,7 @@ def main(args: Namespace):
         open()
         gripper_open()  # to make sure the gripper is open before gripping
         gripper_close()
-        # go back to the orgin
+        # go back to the origin
         simple_move(x, y, GRIP_Z, False)
         gripper_open()
     return
@@ -111,46 +99,11 @@ def main(args: Namespace):
 if __name__ == "__main__":
     parser = ArgumentParser(description="YOLOv3 detection on Farmbot")
     # parsers for move
-    parser.add_argument(
-        "-p",
-        "--photo",
-        type=Path,
-        default="../img",
-        help="Mode for FarmBot, 1 for simple move with an assigned detination, 2 for scaning",
-    )
-    # parsers for detect
-    parser.add_argument(
-        "--input",
-        type=str,
-        default="../img",
-        help="image source. It can be a single image, a"
-        "txt with paths to them, or a folder. Image valid"
-        " formats are jpg, jpeg or png."
-        "If no input is given, ",
-    )
-    parser.add_argument(
-        "--batch_size",
-        default=1,
-        type=int,
-        help="number of images to be processed at the same time",
-    )
     parser.add_argument(
         "--weights",
         default="../weights/yolov3-vattenhallen_best.weights",
         help="yolo weights path",
     )
-    parser.add_argument(
-        "--ext_output",
-        action="store_true",
-        default=True,
-        help="display bbox coordinates of detected objects",
-    )
-    parser.add_argument(
-        "--save_labels",
-        action="store_true",
-        default=True,
-        help="save detections bbox for each image in yolo format",
-    )
     parser.add_argument(
         "--config_file",
         default="../cfg/yolov3-vattenhallen-test.cfg",
@@ -165,7 +118,7 @@ if __name__ == "__main__":
         default=0.25,
         help="remove detections with lower confidence",
     )
-    # arguemtns for grip
+    # arguments for grip
     parser.add_argument(
         "-ca",
         "--category",
@@ -181,35 +134,6 @@ if __name__ == "__main__":
         default="../static/camera_no_distortion.mat",
         help="Path to mat file that contains intrinsic camera matrix K",
     )
-    parser.add_argument(
-        "-loc",
-        "--locations",
-        type=Path,
-        default="../img/locations/",
-        help="the path to txt files contains locations from encoders corresponds to each photo",
-    )
-    parser.add_argument(
-        "-a",
-        "--annotations",
-        type=Path,
-        default="../img/annotations",
-        help="the path to txt files contains annotations for each photo",
-    )
-    parser.add_argument(
-        "-o",
-        "--offset",
-        type=Path,
-        default="../static/distance.txt",
-        help="the txt contains distance offset for camera and gripper",
-    )
-    parser.add_argument(
-        "-l", "--log", type=Path, default="../log/main.log", help="Path to the log file"
-    )
-    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode.")
     args = parser.parse_args()
 
-    if args.verbose:
-        basicConfig(filename=args.log, level=DEBUG)
-    else:
-        basicConfig(filename=args.log, level=INFO)
     main(args)
diff --git a/src/farmbot_yolo/move.py b/src/farmbot_yolo/move.py
index f44058db6d4397250cdee05d5409eb4894f25dbb..2f5103d047da1ffb8513073b5d75367c4096ae55 100644
--- a/src/farmbot_yolo/move.py
+++ b/src/farmbot_yolo/move.py
@@ -1,129 +1,31 @@
-"""
-Author: Ziliang Xiong
-This script is for all the functions that drive Farmbot to Move, including:
-1. Taking Photos 2. Move to an assigned point (x, y, z)
-3. Sweep the planting bed 4. Grip a target
-Note: it is for remote server, can ben replaced by a local script
-"""
-from argparse import ArgumentParser
-from logging import getLogger
-from os import path, makedirs, system
-import sys
-from time import sleep, strftime, time
-
-# from serial import Serial, PARITY_NONE, STOPBITS_ONE, EIGHTBITS
-from requests.api import delete
-from typing import List
-from pathlib import Path
-from logging import basicConfig, DEBUG, INFO, error, getLogger
-from urllib import request
+"""Move tool to coordinate."""
 
-from datetime import timezone, datetime
-from dateutil.parser import parse
-from requests import get, delete
-from farmbot_yolo import LOGDIR, TMPDIR
+from argparse import ArgumentParser
 
-from farmbot_yolo import creds
-import farmbot_yolo
 from farmbot_yolo.client import FarmbotClient
-from farmbot_yolo.download import download_images
-
-_SWEEEP_HEIGHT = 0
+from farmbot_yolo.read_credentials import read_credentials
 
-log = getLogger(__name__)
 
+def simple_move(x: float, y: float, z: float) -> None:
+    """Move to coordinate
 
-def scan(
-    img_dir: Path,
-    min_x=0,
-    max_x=1175,
-    min_y=0,
-    max_y=974,
-    delta=300,
-    offset=0,
-) -> List:
-    """
-    scan the bed at a certain height, first move along x axis, then y, like a zig zag;
-    Taking pictures and record the location of the camera that corresponds to the picture
-    The default value of x, y should be from the measurement of Farmbot
-    Input: min_x: left most point on x axis
-           max_x: right most point on x axis
-           min_y: front most point on y axis
-           max_y: back most point on y axis
-           delta: the interval for scaning
-           offset:
-    """
-    pts = []
-    sweep_y_negative = False
-    for x in range(min_x, max_x, delta):
-        y_range = range(min_y, max_y, delta)
-        if sweep_y_negative:
-            y_range = reversed(y_range)
-        sweep_y_negative = not sweep_y_negative
-        for y in y_range:
-            pts.append((x + offset, y + offset))
-
-    log.info("Moving pattern generated")
-
-    client = FarmbotClient(creds.device_id, creds.token)
-    client.move(0, 0, _SWEEEP_HEIGHT)  # ensure moving from original
-    for x, y in pts:
-        client.move(x, y, _SWEEEP_HEIGHT)  # move camera
-        # take_photo(img_path)
-        client.take_photo()
-    client.shutdown()
-
-    return pts
-
-
-def simple_move(x: int, y: int, z: int) -> None:
-    """
-    Move to a place, if flag is true, take a picture
-    Input: x, y,z: destination point
-           photo: take a pic or not
+    Parameters
+    ----------
+    x
+    y
+    z
     """
     creds = read_credentials()
     client = FarmbotClient(creds["device_id"], creds["token"])
     client.move(x, y, z)
     client.shutdown()
-    return None
 
 
 if __name__ == "__main__":
     parser = ArgumentParser()
-    parser.add_argument(
-        "-m",
-        "--mode",
-        type=int,
-        help="Mode for FarmBot, 1 for simple move with an assigned detination, 2 for scanning",
-    )
-    parser.add_argument(
-        "-l",
-        "--log",
-        type=Path,
-        default=LOGDIR / "move.log",
-        help="Path to the log file",
-    )
-    parser.add_argument(
-        "-p",
-        "--photo_dir",
-        type=Path,
-        default=TMPDIR,
-        help="Directory to store photos.",
-    )
-    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode")
+    parser.add_argument("X", type=float, help="X coordinate of the target")
+    parser.add_argument("Y", type=float, help="Y coordinate of the target")
+    parser.add_argument("Z", type=float, help="Z coordinate of the target")
     args = parser.parse_args()
 
-    if args.mode == 1:
-        log.info("Input the destination:")
-        destination_x = int(input("X:"))
-        destination_y = int(input("Y:"))
-        destination_z = int(input("Z:"))
-        simple_move_start = time()
-        simple_move(destination_x, destination_y, destination_z)
-        log.info(f"time cost {time()-simple_move_start}")
-    elif args.mode == 2:
-        scan(args.photo_dir)
-        download_images(args.photo_dir)
-    else:
-        log.error("Wrong mode number {arguments.mode}")
+    simple_move(args.X, args.Y, args.Z)
diff --git a/src/farmbot_yolo/read_credentials.py b/src/farmbot_yolo/read_credentials.py
index 6a310c218fd6b02fc87482c77f9d2a750d0979d8..26f077ab21d4b85381d92e3081ed60659ba35681 100644
--- a/src/farmbot_yolo/read_credentials.py
+++ b/src/farmbot_yolo/read_credentials.py
@@ -3,9 +3,13 @@ from typing import Dict
 
 from farmbot_yolo import CREDS_PATH
 
-def read_credentials() -> Dict[str]:
+
+def read_credentials() -> Dict[str, str]:
     if not CREDS_PATH.exists():
-        raise RuntimeError("No creds.json found in project root. Run python -m farmbot_yolo.get_credentials.")
+        raise RuntimeError(
+            "No creds.json found in project root. "
+            + "Run python -m farmbot_yolo.request_token"
+        )
 
     with CREDS_PATH.open(mode="r") as fp:
         creds_dict = json.load(fp)
diff --git a/src/farmbot_yolo/request_token.py b/src/farmbot_yolo/request_token.py
index 4c9e726a043afe02f6c2bfbdcd0b0a2373af3d63..5f71926ef21616ae943b117f05653ecf7332547b 100644
--- a/src/farmbot_yolo/request_token.py
+++ b/src/farmbot_yolo/request_token.py
@@ -1,33 +1,53 @@
-#!/usr/bin/env python3
+"""Request a token for my.farmbot.io"""
 
-# request a token (for creds.py)
-
-import argparse
 import json
-from urllib import request
+from argparse import ArgumentParser
+from getpass import getpass
+from logging import getLogger
+
+import requests
+
+from farmbot_yolo import CREDS_PATH
+
+log = getLogger(__name__)
+
+ENDPOINT = "https://my.farmbot.io/api/tokens"
+
+
+def request_token(email: str, password: str) -> None:
+
+    auth_info = {"user": {"email": email, "password": password}}
+
+    res = requests.post(ENDPOINT, json=auth_info)
+
+    res_dict = res.json()
+
+    log.debug(f"MQTT host: {res_dict['token']['unencoded']['mqtt']}")
+
+    creds_dict = {
+        "user_id": res_dict["token"]["unencoded"]["bot"],
+        "token": res_dict["token"]["encoded"],
+    }
 
-from farmbot_yolo import HERE
+    log.debug(f"Credentials from server: {creds_dict}")
 
-parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-parser.add_argument("--email", type=str, help="user email for token request")
-parser.add_argument("--password", type=str, help="user password for token request")
-opts = parser.parse_args()
-print("opts %s" % opts)
+    with CREDS_PATH.open(mode="w") as fp:
+        json.dump(creds_dict, fp)
 
-auth_info = {"user": {"email": opts.email, "password": opts.password}}
 
-req = request.Request("https://my.farmbot.io/api/tokens")
-req.add_header("Content-Type", "application/json")
-response = request.urlopen(req, data=json.dumps(auth_info).encode("utf-8"))
+if __name__ == "__main__":
 
-token_info = json.loads(response.read().decode())
+    parser = ArgumentParser()
+    parser.add_argument("--email", type=str, help="User email for token request")
+    args = parser.parse_args()
 
-print("mqtt host [%s]" % token_info["token"]["unencoded"]["mqtt"])
+    log.debug(f"Args: {args}")
 
-print("rewriting creds.py")
+    if not args.email:
+        email = input("Username: ")
+    else:
+        email = args.email
 
-creds_py_path = HERE / "creds.py"
+    password = getpass()
 
-with creds_py_path.open(mode="w") as f:
-    f.write('device_id="%s"\n' % token_info["token"]["unencoded"]["bot"])
-    f.write('token="%s"\n' % token_info["token"]["encoded"])
+    request_token(email, password)
diff --git a/src/farmbot_yolo/scan_bed.py b/src/farmbot_yolo/scan_bed.py
new file mode 100644
index 0000000000000000000000000000000000000000..32fc245d4d3d05a84a2b32a85aea1fff5c2f35ee
--- /dev/null
+++ b/src/farmbot_yolo/scan_bed.py
@@ -0,0 +1,80 @@
+from argparse import ArgumentParser
+from typing import List
+
+from farmbot_yolo.client import FarmbotClient
+from farmbot_yolo.read_credentials import read_credentials
+
+SCAN_Z_POS = 0
+
+
+def scan_bed(
+    min_x: float = 0,
+    max_x: float = 1175,
+    min_y: float = 0,
+    max_y: float = 974,
+    delta: float = 300,
+) -> List:
+    """Scan the whole bed in a zig zag pattern.
+    The default value of x, y should be based on the bed measurements.
+
+    Parameters
+    ----------
+    min_x
+        The minimum x coordinate.
+    max_x
+        The maximum x coordinate.
+    min_y
+        The minimum y coordinate.
+    max_y
+        The maximum y coordinate.
+    delta
+        The distance between points.
+
+    Returns
+    -------
+    List
+        A list of points
+    """
+    pts = []
+    sweep_y_negative = False
+
+    for x in range(min_x, max_x, delta):
+        y_range = range(min_y, max_y, delta)
+        if sweep_y_negative:
+            y_range = reversed(y_range)
+        sweep_y_negative = not sweep_y_negative
+        for y in y_range:
+            pts.append((x, y))
+
+        pts.append((x, y))
+
+    creds = read_credentials()
+    client = FarmbotClient(creds["device_id"], creds["token"])
+    client.move(0, 0, SCAN_Z_POS)  # move to home
+    for x, y in pts:
+        client.move(x, y, SCAN_Z_POS)  # move camera
+        client.take_photo()
+
+    return pts
+
+
+if __name__ == "__main__":
+    parser = ArgumentParser()
+    parser.add_argument("start_x", type=float, help="Start X coordinate")
+    parser.add_argument("start_y", type=float, help="Start Y coordinate")
+    parser.add_argument("end_x", type=float, help="End X coordinate")
+    parser.add_argument("end_y", type=float, help="End Y coordinate")
+    parser.add_argument(
+        "delta",
+        type=float,
+        help="Distance between points",
+    )
+    args = parser.parse_args()
+
+    scan_bed(
+        min_x=args.start_x,
+        max_x=args.end_x,
+        min_y=args.start_y,
+        max_y=args.end_y,
+        delta=args.delta,
+    )
diff --git a/static/distance.txt b/static/distance.txt
deleted file mode 100644
index ec6f0de0cdd5248d546b69b59205536ac78f9929..0000000000000000000000000000000000000000
--- a/static/distance.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-camera's distance to the encoder
--30
--130
-gripper's distance to the encoder
-45
-0