def calc_fonts(): """ Iterate through all the Latin "1 & 5" fonts, and use ANSI escape sequences to see how many rows/columns the EV3 LCD console can accommodate for each font """ console = Console() files = [ f for f in listdir("/usr/share/consolefonts/") if f.startswith("Lat15") and f.endswith(".psf.gz") ] files.sort() for font in files: console.set_font(font, True) # position cursor at 50, 50, and ask the console to report its actual cursor position console.text_at("\x1b[6n", 50, 50, False) console.text_at(font, 1, 1, False, True) console.clear_to_eol() # now, read the console response of the actual cursor position, in the form of esc[rr;ccR # requires pressing the center button on the EV3 for each read dims = '' while True: ch = stdin.read(1) if ch == '\x1b' or ch == '[' or ch == '\r' or ch == '\n': continue if ch == 'R': break dims += str(ch) (rows, cols) = dims.split(";") print("({}, {}, \"{}\"),".format(rows, cols, font), file=stderr) sleep(.5)
def show_fonts(): """ Iterate over the known Latin "1 & 5" fonts and display each on the EV3 LCD console. Note: `Terminus` fonts are "thinner"; `TerminusBold` and `VGA` offer more contrast on the LCD console and are thus more readable; the `TomThumb` font is waaaaay too small to read! """ # Create a list of tuples with calulated rows, columns, font filename fonts = [ (4, 11, "Lat15-Terminus32x16.psf.gz"), (4, 11, "Lat15-TerminusBold32x16.psf.gz"), (4, 11, "Lat15-VGA28x16.psf.gz"), (4, 11, "Lat15-VGA32x16.psf.gz"), (4, 12, "Lat15-Terminus28x14.psf.gz"), (4, 12, "Lat15-TerminusBold28x14.psf.gz"), (5, 14, "Lat15-Terminus24x12.psf.gz"), (5, 14, "Lat15-TerminusBold24x12.psf.gz"), (5, 16, "Lat15-Terminus22x11.psf.gz"), (5, 16, "Lat15-TerminusBold22x11.psf.gz"), (6, 17, "Lat15-Terminus20x10.psf.gz"), (6, 17, "Lat15-TerminusBold20x10.psf.gz"), (7, 22, "Lat15-Fixed18.psf.gz"), (8, 22, "Lat15-Fixed15.psf.gz"), (8, 22, "Lat15-Fixed16.psf.gz"), (8, 22, "Lat15-Terminus16.psf.gz"), (8, 22, "Lat15-TerminusBold16.psf.gz"), (8, 22, "Lat15-TerminusBoldVGA16.psf.gz"), (8, 22, "Lat15-VGA16.psf.gz"), (9, 22, "Lat15-Fixed13.psf.gz"), (9, 22, "Lat15-Fixed14.psf.gz"), (9, 22, "Lat15-Terminus14.psf.gz"), (9, 22, "Lat15-TerminusBold14.psf.gz"), (9, 22, "Lat15-TerminusBoldVGA14.psf.gz"), (9, 22, "Lat15-VGA14.psf.gz"), (10, 29, "Lat15-Terminus12x6.psf.gz"), (16, 22, "Lat15-VGA8.psf.gz"), (21, 44, "Lat15-TomThumb4x6.psf.gz") ] # Paint the screen full of numbers that represent the column number, reversing the even rows console = Console() for rows, cols, font in fonts: print(rows, cols, font, file=stderr) console.set_font(font, True) for row in range(1, rows + 1): for col in range(1, cols + 1): console.text_at("{}".format(col % 10), col, row, False, (row % 2 == 0)) console.text_at(font.split(".")[0], 1, 1, False, True) console.clear_to_eol() sleep(.5)
def show_fonts(): """ Iterate through all the Latin "1 & 5" fonts, and see how many rows/columns the EV3 LCD console can accommodate for each font. Note: ``Terminus`` fonts are "thinner"; ``TerminusBold`` and ``VGA`` offer more contrast on the LCD console and are thus more readable; the ``TomThumb`` font is waaaaay too small to read! """ console = Console() files = [ f for f in listdir("/usr/share/consolefonts/") if f.startswith("Lat15") and f.endswith(".psf.gz") ] files.sort() fonts = [] for font in files: console.set_font(font, True) console.text_at(font, 1, 1, False, True) console.clear_to_eol() console.text_at("{}, {}".format(console.columns, console.rows), column=2, row=4, reset_console=False, inverse=False) print("{}, {}, \"{}\"".format(console.columns, console.rows, font), file=stderr) fonts.append((console.columns, console.rows, font)) fonts.sort(key=lambda f: (f[0], f[1], f[2])) # Paint the screen full of numbers that represent the column number, reversing the even rows for cols, rows, font in fonts: print(cols, rows, font, file=stderr) console.set_font(font, True) for row in range(1, rows + 1): for col in range(1, cols + 1): console.text_at("{}".format(col % 10), col, row, False, (row % 2 == 0)) console.text_at(font.split(".")[0], 1, 1, False, True) console.clear_to_eol()
def write_to_console(self, msg:str, column:int, row:int, reset_console=True, inverse=False, alignment='L', font_size='M'): """Write msg to console at column, and row reset_console clears console first inverse reverses out text alignment: 'L', 'C', or 'R' font_size: 'S', 'M', 'L' Small: 8 rows, 22 columns Medium: 6 rows, 17 columns Large: 4 rows, 12 columns """ console = Console() if font_size == 'S': console.set_font('Lat15-TerminusBoldVGA16.psf.gz', True) elif font_size == 'M': console.set_font('Lat15-Terminus20x10.psf.gz', True) else: console.set_font('Lat15-Terminus32x16.psf.gz', True) console.text_at(msg, column, row, reset_console=reset_console, inverse=inverse, alignment=alignment)
# import Sensor modules and the ev3 ports used for it from ShivaColor import ShivaColor from ev3dev2.sensor import INPUT_1, INPUT_2, INPUT_4 from ev3dev2.sensor.lego import GyroSensor, TouchSensor from ev3dev2.sensor import INPUT_3 from ShivaGyro import ShivaGyro # Creates sound and button objects from ev3dev2.sound import Sound from ev3dev2.button import Button # Sets the font size for robot lcd console = Console() console.set_font('Lat15-VGA16.psf.gz') # Port assignments MEDIUM_MOTOR_LEFT = OUTPUT_A MEDIUM_MOTOR_RIGHT = OUTPUT_D LARGE_MOTOR_LEFT_PORT = OUTPUT_B LARGE_MOTOR_RIGHT_PORT = OUTPUT_C COLORSENSOR_RIGHT = INPUT_1 COLORSENSOR_LEFT = INPUT_3 TOUCHSENSOR_PORT = INPUT_4 GYROSENSOR_PORT = INPUT_2 # Checks every port on the robot to see if its connected properly healthy = False
class MindstormsGadget(AlexaGadget): def __init__(self): super().__init__(gadget_config_path='./auth.ini') # order queue self.queue = Queue() self.button = Button() self.leds = Leds() self.sound = Sound() self.console = Console() self.console.set_font("Lat15-TerminusBold16.psf.gz", True) self.dispense_motor = LargeMotor(OUTPUT_A) self.pump_motor = LargeMotor(OUTPUT_B) self.touch_sensor = TouchSensor(INPUT_1) # Start threads threading.Thread(target=self._handle_queue, daemon=True).start() threading.Thread(target=self._test, daemon=True).start() def on_connected(self, device_addr): self.leds.animate_rainbow(duration=3, block=False) self.sound.play_song((('C4', 'e3'), ('C5', 'e3'))) def on_disconnected(self, device_addr): self.leds.animate_police_lights('RED', 'ORANGE', duration=3, block=False) self.leds.set_color("LEFT", "BLACK") self.leds.set_color("RIGHT", "BLACK") self.sound.play_song((('C5', 'e3'), ('C4', 'e3'))) def _test(self): while 1: self.button.wait_for_pressed('up') order = { 'name': 'Test', 'tea': 'Jasmine', 'sugar': 100, 'ice': 100 } self.queue.put(order) sleep(1) def _handle_queue(self): while 1: if self.queue.empty(): continue order = self.queue.get() self._make(name=order['name'], tea=order['tea'], sugar=order['sugar'], ice=order['ice']) def _send_event(self, name, payload): self.send_custom_event('Custom.Mindstorms.Gadget', name, payload) def _affirm_receive(self): self.leds.animate_flash('GREEN', sleeptime=0.25, duration=0.5, block=False) self.sound.play_song((('C3', 'e3'), ('C3', 'e3'))) def on_custom_mindstorms_gadget_control(self, directive): try: payload = json.loads(directive.payload.decode("utf-8")) print("Control payload: {}".format(payload), file=sys.stderr) control_type = payload["type"] # regular voice commands if control_type == "automatic": self._affirm_receive() order = { "name": payload["name"] or "Anonymous", "tea": payload["tea"] or "Jasmine Milk Tea", "sugar": payload["sugar"] or 100, "ice": payload["ice"] or 100, } self.queue.put(order) # series of voice commands elif control_type == "manual": # Expected params: [command] control_command = payload["command"] if control_command == "dispense": self._affirm_receive() if payload['num']: self._dispense(payload['num']) else: self._dispense() elif control_command == "pour": self._affirm_receive() if payload['num']: self._pour(payload['num']) else: self._pour() except KeyError: print("Missing expected parameters: {}".format(directive), file=sys.stderr) def _make(self, name=None, tea="Jasmine Milk Tea", sugar=100, ice=100): if not self.touch_sensor.is_pressed: # cup is not in place self._send_event('CUP', None) self.touch_sensor.wait_for_pressed() sleep(3) # cup enter delay # mid_col = console.columns // 2 # mid_row = console.rows // 2 # mid_col = 1 # mid_row = 1 # alignment = "L" process = self.sound.play_file('mega.wav', 100, Sound.PLAY_NO_WAIT_FOR_COMPLETE) # dispense boba self._dispense() # dispense liquid self._pour(tea=tea) # self.console.text_at( # s, column=mid_col, row=mid_row, alignment=alignment, reset_console=True # ) # notify alexa that drink is finished payload = { "name": name, "tea": tea, "sugar": sugar, "ice": ice, } self._send_event("DONE", payload) process.kill() # kill song self.sound.play_song((('C4', 'q'), ('C4', 'q'), ('C4', 'q')), delay=0.1) self.touch_sensor.wait_for_released() # dispense liquid def _pour(self, time_in_s=10, tea="Jasmine Milk Tea"): # send event to alexa payload = {"time_in_s": time_in_s, "tea": tea} self._send_event("POUR", payload) self.pump_motor.run_forever(speed_sp=1000) sleep(time_in_s) self.pump_motor.stop() # dispense boba def _dispense(self, cycles=10): # send event to alexa payload = {"cycles": cycles} self._send_event("DISPENSE", payload) # ensure the dispenser resets to the correct position everytime if cycles % 2: cycles += 1 # agitate the boba to make it fall for i in range(cycles): deg = 45 if i % 2 else -45 self.dispense_motor.on_for_degrees(SpeedPercent(75), deg) sleep(0.5)
def display_menu(self, start_page=0, before_run_function=None, after_run_function=None, skip_to_next_page=True): """ Console Menu that accepts choices and corresponding functions to call. The user must press the same button twice: once to see their choice highlited, a second time to confirm and run the function. The EV3 LEDs show each state change: Green = Ready for button, Amber = Ready for second button, Red = Running Parameters: - `choices` a dictionary of tuples "button-name": ("function-name", function-to-call) NOTE: Don't call functions with parentheses, unless preceded by lambda: to defer the call - `before_run_function` when not None, call this function before each function run, passed with function-name - `after_run_function` when not None, call this function after each function run, passed with function-name """ self.current_page = start_page console = Console() leds = Leds() button = Button() leds.all_off() leds.set_color("LEFT", "GREEN") leds.set_color("RIGHT", "GREEN") menu_positions = self.get_menu_positions(console) last = None # the last choice--initialize to None self.menu_tone() self.debug("Starting Menu") while True: # display the menu of choices, but show the last choice in inverse console.reset_console() self.debug("Reset the display screen") console.set_font('Lat15-TerminusBold24x12.psf.gz', True) # store the currently selected menu page menu_page = self.menu_pages[self.current_page] # store the currently selected menu items menu_options_on_page = menu_page.items() for btn, (name, _) in menu_options_on_page: align, col, row = menu_positions[btn] console.text_at(name, col, row, inverse=(btn == last), alignment=align) self.debug("Waiting for button press...") pressed = self.wait_for_button_press(button) self.debug("Registered button press: {}".format(pressed)) # get the choice for the button pressed if pressed in menu_page: if last == pressed: # was same button pressed? console.reset_console() leds.set_color("LEFT", "RED") leds.set_color("RIGHT", "RED") # call the user's subroutine to run the function, but catch any errors try: name, run_function = menu_page[pressed] if before_run_function is not None: self.debug('Running before function') before_run_function(name) self.press_tone() type_of_run_function = type(run_function) self.debug("Type of run_function: {}".format(type_of_run_function)) if isinstance(run_function, str): self.debug("Running {}".format(run_function)) if run_function == 'next': self.debug("About to call next") self.next() elif run_function =='back': self.debug("About to call back") self.back() elif callable(run_function): run_function() except Exception as e: print("**** Exception when running") raise(e) finally: if after_run_function is not None: after_run_function(name) last = None leds.set_color("LEFT", "GREEN") leds.set_color("RIGHT", "GREEN") else: # different button pressed last = pressed leds.set_color("LEFT", "AMBER") leds.set_color("RIGHT", "AMBER")
class Robot: def __init__(self): self.sound = Sound() self.direction_motor = MediumMotor(OUTPUT_D) self.swing_motorL = LargeMotor(OUTPUT_A) self.swing_motorC = LargeMotor(OUTPUT_B) self.swing_motorR = LargeMotor(OUTPUT_C) self.swing_motors = [ self.swing_motorL, self.swing_motorC, self.swing_motorR ] self.touch_sensor = TouchSensor(INPUT_1) self.console = Console(DEFAULT_FONT) self.buttons = Button() self.beeps_enabled = True def beep(self, frequency=700, wait_for_comeplete=False): if not self.beeps_enabled: return play_type = Sound.PLAY_WAIT_FOR_COMPLETE if wait_for_comeplete else Sound.PLAY_NO_WAIT_FOR_COMPLETE self.sound.beep("-f %i" % frequency, play_type=play_type) def calibrate_dir(self): ''' Calibrate direction motor ''' # Run motor with 25% power, and wait until it stops running self.direction_motor.on(SpeedPercent(-10), block=False) # while (not self.direction_motor.STATE_OVERLOADED): # print(self.direction_motor.duty_cycle) self.direction_motor.wait_until(self.direction_motor.STATE_OVERLOADED) self.direction_motor.stop_action = Motor.STOP_ACTION_COAST self.direction_motor.stop() time.sleep(.5) # Reset to straight # self.direction_motor.on_for_seconds(SpeedPercent(10), .835, brake=True, block=True) self.direction_motor.on_for_degrees(SpeedPercent(10), 127, brake=True, block=True) self.direction_motor.reset() print("Motor reset, position: " + str(self.direction_motor.position)) time.sleep(.5) def calibrate_swing(self): for m in self.swing_motors: m.stop_action = m.STOP_ACTION_HOLD m.on(SpeedPercent(6)) self.swing_motorC.wait_until(self.swing_motorC.STATE_OVERLOADED, 2000) for m in self.swing_motors: m.stop_action = m.STOP_ACTION_HOLD m.on_for_degrees(SpeedPercent(5), -15, brake=True, block=False) self.swing_motorC.wait_while('running') for m in self.swing_motors: m.reset() m.stop_action = m.STOP_ACTION_HOLD m.stop() print("Ready Angle: %i" % self.swing_motorC.position) def ready_swing(self, angle: int): right_angle = -(angle / 3) # adjust motors to target angle for m in self.swing_motors: m.stop_action = Motor.STOP_ACTION_HOLD m.on_for_degrees(SpeedPercent(2), right_angle, brake=True, block=False) self.swing_motorC.wait_while('running') for m in self.swing_motors: m.stop_action = Motor.STOP_ACTION_HOLD m.stop() print("Swing Angle: %i" % self.swing_motorC.position) def set_direction(self, direction): print("Setting direction to: " + str(direction)) #direction = self.__aim_correction(direction) self.direction_motor.on_for_degrees(SpeedPercent(10), round(direction * 3)) print("Direction set to: " + str(self.direction_motor.position)) # # def __aim_correction(self, direction): # x = direction # y = -0.00000000429085685725*x**6 + 0.00000004144345630728*x**5 + 0.00001219331494759860*x**4 + 0.00020702946527870400*x**3 + 0.00716486965517779000*x**2 + 1.29675836037884000000*x + 0.27064829453014400000 # # return y def shoot(self, power): print("SHOOT, power: %i" % power) for m in self.swing_motors: m.duty_cycle_sp = -power for m in self.swing_motors: m.run_direct() time.sleep(.5) self.swing_motorC.wait_until_not_moving() for m in self.swing_motors: m.reset() def wait_for_button(self): self.touch_sensor.wait_for_bump() def __set_display(self, str): self.console.set_font(font=LARGER_FONT, reset_console=True) self.console.text_at(str, column=1, row=1, reset_console=True) def wait_for_power_select(self, power=0, angle=0, steps=1): self.__set_display("Pow: %i\nAngle: %i" % (power, angle)) def left(): power -= steps if power < 0: power = 0 self.__set_display("Pow: %i\nAngle: %i" % (power, angle)) self.buttons.wait_for_released(buttons=['left'], timeout_ms=150) def right(): power += steps if power > 100: power = 100 self.__set_display("Pow: %i\nAngle: %i" % (power, angle)) self.buttons.wait_for_released(buttons=['right'], timeout_ms=150) def up(): angle += steps if angle > 110: angle = 110 self.__set_display("Pow: %i\nAngle: %i" % (power, angle)) self.buttons.wait_for_released(buttons=['up'], timeout_ms=150) def down(): angle -= steps if angle < 0: angle = 0 self.__set_display("Pow: %i\nAngle: %i" % (power, angle)) self.buttons.wait_for_released(buttons=['down'], timeout_ms=150) while not self.touch_sensor.is_pressed: if self.buttons.left: left() elif self.buttons.right: right() elif self.buttons.up: up() elif self.buttons.down: down() self.console.set_font(font=DEFAULT_FONT, reset_console=True) return (power, angle) def select_connection_mode(self): self.__set_display("Enable Connection\nLeft: True - Right: False") enabled = True while not self.touch_sensor.is_pressed: if self.buttons.left: enabled = True self.buttons.wait_for_released(buttons=['left']) break elif self.buttons.right: enabled = False self.buttons.wait_for_released(buttons=['right']) break self.console.set_font(font=DEFAULT_FONT, reset_console=True) return enabled def print(self, string): print(string)
#!/usr/bin/env python3 import argparse import requests from time import sleep from ev3dev2.motor import LargeMotor, OUTPUT_A, OUTPUT_B, SpeedPercent from ev3dev2.sound import Sound from ev3dev2.console import Console from agt import AlexaGadget URL = "http://35.230.20.197:5000" or "http://bobafetch.me:5000" console = Console() console.set_font("Lat15-TerminusBold16.psf.gz", True) # mid_col = console.columns // 2 # mid_row = console.rows // 2 mid_col = 1 mid_row = 1 alignment = "L" def main(): console.text_at( "Mindstorms is running", column=mid_col, row=mid_row, alignment=alignment, reset_console=True,