diff --git a/plugins/rpi/README.md b/plugins/rpi/README.md new file mode 100644 index 00000000..b693ff6e --- /dev/null +++ b/plugins/rpi/README.md @@ -0,0 +1,74 @@ +# RPi Plugin + +This plugin will leverage the pins of Raspberry Pi to attach and control several peripherals to it. This plugin uses the *adafruit_blinka* library to communicate with sensors and components. Combining them with turtle blocks makes things as simple as they can get. + +This plugin is compatible not only with Raspberry Pi but also with several other linux SBCs; OrangePi, BeagleBone, Google Coral Dev Boad, Jetson, Banana Pi, and NanoPi to name a few. + +In Raspberry Pi, for using each component/sensor you will have to enter the pin number they are connected to with a 'D' in front of them. For example, **D7** for **GPIO 7** as you can see in the pinout diagram (standard for zero/3/4/5) below.\ +Note that GPIO numbers and pin numbers (in the circle) are different. We need the GPIO numbers. + + +![RPi standard pinout](screenshots/pinout_rpi.png) + +
+ +# Installation +Installation is pretty straightforward. Make sure you have an internet access. + + +- Install pip with `sudo apt install python3-pip` +- To install the dependencies for using RPi sensors: + - Go to Raspberry Pi palette and simply run the **"install requirements"** block as shown in the image below. The requirements will start to download in the background. + - After 1-2 minutes restart the TurtleArt Activity, all the blocks will be visible now + +
+ +     + +
+ +# For developers +Improvements and additions are very much welcomed. + +Structure: + +``` +rpi +├── rpi.py (contains blocks linked to functions) +├── functions.py (contains all callable functions) +├── __init__.py (checks if the device is RPi and installs requirements) +├── req.txt (requirements file) +├── sensors +│   └── //scripts to run sensors// +└── //some other files// + +``` + + + + +## How to contribute +For adding more sensors/components you can add turtle blocks in `rpi.py`, their corresponding functions in `functions.py`, and add their requirements in the `req.txt` file. + +The pallet is only visible if the device is Raspberry Pi, so don't forget to disable the check in `__init__.py` when running on a PC + +## Dependencies +- pip +- pip packages + - adafruit-blinka + - adafruit-circuitpython-displayio-ssd1306 + - adafruit-circuitpython-ssd1306 + - adafruit-circuitpython-display-text + - adafruit-circuitpython-hcsr04 + +
+ +# Screenshots + +**Oled Display:** + + + +**Push Button:** + + \ No newline at end of file diff --git a/plugins/rpi/__init__.py b/plugins/rpi/__init__.py new file mode 100755 index 00000000..e2bf024e --- /dev/null +++ b/plugins/rpi/__init__.py @@ -0,0 +1,47 @@ +import os +import subprocess +cwd = os.getcwd() + +# check if the system is Raspberry Pi, if not, raise exception +# Eg. output: Raspberry Pi 5 Model B Rev 1.0 +try: + with open('/sys/firmware/devicetree/base/model', 'r') as file: + info = file.read() + if "Raspberry" in info: + is_rpi = True + else: + raise +except Exception: # file not found + raise # change to None if on a PC + + +# check if requirements are met +req_met = True +with open(cwd + '/plugins/rpi/req.txt', 'r') as file: + req_list = file.read().split("\n") +installed_modules = os.popen("pip freeze").read() + +for req in req_list: + if (req not in installed_modules): + req_met = False + + +# to install req. - called from rpi.py +def install_req(): + subprocess.Popen(['pip', 'install', '--break-system-packages', '-r', + cwd + '/plugins/rpi/req.txt']) + # todo: send notif to restart after a minute + # todo: send notif if no internet + + # configure i2c, spi, etc comms + os.system('''sudo raspi-config nonint do_i2c 0 && + sudo raspi-config nonintdo_spi 0 && + sudo raspi-config nonint do_serial_hw 0 && + sudo raspi-config nonint do_ssh 0 && + sudo raspi-config nonint do_camera 0 && + sudo raspi-config nonint disable_raspi_config_at_boot 0''') + + +# -- Add this line to config.txt to increase the speed of i2c devices -- # +# os.system("""sudo sed -i '$ a\dtparam=i2c_baudrate=1000000' +# /boot/firmware/config.txt""") diff --git a/plugins/rpi/functions.py b/plugins/rpi/functions.py new file mode 100755 index 00000000..101b3094 --- /dev/null +++ b/plugins/rpi/functions.py @@ -0,0 +1,101 @@ +import time +import os +import board +import digitalio +import adafruit_hcsr04 +import subprocess + +cwd = os.getcwd() +import logging + +_logger = logging.getLogger("turtleart-activity RPi plugin") + + +# Digital output +def digitalWrite(pin_no: str, val: float): + if not hasattr(board, pin_no): + return # + send a message + pin = digitalio.DigitalInOut(getattr(board, pin_no)) + pin.direction = digitalio.Direction.OUTPUT + pin.value = int(val) + + +# Digital input +def digitalRead(pin_no: str): + if not hasattr(board, pin_no): + return + pin = digitalio.DigitalInOut(getattr(board, pin_no)) + pin.direction = digitalio.Direction.INPUT + return bool(pin.value) + + +# delay +def delay(ms: int): + time.sleep(ms / 1000) + + +# Button +def btn(pin: str): + button = 0 + try: + button = digitalio.DigitalInOut(getattr(board, pin)) + button.direction = digitalio.Direction.INPUT + except Exception: + None + button.pull = digitalio.Pull.UP + return not button.value + + +# HC-SR04 Ultrasonic distance sensor +class _dist: + def def_dist(self, trigger: str, echo: str): + try: + self.sonar = adafruit_hcsr04.HCSR04( + trigger_pin=getattr(board, trigger), + echo_pin=getattr(board, echo) + ) + except Exception: + return + + def dst(self): + try: + s = 0.0 + for i in range(20): + if self.sonar.distance < 1: + i = i - 1 + continue + s += self.sonar.distance + return round(s / 20, 1) + except Exception: + return 0.0 + + +dist = _dist() + + +# OLED display +class _oled: + def define(self, _height, _width, _text_color): + self.width = int(_width) + self.height = int(_height) + self.text_color = int(_text_color) + + def print(self, text): + subprocess.Popen( + "python3 " + cwd + "/plugins/rpi/sensors/oled_display.py " + + str(self.height) + + " " + + str(self.width) + + " " + + str(self.text_color) + + " '" + + str(text) + + "'", + shell=True, + ) + + +oled = _oled() + +# except Exception as err: +# print(Exception, err) diff --git a/plugins/rpi/icons/RaspberryPioff.svg b/plugins/rpi/icons/RaspberryPioff.svg new file mode 100755 index 00000000..c666111f --- /dev/null +++ b/plugins/rpi/icons/RaspberryPioff.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/rpi/icons/RaspberryPioff2.svg b/plugins/rpi/icons/RaspberryPioff2.svg new file mode 100755 index 00000000..0f111368 --- /dev/null +++ b/plugins/rpi/icons/RaspberryPioff2.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/plugins/rpi/icons/RaspberryPion.svg b/plugins/rpi/icons/RaspberryPion.svg new file mode 100755 index 00000000..a45de14b --- /dev/null +++ b/plugins/rpi/icons/RaspberryPion.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/rpi/req.txt b/plugins/rpi/req.txt new file mode 100644 index 00000000..9fd7116b --- /dev/null +++ b/plugins/rpi/req.txt @@ -0,0 +1,6 @@ +RPi.GPIO +adafruit-blinka +adafruit-circuitpython-displayio-ssd1306 +adafruit-circuitpython-ssd1306 +adafruit-circuitpython-display-text +adafruit-circuitpython-hcsr04 \ No newline at end of file diff --git a/plugins/rpi/rpi.py b/plugins/rpi/rpi.py new file mode 100755 index 00000000..3915229e --- /dev/null +++ b/plugins/rpi/rpi.py @@ -0,0 +1,208 @@ +from plugins.plugin import Plugin +from gettext import gettext as _ +import plugins.rpi.__init__ as init +if init.req_met: + import plugins.rpi.functions as fns +from TurtleArt.taprimitive import ArgSlot, Primitive +from TurtleArt.tatype import TYPE_NUMBER, TYPE_OBJECT, TYPE_BOOL +from TurtleArt.tawindow import TurtleArtWindow +from TurtleArt.tapalette import make_palette + +import logging +_logger = logging.getLogger("turtleart-activity RPi plugin") + + +class Rpi(Plugin): + def __init__(self, parent: TurtleArtWindow): + Plugin.__init__(self) + self._parent = parent + self.running_sugar = self._parent.running_sugar + + def setup(self): + palette = make_palette( + "RaspberryPi", + colors=["#abe6ff", "#008080"], + help_string=_("Palette of RPi pins"), + position=6, + ) + + # check if req is met, if not then show only install block + if not init.req_met: + palette.add_block( + "req", + style="basic-style", + label="install requirements", + prim_name="req", + logo_command="req", + ) + self._parent.lc.def_prim( + "req", + 0, + Primitive( + init.install_req, + ), + ) + raise + + # Digital Output + palette.add_block( + "digitalWrite", + style="basic-style-2arg", + label=[_("GPIO"), _("set"), _("to")], + default="Dx", + prim_name="digitalWrite", + help_string=_("sets pin to high/low"), + logo_command="digitalWrite", + ) + self._parent.lc.def_prim( + "digitalWrite", + 2, + Primitive( + fns.digitalWrite, arg_descs=[ArgSlot(TYPE_OBJECT), + ArgSlot(TYPE_NUMBER)] + ), + ) + # Digital Input + palette.add_block( + "digitalRead", + style="basic-style-1arg", + label=["GPIO\t ", "read"], + default="Dx", + prim_name="digitalRead", + help_string=_("sets pin to high/low"), + logo_command="digitalRead", + ) + self._parent.lc.def_prim( + "digitalRead", + 1, + Primitive(fns.digitalRead, arg_descs=ArgSlot(TYPE_OBJECT), + return_type=TYPE_BOOL)) + + # Delay + palette.add_block( + "delay", + style="basic-style-1arg", + label="delay (ms)", + prim_name="delay", + logo_command="delay", + default=1000, + ) + self._parent.lc.def_prim( + "delay", 1, Primitive(fns.delay, arg_descs=ArgSlot(TYPE_NUMBER)) + ) + + # Button + palette.add_block( + "btn", + style="boolean-1arg-block-style", + label=_("button"), + prim_name="btn", + default="Dx", + help_string=_("sets pin to high/low"), + logo_command="btn", + ) + self._parent.lc.def_prim( + "btn", + 1, + Primitive( + fns.btn, return_type=TYPE_BOOL, + arg_descs=([ArgSlot(TYPE_OBJECT)]) + ), + ) + + # HIGH/LOW Blocks + palette.add_block( + "high", + style="box-style", + label=_("HIGH"), + prim_name="high", + help_string=_("toggles gpio pin high"), + ) + + def _high(): + return 1 + + self._parent.lc.def_prim("high", 0, Primitive(_high, + return_type=TYPE_NUMBER)) + + palette.add_block( + "low", + style="box-style", + label=_("LOW"), + prim_name="low", + help_string=_("toggles gpio pin low"), + ) + + def _low(): + return 0 + + self._parent.lc.def_prim("low", 0, Primitive(_low, + return_type=TYPE_NUMBER)) + + # HC-SR04 distance sensor + palette.add_block("dist", style="box-style", + prim_name="dist", label="distance") + self._parent.lc.def_prim( + "dist", 0, Primitive(fns.dist.dst, return_type=TYPE_NUMBER) + ) + + palette.add_block( + "def_dist", + style="basic-style-2arg", + label=["HCSR04", "trig pin", "echo pin"], + default=["Dx", "Dx"], + prim_name="def_dist", + help_string=_( + "measure distance using HC-SR04,\ + distance sensor" + ), + ) + self._parent.lc.def_prim( + "def_dist", + 2, + Primitive( + fns.dist.def_dist, + arg_descs=([ArgSlot(TYPE_OBJECT), ArgSlot(TYPE_OBJECT)]), + ), + ) + + # String editable blocks + # palette.add_block('string', + # style='box-style', + # help_string=_('text')) + + # OLED-Display + palette.add_block( + "define_oled_display", + style="basic-style-3arg", + label=[_("OLED\t\t"), _("height"), _("width"), + _("text color\n(0 or 1) ")], + default=[128, 32, 1], + prim_name="define_oled_display", + logo_command="define_oled_display", + ) + self._parent.lc.def_prim( + "define_oled_display", + 3, + Primitive( + fns.oled.define, + arg_descs=( + [ArgSlot(TYPE_NUMBER), + ArgSlot(TYPE_NUMBER), + ArgSlot(TYPE_NUMBER)] + ), + ), + ) + + palette.add_block( + "oled_print", + style="basic-style-1arg", + default="text", + label="Display", + prim_name="oled_print", + ) + self._parent.lc.def_prim( + "oled_print", + 1, + Primitive(fns.oled.print, arg_descs=([ArgSlot(TYPE_OBJECT)])), + ) diff --git a/plugins/rpi/screenshots/after_req.png b/plugins/rpi/screenshots/after_req.png new file mode 100644 index 00000000..ab598505 Binary files /dev/null and b/plugins/rpi/screenshots/after_req.png differ diff --git a/plugins/rpi/screenshots/before_req.png b/plugins/rpi/screenshots/before_req.png new file mode 100644 index 00000000..a515d6e5 Binary files /dev/null and b/plugins/rpi/screenshots/before_req.png differ diff --git a/plugins/rpi/screenshots/btn.gif b/plugins/rpi/screenshots/btn.gif new file mode 100644 index 00000000..6b1d6f4b Binary files /dev/null and b/plugins/rpi/screenshots/btn.gif differ diff --git a/plugins/rpi/screenshots/oled.jpg b/plugins/rpi/screenshots/oled.jpg new file mode 100644 index 00000000..2c6dafe5 Binary files /dev/null and b/plugins/rpi/screenshots/oled.jpg differ diff --git a/plugins/rpi/screenshots/pinout_rpi.png b/plugins/rpi/screenshots/pinout_rpi.png new file mode 100644 index 00000000..87263297 Binary files /dev/null and b/plugins/rpi/screenshots/pinout_rpi.png differ diff --git a/plugins/rpi/screenshots/ta_btn.png b/plugins/rpi/screenshots/ta_btn.png new file mode 100644 index 00000000..18c86f4d Binary files /dev/null and b/plugins/rpi/screenshots/ta_btn.png differ diff --git a/plugins/rpi/screenshots/ta_oled.png b/plugins/rpi/screenshots/ta_oled.png new file mode 100644 index 00000000..ccb5c1d5 Binary files /dev/null and b/plugins/rpi/screenshots/ta_oled.png differ diff --git a/plugins/rpi/sensors/oled_display.py b/plugins/rpi/sensors/oled_display.py new file mode 100755 index 00000000..50375174 --- /dev/null +++ b/plugins/rpi/sensors/oled_display.py @@ -0,0 +1,59 @@ +import sys +import time +import board +import displayio +from i2cdisplaybus import I2CDisplayBus +from fourwire import FourWire +import terminalio +from adafruit_display_text import label +import adafruit_displayio_ssd1306 + + +def main(width, height, text_color, text): + height = int(height) + width = int(width) + text_color = int(text_color) + displayio.release_displays() + oled_reset = board.D27 + + # Use for I2C + i2c = board.I2C() + display_bus = I2CDisplayBus(i2c, device_address=0x3C, reset=oled_reset) + + # Use for SPI + # spi = board.SPI() + # oled_cs = board.D5 + # oled_dc = board.D6 + # display_bus = FourWire(spi, command=oled_dc, chip_select=oled_cs, + # reset=oled_reset, baudrate=1000000) + display = adafruit_displayio_ssd1306.SSD1306( + display_bus, width=width, height=height + ) + splash = displayio.Group() + display.root_group = splash + + # clear oled + color_bitmap = displayio.Bitmap(width, height, 1) + color_palette = displayio.Palette(1) + color_palette[0] = 0xFFFFFF if text_color == 0 else 0x000000 + bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, + x=0, y=0) + splash.append(bg_sprite) + + # print on oled + if text != "": + if len(text) > 20: + text = text[:20] + text_area = label.Label( + terminalio.FONT, + text=text, + color=0x000000 if text_color == 0 else 0xFFFFFF, + x=5, + y=5, + ) + splash.append(text_area) + time.sleep(0.2) + + +if __name__ == "__main__": + main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]) diff --git a/plugins/rpi/setup.py b/plugins/rpi/setup.py new file mode 100755 index 00000000..e69de29b