Skip to content

Commit

Permalink
Merge pull request #10 from FarmBot-Labs/jmash/verbosity
Browse files Browse the repository at this point in the history
Jmash/verbosity
  • Loading branch information
roryaronson authored Aug 23, 2024
2 parents 636234d + b3539c9 commit 00c96bc
Show file tree
Hide file tree
Showing 14 changed files with 164 additions and 82 deletions.
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,32 @@ bot.set_verbosity(2)
| `1` The name of the function will be output. | `e_stop called` |
| `2` The name of the function will be output with additional information about the return value. | `Triggered device emergency stop at: 2024-08-21 11:16:18.547813` |

### Test 1: Add a new plant to your garden

This test will help familiarize you with sending commands via the [API](https://developer.farm.bot/docs/rest-api).
```
new_cabbage = {
"name": "Cabbage", # Point name
"pointer_type": "Plant", # Point type
"x": 400, # x-coordinate
"y": 300, # y-coordinate
"z": 0, # z-coordinate
"openfarm_slug": "cabbage", # Plant type
"plant_stage": "planned", # Point status
}
bot.add_info("points", new_cabbage) # Add plant to endpoint
```

### Test 2: Turn your LED strip on and off

This test will help familiarize you with sending commands via the [Message Broker](https://developer.farm.bot/docs/message-broker).
```
bot.on(7) # Turn ON pin 7 (LED strip)
bot.wait(2000) # Wait 2000 milliseconds
bot.off(7) # Turn OFF pin 7 (LED strip)
```

## :compass: Functions

```
Expand Down Expand Up @@ -159,7 +185,7 @@ sidecar-starter-pack/
> Making requests other than `GET` to the API will permanently alter the data in your account. `DELETE` and `POST` requests may destroy data that cannot be recovered. Altering data through the API may cause account instability.
> [!NOTE]
> Not sure which endpoint to access? [Find the list here](https://developer.farm.bot/v15/docs/web-app/api-docs).
> Not sure which endpoint to access? [Find the list here](https://developer.farm.bot/docs/api-docs).
| class `Information()` | Description |
| :--- | :--- |
Expand Down Expand Up @@ -239,7 +265,7 @@ sidecar-starter-pack/
### Formatting message broker messages

> [!NOTE]
> Messages sent via the message broker contain [CeleryScript nodes](https://developer.farm.bot/v15/docs/celery-script/nodes.html) which require special formatting.
> Messages sent via the message broker contain [CeleryScript nodes](https://developer.farm.bot/docs/celery-script/nodes.html) which require special formatting.
```
message = {
Expand Down
9 changes: 8 additions & 1 deletion functions/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def get_token(self, email, password, server="https://my.farm.bot"):
if response.status_code == 200:
self.state.token = response.json()
self.state.error = None
self.state.print_status("get_token()", description=f"Sucessfully fetched token from {server}.")
return response.json()
elif response.status_code == 404:
self.state.error = "HTTP ERROR: The server address does not exist."
Expand All @@ -44,19 +45,21 @@ def get_token(self, email, password, server="https://my.farm.bot"):
self.state.error = f"DNS ERROR: An unexpected error occurred: {str(e)}"

self.state.token = None
self.state.print_status("get_token()", description=self.state.error)
return self.state.error

def check_token(self):
"""Ensure the token persists throughout sidecar."""

if self.state.token is None:
print("ERROR: You have no token, please call `get_token` using your login credentials and the server you wish to connect to.")
self.state.print_status("check_token()", description="ERROR: You have no token, please call `get_token` using your login credentials and the server you wish to connect to.")
sys.exit(1)

return

def request_handling(self, response):
"""Handle errors associated with different endpoint errors."""

error_messages = {
404: "The specified endpoint does not exist.",
400: "The specified ID is invalid or you do not have access to it.",
Expand All @@ -66,6 +69,7 @@ def request_handling(self, response):

# Handle HTTP status codes
if response.status_code == 200:
self.state.print_status("check_token()", description="Successfully sent request via API.")
return 200
elif 400 <= response.status_code < 500:
self.state.error = json.dumps(f"CLIENT ERROR {response.status_code}: {error_messages.get(response.status_code, response.reason)}", indent=2)
Expand All @@ -74,6 +78,7 @@ def request_handling(self, response):
else:
self.state.error = json.dumps(f"UNEXPECTED ERROR {response.status_code}: {response.text}", indent=2)

self.state.print_status("request_handling()", description=self.state.error)
return

def request(self, method, endpoint, database_id, payload=None):
Expand All @@ -96,6 +101,8 @@ def request(self, method, endpoint, database_id, payload=None):

if self.request_handling(response) == 200:
self.state.error = None
self.state.print_status("request()", description="Successfully returned request contents.")
return response.json()
else:
self.state.print_status("request()", description="There was an error processing the request...")
return self.state.error
35 changes: 5 additions & 30 deletions functions/basic_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ def __init__(self, state):
def wait(self, duration):
"""Pauses execution for a certain number of milliseconds."""

verbosity_level = {
1: lambda: print("`wait` called"),
2: lambda: print(f"Waiting for {duration} milliseconds...")
}

verbosity_level[self.broker.state.verbosity]()
self.broker.state.print_status("wait()", description=f"Waiting for {duration} milliseconds...")

wait_message = {
"kind": "rpc_request",
Expand All @@ -46,12 +41,7 @@ def wait(self, duration):
def e_stop(self):
"""Emergency locks (E-stops) the Farmduino microcontroller."""

verbosity_level = {
1: lambda: print("`e_stop` called"),
2: lambda: print(f"Triggered device emergency stop at: {datetime.now()}")
}

verbosity_level[self.broker.state.verbosity]()
self.broker.state.print_status("e_stop()", description=f"Triggered device emergency stop at: {datetime.now()}")

stop_message = {
"kind": "rpc_request",
Expand All @@ -71,12 +61,7 @@ def e_stop(self):
def unlock(self):
"""Unlocks a locked (E-stopped) device."""

verbosity_level = {
1: lambda: print("`unlock` called"),
2: lambda: print(f"Triggered device unlock at: {datetime.now()}")
}

verbosity_level[self.broker.state.verbosity]()
self.broker.state.print_status("unlock()", description=f"Triggered device unlock at: {datetime.now()}")

unlock_message = {
"kind": "rpc_request",
Expand All @@ -96,12 +81,7 @@ def unlock(self):
def reboot(self):
"""Reboots the FarmBot OS and reinitializes the device."""

verbosity_level = {
1: lambda: print("`reboot` called"),
2: lambda: print(f"Triggered device reboot at: {datetime.now()}")
}

verbosity_level[self.broker.state.verbosity]()
self.broker.state.print_status("reboot()", description=f"Triggered device reboot at: {datetime.now()}")

reboot_message = {
**RPC_REQUEST,
Expand All @@ -119,12 +99,7 @@ def reboot(self):
def shutdown(self):
"""Shuts down the FarmBot OS and turns the device off."""

verbosity_level = {
1: lambda: print("`shutdown` called"),
2: lambda: print(f"Triggered device shutdown at: {datetime.now()}")
}

verbosity_level[self.broker.state.verbosity]()
self.broker.state.print_status("shutdown()", description=f"Triggered device shutdown at: {datetime.now()}")

shutdown_message = {
**RPC_REQUEST,
Expand Down
16 changes: 13 additions & 3 deletions functions/broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ def connect(self):
)

self.client.loop_start()

if self.client is None:
self.state.print_status("connect()", description="There was an error connecting to the message broker...")
else:
self.state.print_status("request()", description="Connected to message broker.")

def disconnect(self):
"""Disconnect from the message broker."""
Expand All @@ -42,6 +47,9 @@ def disconnect(self):
self.client.loop_stop()
self.client.disconnect()

if self.client is None:
self.state.print_status("disoconnect()", description="Disconnected from message broker.")

return

def publish(self, message):
Expand All @@ -58,15 +66,15 @@ def on_connect(self, _client, _userdata, _flags, _rc, channel):

self.client.subscribe(
f"bot/{self.state.token['token']['unencoded']['bot']}/{channel}")

self.state.print_status("on_connect()", description=f"Connected to message broker channel {channel}")

def on_message(self, _client, _userdata, msg):
"""Callback function when message received from message broker."""

self.state.last_message = json.loads(msg.payload)

# print('-' * 100)
# print(f'{msg.topic} ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})\n')
# print(json.dumps(json.loads(msg.payload), indent=4))
self.state.print_status("on_message()", endpoint_json=json.loads(msg.payload), description=f"TOPIC: {msg.topic} ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})\n")

def start_listen(self, channel="#"):
"""Establish persistent subscription to message broker channels."""
Expand All @@ -79,11 +87,13 @@ def start_listen(self, channel="#"):
self.client.on_message = self.on_message

self.client.loop_start()
self.state.print_status("start_listen()", description=f"Now listening to message broker channel {channel}.")

def stop_listen(self):
"""End subscription to all message broker channels."""

self.client.loop_stop()
self.client.disconnect()

self.state.print_status("stop_listen()", description="Stopped listening to all message broker channels.")
return
14 changes: 2 additions & 12 deletions functions/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ def __init__(self, state):
def calibrate_camera(self):
"""Performs camera calibration. This action will reset camera calibration settings."""

verbosity_level = {
1: lambda: print("`calibrate_camera` called"),
2: lambda: print(f"Triggered camera calibration at: {datetime.now()}")
}

verbosity_level[self.broker.state.verbosity]()
self.broker.state.print_status("calibrate_camera()", description=f"Triggered camera calibration at: {datetime.now()}")

calibrate_message = {
**RPC_REQUEST,
Expand All @@ -40,12 +35,7 @@ def calibrate_camera(self):
def take_photo(self):
"""Takes photo using the device camera and uploads it to the web app."""

verbosity_level = {
1: lambda: print("`take_photo` called"),
2: lambda: print(f"Took a photo at: {datetime.now()}")
}

verbosity_level[self.broker.state.verbosity]()
self.broker.state.print_status("take_photo()", description=f"Took a photo at: {datetime.now()}")

photo_message = {
**RPC_REQUEST,
Expand Down
46 changes: 28 additions & 18 deletions functions/information.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

# └── functions/information
# ├── [API] get_info()
# ├── [API] set_info()
# ├── [API] edit_info()
# ├── [API] add_info()
# ├── [API] safe_z()
# ├── [API] garden_size()
# ├── [API] group()
Expand All @@ -25,33 +26,37 @@ def __init__(self, state):
def get_info(self, endpoint, id=None):
"""Get information about a specific endpoint."""

endpoint_data = self.auth.request('GET', endpoint, id)
endpoint_data = self.auth.request("GET", endpoint, id)

verbosity_level = {
1: lambda: print("`get_info` called"),
2: lambda: print(json.dumps(endpoint_data, indent=4))
}
self.broker.state.print_status("get_info()", endpoint_json=endpoint_data)
return endpoint_data

def edit_info(self, endpoint, new_data, id=None):
"""Change information contained within an endpoint."""

verbosity_level[self.auth.state.verbosity]()
self.auth.request("PATCH", endpoint, database_id=id, payload=new_data)
endpoint_data = self.get_info(endpoint, id)

self.broker.state.print_status("edit_info()", endpoint_json=endpoint_data)
return endpoint_data

def set_info(self, endpoint, field, value, id=None):
"""Change information contained within an endpoint."""
def add_info(self, endpoint, new_data):
"""Create new information contained within an endpoint."""

new_value = {
field: value
}
self.auth.request("POST", endpoint, database_id=None, payload=new_data)

self.auth.request('PATCH', endpoint, id, new_value)
return self.get_info(endpoint, id)
endpoint_data = self.get_info(endpoint, id=None)

self.broker.state.print_status("add_info()", endpoint_json=endpoint_data)
return endpoint_data

def safe_z(self):
"""Returns the highest safe point along the z-axis."""

config_data = self.get_info('fbos_config')
z_value = config_data["safe_height"]

self.broker.state.print_status("safe_z()", description=f"Safe z={z_value}")
return z_value

def garden_size(self):
Expand All @@ -69,6 +74,7 @@ def garden_size(self):
length_y = y_steps / y_mm
area = length_x * length_y

self.broker.state.print_status("garden_size()", description=f"X-axis length={length_x}\n Y-axis length={length_y}\n Area={area}")
return length_x, length_y, area

def group(self, id=None):
Expand All @@ -79,6 +85,7 @@ def group(self, id=None):
else:
group_data = self.get_info('point_groups', id)

self.broker.state.print_status("group()", endpoint_json=group_data)
return group_data

def curve(self, id=None):
Expand All @@ -89,6 +96,7 @@ def curve(self, id=None):
else:
curve_data = self.get_info('curves', id)

self.broker.state.print_status("curve()", endpoint_json=curve_data)
return curve_data

def soil_height(self):
Expand All @@ -105,11 +113,12 @@ def soil_height(self):
}

self.broker.publish(soil_height_message)
return # TODO: return soil height as value
return # TODO: return soil height as value(?)

def read_status(self):
"""Returns the FarmBot status tree."""

self.broker.start_listen("status")
status_message = {
"kind": "rpc_request",
"args": {
Expand All @@ -123,11 +132,12 @@ def read_status(self):
}
self.broker.publish(status_message)

self.broker.start_listen("status")
time.sleep(5)
time.sleep(15)
self.broker.stop_listen()

status_tree = self.broker.state.last_message

self.broker.state.print_status("read_status()", endpoint_json=status_tree)
return status_tree

def read_sensor(self, id):
Expand Down Expand Up @@ -155,4 +165,4 @@ def read_sensor(self, id):
}

self.broker.publish(sensor_message)
return # TODO
return # TODO return sensor output(?)
Loading

0 comments on commit 00c96bc

Please sign in to comment.