Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial work on the API library. #5

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
31 changes: 31 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Lint

on:
- push
- workflow_dispatch

jobs:
lint:
runs-on: ubuntu-latest
name: Lint
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
cache: 'pip'
python-version: "3.12"
- name: 'Install requirements'
run: |
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
- name: 'Modify Actions PATH'
run: echo "$PWD/.venv/bin" >> $GITHUB_PATH
- name: 'Lint: Pyright'
uses: jakebailey/pyright-action@v2
with:
version: PATH
- name: 'Lint: flake8'
uses: py-actions/[email protected]
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.mypy-cache
.vscode
.venv
__pycache__
docs/firmware
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ koristi za buduće letnje projekte koji se bave izradom robota.

U ovom repozitorijumu se može naći kod i potrebna dokumentacija za Miloja.

## Doprinošenje
[![Lint](https://github.com/pfe-rs/lk-s-2024-miloje-api/actions/workflows/lint.yml/badge.svg)](https://github.com/pfe-rs/lk-s-2024-miloje-api/actions/workflows/lint.yml)

Ovaj repozitorijum koristi dva lintera:

- [flake8](https://flake8.pycqa.org/), koji proverava da li stil Python koda
prati [PEP 8](https://pep8.org/), i
- [Pyright](https://microsoft.github.io/pyright/), koji proverava dinamičke i
statičke tipove korišćene u Python kodu i određuje da li taj kod ima smisla sa
tim tipovima u vidu.

Kako biste doprineli kod na glavnu granu, potrebno je da vaš kod prođe proveru
oba lintera. Kako biste tokom razvoja bili sigurni da poštujete pravila ova dva
lintera, možete instalirati
[Pyright](https://marketplace.visualstudio.com/items?itemName=ms-pyright.pyright)
i [Flake8](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8)
ekstenzije za [Visual Studio Code](https://code.visualstudio.com/), koje će vam
automatski prijaviti ukoliko vaš kod ima ove greške.

## Istorija

Ovaj projekat su radili:
Expand Down
10 changes: 10 additions & 0 deletions api/actuators/actuator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from abc import ABC

from communication.communication import Communication


class Actuator(ABC):
"""Base Actuator class"""
def __init__(self, communication: Communication, actuator_id: int):
self.communication = communication
self.id = actuator_id
7 changes: 7 additions & 0 deletions api/actuators/servo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from actuators.actuator import Actuator


class Servo(Actuator):
"""Class for controlling a servo motor"""
def set_position(self, angle):
self.communication.send(f"{self.id} {angle}")
10 changes: 10 additions & 0 deletions api/actuators/stepper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from actuators.actuator import Actuator


class Stepper(Actuator):
"""Class for controlling a stepper motor"""
def clockwise(self, speed: int, distance: int):
self.communication.send(f"{self.id} F {round(speed * 7.87)} {round(distance * 7.87)}")

def anticlockwise(self, speed: int, distance: int):
self.communication.send(f"{self.id} B {round(speed * 7.87)} {round(distance * 7.87)}")
27 changes: 27 additions & 0 deletions api/actuators/wheels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from actuators.stepper import Stepper


class Wheels:
def __init__(self, stepper1: Stepper, stepper2: Stepper):
self.stepper1 = stepper1
self.stepper2 = stepper2

def forward(self, speed: int, distance: int):
"""Move MILOJE forward"""
self.stepper1.clockwise(speed, distance)
self.stepper2.anticlockwise(speed, distance)

def backward(self, speed: int, distance: int):
"""Move MILOJE backward"""
self.stepper1.anticlockwise(speed, distance)
self.stepper2.clockwise(speed, distance)

def spin_clockwise(self, speed: int, angle: int):
"""Spin MILOJE in the clockwise direction"""
self.stepper1.anticlockwise(speed, (2*angle)//8)
self.stepper2.anticlockwise(speed, (2*angle)//8)

def spin_anticlockwise(self, speed: int, angle: int):
"""Spin MILOJE in the anticlockwise direction"""
self.stepper1.clockwise(speed, (2*angle)//8)
self.stepper2.clockwise(speed, (2*angle)//8)
3 changes: 3 additions & 0 deletions api/buzzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Buzzer:
def __init__(self, communication, index):
...
13 changes: 13 additions & 0 deletions api/communication/communication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from abc import ABC, abstractmethod


class Communication(ABC):
@abstractmethod
def send(self, data):
"""Send data to the communication channel"""
pass

@abstractmethod
def receive(self) -> str:
"""Receive data from the communication channel"""
return ""
33 changes: 33 additions & 0 deletions api/communication/uart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import time

import serial
from communication.communication import Communication


class UART(Communication):
def __init__(self, port: str, baud_rate: int, timeout: int | None = None):
self.baud_rate = baud_rate
self.port = port
self.conn = serial.Serial(
port=self.port,
baudrate=self.baud_rate,
timeout=timeout
)
time.sleep(1)

def send(self, data: str):
"""Send data to the communication channel"""
self.conn.write(data.encode() + b"\n")

def receive(self) -> str:
"""Receive data from the communication channel"""
data = self.conn.read_all()
if data is not None:
data = data.decode()
else:
while data is None or len(data) == 0:
time.sleep(0.5)
data = self.conn.read_all()
if data is not None:
data = data.decode()
return data.strip()
20 changes: 20 additions & 0 deletions api/examples/example1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from communication.uart import UART
from miloje import Miloje

if __name__ == "__main__":
miloje = Miloje(UART("/dev/rfcomm0", 9600, 1))

wheels = miloje.get_wheels()
head = miloje.get_head()

if wheels:
while True:
query = input("? ")
if query == "w":
wheels.forward(500, 212)
elif query == "s":
wheels.backward(500, 212)
elif query == "a":
wheels.spin_anticlockwise(300, 30)
elif query == "d":
wheels.spin_clockwise(300, 30)
18 changes: 18 additions & 0 deletions api/head.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from actuators.servo import Servo
from sensors.ultrasonic import UltraSonic


class Head:
def __init__(self, servo1: Servo, servo2: Servo, ultra_sonic: UltraSonic):
self.servo1 = servo1
self.servo2 = servo2
self.ultra_sonic = ultra_sonic

def horizontal_angle(self, angle: int):
self.servo1.set_position(angle)

def vertical_angle(self, angle: int):
self.servo2.set_position(angle)

def ultrasonic_read(self) -> float:
return self.ultra_sonic.get_data()
59 changes: 59 additions & 0 deletions api/miloje.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from typing import List

from actuators.servo import Servo
from actuators.stepper import Stepper
from actuators.wheels import Wheels
from buzzer import Buzzer
from communication.communication import Communication
from head import Head
from sensors.battery import Battery
from sensors.ultrasonic import UltraSonic

CMD_CAPABILITIES = "C 1"
CAPABILITY_TYPES = {
"M": Stepper,
"S": Servo,
"B": Battery,
"U": UltraSonic,
"Z": Buzzer
}


class Miloje:
def __init__(self, communication: Communication):
self.communication = communication
self.capabilities = {}
self.wheels = None
self.head = None

self.communication.send(CMD_CAPABILITIES)
data = self.communication.receive()
for index, char in enumerate(data):
if char not in self.capabilities and char.isalpha():
self.capabilities[char] = []
self.capabilities[char].append(CAPABILITY_TYPES[char](self.communication, index))

try:
self.wheels = Wheels(self.capabilities["M"][0],
self.capabilities["M"][1])
except IndexError:
self.wheels = None

try:
self.head = Head(self.capabilities["S"][0],
self.capabilities["S"][1],
self.capabilities["U"][0])
except IndexError:
self.head = None

def get_steppers(self) -> List[Stepper]:
return self.capabilities["M"]

def get_servos(self) -> List[Servo]:
return self.capabilities["S"]

def get_head(self) -> Head | None:
return self.head

def get_wheels(self) -> Wheels | None:
return self.wheels
8 changes: 8 additions & 0 deletions api/sensors/battery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from sensors.sensor import Sensor


class Battery(Sensor):
def get_data(self) -> float:
"""Get value (voltage) from the battery"""
self.communication.send(f"{self.id}")
return float(self.communication.receive())
13 changes: 13 additions & 0 deletions api/sensors/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from abc import ABC, abstractmethod

from communication.communication import Communication


class Sensor(ABC):
def __init__(self, communication: Communication, sensor_id: int):
self.communication = communication
self.id = sensor_id

@abstractmethod
def get_data(self) -> str | float:
return ""
8 changes: 8 additions & 0 deletions api/sensors/ultrasonic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from sensors.sensor import Sensor


class UltraSonic(Sensor):
def get_data(self) -> float:
"""Get value (distance) from the ultrasonic sensor"""
self.communication.send(f"{self.id}")
return float(self.communication.receive())
17 changes: 17 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
flake8==7.1.0
flake8-builtins==2.5.0
# flake8-docstrings==1.7.0
flake8-isort==6.1.1
flake8-pep3101==2.1.0
flake8-quotes==3.4.0
flake8-string-format==0.3.0
flake8-tidy-imports==4.10.0
isort==5.13.2
mccabe==0.7.0
nodeenv==1.9.1
pep8-naming==0.14.1
pycodestyle==2.12.0
pyflakes==3.2.0
pyright==1.1.373
pyserial==3.5
types-keyboard==0.13.2.20240310
6 changes: 6 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[flake8]
exclude =
.venv
__pycache__
max-line-length = 120
inline-quotes = "
Loading