From 0a63b244cc5ae03fa2d5e817b7cb68f726d5307f Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 1 Nov 2019 17:19:35 -0700 Subject: [PATCH 01/14] added roboclaw support --- webapp/inputs/HWconfig_template.json | 15 +- webapp/inputs/config.py | 18 +- webapp/inputs/ext_node.py | 30 + webapp/outputs/roboclaw_3.py | 1078 ++++++++++++++++++++++++++ 4 files changed, 1127 insertions(+), 14 deletions(-) create mode 100644 webapp/outputs/roboclaw_3.py diff --git a/webapp/inputs/HWconfig_template.json b/webapp/inputs/HWconfig_template.json index 8c77f71..1c9e51f 100644 --- a/webapp/inputs/HWconfig_template.json +++ b/webapp/inputs/HWconfig_template.json @@ -18,12 +18,21 @@ { "motors":[ { - "driver":"NRF24L01", + "driver":"NRF24L01tx", "address":"5,8", "name":"rfpi0" } ], - "type":"External" + "type":"NRF24L01" + }, + { + "motors":[ + { + "driver":"ROBOCLAW", + "address":"/dev/ttyUSB0" + } + ], + "type":"Roboclaw" } ], "IMU":[ @@ -53,4 +62,4 @@ "address":"/dev/ttyACM0" } ] -} \ No newline at end of file +} diff --git a/webapp/inputs/config.py b/webapp/inputs/config.py index bbcafdc..8ebdd32 100644 --- a/webapp/inputs/config.py +++ b/webapp/inputs/config.py @@ -6,10 +6,10 @@ pass # addressed by has_gpio_pins variable from gps_serial import GPSserial from .check_platform import ON_RASPI, ON_JETSON +from .ext_node import ROBOCLAW if ON_RASPI: - from drivetrain.drivetrain import Tank, Automotive, External - from drivetrain.motor import Solenoid, BiMotor, PhasedMotor, NRF24L01, USB + from drivetrain import Tank, Automotive, Solenoid, BiMotor, PhasedMotor, NRF24L01tx from adafruit_lsm9ds1 import LSM9DS1_I2C from circuitpython_mpu6050 import MPU6050 @@ -67,7 +67,7 @@ if 'Drivetrains' in SYSTEM_CONF['Check-Hardware']: # handle drivetrain for d in SYSTEM_CONF['Drivetrains']: - if d['type'] in ('Tank', 'Automotive', 'External'): + if d['type'] in ('Tank', 'Automotive', 'NRF24L01', 'Roboclaw'): # instantiate motors motors = [] for m in d['motors']: @@ -83,18 +83,14 @@ motors.append(BiMotor(pins)) elif m['driver'].startswith('PhasedMotor') and len(pins) == 2 and has_gpio_pins: motors.append(PhasedMotor(pins)) - elif m['driver'].startswith('NRF24L01') and has_gpio_pins: - motors.append( - NRF24L01(SPI_BUS, pins, bytes(m['name'].encode('utf-8')))) - elif m['driver'].startswith('USB'): - motors.append(USB(m['address'])) + elif m['driver'].startswith('ROBOCLAW'): + d_train.append(ROBOCLAW(m['address'])) + elif m['driver'].startswith('NRF24L01tx') and has_gpio_pins: + d_train.append(NRF24L01tx(SPI_BUS, pins, bytes(m['name'].encode('utf-8')))) if d['type'].startswith('Tank') and has_gpio_pins: d_train.append(Tank(motors, int(d['max speed']))) elif d['type'].startswith('Automotive') and has_gpio_pins: d_train.append(Automotive(motors, int(d['max speed']))) - elif d['type'].startswith('External'): - if motors: - d_train.append(External(motors[0])) if 'IMU' in SYSTEM_CONF['Check-Hardware']: for imu in SYSTEM_CONF['IMU']: diff --git a/webapp/inputs/ext_node.py b/webapp/inputs/ext_node.py index c5e939e..f7a03c0 100644 --- a/webapp/inputs/ext_node.py +++ b/webapp/inputs/ext_node.py @@ -1,6 +1,7 @@ """ A collection of wrapper classes that extend communication with external devices (ie arduino or another raspberry pi) """ +from webapp.outputs.roboclaw_3 import Roboclaw class EXTnode(): """ @@ -29,3 +30,32 @@ def get_all_data(self): if temp: self.heading = float(temp[0]) return self.heading + +class ROBOCLAW: + def __init__(self, address, claw_address=0x80): + self._address = claw_address + self._device = Roboclaw(address, 38400) + + def go(self, cmds): + if len(cmds) < 2: + raise ValueError('Commands list needs to be atleast 2 items long') + cmds[0] = max(-65535, min(65535, cmds[0])) + cmds[1] = max(-65535, min(65535, cmds[1])) + left = cmds[1] + right = cmds[1] + if not cmds[1]: + # if forward/backward axis is null ("turning on a dime" functionality) + # re-apply speed governor to only axis with a non-zero value + right = cmds[0] + left = cmds[0] * -1 + else: + # if forward/backward axis is not null and left/right axis is not null + # apply differential to motors accordingly + offset = (65535 - abs(cmds[0])) / 65535.0 + if cmds[0] > 0: + right *= offset + elif cmds[0] < 0: + left *= offset + # send translated commands to motors + self._device.ForwardBackwardM1(self._address, int(left * 127 / 131070 + 64)) + self._device.ForwardBackwardM2(self._address, int(right * 127 / 131070 + 64)) diff --git a/webapp/outputs/roboclaw_3.py b/webapp/outputs/roboclaw_3.py new file mode 100644 index 0000000..77c03d3 --- /dev/null +++ b/webapp/outputs/roboclaw_3.py @@ -0,0 +1,1078 @@ +"""RoboClaw library ported from original code written by a 7th grader""" +import time +import random +import serial + +# pylint: disable=line-too-long,invalid-name,missing-function-docstring +class Cmd: + """ serial commands to the roboclaw """ + M1FORWARD = 0 + M1BACKWARD = 1 + SETMINMB = 2 + SETMAXMB = 3 + M2FORWARD = 4 + M2BACKWARD = 5 + M17BIT = 6 + M27BIT = 7 + MIXEDFORWARD = 8 + MIXEDBACKWARD = 9 + MIXEDRIGHT = 10 + MIXEDLEFT = 11 + MIXEDFB = 12 + MIXEDLR = 13 + GETM1ENC = 16 + GETM2ENC = 17 + GETM1SPEED = 18 + GETM2SPEED = 19 + RESETENC = 20 + GETVERSION = 21 + SETM1ENCCOUNT = 22 + SETM2ENCCOUNT = 23 + GETMBATT = 24 + GETLBATT = 25 + SETMINLB = 26 + SETMAXLB = 27 + SETM1PID = 28 + SETM2PID = 29 + GETM1ISPEED = 30 + GETM2ISPEED = 31 + M1DUTY = 32 + M2DUTY = 33 + MIXEDDUTY = 34 + M1SPEED = 35 + M2SPEED = 36 + MIXEDSPEED = 37 + M1SPEEDACCEL = 38 + M2SPEEDACCEL = 39 + MIXEDSPEEDACCEL = 40 + M1SPEEDDIST = 41 + M2SPEEDDIST = 42 + MIXEDSPEEDDIST = 43 + M1SPEEDACCELDIST = 44 + M2SPEEDACCELDIST = 45 + MIXEDSPEEDACCELDIST = 46 + GETBUFFERS = 47 + GETPWMS = 48 + GETCURRENTS = 49 + MIXEDSPEED2ACCEL = 50 + MIXEDSPEED2ACCELDIST = 51 + M1DUTYACCEL = 52 + M2DUTYACCEL = 53 + MIXEDDUTYACCEL = 54 + READM1PID = 55 + READM2PID = 56 + SETMAINVOLTAGES = 57 + SETLOGICVOLTAGES = 58 + GETMINMAXMAINVOLTAGES = 59 + GETMINMAXLOGICVOLTAGES = 60 + SETM1POSPID = 61 + SETM2POSPID = 62 + READM1POSPID = 63 + READM2POSPID = 64 + M1SPEEDACCELDECCELPOS = 65 + M2SPEEDACCELDECCELPOS = 66 + MIXEDSPEEDACCELDECCELPOS = 67 + SETM1DEFAULTACCEL = 68 + SETM2DEFAULTACCEL = 69 + SETPINFUNCTIONS = 74 + GETPINFUNCTIONS = 75 + SETDEADBAND = 76 + GETDEADBAND = 77 + RESTOREDEFAULTS = 80 + GETTEMP = 82 + GETTEMP2 = 83 + GETERROR = 90 + GETENCODERMODE = 91 + SETM1ENCODERMODE = 92 + SETM2ENCODERMODE = 93 + WRITENVM = 94 + READNVM = 95 + SETCONFIG = 98 + GETCONFIG = 99 + SETM1MAXCURRENT = 133 + SETM2MAXCURRENT = 134 + GETM1MAXCURRENT = 135 + GETM2MAXCURRENT = 136 + SETPWMMODE = 148 + GETPWMMODE = 149 + READEEPROM = 252 + WRITEEEPROM = 253 + FLAGBOOTLOADER = 255 + +class Roboclaw: + """Roboclaw Interface Class""" + def __init__(self, comport, rate, timeout=0.01, retries=3): + self.comport = comport + self.rate = rate + self.timeout = timeout + self._trystimeout = retries + self._crc = 0 + self._port = None + + # Private Functions + def _crc_clear(self): + self._crc = 0 + return + + def _crc_update(self, data): + self._crc = self._crc ^ (data << 8) + for _ in range(0, 8): # for each bit + if (self._crc & 0x8000) == 0x8000: + self._crc = ((self._crc << 1) ^ 0x1021) + else: + self._crc = self._crc << 1 + + def _sendcommand(self, address, command): + self._crc_clear() + self._crc_update(address) + self._port.write(bytes([address])) + self._crc_update(command) + self._port.write(bytes([command])) + return + + def _readchecksumword(self): + data = self._port.read(2) + if len(data) == 2: + # crc = (ord(data[0])<<8) | ord(data[1]) + crc = (data[0] << 8) | data[1] + return (1, crc) + return (0, 0) + + def _readbyte(self): + data = self._port.read(1) + if data: + val = ord(data) + self._crc_update(val) + return (1, val) + return (0, 0) + + def _readword(self): + val1 = self._readbyte() + if val1[0]: + val2 = self._readbyte() + if val2[0]: + return (1, val1[1] << 8 | val2[1]) + return (0, 0) + + def _readlong(self): + val1 = self._readbyte() + if val1[0]: + val2 = self._readbyte() + if val2[0]: + val3 = self._readbyte() + if val3[0]: + val4 = self._readbyte() + if val4[0]: + return (1, val1[1] << 24 | val2[1] << 16 | val3[1] << 8 | val4[1]) + return (0, 0) + + def _readslong(self): + val = self._readlong() + if val[0]: + if val[1] & 0x80000000: + return (val[0], val[1] - 0x100000000) + return (val[0], val[1]) + return (0, 0) + + def _writebyte(self, val): + self._crc_update(val & 0xFF) + self._port.write(bytes([val])) + + def _writesbyte(self, val): + self._writebyte(val) + + def _writeword(self, val): + self._writebyte((val >> 8) & 0xFF) + self._writebyte(val & 0xFF) + + def _writesword(self, val): + self._writeword(val) + + def _writelong(self, val): + self._writebyte((val >> 24) & 0xFF) + self._writebyte((val >> 16) & 0xFF) + self._writebyte((val >> 8) & 0xFF) + self._writebyte(val & 0xFF) + + def _writeslong(self, val): + self._writelong(val) + + def _read1(self, address, cmd): + trys = self._trystimeout + while 1: + self._port.flushInput() + self._sendcommand(address, cmd) + val1 = self._readbyte() + if val1[0]: + crc = self._readchecksumword() + if crc[0]: + if self._crc & 0xFFFF != crc[1] & 0xFFFF: + return (0, 0) + return (1, val1[1]) + trys -= 1 + if not trys: + break + return (0, 0) + + def _read2(self, address, cmd): + trys = self._trystimeout + while 1: + self._port.flushInput() + self._sendcommand(address, cmd) + val1 = self._readword() + if val1[0]: + crc = self._readchecksumword() + if crc[0]: + if self._crc & 0xFFFF != crc[1] & 0xFFFF: + return (0, 0) + return (1, val1[1]) + trys -= 1 + if not trys: + break + return (0, 0) + + def _read4(self, address, cmd): + trys = self._trystimeout + while 1: + self._port.flushInput() + self._sendcommand(address, cmd) + val1 = self._readlong() + if val1[0]: + crc = self._readchecksumword() + if crc[0]: + if self._crc & 0xFFFF != crc[1] & 0xFFFF: + return (0, 0) + return (1, val1[1]) + trys -= 1 + if not trys: + break + return (0, 0) + + def _read4_1(self, address, cmd): + trys = self._trystimeout + while 1: + self._port.flushInput() + self._sendcommand(address, cmd) + val1 = self._readslong() + if val1[0]: + val2 = self._readbyte() + if val2[0]: + crc = self._readchecksumword() + if crc[0]: + if self._crc & 0xFFFF != crc[1] & 0xFFFF: + return (0, 0) + return (1, val1[1], val2[1]) + trys -= 1 + if not trys: + break + return (0, 0) + + def _read_n(self, address, cmd, args): + trys = self._trystimeout + while 1: + self._port.flushInput() + trys -= 1 + if not trys: + break + failed = False + self._sendcommand(address, cmd) + data = [1, ] + for _ in range(0, args): + val = self._readlong() + if not val[0]: + failed = True + break + data.append(val[1]) + if failed: + continue + crc = self._readchecksumword() + if crc[0]: + if self._crc & 0xFFFF == crc[1] & 0xFFFF: + return data + return (0, 0, 0, 0, 0) + + def _writechecksum(self): + self._writeword(self._crc & 0xFFFF) + val = self._readbyte() + if val: + if val[0]: + return True + return False + + def _write0(self, address, cmd): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write1(self, address, cmd, val): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writebyte(val) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write11(self, address, cmd, val1, val2): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writebyte(val1) + self._writebyte(val2) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write111(self, address, cmd, val1, val2, val3): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writebyte(val1) + self._writebyte(val2) + self._writebyte(val3) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write2(self, address, cmd, val): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writeword(val) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS2(self, address, cmd, val): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writesword(val) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write22(self, address, cmd, val1, val2): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writeword(val1) + self._writeword(val2) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS22(self, address, cmd, val1, val2): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writesword(val1) + self._writeword(val2) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS2S2(self, address, cmd, val1, val2): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writesword(val1) + self._writesword(val2) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS24(self, address, cmd, val1, val2): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writesword(val1) + self._writelong(val2) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS24S24(self, address, cmd, val1, val2, val3, val4): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writesword(val1) + self._writelong(val2) + self._writesword(val3) + self._writelong(val4) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4(self, address, cmd, val): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS4(self, address, cmd, val): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writeslong(val) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write44(self, address, cmd, val1, val2): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writelong(val2) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4S4(self, address, cmd, val1, val2): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writeslong(val2) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS4S4(self, address, cmd, val1, val2): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writeslong(val1) + self._writeslong(val2) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write441(self, address, cmd, val1, val2, val3): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writelong(val2) + self._writebyte(val3) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS441(self, address, cmd, val1, val2, val3): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writeslong(val1) + self._writelong(val2) + self._writebyte(val3) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4S4S4(self, address, cmd, val1, val2, val3): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writeslong(val2) + self._writeslong(val3) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4S441(self, address, cmd, val1, val2, val3, val4): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writeslong(val2) + self._writelong(val3) + self._writebyte(val4) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4444(self, address, cmd, val1, val2, val3, val4): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writelong(val2) + self._writelong(val3) + self._writelong(val4) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4S44S4(self, address, cmd, val1, val2, val3, val4): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writeslong(val2) + self._writelong(val3) + self._writeslong(val4) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write44441(self, address, cmd, val1, val2, val3, val4, val5): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writelong(val2) + self._writelong(val3) + self._writelong(val4) + self._writebyte(val5) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _writeS44S441(self, address, cmd, val1, val2, val3, val4, val5): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writeslong(val1) + self._writelong(val2) + self._writeslong(val3) + self._writelong(val4) + self._writebyte(val5) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4S44S441(self, address, cmd, val1, val2, val3, val4, val5, val6): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writeslong(val2) + self._writelong(val3) + self._writeslong(val4) + self._writelong(val5) + self._writebyte(val6) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4S444S441(self, address, cmd, val1, val2, val3, val4, val5, val6, val7): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writeslong(val2) + self._writelong(val3) + self._writelong(val4) + self._writeslong(val5) + self._writelong(val6) + self._writebyte(val7) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write4444444(self, address, cmd, val1, val2, val3, val4, val5, val6, val7): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writelong(val2) + self._writelong(val3) + self._writelong(val4) + self._writelong(val5) + self._writelong(val6) + self._writelong(val7) + if self._writechecksum(): + return True + trys -= 1 + return False + + def _write444444441(self, address, cmd, val1, val2, val3, val4, val5, val6, val7, val8, val9): + trys = self._trystimeout + while trys: + self._sendcommand(address, cmd) + self._writelong(val1) + self._writelong(val2) + self._writelong(val3) + self._writelong(val4) + self._writelong(val5) + self._writelong(val6) + self._writelong(val7) + self._writelong(val8) + self._writebyte(val9) + if self._writechecksum(): + return True + trys -= 1 + return False + + # User accessible functions + def SendRandomData(self, cnt): + for _ in range(0, cnt): + byte = random.getrandbits(8) + self._port.write(bytes([byte])) + return + + def ForwardM1(self, address, val): + return self._write1(address, Cmd.M1FORWARD, val) + + def BackwardM1(self, address, val): + return self._write1(address, Cmd.M1BACKWARD, val) + + def SetMinVoltageMainBattery(self, address, val): + return self._write1(address, Cmd.SETMINMB, val) + + def SetMaxVoltageMainBattery(self, address, val): + return self._write1(address, Cmd.SETMAXMB, val) + + def ForwardM2(self, address, val): + return self._write1(address, Cmd.M2FORWARD, val) + + def BackwardM2(self, address, val): + return self._write1(address, Cmd.M2BACKWARD, val) + + def ForwardBackwardM1(self, address, val): + return self._write1(address, Cmd.M17BIT, val) + + def ForwardBackwardM2(self, address, val): + return self._write1(address, Cmd.M27BIT, val) + + def ForwardMixed(self, address, val): + return self._write1(address, Cmd.MIXEDFORWARD, val) + + def BackwardMixed(self, address, val): + return self._write1(address, Cmd.MIXEDBACKWARD, val) + + def TurnRightMixed(self, address, val): + return self._write1(address, Cmd.MIXEDRIGHT, val) + + def TurnLeftMixed(self, address, val): + return self._write1(address, Cmd.MIXEDLEFT, val) + + def ForwardBackwardMixed(self, address, val): + return self._write1(address, Cmd.MIXEDFB, val) + + def LeftRightMixed(self, address, val): + return self._write1(address, Cmd.MIXEDLR, val) + + def ReadEncM1(self, address): + return self._read4_1(address, Cmd.GETM1ENC) + + def ReadEncM2(self, address): + return self._read4_1(address, Cmd.GETM2ENC) + + def ReadSpeedM1(self, address): + return self._read4_1(address, Cmd.GETM1SPEED) + + def ReadSpeedM2(self, address): + return self._read4_1(address, Cmd.GETM2SPEED) + + def ResetEncoders(self, address): + return self._write0(address, Cmd.RESETENC) + + # TODO replace str var with "string" + def ReadVersion(self, address): + trys = self._trystimeout + while 1: + self._port.flushInput() + self._sendcommand(address, Cmd.GETVERSION) + string = "" + passed = True + for _ in range(0, 48): + data = self._port.read(1) + if data: + val = ord(data) + self._crc_update(val) + if not val: + break + # string += data[0] + string += chr(data[0]) + else: + passed = False + break + if passed: + crc = self._readchecksumword() + if crc[0]: + if self._crc & 0xFFFF == crc[1] & 0xFFFF: + return (1, string) + else: + time.sleep(0.01) + trys -= 1 + if not trys: + break + return (0, 0) + + def SetEncM1(self, address, cnt): + return self._write4(address, Cmd.SETM1ENCCOUNT, cnt) + + def SetEncM2(self, address, cnt): + return self._write4(address, Cmd.SETM2ENCCOUNT, cnt) + + def ReadMainBatteryVoltage(self, address): + return self._read2(address, Cmd.GETMBATT) + + def ReadLogicBatteryVoltage(self, address,): + return self._read2(address, Cmd.GETLBATT) + + def SetMinVoltageLogicBattery(self, address, val): + return self._write1(address, Cmd.SETMINLB, val) + + def SetMaxVoltageLogicBattery(self, address, val): + return self._write1(address, Cmd.SETMAXLB, val) + + def SetM1VelocityPID(self, address, p, i, d, qpps): + return self._write4444(address, Cmd.SETM1PID, d * 65536, p * 65536, i * 65536, qpps) + + def SetM2VelocityPID(self, address, p, i, d, qpps): + return self._write4444(address, Cmd.SETM2PID, d * 65536, p * 65536, i * 65536, qpps) + + def ReadISpeedM1(self, address): + return self._read4_1(address, Cmd.GETM1ISPEED) + + def ReadISpeedM2(self, address): + return self._read4_1(address, Cmd.GETM2ISPEED) + + def DutyM1(self, address, val): + return self._writeS2(address, Cmd.M1DUTY, val) + + def DutyM2(self, address, val): + return self._writeS2(address, Cmd.M2DUTY, val) + + def DutyM1M2(self, address, m1, m2): + return self._writeS2S2(address, Cmd.MIXEDDUTY, m1, m2) + + def SpeedM1(self, address, val): + return self._writeS4(address, Cmd.M1SPEED, val) + + def SpeedM2(self, address, val): + return self._writeS4(address, Cmd.M2SPEED, val) + + def SpeedM1M2(self, address, m1, m2): + return self._writeS4S4(address, Cmd.MIXEDSPEED, m1, m2) + + def SpeedAccelM1(self, address, accel, speed): + return self._write4S4(address, Cmd.M1SPEEDACCEL, accel, speed) + + def SpeedAccelM2(self, address, accel, speed): + return self._write4S4(address, Cmd.M2SPEEDACCEL, accel, speed) + + def SpeedAccelM1M2(self, address, accel, speed1, speed2): + return self._write4S4S4(address, Cmd.MIXEDSPEEDACCEL, accel, speed1, speed2) + + def SpeedDistanceM1(self, address, speed, distance, buffer): + return self._writeS441(address, Cmd.M1SPEEDDIST, speed, distance, buffer) + + def SpeedDistanceM2(self, address, speed, distance, buffer): + return self._writeS441(address, Cmd.M2SPEEDDIST, speed, distance, buffer) + + def SpeedDistanceM1M2(self, address, speed1, distance1, speed2, distance2, buffer): + return self._writeS44S441(address, Cmd.MIXEDSPEEDDIST, speed1, distance1, speed2, distance2, buffer) + + def SpeedAccelDistanceM1(self, address, accel, speed, distance, buffer): + return self._write4S441(address, Cmd.M1SPEEDACCELDIST, accel, speed, distance, buffer) + + def SpeedAccelDistanceM2(self, address, accel, speed, distance, buffer): + return self._write4S441(address, Cmd.M2SPEEDACCELDIST, accel, speed, distance, buffer) + + def SpeedAccelDistanceM1M2(self, address, accel, speed1, distance1, speed2, distance2, buffer): + return self._write4S44S441(address, Cmd.MIXEDSPEEDACCELDIST, accel, speed1, distance1, speed2, distance2, buffer) + + def ReadBuffers(self, address): + val = self._read2(address, Cmd.GETBUFFERS) + if val[0]: + return (1, val[1] >> 8, val[1] & 0xFF) + return (0, 0, 0) + + def ReadPWMs(self, address): + val = self._read4(address, Cmd.GETPWMS) + if val[0]: + pwm1 = val[1] >> 16 + pwm2 = val[1] & 0xFFFF + if pwm1 & 0x8000: + pwm1 -= 0x10000 + if pwm2 & 0x8000: + pwm2 -= 0x10000 + return (1, pwm1, pwm2) + return (0, 0, 0) + + def ReadCurrents(self, address): + val = self._read4(address, Cmd.GETCURRENTS) + if val[0]: + cur1 = val[1] >> 16 + cur2 = val[1] & 0xFFFF + if cur1 & 0x8000: + cur1 -= 0x10000 + if cur2 & 0x8000: + cur2 -= 0x10000 + return (1, cur1, cur2) + return (0, 0, 0) + + def SpeedAccelM1M2_2(self, address, accel1, speed1, accel2, speed2): + return self._write4S44S4(address, Cmd.MIXEDSPEED2ACCEL, accel1, speed1, accel2, speed2) + + def SpeedAccelDistanceM1M2_2(self, address, accel1, speed1, distance1, accel2, speed2, distance2, buffer): + return self._write4S444S441(address, Cmd.MIXEDSPEED2ACCELDIST, accel1, speed1, distance1, accel2, speed2, distance2, buffer) + + def DutyAccelM1(self, address, accel, duty): + return self._writeS24(address, Cmd.M1DUTYACCEL, duty, accel) + + def DutyAccelM2(self, address, accel, duty): + return self._writeS24(address, Cmd.M2DUTYACCEL, duty, accel) + + def DutyAccelM1M2(self, address, accel1, duty1, accel2, duty2): + return self._writeS24S24(address, Cmd.MIXEDDUTYACCEL, duty1, accel1, duty2, accel2) + + def ReadM1VelocityPID(self, address): + data = self._read_n(address, Cmd.READM1PID, 4) + if data[0]: + data[1] /= 65536.0 + data[2] /= 65536.0 + data[3] /= 65536.0 + return data + return (0, 0, 0, 0, 0) + + def ReadM2VelocityPID(self, address): + data = self._read_n(address, Cmd.READM2PID, 4) + if data[0]: + data[1] /= 65536.0 + data[2] /= 65536.0 + data[3] /= 65536.0 + return data + return (0, 0, 0, 0, 0) + + def SetMainVoltages(self, address, minimum, maximum): + return self._write22(address, Cmd.SETMAINVOLTAGES, minimum, maximum) + + def SetLogicVoltages(self, address, minimum, maximum): + return self._write22(address, Cmd.SETLOGICVOLTAGES, minimum, maximum) + + def ReadMinMaxMainVoltages(self, address): + val = self._read4(address, Cmd.GETMINMAXMAINVOLTAGES) + if val[0]: + minimum = val[1] >> 16 + maximum = val[1] & 0xFFFF + return (1, minimum, maximum) + return (0, 0, 0) + + def ReadMinMaxLogicVoltages(self, address): + val = self._read4(address, Cmd.GETMINMAXLOGICVOLTAGES) + if val[0]: + minimum = val[1] >> 16 + maximum = val[1] & 0xFFFF + return (1, minimum, maximum) + return (0, 0, 0) + + def SetM1PositionPID(self, address, kp, ki, kd, kimax, deadzone, minimum, maximum): + # return self._write4444444(address, Cmd.SETM1POSPID, long(kd * 1024), long(kp * 1024), long(ki * 1024), kimax, deadzone, minimum, maximum) + return self._write4444444(address, Cmd.SETM1POSPID, kd * 1024, kp * 1024, ki * 1024, kimax, deadzone, minimum, maximum) + + def SetM2PositionPID(self, address, kp, ki, kd, kimax, deadzone, miimum, maximum): + # return self._write4444444(address, Cmd.SETM2POSPID, long(kd * 1024), long(kp * 1024), long(ki * 1024), kimax, deadzone, miimum, maximum) + return self._write4444444(address, Cmd.SETM2POSPID, kd * 1024, kp * 1024, ki * 1024, kimax, deadzone, miimum, maximum) + + def ReadM1PositionPID(self, address): + data = self._read_n(address, Cmd.READM1POSPID, 7) + if data[0]: + data[1] /= 1024.0 + data[2] /= 1024.0 + data[3] /= 1024.0 + return data + return (0, 0, 0, 0, 0, 0, 0, 0) + + def ReadM2PositionPID(self, address): + data = self._read_n(address, Cmd.READM2POSPID, 7) + if data[0]: + data[1] /= 1024.0 + data[2] /= 1024.0 + data[3] /= 1024.0 + return data + return (0, 0, 0, 0, 0, 0, 0, 0) + + def SpeedAccelDeccelPositionM1(self, address, accel, speed, deccel, position, buffer): + return self._write44441(address, Cmd.M1SPEEDACCELDECCELPOS, accel, speed, deccel, position, buffer) + + def SpeedAccelDeccelPositionM2(self, address, accel, speed, deccel, position, buffer): + return self._write44441(address, Cmd.M2SPEEDACCELDECCELPOS, accel, speed, deccel, position, buffer) + + def SpeedAccelDeccelPositionM1M2(self, address, accel1, speed1, deccel1, position1, accel2, speed2, deccel2, position2, buffer): + return self._write444444441(address, Cmd.MIXEDSPEEDACCELDECCELPOS, accel1, speed1, deccel1, position1, accel2, speed2, deccel2, position2, buffer) + + def SetM1DefaultAccel(self, address, accel): + return self._write4(address, Cmd.SETM1DEFAULTACCEL, accel) + + def SetM2DefaultAccel(self, address, accel): + return self._write4(address, Cmd.SETM2DEFAULTACCEL, accel) + + def SetPinFunctions(self, address, S3mode, S4mode, S5mode): + return self._write111(address, Cmd.SETPINFUNCTIONS, S3mode, S4mode, S5mode) + + def ReadPinFunctions(self, address): + trys = self._trystimeout + while 1: + self._sendcommand(address, Cmd.GETPINFUNCTIONS) + val1 = self._readbyte() + if val1[0]: + val2 = self._readbyte() + if val1[0]: + val3 = self._readbyte() + if val1[0]: + crc = self._readchecksumword() + if crc[0]: + if self._crc & 0xFFFF != crc[1] & 0xFFFF: + return (0, 0) + return (1, val1[1], val2[1], val3[1]) + trys -= 1 + if not trys: + break + return (0, 0) + + def SetDeadBand(self, address, mimum, maximum): + return self._write11(address, Cmd.SETDEADBAND, mimum, maximum) + + def GetDeadBand(self, address): + val = self._read2(address, Cmd.GETDEADBAND) + if val[0]: + return (1, val[1] >> 8, val[1] & 0xFF) + return (0, 0, 0) + + # Warning(TTL Serial): Baudrate will change if not already set to 38400. Communications will be lost + def RestoreDefaults(self, address): + return self._write0(address, Cmd.RESTOREDEFAULTS) + + def ReadTemp(self, address): + return self._read2(address, Cmd.GETTEMP) + + def ReadTemp2(self, address): + return self._read2(address, Cmd.GETTEMP2) + + def ReadError(self, address): + return self._read4(address, Cmd.GETERROR) + + def ReadEncoderModes(self, address): + val = self._read2(address, Cmd.GETENCODERMODE) + if val[0]: + return (1, val[1] >> 8, val[1] & 0xFF) + return (0, 0, 0) + + def SetM1EncoderMode(self, address, mode): + return self._write1(address, Cmd.SETM1ENCODERMODE, mode) + + def SetM2EncoderMode(self, address, mode): + return self._write1(address, Cmd.SETM2ENCODERMODE, mode) + + # saves active settings to NVM + def WriteNVM(self, address): + return self._write4(address, Cmd.WRITENVM, 0xE22EAB7A) + + # restores settings from NVM + # Warning(TTL Serial): If baudrate changes or the control mode changes communications will be lost + def ReadNVM(self, address): + return self._write0(address, Cmd.READNVM) + + # Warning(TTL Serial): If control mode is changed from packet serial mode when setting config communications will be lost! + # Warning(TTL Serial): If baudrate of packet serial mode is changed communications will be lost! + def SetConfig(self, address, config): + return self._write2(address, Cmd.SETCONFIG, config) + + def GetConfig(self, address): + return self._read2(address, Cmd.GETCONFIG) + + def SetM1MaxCurrent(self, address, maximum): + return self._write44(address, Cmd.SETM1MAXCURRENT, maximum, 0) + + def SetM2MaxCurrent(self, address, maximum): + return self._write44(address, Cmd.SETM2MAXCURRENT, maximum, 0) + + def ReadM1MaxCurrent(self, address): + data = self._read_n(address, Cmd.GETM1MAXCURRENT, 2) + if data[0]: + return (1, data[1]) + return (0, 0) + + def ReadM2MaxCurrent(self, address): + data = self._read_n(address, Cmd.GETM2MAXCURRENT, 2) + if data[0]: + return (1, data[1]) + return (0, 0) + + def SetPWMMode(self, address, mode): + return self._write1(address, Cmd.SETPWMMODE, mode) + + def ReadPWMMode(self, address): + return self._read1(address, Cmd.GETPWMMODE) + + def ReadEeprom(self, address, ee_address): + trys = self._trystimeout + while 1: + self._port.flushInput() + self._sendcommand(address, Cmd.READEEPROM) + self._crc_update(ee_address) + self._port.write(bytes([ee_address])) + val1 = self._readword() + if val1[0]: + crc = self._readchecksumword() + if crc[0]: + if self._crc & 0xFFFF != crc[1] & 0xFFFF: + return (0, 0) + return (1, val1[1]) + trys -= 1 + if not trys: + break + return (0, 0) + + def WriteEeprom(self, address, ee_address, ee_word): + retval = self._write111(address, Cmd.WRITEEEPROM, + ee_address, ee_word >> 8, ee_word & 0xFF) + if retval: + trys = self._trystimeout + while 1: + self._port.flushInput() + val1 = self._readbyte() + if val1[0]: + if val1[1] == 0xaa: + return True + trys -= 1 + if not trys: + break + return False + + def Open(self): + try: + self._port = serial.Serial( + port=self.comport, baudrate=self.rate, timeout=1, interCharTimeout=self.timeout) + except serial.SerialException: + return 0 + return 1 From fd4ba1e68580451ec5ddc10fa0aedf48de18f15e Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 17 Nov 2019 01:36:33 -0800 Subject: [PATCH 02/14] This works for the Roboclaw --- webapp/inputs/ext_node.py | 1 + webapp/static/scripts/remote/controller.js | 20 ++++++++++---------- webapp/users.py | 9 ++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/webapp/inputs/ext_node.py b/webapp/inputs/ext_node.py index f7a03c0..b8d6a63 100644 --- a/webapp/inputs/ext_node.py +++ b/webapp/inputs/ext_node.py @@ -35,6 +35,7 @@ class ROBOCLAW: def __init__(self, address, claw_address=0x80): self._address = claw_address self._device = Roboclaw(address, 38400) + self._device.Open() def go(self, cmds): if len(cmds) < 2: diff --git a/webapp/static/scripts/remote/controller.js b/webapp/static/scripts/remote/controller.js index 5c6c77f..0d8ae79 100644 --- a/webapp/static/scripts/remote/controller.js +++ b/webapp/static/scripts/remote/controller.js @@ -55,21 +55,21 @@ function adjustSliderSizes() { function initRemote(){ speedSlider = new Slider(speedController, !speedController.className.includes("vertical")); turnSlider = new Slider(turnController, !turnController.className.includes("vertical")); - + console.log("selection:", selectRemote.value); let controls = [{el: turnController, obj: turnSlider}, {el: speedController, obj: speedSlider}]; adjustSliderSizes(); window.addEventListener('resize', adjustSliderSizes); for (let ctrl of controls){ ctrl.obj.draw(); - ctrl.el.addEventListener('touchstart', touchStartOnSliders); - ctrl.el.addEventListener('touchmove', touchMoveOnSliders); - ctrl.el.addEventListener('touchend', touchEndOnSliders); - ctrl.el.addEventListener('touchcancel', touchEndOnSliders); - ctrl.el.addEventListener('mousedown', function(e){mouseStartOnSliders(e, ctrl.obj);}); - ctrl.el.addEventListener('mouseup', function(e){mouseEndOnSliders(e, ctrl.obj);}); - ctrl.el.addEventListener('mousemove', function(e){mouseMoveOnSliders(e, ctrl.obj);}); - ctrl.el.addEventListener('mouseleave', function(e){mouseEndOnSliders(e, ctrl.obj);}); + ctrl.el.addEventListener('touchstart', touchStartOnSliders, false); + ctrl.el.addEventListener('touchmove', touchMoveOnSliders, false); + ctrl.el.addEventListener('touchend', touchEndOnSliders, false); + ctrl.el.addEventListener('touchcancel', touchEndOnSliders, false); + ctrl.el.addEventListener('mousedown', function(e){mouseStartOnSliders(e, ctrl.obj);}, false); + ctrl.el.addEventListener('mouseup', function(e){mouseEndOnSliders(e, ctrl.obj);}, false); + ctrl.el.addEventListener('mousemove', function(e){mouseMoveOnSliders(e, ctrl.obj);}, false); + ctrl.el.addEventListener('mouseleave', function(e){mouseEndOnSliders(e, ctrl.obj);}, false); } // event listeners for connected gamepads @@ -145,7 +145,7 @@ function mouseStartOnSliders(e, obj) { function mouseMoveOnSliders(e, obj) { if(e.buttons == 1){ getMousePosOnSliders(e, obj); - } + } e.preventDefault(); } diff --git a/webapp/users.py b/webapp/users.py index 1731574..6a33190 100644 --- a/webapp/users.py +++ b/webapp/users.py @@ -11,8 +11,7 @@ class Remote: """A class used for instantiating a user's saved remote control configurations. - There should be 1 object of this class type per remote control. - + There should be 1 object of this class type per remote control. """ def __init__(self, name, link='/remote'): self.name = name @@ -20,8 +19,7 @@ def __init__(self, name, link='/remote'): class User(UserMixin): """A class for instantiating saved user accounts. - There should be 1 object of this class type per user account. - + There should be 1 object of this class type per user account. """ def __init__(self, name): self._id = name @@ -35,7 +33,8 @@ def get_id(self): def _load_config(self): """This function will load the information about a user's preferences - (including account settings and remote control configurations) from the backup json file titled with the self._id atrtribute. + (including account settings and remote control configurations) from the backup json + file titled with the self._id attribute. """ try: with open('backup\\{}.json'.format(self._id), 'r') as acct_file: From 6a91eed0b3f569a6d868a790fc145623ab4866a5 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 18 Nov 2019 16:04:56 -0800 Subject: [PATCH 03/14] update req's.txt w/ new adafruit releases --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b842677..391a611 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -git+https://github.com/2bndy5/Adafruit_CircuitPython_LSM9DS1.git -git+https://github.com/2bndy5/CircuitPython_MPU6050.git +adafruit_circuitpython_lsm9ds1==2.0.5 +adafruit_circuitpython_mpu6050==1.0.1 git+https://github.com/DVC-Viking-Robotics/Drivetrain.git git+https://github.com/DVC-Viking-Robotics/GPS_Serial.git circuitpython-nrf24l01==1.1.0 From 45a193db899dd74d86d3d2ac36916774ebb1bcee Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 20 Nov 2019 17:21:30 -0800 Subject: [PATCH 04/14] passing drivetrain dict to remote.html --- webapp/inputs/HWconfig_template.json | 40 +++++++++++++++++++--- webapp/inputs/config.py | 21 +++++++----- webapp/routes.py | 4 +-- webapp/sockets.py | 12 ++++--- webapp/static/scripts/remote/controller.js | 6 ++-- webapp/templates/remote.html | 9 ++--- 6 files changed, 65 insertions(+), 27 deletions(-) diff --git a/webapp/inputs/HWconfig_template.json b/webapp/inputs/HWconfig_template.json index 1c9e51f..7fb68fd 100644 --- a/webapp/inputs/HWconfig_template.json +++ b/webapp/inputs/HWconfig_template.json @@ -13,17 +13,20 @@ } ], "type":"Tank", - "max speed":"85" + "max speed":"85", + "name":"integrated drive" }, { "motors":[ { "driver":"NRF24L01tx", - "address":"5,8", - "name":"rfpi0" + "address":"5,4", + "name":"rfpi0", + "cmd_template":"llb" } ], - "type":"NRF24L01" + "type":"nRF24L01", + "name":"RF 2.4GHz" }, { "motors":[ @@ -32,7 +35,34 @@ "address":"/dev/ttyUSB0" } ], - "type":"Roboclaw" + "type":"Roboclaw", + "name":"MIEL" + }, + { + "motors":[ + { + "driver":"Solenoid", + "address":"22,13" + } + ], + "switch_pin":"4", + "type":"Locomotive", + "name":"Railroad drive" + }, + { + "motors":[ + { + "driver":"BiMotor", + "address":"22,13" + }, + { + "driver":"BiMotor", + "address":"17,18" + } + ], + "type":"Automotive", + "max speed":"85", + "name":"RC Car drive" } ], "IMU":[ diff --git a/webapp/inputs/config.py b/webapp/inputs/config.py index 8ebdd32..6613869 100644 --- a/webapp/inputs/config.py +++ b/webapp/inputs/config.py @@ -9,10 +9,11 @@ from .ext_node import ROBOCLAW if ON_RASPI: - from drivetrain import Tank, Automotive, Solenoid, BiMotor, PhasedMotor, NRF24L01tx + from drivetrain import Tank, Automotive, Locomotive, Solenoid, BiMotor, PhasedMotor, NRF24L01tx from adafruit_lsm9ds1 import LSM9DS1_I2C from circuitpython_mpu6050 import MPU6050 - + from circuitpython_nrf24l01 import RF24 + from digitalio import DigitalInOut as Dio from .imu import MAG3110 CONFIG_FILE_LOCATION = u'webapp/inputs/HWconfig.json' @@ -58,7 +59,7 @@ '27': board.D27, } -d_train = [] +d_train = {"test": 0x00} IMUs = [] gps = [] nav = None @@ -67,7 +68,7 @@ if 'Drivetrains' in SYSTEM_CONF['Check-Hardware']: # handle drivetrain for d in SYSTEM_CONF['Drivetrains']: - if d['type'] in ('Tank', 'Automotive', 'NRF24L01', 'Roboclaw'): + if d['type'] in ('Tank', 'Automotive', 'Locomotive', 'nRF24L01', 'Roboclaw'): # instantiate motors motors = [] for m in d['motors']: @@ -84,13 +85,17 @@ elif m['driver'].startswith('PhasedMotor') and len(pins) == 2 and has_gpio_pins: motors.append(PhasedMotor(pins)) elif m['driver'].startswith('ROBOCLAW'): - d_train.append(ROBOCLAW(m['address'])) + d_train[d["name"]] = ROBOCLAW(m['address']) elif m['driver'].startswith('NRF24L01tx') and has_gpio_pins: - d_train.append(NRF24L01tx(SPI_BUS, pins, bytes(m['name'].encode('utf-8')))) + d_train[d["name"]] = NRF24L01tx( + RF24(SPI_BUS, Dio(pins[0]), Dio(pins[1])), + bytes(m['name'].encode())) if d['type'].startswith('Tank') and has_gpio_pins: - d_train.append(Tank(motors, int(d['max speed']))) + d_train[d["name"]] = Tank(motors, int(d['max speed'])) elif d['type'].startswith('Automotive') and has_gpio_pins: - d_train.append(Automotive(motors, int(d['max speed']))) + d_train[d["name"]] = Automotive(motors, int(d['max speed'])) + elif d['type'].startswith('Locomotive') and has_gpio_pins: + d_train[d["name"]] = Locomotive(motors, RPI_PIN_ALIAS[d['switch_pin']]) if 'IMU' in SYSTEM_CONF['Check-Hardware']: for imu in SYSTEM_CONF['IMU']: diff --git a/webapp/routes.py b/webapp/routes.py index bd7a7d7..c7065dd 100644 --- a/webapp/routes.py +++ b/webapp/routes.py @@ -6,10 +6,10 @@ from flask_login import login_required, login_user, logout_user from .users import users, User from .sockets import socketio +from webapp.inputs.config import d_train blueprint = Blueprint('blueprint', __name__) - @blueprint.route('/', methods=['GET', 'POST']) @blueprint.route('/login', methods=['GET', 'POST']) def login(): @@ -49,7 +49,7 @@ def home(): @blueprint.route('/remote') @login_required def remote(): - return render_template('remote.html', title='Remote Control') + return render_template('remote.html', title='Remote Control', drivetrains=d_train) @blueprint.route('/camera') diff --git a/webapp/sockets.py b/webapp/sockets.py index 1b0db37..27e91d1 100644 --- a/webapp/sockets.py +++ b/webapp/sockets.py @@ -6,7 +6,6 @@ from flask_socketio import SocketIO, emit from circuitpython_mpu6050 import MPU6050 from adafruit_lsm9ds1 import LSM9DS1_I2C - from .inputs.check_platform import ON_RASPI, ON_WINDOWS # , ON_JETSON from .inputs.config import d_train, IMUs, gps, nav from .inputs.imu import MAG3110, calc_heading, calc_yaw_pitch_roll @@ -124,11 +123,14 @@ def handle_DoF_request(): print('DoF sensor data sent') @socketio.on('remoteOut') -def handle_remoteOut(arg): +def handle_remoteOut(args, drivetrain_name): + # convert first 2 d_train cmds + for i in range(2): + args[i] = int(args[i] * 655.35) # for debugging - print('remote =', repr(arg)) - if d_train: # if there is a drivetrain connected - d_train[0].go([arg[0] * 655.35, arg[1] * 655.35]) + print(drivetrain_name, 'remote =', repr(args)) + if not ON_WINDOWS: # if there is a compatible drivetrain + d_train[drivetrain_name].go(args) # NOTE: Source for virtual terminal functions: https://github.com/cs01/pyxterm.js diff --git a/webapp/static/scripts/remote/controller.js b/webapp/static/scripts/remote/controller.js index 0d8ae79..a783c71 100644 --- a/webapp/static/scripts/remote/controller.js +++ b/webapp/static/scripts/remote/controller.js @@ -6,7 +6,7 @@ var speedOMeter = document.getElementById('speed-o-meter'); var turnOMeter = document.getElementById('turn-o-meter'); var speedSlider; var turnSlider; -var selectRemote = document.getElementById("selectRemote"); +var selectDrivetrain = document.getElementById("selectDrivetrain"); // prototype list of all data on any connected gamepads // each item in list represents a gamepad (if any) // each gamepad has all info about axis and buttons @@ -36,7 +36,7 @@ function sendSpeedTurnValues(gamepadAxes = []) { // only send data if it has changed if (prevArgs[0] != args[0] || prevArgs[1] != args[1]){ prevArgs = args; - socket.emit('remoteOut', args); + socket.emit('remoteOut', args, selectDrivetrain.value); } } @@ -56,7 +56,7 @@ function initRemote(){ speedSlider = new Slider(speedController, !speedController.className.includes("vertical")); turnSlider = new Slider(turnController, !turnController.className.includes("vertical")); - console.log("selection:", selectRemote.value); + // console.log("selection:", selectDrivetrain.value); let controls = [{el: turnController, obj: turnSlider}, {el: speedController, obj: speedSlider}]; adjustSliderSizes(); window.addEventListener('resize', adjustSliderSizes); diff --git a/webapp/templates/remote.html b/webapp/templates/remote.html index e642ad5..455166a 100644 --- a/webapp/templates/remote.html +++ b/webapp/templates/remote.html @@ -17,9 +17,10 @@
- + {% for drive_name in drivetrains.keys(): %} + + {% endfor %}
@@ -72,4 +73,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} From e61e9719cde208a23db7722d25a822e769f30972 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 21 Nov 2019 00:44:16 -0800 Subject: [PATCH 05/14] touch is reacting but not accurate --- webapp/static/scripts/remote/controller.js | 12 ++++++------ webapp/static/scripts/remote/slider.js | 11 ++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/webapp/static/scripts/remote/controller.js b/webapp/static/scripts/remote/controller.js index a783c71..d825977 100644 --- a/webapp/static/scripts/remote/controller.js +++ b/webapp/static/scripts/remote/controller.js @@ -12,7 +12,7 @@ var selectDrivetrain = document.getElementById("selectDrivetrain"); // each gamepad has all info about axis and buttons var gamepads = []; // avoid cluttering socket with duplicate data due to setInterval polling of gamepads -var prevArgs = []; +var prevArgs = [0, 0]; // Grab the speed and turning values and update the text as well as send them to the robot function sendSpeedTurnValues(gamepadAxes = []) { @@ -56,7 +56,6 @@ function initRemote(){ speedSlider = new Slider(speedController, !speedController.className.includes("vertical")); turnSlider = new Slider(turnController, !turnController.className.includes("vertical")); - // console.log("selection:", selectDrivetrain.value); let controls = [{el: turnController, obj: turnSlider}, {el: speedController, obj: speedSlider}]; adjustSliderSizes(); window.addEventListener('resize', adjustSliderSizes); @@ -115,14 +114,15 @@ function getTouchPos(e) { let controls = [turnSlider, speedSlider]; let touchesHandled = [false, false]; for (let touch of e.touches) { - let touchX = touch.clientX; - let touchY = touch.clientY; + let touchX = touch.pageX; + let touchY = touch.pageY; for (let i = 0; i < controls.length; i++){ if (isWithinRect([touchX, touchY], controls[i].rect)){ - touchX = (touchX / controls[i].width * 2 - 1) / ((controls[i].width - controls[i].stick.radius * 2) / controls[i].width) * 100; - touchY = (touchY / controls[i].height * -2 + 1) / ((controls[i].height - controls[i].stick.radius * 2) / controls[i].height) * 100; + touchX = ((touchX - controls[i].rect.left) / controls[i].width * 2 - 1) / ((controls[i].width - controls[i].stick.radius * 2) / controls[i].width) * 100; + touchY = ((touchY - controls[i].rect.top) / controls[i].height * -2 + 1) / ((controls[i].height - controls[i].stick.radius * 2) / controls[i].height) * 100; controls[i].manip = true; controls[i].value = controls[i].horizontal ? touchX : touchY; + console.log((controls[i].horizontal ? "horizontal" : "vertical") + " slider value: ", controls[i].value); touchesHandled[i] = true; } } diff --git a/webapp/static/scripts/remote/slider.js b/webapp/static/scripts/remote/slider.js index 5572f89..dae09af 100644 --- a/webapp/static/scripts/remote/slider.js +++ b/webapp/static/scripts/remote/slider.js @@ -2,6 +2,7 @@ class Slider { constructor(canvas, horizontal = true) { this.canvas = canvas; + this.rect = this.canvas.getBoundingClientRect(); this.height = this.canvas.width; this.width = this.canvas.height; this.horizontal = horizontal; @@ -18,10 +19,10 @@ class Slider { } resize(){ // let currentStyle = window.getComputedStyle(this.canvas.id); - let currentStyle = this.canvas.getBoundingClientRect(); + this.rect = this.canvas.getBoundingClientRect(); // console.log(this.canvas.id, currentStyle.width, currentStyle.height); - this.height = currentStyle.height; - this.width = currentStyle.width; + this.height = this.rect.height; + this.width = this.rect.width; this.draw(); } set value(val){ @@ -31,7 +32,7 @@ class Slider { get value(){ return this.pos; } - draw() { + draw(){ let ctx = this.canvas.getContext("2d"); ctx.clearRect(0, 0, this.width, this.height); ctx.strokeStyle = this.color; @@ -65,7 +66,7 @@ class Slider { let gradient = ctx.createRadialGradient(this.stick.x, this.stick.y, this.stick.radius * 0.25, this.stick.x, this.stick.y, this.stick.radius * 0.6); gradient.addColorStop(0, '#a3a3a3'); gradient.addColorStop(0.7, '#f3f3f3'); - ctx.fillStyle = gradient; + ctx.fillStyle = gradient; ctx.fill(); } }// end canvas's slider object From ffa42a3748fbf6c8d8a9f80d1552d0f7869e4772 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 21 Nov 2019 00:46:16 -0800 Subject: [PATCH 06/14] ready to test on RPi --- webapp/inputs/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/inputs/config.py b/webapp/inputs/config.py index 6613869..9f402ad 100644 --- a/webapp/inputs/config.py +++ b/webapp/inputs/config.py @@ -59,7 +59,7 @@ '27': board.D27, } -d_train = {"test": 0x00} +d_train = {} IMUs = [] gps = [] nav = None From 4436e4140032c7ba23aa65e04be84cfb7019687f Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 21 Nov 2019 02:13:13 -0800 Subject: [PATCH 07/14] testing mecanum via nRf24L01 --- webapp/inputs/config.py | 3 ++- webapp/sockets.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/webapp/inputs/config.py b/webapp/inputs/config.py index 9f402ad..5b8fdf7 100644 --- a/webapp/inputs/config.py +++ b/webapp/inputs/config.py @@ -89,7 +89,8 @@ elif m['driver'].startswith('NRF24L01tx') and has_gpio_pins: d_train[d["name"]] = NRF24L01tx( RF24(SPI_BUS, Dio(pins[0]), Dio(pins[1])), - bytes(m['name'].encode())) + address=bytes(m['name'].encode()), + cmd_template=m['cmd_template']) if d['type'].startswith('Tank') and has_gpio_pins: d_train[d["name"]] = Tank(motors, int(d['max speed'])) elif d['type'].startswith('Automotive') and has_gpio_pins: diff --git a/webapp/sockets.py b/webapp/sockets.py index 27e91d1..c50c032 100644 --- a/webapp/sockets.py +++ b/webapp/sockets.py @@ -127,6 +127,7 @@ def handle_remoteOut(args, drivetrain_name): # convert first 2 d_train cmds for i in range(2): args[i] = int(args[i] * 655.35) + args.append(1) # hard-coding mecanum drive to strafe # for debugging print(drivetrain_name, 'remote =', repr(args)) if not ON_WINDOWS: # if there is a compatible drivetrain From 7e8d1ff356df08497c4b0fb576ec078b2a3cdece Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 21 Nov 2019 02:21:21 -0800 Subject: [PATCH 08/14] args emit from remote.html in backward order --- webapp/static/scripts/remote/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/static/scripts/remote/controller.js b/webapp/static/scripts/remote/controller.js index d825977..d28660e 100644 --- a/webapp/static/scripts/remote/controller.js +++ b/webapp/static/scripts/remote/controller.js @@ -32,7 +32,7 @@ function sendSpeedTurnValues(gamepadAxes = []) { speedOMeter.innerText = speed; turnOMeter.innerText = turn; - var args = [speed, turn]; + var args = [turn, speed]; // only send data if it has changed if (prevArgs[0] != args[0] || prevArgs[1] != args[1]){ prevArgs = args; From 8b84a2bea2cc27ba8dd0314f097e4f382ee1463b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 21 Nov 2019 10:01:54 -0800 Subject: [PATCH 09/14] Update sockets.py --- webapp/sockets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/sockets.py b/webapp/sockets.py index 1b0db37..8d557e9 100644 --- a/webapp/sockets.py +++ b/webapp/sockets.py @@ -4,7 +4,7 @@ import base64 from flask_socketio import SocketIO, emit -from circuitpython_mpu6050 import MPU6050 +from adafruit_mpu6050 import MPU6050 from adafruit_lsm9ds1 import LSM9DS1_I2C from .inputs.check_platform import ON_RASPI, ON_WINDOWS # , ON_JETSON From 230aee780088460ff6209830f80f206e2a9278dd Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 21 Nov 2019 10:03:12 -0800 Subject: [PATCH 10/14] Update config.py --- webapp/inputs/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/inputs/config.py b/webapp/inputs/config.py index 8ebdd32..9ce8847 100644 --- a/webapp/inputs/config.py +++ b/webapp/inputs/config.py @@ -11,7 +11,7 @@ if ON_RASPI: from drivetrain import Tank, Automotive, Solenoid, BiMotor, PhasedMotor, NRF24L01tx from adafruit_lsm9ds1 import LSM9DS1_I2C - from circuitpython_mpu6050 import MPU6050 + from adafruit_mpu6050 import MPU6050 from .imu import MAG3110 From 579e21207f4666bd799f7e15ffe8cc19035761b6 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 21 Nov 2019 10:15:18 -0800 Subject: [PATCH 11/14] cleaning whitespace from merger --- webapp/inputs/config.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/webapp/inputs/config.py b/webapp/inputs/config.py index e104faa..71bb4e1 100644 --- a/webapp/inputs/config.py +++ b/webapp/inputs/config.py @@ -11,11 +11,7 @@ if ON_RASPI: from drivetrain import Tank, Automotive, Locomotive, Solenoid, BiMotor, PhasedMotor, NRF24L01tx from adafruit_lsm9ds1 import LSM9DS1_I2C - from adafruit_mpu6050 import MPU6050 - - - from circuitpython_nrf24l01 import RF24 from digitalio import DigitalInOut as Dio From 2b1ae4e3e98bcd1252a7e1753fc10004a3466a40 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 30 Nov 2019 14:06:51 -0800 Subject: [PATCH 12/14] added a strafe checkbox connected to butt R1 on gp --- webapp/sockets.py | 1 - webapp/static/css/bulma-switch.min.css | 893 +++++++++++++++++++++ webapp/static/scripts/remote/controller.js | 25 +- webapp/templates/layout.html | 3 +- webapp/templates/remote.html | 5 +- 5 files changed, 914 insertions(+), 13 deletions(-) create mode 100644 webapp/static/css/bulma-switch.min.css diff --git a/webapp/sockets.py b/webapp/sockets.py index cbc124e..949dc5b 100644 --- a/webapp/sockets.py +++ b/webapp/sockets.py @@ -127,7 +127,6 @@ def handle_remoteOut(args, drivetrain_name): # convert first 2 d_train cmds for i in range(2): args[i] = int(args[i] * 655.35) - args.append(1) # hard-coding mecanum drive to strafe # for debugging print(drivetrain_name, 'remote =', repr(args)) if not ON_WINDOWS: # if there is a compatible drivetrain diff --git a/webapp/static/css/bulma-switch.min.css b/webapp/static/css/bulma-switch.min.css new file mode 100644 index 0000000..ecf4a4a --- /dev/null +++ b/webapp/static/css/bulma-switch.min.css @@ -0,0 +1,893 @@ +@-webkit-keyframes spinAround { + from { + -webkit-transform: rotate(0); + transform: rotate(0) + } + + to { + -webkit-transform: rotate(359deg); + transform: rotate(359deg) + } +} + +@keyframes spinAround { + from { + -webkit-transform: rotate(0); + transform: rotate(0) + } + + to { + -webkit-transform: rotate(359deg); + transform: rotate(359deg) + } +} + +.switch[type=checkbox] { + outline: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + display: inline-block; + position: absolute; + opacity: 0 +} + +.switch[type=checkbox]:focus+label::after, +.switch[type=checkbox]:focus+label::before, +.switch[type=checkbox]:focus+label:after, +.switch[type=checkbox]:focus+label:before { + outline: 1px dotted #b5b5b5 +} + +.switch[type=checkbox][disabled] { + cursor: not-allowed +} + +.switch[type=checkbox][disabled]+label { + opacity: .5 +} + +.switch[type=checkbox][disabled]+label::before, +.switch[type=checkbox][disabled]+label:before { + opacity: .5 +} + +.switch[type=checkbox][disabled]+label::after, +.switch[type=checkbox][disabled]+label:after { + opacity: .5 +} + +.switch[type=checkbox][disabled]+label:hover { + cursor: not-allowed +} + +.switch[type=checkbox]+label { + position: relative; + display: initial; + font-size: 1rem; + line-height: initial; + padding-left: 3.5rem; + padding-top: .2rem; + cursor: pointer +} + +.switch[type=checkbox]+label::before, +.switch[type=checkbox]+label:before { + position: absolute; + display: block; + top: 0; + left: 0; + width: 3rem; + height: 1.5rem; + border: .1rem solid transparent; + border-radius: 4px; + background: #b5b5b5; + content: '' +} + +.switch[type=checkbox]+label::after, +.switch[type=checkbox]+label:after { + display: block; + position: absolute; + top: .75rem-calc($switch-height - ($switch-paddle-offset * 2))/2; + left: .25rem; + width: calc($switch-height - ($switch-paddle-offset * 2)); + height: calc($switch-height - ($switch-paddle-offset * 2)); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + border-radius: 4px; + background: #fff; + transition: all .25s ease-out; + content: '' +} + +.switch[type=checkbox].is-rtl+label { + padding-left: 0; + padding-right: 3.5rem +} + +.switch[type=checkbox].is-rtl+label::before, +.switch[type=checkbox].is-rtl+label:before { + left: auto; + right: 0 +} + +.switch[type=checkbox].is-rtl+label::after, +.switch[type=checkbox].is-rtl+label:after { + left: auto; + right: calc($switch-width - $paddle-width - ($switch-paddle-offset * 1.5)) +} + +.switch[type=checkbox]:checked+label::before, +.switch[type=checkbox]:checked+label:before { + background: #00d1b2 +} + +.switch[type=checkbox]:checked+label::after { + left: calc($switch-width - $paddle-width - ($switch-paddle-offset * 1.5)) +} + +.switch[type=checkbox]:checked.is-rtl+label::after, +.switch[type=checkbox]:checked.is-rtl+label:after { + left: auto; + right: .25rem +} + +.switch[type=checkbox].is-outlined+label::before, +.switch[type=checkbox].is-outlined+label:before { + background-color: transparent; + border-color: #b5b5b5 +} + +.switch[type=checkbox].is-outlined+label::after, +.switch[type=checkbox].is-outlined+label:after { + background: #b5b5b5 +} + +.switch[type=checkbox].is-outlined:checked+label::before, +.switch[type=checkbox].is-outlined:checked+label:before { + background-color: transparent; + border-color: #00d1b2 +} + +.switch[type=checkbox].is-outlined:checked+label::after, +.switch[type=checkbox].is-outlined:checked+label:after { + background: #00d1b2 +} + +.switch[type=checkbox].is-thin+label::before, +.switch[type=checkbox].is-thin+label:before { + top: .54545rem; + height: .375rem +} + +.switch[type=checkbox].is-thin+label::after, +.switch[type=checkbox].is-thin+label:after { + box-shadow: 0 0 3px #7a7a7a +} + +.switch[type=checkbox].is-rounded+label::before, +.switch[type=checkbox].is-rounded+label:before { + border-radius: 24px +} + +.switch[type=checkbox].is-rounded+label::after, +.switch[type=checkbox].is-rounded+label:after { + border-radius: 50% +} + +.switch[type=checkbox].is-small+label { + position: relative; + display: initial; + font-size: .75rem; + line-height: initial; + padding-left: 2.75rem; + padding-top: .2rem; + cursor: pointer +} + +.switch[type=checkbox].is-small+label::before, +.switch[type=checkbox].is-small+label:before { + position: absolute; + display: block; + top: 0; + left: 0; + width: 2.25rem; + height: 1.125rem; + border: .1rem solid transparent; + border-radius: 4px; + background: #b5b5b5; + content: '' +} + +.switch[type=checkbox].is-small+label::after, +.switch[type=checkbox].is-small+label:after { + display: block; + position: absolute; + top: .5625rem-calc($switch-height - ($switch-paddle-offset * 2))/2; + left: .25rem; + width: calc($switch-height - ($switch-paddle-offset * 2)); + height: calc($switch-height - ($switch-paddle-offset * 2)); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + border-radius: 4px; + background: #fff; + transition: all .25s ease-out; + content: '' +} + +.switch[type=checkbox].is-small.is-rtl+label { + padding-left: 0; + padding-right: 2.75rem +} + +.switch[type=checkbox].is-small.is-rtl+label::before, +.switch[type=checkbox].is-small.is-rtl+label:before { + left: auto; + right: 0 +} + +.switch[type=checkbox].is-small.is-rtl+label::after, +.switch[type=checkbox].is-small.is-rtl+label:after { + left: auto; + right: calc($switch-width - $paddle-width - ($switch-paddle-offset * 1.5)) +} + +.switch[type=checkbox].is-small:checked+label::before, +.switch[type=checkbox].is-small:checked+label:before { + background: #00d1b2 +} + +.switch[type=checkbox].is-small:checked+label::after { + left: calc($switch-width - $paddle-width - ($switch-paddle-offset * 1.5)) +} + +.switch[type=checkbox].is-small:checked.is-rtl+label::after, +.switch[type=checkbox].is-small:checked.is-rtl+label:after { + left: auto; + right: .25rem +} + +.switch[type=checkbox].is-small.is-outlined+label::before, +.switch[type=checkbox].is-small.is-outlined+label:before { + background-color: transparent; + border-color: #b5b5b5 +} + +.switch[type=checkbox].is-small.is-outlined+label::after, +.switch[type=checkbox].is-small.is-outlined+label:after { + background: #b5b5b5 +} + +.switch[type=checkbox].is-small.is-outlined:checked+label::before, +.switch[type=checkbox].is-small.is-outlined:checked+label:before { + background-color: transparent; + border-color: #00d1b2 +} + +.switch[type=checkbox].is-small.is-outlined:checked+label::after, +.switch[type=checkbox].is-small.is-outlined:checked+label:after { + background: #00d1b2 +} + +.switch[type=checkbox].is-small.is-thin+label::before, +.switch[type=checkbox].is-small.is-thin+label:before { + top: .40909rem; + height: .28125rem +} + +.switch[type=checkbox].is-small.is-thin+label::after, +.switch[type=checkbox].is-small.is-thin+label:after { + box-shadow: 0 0 3px #7a7a7a +} + +.switch[type=checkbox].is-small.is-rounded+label::before, +.switch[type=checkbox].is-small.is-rounded+label:before { + border-radius: 24px +} + +.switch[type=checkbox].is-small.is-rounded+label::after, +.switch[type=checkbox].is-small.is-rounded+label:after { + border-radius: 50% +} + +.switch[type=checkbox].is-medium+label { + position: relative; + display: initial; + font-size: 1.25rem; + line-height: initial; + padding-left: 4.25rem; + padding-top: .2rem; + cursor: pointer +} + +.switch[type=checkbox].is-medium+label::before, +.switch[type=checkbox].is-medium+label:before { + position: absolute; + display: block; + top: 0; + left: 0; + width: 3.75rem; + height: 1.875rem; + border: .1rem solid transparent; + border-radius: 4px; + background: #b5b5b5; + content: '' +} + +.switch[type=checkbox].is-medium+label::after, +.switch[type=checkbox].is-medium+label:after { + display: block; + position: absolute; + top: .9375rem-calc($switch-height - ($switch-paddle-offset * 2))/2; + left: .25rem; + width: calc($switch-height - ($switch-paddle-offset * 2)); + height: calc($switch-height - ($switch-paddle-offset * 2)); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + border-radius: 4px; + background: #fff; + transition: all .25s ease-out; + content: '' +} + +.switch[type=checkbox].is-medium.is-rtl+label { + padding-left: 0; + padding-right: 4.25rem +} + +.switch[type=checkbox].is-medium.is-rtl+label::before, +.switch[type=checkbox].is-medium.is-rtl+label:before { + left: auto; + right: 0 +} + +.switch[type=checkbox].is-medium.is-rtl+label::after, +.switch[type=checkbox].is-medium.is-rtl+label:after { + left: auto; + right: calc($switch-width - $paddle-width - ($switch-paddle-offset * 1.5)) +} + +.switch[type=checkbox].is-medium:checked+label::before, +.switch[type=checkbox].is-medium:checked+label:before { + background: #00d1b2 +} + +.switch[type=checkbox].is-medium:checked+label::after { + left: calc($switch-width - $paddle-width - ($switch-paddle-offset * 1.5)) +} + +.switch[type=checkbox].is-medium:checked.is-rtl+label::after, +.switch[type=checkbox].is-medium:checked.is-rtl+label:after { + left: auto; + right: .25rem +} + +.switch[type=checkbox].is-medium.is-outlined+label::before, +.switch[type=checkbox].is-medium.is-outlined+label:before { + background-color: transparent; + border-color: #b5b5b5 +} + +.switch[type=checkbox].is-medium.is-outlined+label::after, +.switch[type=checkbox].is-medium.is-outlined+label:after { + background: #b5b5b5 +} + +.switch[type=checkbox].is-medium.is-outlined:checked+label::before, +.switch[type=checkbox].is-medium.is-outlined:checked+label:before { + background-color: transparent; + border-color: #00d1b2 +} + +.switch[type=checkbox].is-medium.is-outlined:checked+label::after, +.switch[type=checkbox].is-medium.is-outlined:checked+label:after { + background: #00d1b2 +} + +.switch[type=checkbox].is-medium.is-thin+label::before, +.switch[type=checkbox].is-medium.is-thin+label:before { + top: .68182rem; + height: .46875rem +} + +.switch[type=checkbox].is-medium.is-thin+label::after, +.switch[type=checkbox].is-medium.is-thin+label:after { + box-shadow: 0 0 3px #7a7a7a +} + +.switch[type=checkbox].is-medium.is-rounded+label::before, +.switch[type=checkbox].is-medium.is-rounded+label:before { + border-radius: 24px +} + +.switch[type=checkbox].is-medium.is-rounded+label::after, +.switch[type=checkbox].is-medium.is-rounded+label:after { + border-radius: 50% +} + +.switch[type=checkbox].is-large+label { + position: relative; + display: initial; + font-size: 1.5rem; + line-height: initial; + padding-left: 5rem; + padding-top: .2rem; + cursor: pointer +} + +.switch[type=checkbox].is-large+label::before, +.switch[type=checkbox].is-large+label:before { + position: absolute; + display: block; + top: 0; + left: 0; + width: 4.5rem; + height: 2.25rem; + border: .1rem solid transparent; + border-radius: 4px; + background: #b5b5b5; + content: '' +} + +.switch[type=checkbox].is-large+label::after, +.switch[type=checkbox].is-large+label:after { + display: block; + position: absolute; + top: 1.125rem-calc($switch-height - ($switch-paddle-offset * 2))/2; + left: .25rem; + width: calc($switch-height - ($switch-paddle-offset * 2)); + height: calc($switch-height - ($switch-paddle-offset * 2)); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + border-radius: 4px; + background: #fff; + transition: all .25s ease-out; + content: '' +} + +.switch[type=checkbox].is-large.is-rtl+label { + padding-left: 0; + padding-right: 5rem +} + +.switch[type=checkbox].is-large.is-rtl+label::before, +.switch[type=checkbox].is-large.is-rtl+label:before { + left: auto; + right: 0 +} + +.switch[type=checkbox].is-large.is-rtl+label::after, +.switch[type=checkbox].is-large.is-rtl+label:after { + left: auto; + right: calc($switch-width - $paddle-width - ($switch-paddle-offset * 1.5)) +} + +.switch[type=checkbox].is-large:checked+label::before, +.switch[type=checkbox].is-large:checked+label:before { + background: #00d1b2 +} + +.switch[type=checkbox].is-large:checked+label::after { + left: calc($switch-width - $paddle-width - ($switch-paddle-offset * 1.5)) +} + +.switch[type=checkbox].is-large:checked.is-rtl+label::after, +.switch[type=checkbox].is-large:checked.is-rtl+label:after { + left: auto; + right: .25rem +} + +.switch[type=checkbox].is-large.is-outlined+label::before, +.switch[type=checkbox].is-large.is-outlined+label:before { + background-color: transparent; + border-color: #b5b5b5 +} + +.switch[type=checkbox].is-large.is-outlined+label::after, +.switch[type=checkbox].is-large.is-outlined+label:after { + background: #b5b5b5 +} + +.switch[type=checkbox].is-large.is-outlined:checked+label::before, +.switch[type=checkbox].is-large.is-outlined:checked+label:before { + background-color: transparent; + border-color: #00d1b2 +} + +.switch[type=checkbox].is-large.is-outlined:checked+label::after, +.switch[type=checkbox].is-large.is-outlined:checked+label:after { + background: #00d1b2 +} + +.switch[type=checkbox].is-large.is-thin+label::before, +.switch[type=checkbox].is-large.is-thin+label:before { + top: .81818rem; + height: .5625rem +} + +.switch[type=checkbox].is-large.is-thin+label::after, +.switch[type=checkbox].is-large.is-thin+label:after { + box-shadow: 0 0 3px #7a7a7a +} + +.switch[type=checkbox].is-large.is-rounded+label::before, +.switch[type=checkbox].is-large.is-rounded+label:before { + border-radius: 24px +} + +.switch[type=checkbox].is-large.is-rounded+label::after, +.switch[type=checkbox].is-large.is-rounded+label:after { + border-radius: 50% +} + +.switch[type=checkbox].is-white:checked+label::before, +.switch[type=checkbox].is-white:checked+label:before { + background: #fff +} + +.switch[type=checkbox].is-white.is-outlined:checked+label::before, +.switch[type=checkbox].is-white.is-outlined:checked+label:before { + background-color: transparent; + border-color: #fff !important +} + +.switch[type=checkbox].is-white.is-outlined:checked+label::after, +.switch[type=checkbox].is-white.is-outlined:checked+label:after { + background: #fff +} + +.switch[type=checkbox].is-white.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-white.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-white+label::before, +.switch[type=checkbox].is-unchecked-white+label:before { + background: #fff +} + +.switch[type=checkbox].is-unchecked-white.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-white.is-outlined+label:before { + background-color: transparent; + border-color: #fff !important +} + +.switch[type=checkbox].is-unchecked-white.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-white.is-outlined+label:after { + background: #fff +} + +.switch[type=checkbox].is-black:checked+label::before, +.switch[type=checkbox].is-black:checked+label:before { + background: #0a0a0a +} + +.switch[type=checkbox].is-black.is-outlined:checked+label::before, +.switch[type=checkbox].is-black.is-outlined:checked+label:before { + background-color: transparent; + border-color: #0a0a0a !important +} + +.switch[type=checkbox].is-black.is-outlined:checked+label::after, +.switch[type=checkbox].is-black.is-outlined:checked+label:after { + background: #0a0a0a +} + +.switch[type=checkbox].is-black.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-black.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-black+label::before, +.switch[type=checkbox].is-unchecked-black+label:before { + background: #0a0a0a +} + +.switch[type=checkbox].is-unchecked-black.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-black.is-outlined+label:before { + background-color: transparent; + border-color: #0a0a0a !important +} + +.switch[type=checkbox].is-unchecked-black.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-black.is-outlined+label:after { + background: #0a0a0a +} + +.switch[type=checkbox].is-light:checked+label::before, +.switch[type=checkbox].is-light:checked+label:before { + background: #f5f5f5 +} + +.switch[type=checkbox].is-light.is-outlined:checked+label::before, +.switch[type=checkbox].is-light.is-outlined:checked+label:before { + background-color: transparent; + border-color: #f5f5f5 !important +} + +.switch[type=checkbox].is-light.is-outlined:checked+label::after, +.switch[type=checkbox].is-light.is-outlined:checked+label:after { + background: #f5f5f5 +} + +.switch[type=checkbox].is-light.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-light.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-light+label::before, +.switch[type=checkbox].is-unchecked-light+label:before { + background: #f5f5f5 +} + +.switch[type=checkbox].is-unchecked-light.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-light.is-outlined+label:before { + background-color: transparent; + border-color: #f5f5f5 !important +} + +.switch[type=checkbox].is-unchecked-light.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-light.is-outlined+label:after { + background: #f5f5f5 +} + +.switch[type=checkbox].is-dark:checked+label::before, +.switch[type=checkbox].is-dark:checked+label:before { + background: #363636 +} + +.switch[type=checkbox].is-dark.is-outlined:checked+label::before, +.switch[type=checkbox].is-dark.is-outlined:checked+label:before { + background-color: transparent; + border-color: #363636 !important +} + +.switch[type=checkbox].is-dark.is-outlined:checked+label::after, +.switch[type=checkbox].is-dark.is-outlined:checked+label:after { + background: #363636 +} + +.switch[type=checkbox].is-dark.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-dark.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-dark+label::before, +.switch[type=checkbox].is-unchecked-dark+label:before { + background: #363636 +} + +.switch[type=checkbox].is-unchecked-dark.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-dark.is-outlined+label:before { + background-color: transparent; + border-color: #363636 !important +} + +.switch[type=checkbox].is-unchecked-dark.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-dark.is-outlined+label:after { + background: #363636 +} + +.switch[type=checkbox].is-primary:checked+label::before, +.switch[type=checkbox].is-primary:checked+label:before { + background: #00d1b2 +} + +.switch[type=checkbox].is-primary.is-outlined:checked+label::before, +.switch[type=checkbox].is-primary.is-outlined:checked+label:before { + background-color: transparent; + border-color: #00d1b2 !important +} + +.switch[type=checkbox].is-primary.is-outlined:checked+label::after, +.switch[type=checkbox].is-primary.is-outlined:checked+label:after { + background: #00d1b2 +} + +.switch[type=checkbox].is-primary.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-primary.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-primary+label::before, +.switch[type=checkbox].is-unchecked-primary+label:before { + background: #00d1b2 +} + +.switch[type=checkbox].is-unchecked-primary.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-primary.is-outlined+label:before { + background-color: transparent; + border-color: #00d1b2 !important +} + +.switch[type=checkbox].is-unchecked-primary.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-primary.is-outlined+label:after { + background: #00d1b2 +} + +.switch[type=checkbox].is-link:checked+label::before, +.switch[type=checkbox].is-link:checked+label:before { + background: #3273dc +} + +.switch[type=checkbox].is-link.is-outlined:checked+label::before, +.switch[type=checkbox].is-link.is-outlined:checked+label:before { + background-color: transparent; + border-color: #3273dc !important +} + +.switch[type=checkbox].is-link.is-outlined:checked+label::after, +.switch[type=checkbox].is-link.is-outlined:checked+label:after { + background: #3273dc +} + +.switch[type=checkbox].is-link.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-link.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-link+label::before, +.switch[type=checkbox].is-unchecked-link+label:before { + background: #3273dc +} + +.switch[type=checkbox].is-unchecked-link.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-link.is-outlined+label:before { + background-color: transparent; + border-color: #3273dc !important +} + +.switch[type=checkbox].is-unchecked-link.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-link.is-outlined+label:after { + background: #3273dc +} + +.switch[type=checkbox].is-info:checked+label::before, +.switch[type=checkbox].is-info:checked+label:before { + background: #209cee +} + +.switch[type=checkbox].is-info.is-outlined:checked+label::before, +.switch[type=checkbox].is-info.is-outlined:checked+label:before { + background-color: transparent; + border-color: #209cee !important +} + +.switch[type=checkbox].is-info.is-outlined:checked+label::after, +.switch[type=checkbox].is-info.is-outlined:checked+label:after { + background: #209cee +} + +.switch[type=checkbox].is-info.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-info.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-info+label::before, +.switch[type=checkbox].is-unchecked-info+label:before { + background: #209cee +} + +.switch[type=checkbox].is-unchecked-info.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-info.is-outlined+label:before { + background-color: transparent; + border-color: #209cee !important +} + +.switch[type=checkbox].is-unchecked-info.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-info.is-outlined+label:after { + background: #209cee +} + +.switch[type=checkbox].is-success:checked+label::before, +.switch[type=checkbox].is-success:checked+label:before { + background: #23d160 +} + +.switch[type=checkbox].is-success.is-outlined:checked+label::before, +.switch[type=checkbox].is-success.is-outlined:checked+label:before { + background-color: transparent; + border-color: #23d160 !important +} + +.switch[type=checkbox].is-success.is-outlined:checked+label::after, +.switch[type=checkbox].is-success.is-outlined:checked+label:after { + background: #23d160 +} + +.switch[type=checkbox].is-success.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-success.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-success+label::before, +.switch[type=checkbox].is-unchecked-success+label:before { + background: #23d160 +} + +.switch[type=checkbox].is-unchecked-success.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-success.is-outlined+label:before { + background-color: transparent; + border-color: #23d160 !important +} + +.switch[type=checkbox].is-unchecked-success.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-success.is-outlined+label:after { + background: #23d160 +} + +.switch[type=checkbox].is-warning:checked+label::before, +.switch[type=checkbox].is-warning:checked+label:before { + background: #ffdd57 +} + +.switch[type=checkbox].is-warning.is-outlined:checked+label::before, +.switch[type=checkbox].is-warning.is-outlined:checked+label:before { + background-color: transparent; + border-color: #ffdd57 !important +} + +.switch[type=checkbox].is-warning.is-outlined:checked+label::after, +.switch[type=checkbox].is-warning.is-outlined:checked+label:after { + background: #ffdd57 +} + +.switch[type=checkbox].is-warning.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-warning.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-warning+label::before, +.switch[type=checkbox].is-unchecked-warning+label:before { + background: #ffdd57 +} + +.switch[type=checkbox].is-unchecked-warning.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-warning.is-outlined+label:before { + background-color: transparent; + border-color: #ffdd57 !important +} + +.switch[type=checkbox].is-unchecked-warning.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-warning.is-outlined+label:after { + background: #ffdd57 +} + +.switch[type=checkbox].is-danger:checked+label::before, +.switch[type=checkbox].is-danger:checked+label:before { + background: #ff3860 +} + +.switch[type=checkbox].is-danger.is-outlined:checked+label::before, +.switch[type=checkbox].is-danger.is-outlined:checked+label:before { + background-color: transparent; + border-color: #ff3860 !important +} + +.switch[type=checkbox].is-danger.is-outlined:checked+label::after, +.switch[type=checkbox].is-danger.is-outlined:checked+label:after { + background: #ff3860 +} + +.switch[type=checkbox].is-danger.is-thin.is-outlined+label::after, +.switch[type=checkbox].is-danger.is-thin.is-outlined+label:after { + box-shadow: none +} + +.switch[type=checkbox].is-unchecked-danger+label::before, +.switch[type=checkbox].is-unchecked-danger+label:before { + background: #ff3860 +} + +.switch[type=checkbox].is-unchecked-danger.is-outlined+label::before, +.switch[type=checkbox].is-unchecked-danger.is-outlined+label:before { + background-color: transparent; + border-color: #ff3860 !important +} + +.switch[type=checkbox].is-unchecked-danger.is-outlined+label::after, +.switch[type=checkbox].is-unchecked-danger.is-outlined+label:after { + background: #ff3860 +} diff --git a/webapp/static/scripts/remote/controller.js b/webapp/static/scripts/remote/controller.js index d28660e..b2a4670 100644 --- a/webapp/static/scripts/remote/controller.js +++ b/webapp/static/scripts/remote/controller.js @@ -4,6 +4,7 @@ var speedController = document.getElementById("speed"); var turnController = document.getElementById("turn"); var speedOMeter = document.getElementById('speed-o-meter'); var turnOMeter = document.getElementById('turn-o-meter'); +var strafingToggle = document.getElementById('isStrafing'); var speedSlider; var turnSlider; var selectDrivetrain = document.getElementById("selectDrivetrain"); @@ -12,19 +13,22 @@ var selectDrivetrain = document.getElementById("selectDrivetrain"); // each gamepad has all info about axis and buttons var gamepads = []; // avoid cluttering socket with duplicate data due to setInterval polling of gamepads -var prevArgs = [0, 0]; +var prevArgs = [0, 0, 0]; // Grab the speed and turning values and update the text as well as send them to the robot -function sendSpeedTurnValues(gamepadAxes = []) { +function sendSpeedTurnValues(gamepadCtrls = []) { let speed = null; let turn = null; - if (gamepadAxes.length){ - speed = Math.round(gamepadAxes[0] * 100); - turn = Math.round(gamepadAxes[1] * 100); + if (gamepadCtrls.length){ + speed = Math.round(gamepadCtrls[0] * 100); + turn = Math.round(gamepadCtrls[1] * 100); + strafe = Boolean(gamepadCtrls[2]); + strafingToggle.checked = strafe; speedSlider.value = speed; turnSlider.value = turn; } else{ + strafe = strafingToggle.checked; speed = speedSlider.value; turn = turnSlider.value; } @@ -32,9 +36,9 @@ function sendSpeedTurnValues(gamepadAxes = []) { speedOMeter.innerText = speed; turnOMeter.innerText = turn; - var args = [turn, speed]; + var args = [turn, speed, strafe]; // only send data if it has changed - if (prevArgs[0] != args[0] || prevArgs[1] != args[1]){ + if (prevArgs[0] != args[0] || prevArgs[1] != args[1] || prevArgs[0] != args[0] ){ prevArgs = args; socket.emit('remoteOut', args, selectDrivetrain.value); } @@ -194,9 +198,11 @@ function getGamepadChanges() { else{ // axis is within deadzone result.push(0); } + result.push(gamepads[0].buttons[5].pressed); } - /* - for (i = 0; i < gamepads.length; i++){ + } + + /* for (i = 0; i < gamepads.length; i++){ // show axes data for (j = 0; j < gamepads[i].axes.length; j++) { var temp = gamepads[i].axes[j]; @@ -212,7 +218,6 @@ function getGamepadChanges() { } } */ - } } // result is empty if no usable data was detected // otherwise result = [speed, turn] diff --git a/webapp/templates/layout.html b/webapp/templates/layout.html index 5cb8d2b..38b53a1 100644 --- a/webapp/templates/layout.html +++ b/webapp/templates/layout.html @@ -7,6 +7,7 @@ {{ title }} + @@ -236,4 +237,4 @@ - \ No newline at end of file + diff --git a/webapp/templates/remote.html b/webapp/templates/remote.html index 455166a..6a735fc 100644 --- a/webapp/templates/remote.html +++ b/webapp/templates/remote.html @@ -42,7 +42,10 @@
- +
+ + +
From 7da591a3e46821b8fa272aa6f0d3dfef2a78a8e3 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 30 Nov 2019 14:17:07 -0800 Subject: [PATCH 13/14] default checkbox to unchecked --- webapp/templates/remote.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/templates/remote.html b/webapp/templates/remote.html index 6a735fc..601fbd2 100644 --- a/webapp/templates/remote.html +++ b/webapp/templates/remote.html @@ -43,7 +43,7 @@
- +
From b9f74b674aacf1f7c630b630baa4e86209f6137d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 7 Dec 2019 02:40:34 -0800 Subject: [PATCH 14/14] remote templates setup --- wiring.fzz => docs/diagrams/wiring.fzz | Bin webapp/routes.py | 2 +- webapp/templates/layout.html | 2 +- .../{remote.html => remotes/generic.html} | 42 +----------------- webapp/templates/remotes/remote.html | 42 ++++++++++++++++++ 5 files changed, 45 insertions(+), 43 deletions(-) rename wiring.fzz => docs/diagrams/wiring.fzz (100%) rename webapp/templates/{remote.html => remotes/generic.html} (55%) create mode 100644 webapp/templates/remotes/remote.html diff --git a/wiring.fzz b/docs/diagrams/wiring.fzz similarity index 100% rename from wiring.fzz rename to docs/diagrams/wiring.fzz diff --git a/webapp/routes.py b/webapp/routes.py index c7065dd..50b3669 100644 --- a/webapp/routes.py +++ b/webapp/routes.py @@ -49,7 +49,7 @@ def home(): @blueprint.route('/remote') @login_required def remote(): - return render_template('remote.html', title='Remote Control', drivetrains=d_train) + return render_template('remotes/remote.html', title='Remote Control', drivetrains=d_train) @blueprint.route('/camera') diff --git a/webapp/templates/layout.html b/webapp/templates/layout.html index 38b53a1..d9bb2fb 100644 --- a/webapp/templates/layout.html +++ b/webapp/templates/layout.html @@ -12,7 +12,7 @@ - diff --git a/webapp/templates/remote.html b/webapp/templates/remotes/generic.html similarity index 55% rename from webapp/templates/remote.html rename to webapp/templates/remotes/generic.html index 601fbd2..0997f44 100644 --- a/webapp/templates/remote.html +++ b/webapp/templates/remotes/generic.html @@ -1,31 +1,4 @@ -{% extends "layout.html" %} - -{% block content %} - - - - -
-
-
- -
-
-
- +{% block remote %}
@@ -63,17 +36,4 @@
- - - - - - - - - - - - - {% endblock %} diff --git a/webapp/templates/remotes/remote.html b/webapp/templates/remotes/remote.html new file mode 100644 index 0000000..f45dbc7 --- /dev/null +++ b/webapp/templates/remotes/remote.html @@ -0,0 +1,42 @@ +{% extends "layout.html" %} +{% block content %} + + + + +
+
+
+ +
+
+
+{% block remote %}{% endblock %} +{% include "remotes/generic.html" %} + + + + + + + + + + + + + +{% endblock %}