<gh_stars>1-10
import asyncio
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
from evdev import InputDevice, list_devices, categorize, ecodes
import json
import logging
import os
import RPi.GPIO as GPIO

ABS_RIGHT = 255
ABS_UP = 0
ABS_DOWN = 0
ABS_LEFT = 0
ABS_X = 0
ABS_Y = 1
KEY_JOYSTICK = 'BTN_JOYSTICK'
KEY_BTNA = 'BTN_A'
KEY_THUMB = 'BTN_THUMB'
KEY_TL2 = 'BTN_TL2'
MOVE_RIGHT = 'right'
MOVE_LEFT = 'left'
MOVE_FORWARD = 'forward'
DOWN = 1
UP = 0
JOYSTICKNAMES = ['Microntek              USB Joystick          ',
    'Sony PLAYSTATION(R)3 Controller']
JOYSTICKSBYINPUT = ['red','blue','white','black']
ENDPOINT = 'a24t36khlmixhw-ats.iot.us-east-1.amazonaws.com'
ROOT = '/home/pi/root/AmazonRootCA1.pem'
CERT_LOCATION = '/home/pi/certs/'
PRIVATE = '-private.pem.key'
CERTIFICATE = '-certificate.pem.crt'
LIGHTS = [{'red':{'pin':9, 'on': False}, 'yellow':{'pin':10, 'on':False},
    'green':{'pin':11, 'on': False}},{'red':{'pin':20, 'on': False},
    'yellow':{'pin':21, 'on':False}, 'green':{'pin':16, 'on': False}}]
PINS = [9, 10, 11, 21, 20 ,16]
RED = 'red'
YELLOW = 'yellow'
GREEN = 'green'
READY_FOR_MOVES = 1
MOVE_LOCKED = 2
MOVE_BAD = 3
MOVE_GOOD = 4
ALL_OFF = 5
LOG_FILENAME = '/home/pi/joystick.log'

joysticks = []

async def handle_event(device, myMQTTClient):
    async for event in device.async_read_loop():
        for num, joystick in enumerate(joysticks):
            if joystick['device']==device:
                jsindex = num
        categorized = categorize(event)
        if event.type == ecodes.EV_KEY:
            logging.debug(f'button push {joysticks[jsindex]["color"]} {joysticks[jsindex]["path"]}: {categorized.keycode}, {categorized.keystate}')
            logging.info(f'move {joysticks[jsindex]["move"]} locked {joysticks[jsindex]["movelocked"]}')
            if (categorized.keycode[0] == KEY_JOYSTICK or categorized.keycode[0]
                == KEY_BTNA) and categorized.keystate == 1 and not (
                joysticks[jsindex]['move'] == '') and not (
                joysticks[jsindex]['movelocked'] == True):
                #submit move if there is one
                logging.debug(f'move submitted for {joysticks[jsindex]["color"]}, {joysticks[jsindex]["device"]}:{joysticks[jsindex]["move"]}')
                joysticks[jsindex]['movelocked']=True
                message = {}
                message['joystick']=joysticks[jsindex]['color']
                message['move']=joysticks[jsindex]['move']
                jsonmsg = json.dumps(message)
                topic = joysticks[jsindex]['movetopic']
                myMQTTClient.publish(topic, jsonmsg, 0)
                logging.info(f'posted move {jsonmsg}')
                await setLightStatus(jsindex, MOVE_LOCKED)
            elif (categorized.keycode==KEY_THUMB or categorized.keycode==KEY_TL2
                ) and categorized.keystate==1:
                #for debugging, clear move
                joysticks[jsindex]['move'] = ''
                joysticks[jsindex]['movelocked']=False
                await setLightStatus(jsindex, READY_FOR_MOVES)
                logging.info(f'{joysticks[jsindex]["color"]} {joysticks[jsindex]["path"]} unlocked')
                logging.info(f'{joysticks}')
        elif event.type == ecodes.EV_ABS and joysticks[jsindex]['movelocked']==False and (
            event.value == ABS_LEFT or event.value==ABS_RIGHT):
            logging.debug(f'joystick move {joysticks[jsindex]["move"]} {joysticks[jsindex]["path"]} value: {event.value} {event}')
            if event.code == ABS_X:
                if event.value == ABS_RIGHT:
                    joysticks[jsindex]['move'] = MOVE_RIGHT
                    logging.info(f'{joysticks[jsindex]["color"]} {joysticks[jsindex]["path"]} right')
                elif event.value == ABS_LEFT:
                    joysticks[jsindex]['move'] = MOVE_LEFT
                    logging.info(f'{joysticks[jsindex]["color"]} {joysticks[jsindex]["path"]} left')
            elif event.code == ABS_Y:
                if event.value == ABS_UP:
                    joysticks[jsindex]['move'] = MOVE_FORWARD
                    logging.info(f'{joysticks[jsindex]["color"]} {joysticks[jsindex]["path"]} forward')

async def setLightStatus(player, state):
    GPIO.output(joysticks[player]['lights'][RED]['pin'], False)
    GPIO.output(joysticks[player]['lights'][YELLOW]['pin'], False)
    GPIO.output(joysticks[player]['lights'][GREEN]['pin'], False)
    if state == READY_FOR_MOVES:
        GPIO.output(joysticks[player]['lights'][GREEN]['pin'], True)
    elif state == MOVE_LOCKED:
        GPIO.output(joysticks[player]['lights'][YELLOW]['pin'], True)
    elif state == MOVE_GOOD:
        GPIO.output(joysticks[player]['lights'][GREEN]['pin'], True)
    elif state == MOVE_BAD:
        GPIO.output(joysticks[player]['lights'][RED]['pin'], True)
        await asyncio.sleep(3)
    return

def statusCallback(client, userdata, message):
    """Callback for game/status"""
    msg = json.loads(message.payload.decode('utf-8').replace("'",'"'))
    logging.info(msg['status'])
    if msg['status']=='running':
        #show the timer
        logging.info('Timer {}'.format(msg['timer']))
    elif msg['status']=='over':
        #lock all possible moves
        for i, joystick in enumerate(joysticks):
            joystick['movelocked'] = True
            asyncio.run(setLightStatus(i, MOVE_LOCKED))
    elif msg['status']=='ready':
        #unlock all controllers
        for i, joystick in enumerate(joysticks):
            joystick['movelocked'] = False
            asyncio.run(setLightStatus(i, READY_FOR_MOVES))

def joystickCallback(client, userdata, message):
    """Callback for joystick/#/#"""
    msg = json.loads(message.payload.decode('utf-8').replace("'",'"'))
    logging.info(msg)
    try:
        for i, joystick in enumerate(joysticks):
            if joystick['color'] == msg['joystick']:
                logging.info(f'{joystick["color"]} unlocked due to {msg["reason"]}')
                joystick['movelocked']=False
                if msg['reason']=='Invalid Move':
                    asyncio.run(setLightStatus(i, MOVE_BAD))
                joystick['move'] = ''
                asyncio.run(setLightStatus(i, READY_FOR_MOVES))
    except ValueError:
        #unknown message, not matching topic
        logging.info(f'cannot figure out why {msg} was sent on {message.topic}')

def commandCallback(client, userdata, message):
    """Callback for commands on commands/"""
    msg = json.loads(message.payload.decode('utf-8').replace("'",'"'))
    if msg['command']=='reset':
        logging.info('reset message received')

def main(myMQTTClient):
    logging.info('Hit Main')
    myMQTTClient.subscribe("game/status", 1, statusCallback)
    myMQTTClient.subscribe("commands", 1, commandCallback)
    #subscribe only to the topics matching joysticks registered
    for joystick in joysticks:
        logging.debug(f'{joystick}')
        myMQTTClient.subscribe(joystick["statustopic"],1, joystickCallback)
        asyncio.ensure_future(handle_event(joystick['device'], myMQTTClient))
    loop = asyncio.get_event_loop()
    loop.run_forever()
    return 'done'

def setupMQTT():
    """setup AWS IoT Connection - this is going to broadcast all joystick devices
    from one.  However, you can use multiple devices to broadcast these topics
    by using a different set of certs and a different USB port on each device"""
    hostname = os.uname()[1]
    myMQTTClient = AWSIoTMQTTClient(hostname)
    myMQTTClient.configureEndpoint(ENDPOINT, 8883)
    myMQTTClient.configureCredentials(ROOT, CERT_LOCATION + hostname + PRIVATE, CERT_LOCATION + hostname + CERTIFICATE)
    myMQTTClient.configureOfflinePublishQueueing(-1)
    myMQTTClient.configureDrainingFrequency(2)
    myMQTTClient.configureConnectDisconnectTimeout(10)
    myMQTTClient.configureMQTTOperationTimeout(5)
    logging.info('Done configuring MQTT Connection')
    return myMQTTClient

def setupGPIO():
    """Standard GPIO work"""
    GPIO.setmode(GPIO.BCM)
    for pin in PINS:
        GPIO.setup(pin, GPIO.OUT)
        GPIO.output(pin, False)

def setupJoysticks():
    """Search for joysticks"""
    devices = [InputDevice(path) for path in list_devices()]
    for device in devices:
        try:
            index = JOYSTICKNAMES.index(device.name)
            #this is a known joystick, add it to the array
            #index will throw a ValueError if not found
            logging.debug(f'adding {device}')
            joystick = {}
            joystick['device'] = device
            joystick['path'] = device.path
            #force joystick color by USB port
            if device.phys.find('usb-1.3')>0:
                #top right, red
                index = 0
            elif device.phys.find('usb-1.2')>0:
                #bottom right, blue
                index = 1
            elif device.phys.find('usb-1.1.2')>0:
                #top left, white
                index = 2
            elif device.phys.find('usb-1.1.3')>0:
                #bottom left, black
                index = 3
            joystick['color'] = JOYSTICKSBYINPUT[index]
            joystick['move'] = ''
            joystick['movelocked'] = False
            joystick['movetopic'] = 'joystick/move/' + JOYSTICKSBYINPUT[index]
            joystick['statustopic'] = 'joystick/status/' + JOYSTICKSBYINPUT[index]
            joystick['lights'] = LIGHTS[len(joysticks)]
            joysticks.append(joystick)
            logging.info(f'{joysticks}')
            logging.info(f'{joysticks[len(joysticks)-1]} joystick added')
        except ValueError:
                logging.debug(f'not adding {device.path} : {device.name}')
    logging.info(f'Joysticks found: {joysticks}')

if __name__ == '__main__':
    #logging.basicConfig(filename=LOG_FILENAME, level=logging.NOTSET, format='%(asctime)s - %(levelname)s -%(message)s')
    logging.basicConfig(level=logging.NOTSET, format='%(asctime)s - %(levelname)s -%(message)s')
    myMQTTClient = setupMQTT()
    setupGPIO()
    setupJoysticks()
    try:
        logging.info('start of try')
        myMQTTClient.connect()
        logging.info('connected')
        main(myMQTTClient)
    finally:
        myMQTTClient.disconnect()
        GPIO.cleanup()
