Skip to content

Commit

Permalink
Merge pull request #6 from FarmBot-Labs/jmashon/new-functions
Browse files Browse the repository at this point in the history
Jmashon/new functions
  • Loading branch information
roryaronson authored Aug 1, 2024
2 parents eedce48 + 0bcd5ae commit a962200
Show file tree
Hide file tree
Showing 5 changed files with 396 additions and 395 deletions.
37 changes: 12 additions & 25 deletions api_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def get_token(self, email, password, server):
# Handle HTTP status codes
if response.status_code == 200:
token_data = response.json()
self.token = token_data
self.token = token_data # TODO
self.error = None
return token_data
elif response.status_code == 404:
Expand Down Expand Up @@ -71,40 +71,27 @@ def check_token(self):
print("ERROR: You have no token, please call `get_token` using your login credentials and the server you wish to connect to.")
sys.exit(1)

def request(self, method, endpoint, id, payload):
"""Send requests from user-accessible functions via API."""
def request(self, method, endpoint, database_id, payload=None):
"""Send requests to the API using various methods."""

self.check_token()

if id is None:
# use 'GET' method to view endpoint data
# use 'POST' method to overwrite/create new endpoint data
# use 'PATCH' method to edit endpoint data (used for new logs)
# use 'DELETE' method to delete endpoint data (hidden)

if database_id is None:
url = f'https:{self.token["token"]["unencoded"]["iss"]}/api/{endpoint}'
else:
url = f'https:{self.token["token"]["unencoded"]["iss"]}/api/{endpoint}/{id}'
url = f'https:{self.token["token"]["unencoded"]["iss"]}/api/{endpoint}/{database_id}'

headers = {'authorization': self.token['token']['encoded'], 'content-type': 'application/json'}
response = requests.request(method, url, headers=headers, json=payload)

if self.request_handling(response) == 200:
user_request = response.json()
self.error = None
return user_request
request_data = response.json()
return request_data
else:
return self.error

## REQUEST METHODS

def get(self, endpoint, id):
"""METHOD: 'get' allows user to view endpoint data."""
return self.request('GET', endpoint, id, payload=None)

def post(self, endpoint, id, payload):
"""METHOD: 'post' allows user to overwrite/create new endpoint data."""
return self.request('POST', endpoint, id, payload)

def patch(self, endpoint, id, payload):
"""METHOD: 'patch' allows user to edit endpoint data (used for new logs)."""
return self.request('PATCH', endpoint, id, payload)

def delete(self, endpoint, id):
"""METHOD: 'delete' allows user to delete endpoint data (hidden)."""
return self.request('DELETE', endpoint, id, payload=None)
88 changes: 43 additions & 45 deletions api_functions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from api_connect import ApiConnect

class ApiFunctions():
Expand All @@ -7,52 +9,31 @@ def __init__(self):
self.echo = True
self.verbose = True

def __return_config(self, return_value, json_val=False): # TODO: which functions return json()
"""Configure echo and verbosity of function returns."""

if self.echo is True and self.verbose is True:
print('-' * 100)
if json_val is True:
print(f'FUNCTION: {return_value}\n')
return print(return_value)
else:
print(f'FUNCTION: {return_value}\n')
return print(return_value)
elif self.echo is True and self.verbose is False:
print('-' * 100)
return print(return_value)
elif self.echo is False and self.verbose is False:
return return_value
else:
print('-' * 100)
return print("ERROR: Incompatible return configuration.")

def get_token(self, email, password, server='https://my.farm.bot'):
# Generate user authentication token
token_str = self.api_connect.get_token(email, password, server)
# Return token as json object: token[""]
return token_str

# data = get_info() and like functions will assign 'data' JSON object
# data["name"] will access the field "name" and return the field value

def get_info(self, endpoint, id=None):
return self.api_connect.get(endpoint, id)
# Get endpoint info
endpoint_data = self.api_connect.request('GET', endpoint, id)
# Return endpoint info as json object: endpoint[""]
return endpoint_data

def set_info(self, endpoint, field, value, id=None):
# Edit endpoint info
new_value = {
field: value
}
self.api_connect.request('PATCH', endpoint, id, new_value)

self.api_connect.patch(endpoint, id, new_value)
return self.api_connect.get(endpoint, id)

def env(self, id=None, field=None, new_val=None): # TODO: Fix
if id is None:
data = self.api_connect.get('farmware_envs', id=None)
else:
data = self.api_connect.get('farmware_envs', id)
# return ...
# Return endpoint info as json object: endpoint[""]
new_endpoint_data = self.api_connect.request('GET', endpoint, id)
return new_endpoint_data

def log(self, message, type=None, channel=None):
# Send new log message via API
log_message = {
"message": message,
"type": type, # https://software.farm.bot/v15/app/intro/jobs-and-logs#log-types
Expand All @@ -62,14 +43,19 @@ def log(self, message, type=None, channel=None):
endpoint = 'logs'
id = None

self.api_connect.post(endpoint, id, log_message)
# return ...
self.api_connect.request('POST', endpoint, id, log_message)

# No inherent return value

def safe_z(self):
json_data = self.get_info('fbos_config')
return json_data['safe_height']
# Get safe z height via get_info()
config_data = self.get_info('fbos_config')
z_value = config_data["safe_height"]
# Return safe z height as value
return z_value

def garden_size(self):
# Get garden size parameters via get_info()
json_data = self.get_info('firmware_config')

x_steps = json_data['movement_axis_nr_steps_x']
Expand All @@ -82,13 +68,25 @@ def garden_size(self):
length_y = y_steps / y_mm
area = length_x * length_y

return print(f'Garden size:\n'
f'\tx length = {length_x:.2f}\n'
f'\ty length = {length_y:.2f}\n'
f'\tarea = {area:.2f}')
# Return garden size parameters as values
return length_x, length_y, area

def group(self, id=None):
# Get all groups or single by id
if id is None:
group_data = self.get_info("point_groups")
else:
group_data = self.get_info('point_groups', id)

def group(self, id): # TODO: make ID optional return full tree w/o ID
return self.get_info('point_groups', id)
# Return group as json object: group[""]
return group_data

def curve(self, id=None):
# Get all curves or single by id
if id is None:
curve_data = self.get_info("curves")
else:
curve_data = self.get_info('curves', id)

def curve(self, id): # TODO: make ID optional return full tree w/o ID
return self.get_info('curves', id)
# Return curve as json object: curve[""]
return curve_data
45 changes: 21 additions & 24 deletions broker_connect.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import threading
import json
import time

Expand All @@ -11,8 +12,6 @@ def __init__(self):

self.last_message = None

## ERROR HANDLING

## FUNCTIONS -- SENDING MESSAGES

def connect(self):
Expand Down Expand Up @@ -53,14 +52,24 @@ def publish(self, message):
## FUNCTIONS -- RECEIVING MESSAGES

def on_connect(self, _client, _userdata, _flags, _rc, channel):
"""Subscribe to specified broker response channel."""
"""Subscribe to specified message broker channel."""
self.client.subscribe(f"bot/{self.token['token']['unencoded']['bot']}/{channel}")

def on_message(self, _client, _userdata, msg):
"""Update message queue with latest broker response."""
self.last_message = json.loads(msg.payload)

new_message = json.loads(msg.payload)
self.last_message = new_message
def show_connect(self, client, *_args):
# Subscribe to all channels
client.subscribe(f"bot/{self.token['token']['unencoded']['bot']}/#")

def show_message(self, _client, _userdata, msg):
self.last_message = msg.payload
print('-' * 100)
# Print channel
print(f'{msg.topic} ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})\n')
# Print message
print(json.dumps(json.loads(msg.payload), indent=4))

def listen(self, duration, channel):
"""Listen to messages via message broker."""
Expand All @@ -80,32 +89,20 @@ def listen(self, duration, channel):
self.client.loop_stop()
self.client.disconnect()

## FUNCTIONS -- HIDDEN
def start_listening(self):
print("Now listening to message broker...")

def hidden_on_connect(self, _client, _userdata, _flags, _rc):
# Subscribe to all channels
self.client.subscribe(f"bot/{self.token['token']['unencoded']['bot']}/#")

def hidden_on_message(self, _client, _userdata, msg):
# print channel
print('-' * 100)
print(f'{msg.topic} ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})\n')
# print message
print(json.dumps(json.loads(msg.payload), indent=4))

def hidden_listen(self):
if self.client is None:
self.connect()

self.client.on_connect = self.hidden_on_connect
self.client.on_message = self.hidden_on_message
# Wrap on_connect to pass channel argument
self.client.on_connect = self.show_connect
self.client.on_message = self.show_message

# Start loop in a separate thread
self.client.loop_start()

# Sleep for five seconds to listen for messages
time.sleep(60)
def stop_listening(self):
print("Stopped listening to message broker...")

# Stop loop and disconnect after five seconds
self.client.loop_stop()
self.client.disconnect()
Loading

0 comments on commit a962200

Please sign in to comment.