diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c6b44e4..4773a0a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,3 +30,15 @@ jobs: with: options: "--check --verbose" src: "./Software/Testing" + + - name: Flake8 Lint Production + uses: py-actions/flake8@v2 + with: + path: "./Software/Production" + ignore: "E501,E203,E722" #Ignore line to long, we don't care + + - name: Black File Formatter Production + uses: psf/black@stable + with: + options: "--check --verbose" + src: "./Software/Production" diff --git a/Software/Production/StartupState.py b/Software/Production/StartupState.py new file mode 100644 index 0000000..cd221b7 --- /dev/null +++ b/Software/Production/StartupState.py @@ -0,0 +1,67 @@ +import time +import terminalio +from State import State +from adafruit_display_text import label +from setup import ( + display, + keys, + neopixels, +) + + +class StartupState(State): + color = (0, 0, 0) + timer = 0 + stage = 0 + + @property + def name(self): + return "startup" + + def enter(self, machine): + neopixels.fill((0, 0, 0)) + State.enter(self, machine) + + def exit(self, machine): + neopixels.fill((255, 0, 0)) + self.color = (0, 0, 0) + self.timer = 0 + self.stage = 0 + State.exit(self, machine) + + def update(self, machine): + self.timer = self.timer + 1 + if self.stage == 0: + text = " DCZia\n Electric Sampler" + if len(text) > self.timer: + text = text[0 : self.timer] + text_area = label.Label(terminalio.FONT, text=text, x=2, y=5) + display.show(text_area) + self.color = (self.timer, self.timer, 0) + if self.timer > (len(text) * 1.5): + self.timer = 0 + self.stage = 1 + elif self.stage == 1: + text = "Fueled by Green Chile\n and Solder" + if len(text) > self.timer: + text = text[0 : self.timer] + text_area = label.Label(terminalio.FONT, text=text, x=2, y=10) + display.show(text_area) + if self.timer > (len(text) * 1.5): + self.timer = 0 + self.stage = 2 + else: + if self.timer < (255 * 8): + color = (0, self.timer % 255, 0) + neopixels[self.timer // 255] = color + neopixels.show() + self.timer = self.timer + 1 # make it faster + else: + time.sleep(0.1) + machine.go_to_state( + "startup" + ) # TODO: Should go to menu when that is in + # Skip to menu if encoder is pressed + key_event = keys.events.get() + if key_event and key_event.pressed: + machine.go_to_state("startup") # TODO: Should go to menu when that is in diff --git a/Software/Production/State.py b/Software/Production/State.py new file mode 100644 index 0000000..f4426df --- /dev/null +++ b/Software/Production/State.py @@ -0,0 +1,16 @@ +class State(object): + def __init__(self): + pass + + @property + def name(self): + return "" + + def enter(self, machine): + pass + + def exit(self, machine): + pass + + def update(self, machine): + return True diff --git a/Software/Production/lib/adafruit_bus_device/__init__.py b/Software/Production/lib/adafruit_bus_device/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Software/Production/lib/adafruit_bus_device/i2c_device.mpy b/Software/Production/lib/adafruit_bus_device/i2c_device.mpy new file mode 100644 index 0000000..bf45584 Binary files /dev/null and b/Software/Production/lib/adafruit_bus_device/i2c_device.mpy differ diff --git a/Software/Production/lib/adafruit_bus_device/spi_device.mpy b/Software/Production/lib/adafruit_bus_device/spi_device.mpy new file mode 100644 index 0000000..1a2aa3e Binary files /dev/null and b/Software/Production/lib/adafruit_bus_device/spi_device.mpy differ diff --git a/Software/Production/lib/adafruit_display_text/__init__.mpy b/Software/Production/lib/adafruit_display_text/__init__.mpy new file mode 100644 index 0000000..46acbd3 Binary files /dev/null and b/Software/Production/lib/adafruit_display_text/__init__.mpy differ diff --git a/Software/Production/lib/adafruit_display_text/bitmap_label.mpy b/Software/Production/lib/adafruit_display_text/bitmap_label.mpy new file mode 100644 index 0000000..5848ddb Binary files /dev/null and b/Software/Production/lib/adafruit_display_text/bitmap_label.mpy differ diff --git a/Software/Production/lib/adafruit_display_text/label.mpy b/Software/Production/lib/adafruit_display_text/label.mpy new file mode 100644 index 0000000..6b94e37 Binary files /dev/null and b/Software/Production/lib/adafruit_display_text/label.mpy differ diff --git a/Software/Production/lib/adafruit_display_text/scrolling_label.mpy b/Software/Production/lib/adafruit_display_text/scrolling_label.mpy new file mode 100644 index 0000000..86fccff Binary files /dev/null and b/Software/Production/lib/adafruit_display_text/scrolling_label.mpy differ diff --git a/Software/Production/lib/adafruit_displayio_ssd1306.py b/Software/Production/lib/adafruit_displayio_ssd1306.py new file mode 100644 index 0000000..8aa98b4 --- /dev/null +++ b/Software/Production/lib/adafruit_displayio_ssd1306.py @@ -0,0 +1,135 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_displayio_ssd1306` +================================================================================ + +DisplayIO driver for SSD1306 monochrome displays + + +* Author(s): Scott Shawcroft + +Implementation Notes +-------------------- + +**Hardware:** + +* `Monochrome 1.3" 128x64 OLED graphic display `_ +* `Monochrome 128x32 I2C OLED graphic display `_ +* `Monochrome 0.96" 128x64 OLED graphic display `_ +* `Monochrome 128x32 SPI OLED graphic display `_ +* `Adafruit FeatherWing OLED - 128x32 OLED `_ +* Monochrome 0.49" 64x32 I2C OLED graphic display +* Might work on other sub-128 width display: Dots 72x40, 64x48, 96x16 + +**Software and Dependencies:** + +* Adafruit CircuitPython (version 5+) firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +import displayio + +try: + from typing import Union +except ImportError: + pass + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SSD1306.git" + +# Sequence from page 19 here: https://cdn-shop.adafruit.com/datasheets/UG-2864HSWEG01+user+guide.pdf +_INIT_SEQUENCE = ( + b"\xAE\x00" # DISPLAY_OFF + b"\x20\x01\x00" # Set memory addressing to horizontal mode. + b"\x81\x01\xcf" # set contrast control + b"\xA1\x00" # Column 127 is segment 0 + b"\xA6\x00" # Normal display + b"\xc8\x00" # Normal display + b"\xA8\x01\x3f" # Mux ratio is 1/64 + b"\xd5\x01\x80" # Set divide ratio + b"\xd9\x01\xf1" # Set pre-charge period + b"\xda\x01\x12" # Set com configuration + b"\xdb\x01\x40" # Set vcom configuration + b"\x8d\x01\x14" # Enable charge pump + b"\xAF\x00" # DISPLAY_ON +) + + +class SSD1306(displayio.Display): + """ + SSD1306 driver + + :param int width: The width of the display + :param int height: The height of the display + :param int rotation: The rotation of the display in degrees. Default is 0. Must be one of + (0, 90, 180, 270) + """ + + def __init__( + self, bus: Union[displayio.FourWire, displayio.I2CDisplay], **kwargs + ) -> None: + # Patch the init sequence for 32 pixel high displays. + init_sequence = bytearray(_INIT_SEQUENCE) + height = kwargs["height"] + width = kwargs["width"] + if "rotation" in kwargs and kwargs["rotation"] % 180 != 0: + height = kwargs["width"] + width = kwargs["height"] + init_sequence[16] = height - 1 # patch mux ratio + if height == 32 and width == 64: # Make sure this only apply to that resolution + init_sequence[16] = 64 - 1 # FORCED for 64x32 because it fail with formula + if height in (32, 16) and width != 64: + init_sequence[25] = 0x02 # patch com configuration + col_offset = ( + 0 if width == 128 else (128 - width) // 2 + ) # https://github.com/micropython/micropython/pull/7411 + super().__init__( + bus, + init_sequence, + **kwargs, + colstart=col_offset, + rowstart=col_offset, + color_depth=1, + grayscale=True, + pixels_in_byte_share_row=False, + set_column_command=0x21, + set_row_command=0x22, + data_as_commands=True, + brightness_command=0x81, + single_byte_bounds=True, + ) + self._is_awake = True # Display starts in active state (_INIT_SEQUENCE) + + @property + def is_awake(self) -> bool: + """ + The power state of the display. (read-only) + + `True` if the display is active, `False` if in sleep mode. + + :type: bool + """ + return self._is_awake + + def sleep(self) -> None: + """ + Put display into sleep mode. + + Display uses < 10uA in sleep mode. Display remembers display data and operation mode + active prior to sleeping. MP can access (update) the built-in display RAM. + """ + if self._is_awake: + self.bus.send(0xAE, b"") # 0xAE = display off, sleep mode + self._is_awake = False + + def wake(self) -> None: + """ + Wake display from sleep mode + """ + if not self._is_awake: + self.bus.send(0xAF, b"") # 0xAF = display on + self._is_awake = True diff --git a/Software/Production/lib/adafruit_midi/__init__.mpy b/Software/Production/lib/adafruit_midi/__init__.mpy new file mode 100644 index 0000000..f809301 Binary files /dev/null and b/Software/Production/lib/adafruit_midi/__init__.mpy differ diff --git a/Software/Production/lib/adafruit_midi/channel_pressure.mpy b/Software/Production/lib/adafruit_midi/channel_pressure.mpy new file mode 100644 index 0000000..eba5406 Binary files /dev/null and b/Software/Production/lib/adafruit_midi/channel_pressure.mpy differ diff --git a/Software/Production/lib/adafruit_midi/control_change.mpy b/Software/Production/lib/adafruit_midi/control_change.mpy new file mode 100644 index 0000000..223172a Binary files /dev/null and b/Software/Production/lib/adafruit_midi/control_change.mpy differ diff --git a/Software/Production/lib/adafruit_midi/control_change_values.mpy b/Software/Production/lib/adafruit_midi/control_change_values.mpy new file mode 100644 index 0000000..ad845ac Binary files /dev/null and b/Software/Production/lib/adafruit_midi/control_change_values.mpy differ diff --git a/Software/Production/lib/adafruit_midi/midi_continue.mpy b/Software/Production/lib/adafruit_midi/midi_continue.mpy new file mode 100644 index 0000000..ff71f3a Binary files /dev/null and b/Software/Production/lib/adafruit_midi/midi_continue.mpy differ diff --git a/Software/Production/lib/adafruit_midi/midi_message.mpy b/Software/Production/lib/adafruit_midi/midi_message.mpy new file mode 100644 index 0000000..6db8403 Binary files /dev/null and b/Software/Production/lib/adafruit_midi/midi_message.mpy differ diff --git a/Software/Production/lib/adafruit_midi/mtc_quarter_frame.mpy b/Software/Production/lib/adafruit_midi/mtc_quarter_frame.mpy new file mode 100644 index 0000000..ecb9004 Binary files /dev/null and b/Software/Production/lib/adafruit_midi/mtc_quarter_frame.mpy differ diff --git a/Software/Production/lib/adafruit_midi/note_off.mpy b/Software/Production/lib/adafruit_midi/note_off.mpy new file mode 100644 index 0000000..362e419 Binary files /dev/null and b/Software/Production/lib/adafruit_midi/note_off.mpy differ diff --git a/Software/Production/lib/adafruit_midi/note_on.mpy b/Software/Production/lib/adafruit_midi/note_on.mpy new file mode 100644 index 0000000..c6fe45a Binary files /dev/null and b/Software/Production/lib/adafruit_midi/note_on.mpy differ diff --git a/Software/Production/lib/adafruit_midi/pitch_bend.mpy b/Software/Production/lib/adafruit_midi/pitch_bend.mpy new file mode 100644 index 0000000..07ffcaa Binary files /dev/null and b/Software/Production/lib/adafruit_midi/pitch_bend.mpy differ diff --git a/Software/Production/lib/adafruit_midi/polyphonic_key_pressure.mpy b/Software/Production/lib/adafruit_midi/polyphonic_key_pressure.mpy new file mode 100644 index 0000000..d49169b Binary files /dev/null and b/Software/Production/lib/adafruit_midi/polyphonic_key_pressure.mpy differ diff --git a/Software/Production/lib/adafruit_midi/program_change.mpy b/Software/Production/lib/adafruit_midi/program_change.mpy new file mode 100644 index 0000000..0e81cab Binary files /dev/null and b/Software/Production/lib/adafruit_midi/program_change.mpy differ diff --git a/Software/Production/lib/adafruit_midi/start.mpy b/Software/Production/lib/adafruit_midi/start.mpy new file mode 100644 index 0000000..884c31f Binary files /dev/null and b/Software/Production/lib/adafruit_midi/start.mpy differ diff --git a/Software/Production/lib/adafruit_midi/stop.mpy b/Software/Production/lib/adafruit_midi/stop.mpy new file mode 100644 index 0000000..36f7ff3 Binary files /dev/null and b/Software/Production/lib/adafruit_midi/stop.mpy differ diff --git a/Software/Production/lib/adafruit_midi/system_exclusive.mpy b/Software/Production/lib/adafruit_midi/system_exclusive.mpy new file mode 100644 index 0000000..491db16 Binary files /dev/null and b/Software/Production/lib/adafruit_midi/system_exclusive.mpy differ diff --git a/Software/Production/lib/adafruit_midi/timing_clock.mpy b/Software/Production/lib/adafruit_midi/timing_clock.mpy new file mode 100644 index 0000000..ae04b3e Binary files /dev/null and b/Software/Production/lib/adafruit_midi/timing_clock.mpy differ diff --git a/Software/Production/lib/adafruit_sdcard.mpy b/Software/Production/lib/adafruit_sdcard.mpy new file mode 100644 index 0000000..c859bcb Binary files /dev/null and b/Software/Production/lib/adafruit_sdcard.mpy differ diff --git a/Software/Production/lib/neopixel.mpy b/Software/Production/lib/neopixel.mpy new file mode 100644 index 0000000..2739b22 Binary files /dev/null and b/Software/Production/lib/neopixel.mpy differ diff --git a/Software/Production/main.py b/Software/Production/main.py new file mode 100644 index 0000000..044fcee --- /dev/null +++ b/Software/Production/main.py @@ -0,0 +1,53 @@ +import time + +from setup import ( + select_enc, + volume_enc, +) + +from StartupState import StartupState + + +class StateMachine(object): + def __init__(self): + self.state = None + self.states = {} + self.last_select_pos = select_enc.position + self.last_volume_pos = volume_enc.position + self.paused_state = None + self.ticks_ms = 0 + self.animation = None + + def add_state(self, state): + self.states[state.name] = state + + def go_to_state(self, state_name): + if self.state: + self.state.exit(self) + self.state = self.states[state_name] + self.state.enter(self) + + def update(self): + if self.state: + self.state.update(self) + if self.ticks_ms > 0: + time.sleep(self.ticks_ms / 1000) + + # When pausing, don't exit the state + def pause(self): + self.state = self.states["paused"] + self.state.enter(self) + + # When resuming, don't re-enter the state + def resume_state(self, state_name): + if self.state: + self.state.exit(self) + self.state = self.states[state_name] + + +machine = StateMachine() +machine.add_state(StartupState()) +machine.go_to_state("startup") + +while True: + machine.update() diff --git a/Software/Production/setup.py b/Software/Production/setup.py new file mode 100644 index 0000000..13e4edf --- /dev/null +++ b/Software/Production/setup.py @@ -0,0 +1,76 @@ +import adafruit_displayio_ssd1306 +import adafruit_midi +import adafruit_sdcard +import board +import busio +import digitalio +import displayio +import keypad +import neopixel +import rotaryio +import storage +import terminalio +import time +import usb_midi + +from adafruit_display_text import label + +# OLED Screen ( display ) +displayio.release_displays() +i2c = busio.I2C(board.GP15, board.GP14) +display_bus = displayio.I2CDisplay(i2c, device_address=0x3C) +display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32) + +# Neopixels +neopixels = neopixel.NeoPixel(board.GP3, 10, brightness=0.1, auto_write=True) + +# Board LED +led = digitalio.DigitalInOut(board.LED) +led.direction = digitalio.Direction.OUTPUT + +# Sync Out +sync_out = digitalio.DigitalInOut(board.GP7) +sync_out.direction = digitalio.Direction.OUTPUT + +# Sync In +sync_in = digitalio.DigitalInOut(board.GP6) +sync_in.direction = digitalio.Direction.INPUT + +# Buttons +# 0-7 Buttons +# 8 Play +# 9 Function +# 10 Select +# 11 Volume +keys = keypad.KeyMatrix( + row_pins=(board.GP27, board.GP26, board.GP18), + column_pins=(board.GP20, board.GP21, board.GP22, board.GP28), + columns_to_anodes=False, +) + +# Setup rotary encoders +select_enc = rotaryio.IncrementalEncoder(board.GP16, board.GP17) +volume_enc = rotaryio.IncrementalEncoder(board.GP4, board.GP5) + +# MIDI setup +midi_uart = busio.UART(tx=board.GP8, baudrate=31250) +midi_serial_channel = 2 +midi_serial = adafruit_midi.MIDI( + midi_out=midi_uart, out_channel=midi_serial_channel - 1 +) + +midi_usb = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0) + +# Setup the SD card and mount it as /sd +try: + # busio.SPI(clock:, MOSI: , MISO:) + spi = busio.SPI(board.GP10, board.GP11, board.GP12) + cs = digitalio.DigitalInOut(board.GP13) + sdcard = adafruit_sdcard.SDCard(spi, cs) + vfs = storage.VfsFat(sdcard) + storage.mount(vfs, "/sd") +except: + text = "No SD Card Found!" + text_area = label.Label(terminalio.FONT, text=text, color=0xFFFF00, x=2, y=15) + display.show(text_area) + time.sleep(5)