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]
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.mypy-cache
.vscode
__pycache__
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
29 changes: 29 additions & 0 deletions api/Actuator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from Communication import Communication


class Actuator:
'''Base Actuator class'''
def __init__(self, communication: Communication, index: int):
self.communication = communication
self.index = index
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved


class Stepper(Actuator):
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
'''Class for controlling a stepper motor'''
def __init__(self, communication: Communication, index: int):
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
super().__init__(communication, index)

def clockwise(self, speed: int, distance: int):
self.communication.send(f"{self.index} F {round(speed * 7.87)} {round(distance * 7.87)}")

def anticlockwise(self, speed: int, distance: int):
self.communication.send(f"{self.index} B {round(speed * 7.87)} {round(distance * 7.87)}")


class Servo(Actuator):
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
'''Class for controlling a servo motor'''
def __init__(self, communication: Communication, index: int):
super().__init__(communication, index)

def setPosition(self, angle):
self.communication.send(f"{self.index} {angle}")
57 changes: 57 additions & 0 deletions api/Communication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import serial


class Communication():
def __init__(self, baudRate: int):
self.baudRate = baudRate
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved

def send(self, data):
'''Send data to the communication channel'''
pass

def receive(self) -> str:
'''Receive data from the communication channel'''
return ''


class UART(Communication):
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, port: str, baudRate: int, timeout: int = 1):
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
super().__init__(baudRate)
self.port = port
self.byteSize = serial.EIGHTBITS
self.parity = serial.PARITY_NONE
self.stopBits = serial.STOPBITS_ONE
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
self.timeout = timeout
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
try:
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
self.conn = serial.Serial(
port=self.port,
baudrate=self.baudRate,
bytesize=self.byteSize,
parity=self.parity,
stopbits=self.stopBits,
timeout=self.timeout
)
except Exception as err:
print(f"Error: {err}")

def send(self, data: str):
'''Send data to the communication channel'''
try:
if not self.conn.is_open:
raise Exception(f"Serial port {self.port} is not open.")
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved

self.conn.write(data.encode() + b'\n')

except Exception as err:
print(f"Error while sending: {err}")

def receive(self) -> str:
'''Receive data from the communication channel'''
if not self.conn.is_open:
raise Exception(f"Serial port {self.port} is not open.")

data_bytes = self.conn.read_all()
if data_bytes:
return data_bytes.decode()
else:
return ''
20 changes: 20 additions & 0 deletions api/Head.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from Communication import Communication
from Actuator import Servo
from Sensor import UltraSonic


class Head:
def __init__(self, communication: Communication, servo1: Servo, servo2: Servo, ultraSonic: UltraSonic):
self.communication = communication
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
self.servo1 = servo1
self.servo2 = servo2
self.ultraSonic = ultraSonic

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

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

def ultraSonicRead(self) -> str:
return self.ultraSonic.get_data()
46 changes: 46 additions & 0 deletions api/Miloje.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from Sensor import Battery, UltraSonic
from Actuator import Stepper, Servo
from Wheels import Wheels
from Head import Head
from Communication import Communication


class Miloje:
def __init__(self, communication: Communication):
self.communication = communication

# Receiving MILOJE capabilities
self.communication.send("C")
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
capabilityTypes = self.communication.receive()
self.capabilities = {}
for index, char in enumerate(capabilityTypes):
if char not in self.capabilities and char.isalpha():
self.capabilities[char] = []
if char == "M":
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
self.capabilities[char].append(Stepper(self.communication, index))
if char == "S":
self.capabilities[char].append(Servo(self.communication, index))
if char == "B":
self.capabilities[char].append(Battery(self.communication, index))
if char == "U":
self.capabilities[char].append(UltraSonic(self.communication, index))

# MILOJE head
self.wheels = Wheels(self.communication, self.capabilities["M"][0],
self.capabilities["M"][1])

# MILOJE wheels
self.head = Head(self.communication, self.capabilities["S"][0],
self.capabilities["S"][1], self.capabilities["U"][0])

def get_actuators(self):
return self.capabilities["actuators"]
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved

def get_sensors(self):
return self.capabilities["sensors"]

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

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


class Sensor:
def __init__(self, communication: Communication, index: int):
self.communication = communication
self.index = index
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved

def get_data(self) -> str:
return ''


class UltraSonic(Sensor):
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, communication: Communication, index: int):
super().__init__(communication, index)
samoN1k0la marked this conversation as resolved.
Show resolved Hide resolved

def get_data(self) -> str:
'''Get value (distance) from the ultrasonic sensor'''
self.communication.send(f"{self.index}")
return self.communication.receive()


class Battery(Sensor):
def __init__(self, communication: Communication, index: int):
super().__init__(communication, index)

def get_data(self) -> str:
'''Get value (voltage) from the battery'''
self.communication.send(f"{self.index}")
return self.communication.receive()
29 changes: 29 additions & 0 deletions api/Wheels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from Communication import Communication
from Actuator import Stepper


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

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

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

def spinClockwise(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 spinAntiClockwise(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)
9 changes: 9 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
flake8==7.1.0
keyboard==0.13.5
mccabe==0.7.0
nodeenv==1.9.1
pycodestyle==2.12.0
pyflakes==3.2.0
pyright==1.1.373
pyserial==3.5
types-keyboard==0.13.2.20240310
5 changes: 5 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
exclude =
.venv
__pycache__
max-line-length = 120
Loading