Skip to content

Commit

Permalink
Add OLED resolution option (#879)
Browse files Browse the repository at this point in the history
* Add OLED resolution option

* Add OLED resolution option

* Addressed code review
  • Loading branch information
zlite authored Jun 12, 2021
1 parent d1efa9f commit 20bf83c
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 221 deletions.
320 changes: 161 additions & 159 deletions donkeycar/parts/oled.py
Original file line number Diff line number Diff line change
@@ -1,159 +1,161 @@
# requires the Adafruit ssd1306 library: pip install adafruit-circuitpython-ssd1306

import subprocess
import time
from board import SCL, SDA
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306


class OLEDDisplay(object):
'''
Manages drawing of text on the OLED display.
'''
def __init__(self, rotation=0):
# Placeholder
self._EMPTY = ''
# Total number of lines of text
self._SLOT_COUNT = 4
self.slots = [self._EMPTY] * self._SLOT_COUNT
self.display = None
self.rotation = rotation

def init_display(self):
'''
Initializes the OLED display.
'''
if self.display is None:
# Create the I2C interface.
i2c = busio.I2C(SCL, SDA)
# Create the SSD1306 OLED class.
# The first two parameters are the pixel width and pixel height. Change these
# to the right size for your display!
self.display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
self.display.rotation = self.rotation


self.display.fill(0)
self.display.show()

# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
self.width = self.display.width
self.height = self.display.height
self.image = Image.new("1", (self.width, self.height))

# Get drawing object to draw on image.
self.draw = ImageDraw.Draw(self.image)

# Draw a black filled box to clear the image.
self.draw.rectangle((0, 0, self.width, self.height), outline=0, fill=0)
# Load Fonts
self.font = ImageFont.load_default()
self.clear_display()

def clear_display(self):
if self.draw is not None:
self.draw.rectangle((0, 0, self.width, self.height), outline=0, fill=0)

def update_slot(self, index, text):
if index < len(self.slots):
self.slots[index] = text

def clear_slot(self, index):
if index < len(self.slots):
self.slots[index] = self._EMPTY

def update(self):
'''Display text'''
x = 0
top = -2
self.clear_display()
for i in range(self._SLOT_COUNT):
text = self.slots[i]
if len(text) > 0:
self.draw.text((x, top), text, font=self.font, fill=255)
top += 8

# Update
self.display.rotation = self.rotation
self.display.image(self.image)
self.display.show()


class OLEDPart(object):
'''
The part that updates status on the oled display.
'''
def __init__(self, rotation, auto_record_on_throttle=False):
self.rotation = rotation
self.oled = OLEDDisplay(self.rotation)
self.oled.init_display()
self.on = False
if auto_record_on_throttle:
self.recording = 'AUTO'
else:
self.recording = 'NO'
self.num_records = 0
self.user_mode = None
eth0 = OLEDPart.get_ip_address('eth0')
wlan0 = OLEDPart.get_ip_address('wlan0')
if eth0 is not None:
self.eth0 = 'eth0 : %s' % (eth0)
else:
self.eth0 = None
if wlan0 is not None:
self.wlan0 = 'wlan0 : %s' % (wlan0)
else:
self.wlan0 = None

def run(self):
if not self.on:
self.on = True

def run_threaded(self, recording, num_records, user_mode):
if num_records is not None and num_records > 0:
self.num_records = num_records

if recording:
self.recording = 'YES (Records = %s)' % (self.num_records)
else:
self.recording = 'NO (Records = %s)' % (self.num_records)

self.user_mode = 'User Mode (%s)' % (user_mode)

def update_slots(self):
updates = [self.eth0, self.wlan0, self.recording, self.user_mode]
index = 0
# Update slots
for update in updates:
if update is not None:
self.oled.update_slot(index, update)
index += 1

# Update display
self.oled.update()

def update(self):
self.on = True
# Run threaded loop by itself
while self.on:
self.update_slots()

def shutdown(self):
self.oled.clear_display()
self.on = False

# https://github.com/NVIDIA-AI-IOT/jetbot/blob/master/jetbot/utils/utils.py

@classmethod
def get_ip_address(cls, interface):
if OLEDPart.get_network_interface_state(interface) == 'down':
return None
cmd = "ifconfig %s | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'" % interface
return subprocess.check_output(cmd, shell=True).decode('ascii')[:-1]

@classmethod
def get_network_interface_state(cls, interface):
return subprocess.check_output('cat /sys/class/net/%s/operstate' % interface, shell=True).decode('ascii')[:-1]
# requires the Adafruit ssd1306 library: pip install adafruit-circuitpython-ssd1306

import subprocess
import time
from board import SCL, SDA
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306


class OLEDDisplay(object):
'''
Manages drawing of text on the OLED display.
'''
def __init__(self, rotation=0, resolution=1):
# Placeholder
self._EMPTY = ''
# Total number of lines of text
self._SLOT_COUNT = 4
self.slots = [self._EMPTY] * self._SLOT_COUNT
self.display = None
self.rotation = rotation
if resolution == 2:
self.height = 64
else:
self.height = 32

def init_display(self):
'''
Initializes the OLED display.
'''
if self.display is None:
# Create the I2C interface.
i2c = busio.I2C(SCL, SDA)
# Create the SSD1306 OLED class.
# The first two parameters are the pixel width and pixel height. Change these
# to the right size for your display!
self.display = adafruit_ssd1306.SSD1306_I2C(128, self.height, i2c)
self.display.rotation = self.rotation


self.display.fill(0)
self.display.show()

# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
self.width = self.display.width
self.image = Image.new("1", (self.width, self.height))

# Get drawing object to draw on image.
self.draw = ImageDraw.Draw(self.image)

# Draw a black filled box to clear the image.
self.draw.rectangle((0, 0, self.width, self.height), outline=0, fill=0)
# Load Fonts
self.font = ImageFont.load_default()
self.clear_display()

def clear_display(self):
if self.draw is not None:
self.draw.rectangle((0, 0, self.width, self.height), outline=0, fill=0)

def update_slot(self, index, text):
if index < len(self.slots):
self.slots[index] = text

def clear_slot(self, index):
if index < len(self.slots):
self.slots[index] = self._EMPTY

def update(self):
'''Display text'''
x = 0
top = -2
self.clear_display()
for i in range(self._SLOT_COUNT):
text = self.slots[i]
if len(text) > 0:
self.draw.text((x, top), text, font=self.font, fill=255)
top += 8

# Update
self.display.rotation = self.rotation
self.display.image(self.image)
self.display.show()


class OLEDPart(object):
'''
The part that updates status on the oled display.
'''
def __init__(self, rotation, resolution, auto_record_on_throttle=False):
self.oled = OLEDDisplay(rotation, resolution)
self.oled.init_display()
self.on = False
if auto_record_on_throttle:
self.recording = 'AUTO'
else:
self.recording = 'NO'
self.num_records = 0
self.user_mode = None
eth0 = OLEDPart.get_ip_address('eth0')
wlan0 = OLEDPart.get_ip_address('wlan0')
if eth0 is not None:
self.eth0 = 'eth0 : %s' % (eth0)
else:
self.eth0 = None
if wlan0 is not None:
self.wlan0 = 'wlan0 : %s' % (wlan0)
else:
self.wlan0 = None

def run(self):
if not self.on:
self.on = True

def run_threaded(self, recording, num_records, user_mode):
if num_records is not None and num_records > 0:
self.num_records = num_records

if recording:
self.recording = 'YES (Records = %s)' % (self.num_records)
else:
self.recording = 'NO (Records = %s)' % (self.num_records)

self.user_mode = 'User Mode (%s)' % (user_mode)

def update_slots(self):
updates = [self.eth0, self.wlan0, self.recording, self.user_mode]
index = 0
# Update slots
for update in updates:
if update is not None:
self.oled.update_slot(index, update)
index += 1

# Update display
self.oled.update()

def update(self):
self.on = True
# Run threaded loop by itself
while self.on:
self.update_slots()

def shutdown(self):
self.oled.clear_display()
self.on = False

# https://github.com/NVIDIA-AI-IOT/jetbot/blob/master/jetbot/utils/utils.py

@classmethod
def get_ip_address(cls, interface):
if OLEDPart.get_network_interface_state(interface) == 'down':
return None
cmd = "ifconfig %s | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'" % interface
return subprocess.check_output(cmd, shell=True).decode('ascii')[:-1]

@classmethod
def get_network_interface_state(cls, interface):
return subprocess.check_output('cat /sys/class/net/%s/operstate' % interface, shell=True).decode('ascii')[:-1]
32 changes: 13 additions & 19 deletions donkeycar/templates/cfg_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,22 @@
#SSD1306_128_32
USE_SSD1306_128_32 = False # Enable the SSD_1306 OLED Display
SSD1306_128_32_I2C_ROTATION = 0 # 0 = text is right-side up, 1 = rotated 90 degrees clockwise, 2 = 180 degrees (flipped), 3 = 270 degrees
SSD1306_RESOLUTION = 1 # 1 = 128x32; 2 = 128x64

#DRIVETRAIN
#These options specify which chasis and motor setup you are using. Most are using I2C_SERVO.
#These options specify which chasis and motor setup you are using. Most are using SERVO_ESC.
#DC_STEER_THROTTLE uses HBridge pwm to control one steering dc motor, and one drive wheel motor
#DC_TWO_WHEEL uses HBridge pwm to control two drive motors, one on the left, and one on the right.
#SERVO_HBRIDGE_PWM use ServoBlaster to output pwm control from the PiZero directly to control steering, and HBridge for a drive motor.
#PIGPIO_PWM uses Raspberrys internal PWM
DRIVE_TRAIN_TYPE = "I2C_SERVO" # I2C_SERVO|DC_STEER_THROTTLE|DC_TWO_WHEEL|DC_TWO_WHEEL_L298N|SERVO_HBRIDGE_PWM|PIGPIO_PWM|MM1|MOCK
DRIVE_TRAIN_TYPE = "SERVO_ESC" # SERVO_ESC|DC_STEER_THROTTLE|DC_TWO_WHEEL|DC_TWO_WHEEL_L298N|SERVO_HBRIDGE_PWM|PIGPIO_PWM|MM1|MOCK

#STEERING
STEERING_CHANNEL = 1 #channel on the 9685 pwm board 0-15
STEERING_LEFT_PWM = 460 #pwm value for full left steering
STEERING_RIGHT_PWM = 290 #pwm value for full right steering

#STEERING FOR PIGPIO_PWM OUTPUT
#STEERING FOR PIGPIO_PWM
STEERING_PWM_PIN = 13 #Pin numbering according to Broadcom numbers
STEERING_PWM_FREQ = 50 #Frequency for PWM
STEERING_PWM_INVERTED = False #If PWM needs to be inverted
Expand All @@ -70,7 +71,7 @@
THROTTLE_STOPPED_PWM = 370 #pwm value for no movement
THROTTLE_REVERSE_PWM = 220 #pwm value for max reverse throttle

#THROTTLE FOR PIGPIO_PWM OUTPUT
#THROTTLE FOR PIGPIO_PWM
THROTTLE_PWM_PIN = 18 #Pin numbering according to Broadcom numbers
THROTTLE_PWM_FREQ = 50 #Frequency for PWM
THROTTLE_PWM_INVERTED = False #If PWM needs to be inverted
Expand Down Expand Up @@ -103,6 +104,13 @@
LIDAR_LOWER_LIMIT = 90 # angles that will be recorded. Use this to block out obstructed areas on your car, or looking backwards. Note that for the RP A1M8 Lidar, "0" is in the direction of the motor
LIDAR_UPPER_LIMIT = 270


# #RC CONTROL
USE_RC = False
STEERING_RC_GPIO = 26
THROTTLE_RC_GPIO = 20
DATA_WIPER_RC_GPIO = 19

#DC_TWO_WHEEL_L298N - with two wheels as drive, left and right.
#these GPIO pinouts are only used for the DRIVE_TRAIN_TYPE=DC_TWO_WHEEL_L298N
HBRIDGE_L298N_PIN_LEFT_FWD = 16
Expand Down Expand Up @@ -166,7 +174,7 @@
JOYSTICK_MAX_THROTTLE = 0.5 #this scalar is multiplied with the -1 to 1 throttle value to limit the maximum throttle. This can help if you drop the controller or just don't need the full speed available.
JOYSTICK_STEERING_SCALE = 1.0 #some people want a steering that is less sensitve. This scalar is multiplied with the steering -1 to 1. It can be negative to reverse dir.
AUTO_RECORD_ON_THROTTLE = True #if true, we will record whenever throttle is not zero. if false, you must manually toggle recording with some other trigger. Usually circle button on joystick.
CONTROLLER_TYPE = 'xbox' #(ps3|ps4|xbox|pigpio_rc|nimbus|wiiu|F710|rc3|MM1|custom) custom will run the my_joystick.py controller written by the `donkey createjs` command
CONTROLLER_TYPE = 'xbox' #(ps3|ps4|xbox|nimbus|wiiu|F710|rc3|MM1|custom) custom will run the my_joystick.py controller written by the `donkey createjs` command
USE_NETWORKED_JS = False #should we listen for remote joystick control over the network?
NETWORK_JS_SERVER_IP = None #when listening for network joystick control, which ip is serving this information
JOYSTICK_DEADZONE = 0.01 # when non zero, this is the smallest throttle before recording triggered.
Expand All @@ -190,20 +198,6 @@
#SOMBRERO
HAVE_SOMBRERO = False #set to true when using the sombrero hat from the Donkeycar store. This will enable pwm on the hat.

#PIGPIO RC control
STEERING_RC_GPIO = 26
THROTTLE_RC_GPIO = 20
DATA_WIPER_RC_GPIO = 19
PIGPIO_STEERING_MID = 1500 # Adjust this value if your car cannot run in a straight line
PIGPIO_MAX_FORWARD = 2000 # Max throttle to go fowrward. The bigger the faster
PIGPIO_STOPPED_PWM = 1500
PIGPIO_MAX_REVERSE = 1000 # Max throttle to go reverse. The smaller the faster
PIGPIO_SHOW_STEERING_VALUE = False
PIGPIO_INVERT = False
PIGPIO_JITTER = 0.025 # threshold below which no signal is reported



#ROBOHAT MM1
MM1_STEERING_MID = 1500 # Adjust this value if your car cannot run in a straight line
MM1_MAX_FORWARD = 2000 # Max throttle to go fowrward. The bigger the faster
Expand Down
Loading

0 comments on commit 20bf83c

Please sign in to comment.