slacksnap-motor-control/main.py

191 lines
4.9 KiB
Python

import os
import logging
logging.basicConfig(
format='[%(asctime)s] %(levelname)s %(module)s/%(funcName)s - %(message)s',
level=logging.DEBUG if os.environ.get('DEBUG') else logging.INFO)
logging.info('')
logging.info('BOOT UP')
import asyncio
import aiomqtt
import serial
import glob
import dyn4
ENCODER_PPR = 65536
ONE_MOTOR = os.environ.get('ONE_MOTOR', False)
dmm1 = None
dmm2 = None
async def send_mqtt(topic, message):
try:
async with aiomqtt.Client('localhost') as client:
await client.publish(topic, payload=message.encode())
except BaseException as e:
logging.error('Problem sending MQTT topic %s, message %s: %s - %s', topic, message, e.__class__.__name__, e)
await asyncio.sleep(1)
return False
def set_motors(rpm):
# we want these to happen no matter what so motors stay in sync
logging.debug('Setting motor RPMs to %s', rpm)
if dmm1:
try:
dmm1.set_speed(rpm)
except BaseException as e:
logging.error('Problem setting Motor1 rpm %s: %s - %s', rpm, e.__class__.__name__, e)
if dmm2:
try:
dmm2.set_speed(rpm)
except BaseException as e:
logging.error('Problem setting Motor2 rpm %s: %s - %s', rpm, e.__class__.__name__, e)
async def process_mqtt(message):
try:
text = message.payload.decode()
except UnicodeDecodeError:
return
topic = message.topic.value
logging.debug('MQTT topic: %s, message: %s', topic, text)
if topic == 'motion/set_rpm':
rpm = int(float(text))
set_motors(rpm)
async def monitor_mqtt():
await asyncio.sleep(1)
# from https://sbtinstruments.github.io/aiomqtt/reconnection.html
# modified to make new client since their code didn't work
# https://github.com/sbtinstruments/aiomqtt/issues/269
while True:
try:
async with aiomqtt.Client('localhost') as client:
await client.subscribe('motion/#')
async for message in client.messages:
await process_mqtt(message)
except aiomqtt.MqttError:
logging.info('MQTT connection lost, reconnecting in 1 second...')
await asyncio.sleep(1)
async def init_motor(path, name):
try:
dmm = dyn4.DMMDrive(path, 0)
except BaseException as e:
logging.error('Problem opening %s port %s: %s - %s', name, path, e.__class__.__name__, e)
await send_mqtt('server/motor_status', name + ' disconnected')
await asyncio.sleep(1)
return False
logging.info('Port %s connected.', path)
await send_mqtt('server/motor_status', name + ' connected')
dmm.set_speed(0)
return dmm
async def read_motor(dmm, name):
try:
return dmm.read_AbsPos32()
except BaseException as e:
# any problems, kill both motors
# TODO: add tripping e-stop
set_motors(0)
logging.error('Problem reading %s: %s - %s', name, e.__class__.__name__, e)
await send_mqtt('server/motor_status', name + ' disconnected')
dmm = False
return False
async def check_sync(rev1, rev2):
global dmm1, dmm2
difference = rev1 - rev2
if abs(difference) > 1.0:
# out of sync, kill both motors
# TODO: add tripping e-stop
set_motors(0)
dmm1 = False
dmm2 = False
logging.error('MOTOR READINGS OUT OF SYNC! Motor1: %s, Motor2: %s, difference: %s', rev1, rev2, difference)
await send_mqtt('server/motor_status', 'Motor desync')
async def monitor_dyn4():
global dmm1, dmm2
while True:
await asyncio.sleep(0.1)
if not dmm1:
dmm1 = await init_motor('/dev/ttyUSB0', 'Motor1')
continue
if not dmm2 and not ONE_MOTOR:
dmm2 = await init_motor('/dev/ttyUSB1', 'Motor2')
continue
pos1 = await read_motor(dmm1, 'Motor1')
if pos1 is False:
dmm1 = False
continue
rev1 = pos1 / ENCODER_PPR
if ONE_MOTOR:
revolutions = rev1
else:
pos2 = await read_motor(dmm2, 'Motor2')
if pos2 is False:
dmm2 = False
continue
rev2 = pos2 / ENCODER_PPR
revolutions = (rev1 + rev2) / 2.0
await check_sync(rev1, rev2)
topic = 'server/position'
message = str(round(revolutions, 5))
await send_mqtt(topic, message)
def task_died(future):
if os.environ.get('SHELL'):
logging.error('Motion control task died!')
else:
logging.error('Motion control task died! Waiting 60s and exiting...')
time.sleep(60)
exit()
def main():
loop = asyncio.get_event_loop()
a = loop.create_task(monitor_dyn4()).add_done_callback(task_died)
b = loop.create_task(monitor_mqtt()).add_done_callback(task_died)
loop.run_forever()
if __name__ == '__main__':
main()