diff --git a/utility/GY91.py b/utility/GY91.py new file mode 100644 index 000000000..73f3d7367 --- /dev/null +++ b/utility/GY91.py @@ -0,0 +1,425 @@ +# coding: utf-8 +## @package GY91 +# This is a FaBo9Axis_MPU9250 library for the FaBo 9AXIS I2C Brick. +# http://fabo.io/202.html +# FaBo +# +# Further modified by Cary Dreelan to include the BMP280 sensor data +# that is also on the GY91 IMU/MPU board +# +# Released under APACHE LICENSE, VERSION 2.0 +# +# http://www.apache.org/licenses/ +# + +# import smbus # CD - leverage the i2c bus from the Adafruit libs instead +import time +from ctypes import c_short +from ctypes import c_byte +from ctypes import c_ubyte + + +## MPU9250 Default I2C slave address +SLAVE_ADDRESS = 0x68 +## AK8963 I2C slave address +AK8963_SLAVE_ADDRESS = 0x0C +## Device ids +DEVICE_ID = 0x71 +BMPDEVICE_ID = 0x76 + +''' MPU-9250 Register Addresses ''' +## sample rate driver +SMPLRT_DIV = 0x19 +CONFIG = 0x1A +GYRO_CONFIG = 0x1B +ACCEL_CONFIG = 0x1C +ACCEL_CONFIG_2 = 0x1D +LP_ACCEL_ODR = 0x1E +WOM_THR = 0x1F +FIFO_EN = 0x23 +I2C_MST_CTRL = 0x24 +I2C_MST_STATUS = 0x36 +INT_PIN_CFG = 0x37 +INT_ENABLE = 0x38 +INT_STATUS = 0x3A +ACCEL_OUT = 0x3B +TEMP_OUT = 0x41 +GYRO_OUT = 0x43 + +I2C_MST_DELAY_CTRL = 0x67 +SIGNAL_PATH_RESET = 0x68 +MOT_DETECT_CTRL = 0x69 +USER_CTRL = 0x6A +PWR_MGMT_1 = 0x6B +PWR_MGMT_2 = 0x6C +FIFO_R_W = 0x74 +WHO_AM_I = 0x75 + +## Gyro Full Scale Select 250dps +GFS_250 = 0x00 +## Gyro Full Scale Select 500dps +GFS_500 = 0x01 +## Gyro Full Scale Select 1000dps +GFS_1000 = 0x02 +## Gyro Full Scale Select 2000dps +GFS_2000 = 0x03 +## Accel Full Scale Select 2G +AFS_2G = 0x00 +## Accel Full Scale Select 4G +AFS_4G = 0x01 +## Accel Full Scale Select 8G +AFS_8G = 0x02 +## Accel Full Scale Select 16G +AFS_16G = 0x03 + +# AK8963 Register Addresses +AK8963_ST1 = 0x02 +AK8963_MAGNET_OUT = 0x03 +AK8963_CNTL1 = 0x0A +AK8963_CNTL2 = 0x0B +AK8963_ASAX = 0x10 + +# CNTL1 Mode select +## Power down mode +AK8963_MODE_DOWN = 0x00 +## One shot data output +AK8963_MODE_ONE = 0x01 + +## Continous data output 8Hz +AK8963_MODE_C8HZ = 0x02 +## Continous data output 100Hz +AK8963_MODE_C100HZ = 0x06 + +# Magneto Scale Select +## 14bit output +AK8963_BIT_14 = 0x00 +## 16bit output +AK8963_BIT_16 = 0x01 + +# Some BMP280 utility functions +def getShort(data, index): + # return two bytes from data as a signed 16-bit value + return c_short((data[index+1] << 8) + data[index]).value + +def getUShort(data, index): + # return two bytes from data as an unsigned 16-bit value + return (data[index+1] << 8) + data[index] + +def getChar(data,index): + # return one byte from data as a signed char + result = data[index] + if result > 127: + result -= 256 + return result + +def getUChar(data,index): + # return one byte from data as an unsigned char + result = data[index] & 0xFF + return result + +## smbus - CD now pass the 'bus' object into the class rather than be global +# bus = smbus.SMBus(1) # CD - removed to allow using the passed Adafruit i2cbus object + +## GY91 = MPU9250 + BMP280 I2C Controll class +class GY91: + + ## Constructor + # @param [in] address MPU-9250 I2C slave address default:0x68 + def __init__(self, bus, address=SLAVE_ADDRESS): + self.bus = bus + self.address = address + self.configMPU9250(GFS_250, AFS_2G) + self.configAK8963(AK8963_MODE_C8HZ, AK8963_BIT_16) + + ## Search Device + # @param [in] self The object pointer. + # @retval true device connected + # @retval false device error + def searchDevice(self): + who_am_i = self.bus.read_byte_data(self.address, WHO_AM_I) + if(who_am_i == DEVICE_ID): + return true + else: + return false + + ## Configure MPU-9250 + # @param [in] self The object pointer. + # @param [in] gfs Gyro Full Scale Select(default:GFS_250[+250dps]) + # @param [in] afs Accel Full Scale Select(default:AFS_2G[2g]) + def configMPU9250(self, gfs, afs): + if gfs == GFS_250: + self.gres = 250.0/32768.0 + elif gfs == GFS_500: + self.gres = 500.0/32768.0 + elif gfs == GFS_1000: + self.gres = 1000.0/32768.0 + else: # gfs == GFS_2000 + self.gres = 2000.0/32768.0 + + if afs == AFS_2G: + self.ares = 2.0/32768.0 + elif afs == AFS_4G: + self.ares = 4.0/32768.0 + elif afs == AFS_8G: + self.ares = 8.0/32768.0 + else: # afs == AFS_16G: + self.ares = 16.0/32768.0 + + # sleep off + self.bus.write_byte_data(self.address, PWR_MGMT_1, 0x00) + time.sleep(0.1) + # auto select clock source + self.bus.write_byte_data(self.address, PWR_MGMT_1, 0x01) + time.sleep(0.1) + # DLPF_CFG + self.bus.write_byte_data(self.address, CONFIG, 0x03) + # sample rate divider + self.bus.write_byte_data(self.address, SMPLRT_DIV, 0x04) + # gyro full scale select + self.bus.write_byte_data(self.address, GYRO_CONFIG, gfs << 3) + # accel full scale select + self.bus.write_byte_data(self.address, ACCEL_CONFIG, afs << 3) + # A_DLPFCFG + self.bus.write_byte_data(self.address, ACCEL_CONFIG_2, 0x03) + # BYPASS_EN + self.bus.write_byte_data(self.address, INT_PIN_CFG, 0x02) + time.sleep(0.1) + + ## Configure AK8963 + # @param [in] self The object pointer. + # @param [in] mode Magneto Mode Select(default:AK8963_MODE_C8HZ[Continous 8Hz]) + # @param [in] mfs Magneto Scale Select(default:AK8963_BIT_16[16bit]) + def configAK8963(self, mode, mfs): + if mfs == AK8963_BIT_14: + self.mres = 4912.0/8190.0 + else: # mfs == AK8963_BIT_16: + self.mres = 4912.0/32760.0 + + self.bus.write_byte_data(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, 0x00) + time.sleep(0.01) + + # set read FuseROM mode + self.bus.write_byte_data(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, 0x0F) + time.sleep(0.01) + + # read coef data + data = self.bus.read_i2c_block_data(AK8963_SLAVE_ADDRESS, AK8963_ASAX, 3) + + self.magXcoef = (data[0] - 128) / 256.0 + 1.0 + self.magYcoef = (data[1] - 128) / 256.0 + 1.0 + self.magZcoef = (data[2] - 128) / 256.0 + 1.0 + + # set power down mode + self.bus.write_byte_data(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, 0x00) + time.sleep(0.01) + + # set scale&continous mode + self.bus.write_byte_data(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, (mfs<<4|mode)) + time.sleep(0.01) + + ## brief Check data ready + # @param [in] self The object pointer. + # @retval true data is ready + # @retval false data is not ready + def checkDataReady(self): + drdy = self.bus.read_byte_data(self.address, INT_STATUS) + if drdy & 0x01: + return True + else: + return False + + ## Read accelerometer + # @param [in] self The object pointer. + # @retval x : x-axis data + # @retval y : y-axis data + # @retval z : z-axis data + def readAccel(self): + data = self.bus.read_i2c_block_data(self.address, ACCEL_OUT, 6) + x = self.dataConv(data[1], data[0]) + y = self.dataConv(data[3], data[2]) + z = self.dataConv(data[5], data[4]) + + x = round(x*self.ares, 3) + y = round(y*self.ares, 3) + z = round(z*self.ares, 3) + + return {"x":x, "y":y, "z":z} + + ## Read gyro + # @param [in] self The object pointer. + # @retval x : x-gyro data + # @retval y : y-gyro data + # @retval z : z-gyro data + def readGyro(self): + data = self.bus.read_i2c_block_data(self.address, GYRO_OUT, 6) + + x = self.dataConv(data[1], data[0]) + y = self.dataConv(data[3], data[2]) + z = self.dataConv(data[5], data[4]) + + x = round(x*self.gres, 3) + y = round(y*self.gres, 3) + z = round(z*self.gres, 3) + + return {"x":x, "y":y, "z":z} + + ## Read magneto + # @param [in] self The object pointer. + # @retval x : X-magneto data + # @retval y : y-magneto data + # @retval z : Z-magneto data + def readMagnet(self): + x=0 + y=0 + z=0 + + # check data ready + drdy = self.bus.read_byte_data(AK8963_SLAVE_ADDRESS, AK8963_ST1) + if drdy & 0x01 : + data = self.bus.read_i2c_block_data(AK8963_SLAVE_ADDRESS, AK8963_MAGNET_OUT, 7) + + # check overflow + if (data[6] & 0x08)!=0x08: + x = self.dataConv(data[0], data[1]) + y = self.dataConv(data[2], data[3]) + z = self.dataConv(data[4], data[5]) + + x = round(x * self.mres * self.magXcoef, 3) + y = round(y * self.mres * self.magYcoef, 3) + z = round(z * self.mres * self.magZcoef, 3) + + return {"x":x, "y":y, "z":z} + + ## Read temperature from MPU9250 (BMP280 below also has a temp sensor) + # @param [out] temperature temperature(degrees C) + def readTemperature(self): + data = self.bus.read_i2c_block_data(self.address, TEMP_OUT, 2) + temp = self.dataConv(data[1], data[0]) + + temp = round((temp / 333.87 + 21.0), 3) + return temp + + ## Data Convert + # @param [in] self The object pointer. + # @param [in] data1 LSB + # @param [in] data2 MSB + # @retval Value MSB+LSB(int 16bit) + def dataConv(self, data1, data2): + value = data1 | (data2 << 8) + if(value & (1 << 16 - 1)): + value -= (1<<16) + return value + + + # BMP280 stuff + # CD - added when converting class from MPU9250 to GY91 class + # GY91 is a board that has both an MPU9250 & a BMP280 on same i2c bus + # - Note code was taken from a BME280 example (note E not P) that has humidity + # and the P version does not have this ability, so humidity code commented out + + def readBMP280ID(self, addr=BMPDEVICE_ID): + # Chip ID Register Address + REG_ID = 0xD0 + (chip_id, chip_version) = self.bus.read_i2c_block_data(addr, REG_ID, 2) + return (chip_id, chip_version) + + def readBMP280All(self, addr=BMPDEVICE_ID): + # Register Addresses + REG_DATA = 0xF7 + REG_CONTROL = 0xF4 + REG_CONFIG = 0xF5 + + # REG_CONTROL_HUM = 0xF2 + # REG_HUM_MSB = 0xFD + # REG_HUM_LSB = 0xFE + + # Oversample setting - page 27 + OVERSAMPLE_TEMP = 2 + OVERSAMPLE_PRES = 2 + MODE = 1 + + # # Oversample setting for humidity register - page 26 + # OVERSAMPLE_HUM = 2 + # self.bus.write_byte_data(addr, REG_CONTROL_HUM, OVERSAMPLE_HUM) + + control = OVERSAMPLE_TEMP<<5 | OVERSAMPLE_PRES<<2 | MODE + self.bus.write_byte_data(addr, REG_CONTROL, control) + + # Read blocks of calibration data from EEPROM + # See Page 22 data sheet + cal1 = self.bus.read_i2c_block_data(addr, 0x88, 24) + cal2 = self.bus.read_i2c_block_data(addr, 0xA1, 1) + cal3 = self.bus.read_i2c_block_data(addr, 0xE1, 7) + + # Convert byte data to word values + dig_T1 = getUShort(cal1, 0) + dig_T2 = getShort(cal1, 2) + dig_T3 = getShort(cal1, 4) + + dig_P1 = getUShort(cal1, 6) + dig_P2 = getShort(cal1, 8) + dig_P3 = getShort(cal1, 10) + dig_P4 = getShort(cal1, 12) + dig_P5 = getShort(cal1, 14) + dig_P6 = getShort(cal1, 16) + dig_P7 = getShort(cal1, 18) + dig_P8 = getShort(cal1, 20) + dig_P9 = getShort(cal1, 22) + + # dig_H1 = getUChar(cal2, 0) + # dig_H2 = getShort(cal3, 0) + # dig_H3 = getUChar(cal3, 2) + + # dig_H4 = getChar(cal3, 3) + # dig_H4 = (dig_H4 << 24) >> 20 + # dig_H4 = dig_H4 | (getChar(cal3, 4) & 0x0F) + + # dig_H5 = getChar(cal3, 5) + # dig_H5 = (dig_H5 << 24) >> 20 + # dig_H5 = dig_H5 | (getUChar(cal3, 4) >> 4 & 0x0F) + + # dig_H6 = getChar(cal3, 6) + + # Wait in ms (Datasheet Appendix B: Measurement time and current calculation) + wait_time = 1.25 + (2.3 * OVERSAMPLE_TEMP) + ((2.3 * OVERSAMPLE_PRES) + 0.575) # + ((2.3 * OVERSAMPLE_HUM)+0.575) + time.sleep(wait_time/1000) # Wait the required time + + # Read temperature/pressure/humidity + data = self.bus.read_i2c_block_data(addr, REG_DATA, 8) + pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4) + temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4) + # hum_raw = (data[6] << 8) | data[7] + + #Refine temperature + var1 = ((((temp_raw>>3)-(dig_T1<<1)))*(dig_T2)) >> 11 + var2 = (((((temp_raw>>4) - (dig_T1)) * ((temp_raw>>4) - (dig_T1))) >> 12) * (dig_T3)) >> 14 + t_fine = var1+var2 + temperature = float(((t_fine * 5) + 128) >> 8); + + # Refine pressure and adjust for temperature + var1 = t_fine / 2.0 - 64000.0 + var2 = var1 * var1 * dig_P6 / 32768.0 + var2 = var2 + var1 * dig_P5 * 2.0 + var2 = var2 / 4.0 + dig_P4 * 65536.0 + var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0 + var1 = (1.0 + var1 / 32768.0) * dig_P1 + if var1 == 0: + pressure=0 + else: + pressure = 1048576.0 - pres_raw + pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1 + var1 = dig_P9 * pressure * pressure / 2147483648.0 + var2 = pressure * dig_P8 / 32768.0 + pressure = pressure + (var1 + var2 + dig_P7) / 16.0 + + # # Refine humidity + # humidity = t_fine - 76800.0 + # humidity = (hum_raw - (dig_H4 * 64.0 + dig_H5 / 16384.0 * humidity)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * humidity * (1.0 + dig_H3 / 67108864.0 * humidity))) + # humidity = humidity * (1.0 - dig_H1 * humidity / 524288.0) + # if humidity > 100: + # humidity = 100 + # elif humidity < 0: + # humidity = 0 + + return temperature/100.0,pressure/100.0 # ,humidity + diff --git a/utility/dingo_ui.py b/utility/dingo_ui.py index 92439122b..33d1a2679 100755 --- a/utility/dingo_ui.py +++ b/utility/dingo_ui.py @@ -30,14 +30,17 @@ import subprocess import time +import signal -# MCP3021 is DingoCar PCB battery voltage ADC on I2C bus +# IMU library +import GY91 +# MCP3021 is DingoCar PCB battery voltage ADC on I2C bus def get_battery_voltage(): MCP3021_I2CADDR = 0x4d # default address MCP3021_RATIO = 0.0169 # to times ADC value to conv to voltage based on our voltage divider - adc_val = disp._i2c._bus.read_word_data(MCP3021_I2CADDR, 0) + adc_val = i2cbus.read_word_data(MCP3021_I2CADDR, 0) # from this data we need the last 4 bits and the first 6. last_4 = adc_val & 0b1111 # using a bit mask @@ -49,6 +52,20 @@ def get_battery_voltage(): return round(vratio * MCP3021_RATIO, 2) +# Signal catcher to handle graceful termination of process (e.g. shutdown/poweroff/ctrl-C) +def exit_gracefully(signum, frame): + # Display halting message before exiting + image = Image.new('1', (width, height)) + draw = ImageDraw.Draw(image) + draw.text((0, 0), "HALTING", font=font, fill=255) + image = image.rotate(180) + disp.image(image) + disp.display() + raise(SystemExit) + +signal.signal(signal.SIGINT, exit_gracefully) +signal.signal(signal.SIGTERM, exit_gracefully) + # Raspberry Pi pin configuration: RST = None # on the PiOLED this pin isnt used # Note the following are only used with SPI: @@ -63,6 +80,12 @@ def get_battery_voltage(): disp.clear() disp.display() +# Access the same i2c bus that the Adafruit lib uses +i2cbus = disp._i2c._bus + +# Now have i2c bus, initialise the GY91 module +gy91 = GY91.GY91(bus=i2cbus) + # Create blank image for drawing. # Make sure to create image with mode '1' for 1-bit color. width = disp.width @@ -84,30 +107,88 @@ def get_battery_voltage(): # Some other nice fonts to try: http://www.dafont.com/bitmap.php # font = ImageFont.truetype('Minecraftia.ttf', 8) +# Can display multiple pages, which one to display +currentPage = 0 # select which page/group of info to display +framesDisplayed = 0 # increment each time we do an update + while True: + # Setup and draw for every page type draw.rectangle((0,0,width,height), outline=0, fill=0) - - # Shell scripts for system monitoring from here : https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load - cmd = "hostname -I | cut -d\' \' -f1" - IP = subprocess.check_output(cmd, shell = True ).decode('utf-8') - cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'" - CPU = subprocess.check_output(cmd, shell = True ).decode('utf-8') - cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%sMB %.2f%%\", $3,$2,$3*100/$2 }'" - MemUsage = subprocess.check_output(cmd, shell = True ).decode('utf-8') - cmd = "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%dGB %s\", $3,$2,$5}'" - Disk = subprocess.check_output(cmd, shell = True ).decode('utf-8') - - # Battery Voltage battery = get_battery_voltage() - draw.text((x, top), "DingoCar", font=font, fill=255) - draw.text((x, top+8), "Battery:" + str(battery), font=font, fill=255) - draw.text((x, top+16), "IP: " + str(IP), font=font, fill=255) - draw.text((x, top+40), str(CPU), font=font, fill=255) - draw.text((x, top+48), str(MemUsage), font=font, fill=255) - draw.text((x, top+56), str(Disk), font=font, fill=255) + draw.text((x, top+8), "Battery:" + str(battery)+"V", font=font, fill=255) + + if currentPage == 0: + + # Main page + + # Shell scripts for system monitoring from here : https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load + cmd = "hostname -I | cut -d\' \' -f1" + IP = subprocess.check_output(cmd, shell = True ).decode('utf-8') + cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'" + CPU = subprocess.check_output(cmd, shell = True ).decode('utf-8') + cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%sMB %.2f%%\", $3,$2,$3*100/$2 }'" + MemUsage = subprocess.check_output(cmd, shell = True ).decode('utf-8') + cmd = "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%dGB %s\", $3,$2,$5}'" + Disk = subprocess.check_output(cmd, shell = True ).decode('utf-8') + + draw.text((x, top+24), "IP: " + str(IP), font=font, fill=255) + draw.text((x, top+40), str(CPU), font=font, fill=255) + draw.text((x, top+48), str(MemUsage), font=font, fill=255) + draw.text((x, top+56), str(Disk), font=font, fill=255) + if framesDisplayed > 1: + # Delay before next frame + time.sleep(1) + + # Don't switch pages until we know we have an IP + if IP == '\n': + framesDisplayed = 0 + + # See if we go to next page + if framesDisplayed > 4: + currentPage = 1 + framesDisplayed = 0 + + elif currentPage == 1: + + # IMU page + + # read sensor values + accel = gy91.readAccel() + gyro = gy91.readGyro() + mag = gy91.readMagnet() + temp1 = round(gy91.readTemperature(),1) + temperature, pressure = gy91.readBMP280All() + + # display them + + draw.text((x, top+16), "ax: " + str(accel['x']), font=font, fill=255) + draw.text((x, top+24), "ay: " + str(accel['y']), font=font, fill=255) + draw.text((x, top+32), "az: " + str(accel['z']), font=font, fill=255) + + draw.text((x, top+40), "gx: " + str(gyro['x']), font=font, fill=255) + draw.text((x, top+48), "gy: " + str(gyro['y']), font=font, fill=255) + draw.text((x, top+56), "gz: " + str(gyro['z']), font=font, fill=255) + + draw.text((x+64, top+40), "mx: " + str(mag['x']), font=font, fill=255) + draw.text((x+64, top+48), "my: " + str(mag['y']), font=font, fill=255) + draw.text((x+64, top+56), "mz: " + str(mag['z']), font=font, fill=255) + + #draw.text((x+64, top+16), "T1: " + str(temp1), font=font, fill=255) + draw.text((x+64, top+16), "T:" + str(round(temperature,1)) + "C", font=font, fill=255) + draw.text((x+64, top+24), "P:" + str(round(pressure,2)) + "hPa", font=font, fill=255) + + # See if we go to next page + if framesDisplayed > 25: + currentPage = 0 + framesDisplayed = 0 + + time.sleep(0.1) + + # Now display whats been presented image_rotated = image.rotate(180) disp.image(image_rotated) disp.display() - time.sleep(1) + framesDisplayed += 1 + diff --git a/utility/dingo_ui_boot.service b/utility/dingo_ui_boot.service index 82a2973d0..5856bd3bd 100644 --- a/utility/dingo_ui_boot.service +++ b/utility/dingo_ui_boot.service @@ -1,10 +1,9 @@ [Unit] -Description=Start DingoCar control and monitoring +Description=DingoCar OLED user interface After=network.target [Service] ExecStart=/home/pi/play/ai/dingocar/utility/dingo_env.sh dingo_ui.py -ExecStop=/home/pi/play/ai/dingocar/utility/dingo_env.sh oled_message.py Halting WorkingDirectory=/home/pi/play/ai/dingocar/utility StandardOutput=inherit StandardError=inherit diff --git a/utility/dingo_ui_halt.service b/utility/dingo_ui_halt.service deleted file mode 100644 index dbac2ae92..000000000 --- a/utility/dingo_ui_halt.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Turn power off after shutdown -DefaultDependencies=no -Before=umount.target - -[Service] -Type=oneshot -ExecStart=/home/pi/play/ai/dingocar/utility/dingo_env.sh oled_message.py Halt -WorkingDirectory=/home/pi/play/ai/dingocar/utility -User=pi - -[Install] -WantedBy=halt.target poweroff.target diff --git a/utility/oled_message.py b/utility/oled_message.py index 4f30a4356..b1971b63c 100755 --- a/utility/oled_message.py +++ b/utility/oled_message.py @@ -19,11 +19,11 @@ height = disp.height if len(sys.argv) > 1: - message = sys.argv[1] + message = str(' '.join(sys.argv[1:])) image = Image.new('1', (width, height)) text = textwrap.fill( - message, width=8, initial_indent=' ', subsequent_indent=' ', + message, width=21, initial_indent='', subsequent_indent='', break_long_words=False) draw = ImageDraw.Draw(image) draw.text((0, 0), text, font=font, fill=255) diff --git a/utility/oled_power_off.sh b/utility/oled_power_off.sh new file mode 100644 index 000000000..8d1c0b384 --- /dev/null +++ b/utility/oled_power_off.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# First wait a second to let the rest of the system scripts shutdown +#/bin/sleep 1 + +# Send i2c command to directly power off the OLED +# i2c address = 0x3C, command byte = 0x00, display off cmd = 0xAE +/usr/sbin/i2cset -y 1 0x3C 0x00 0xAE \ No newline at end of file diff --git a/utility/upgrade_to_v0.sh b/utility/upgrade_to_v0.sh index 3d12195cb..4af35660b 100755 --- a/utility/upgrade_to_v0.sh +++ b/utility/upgrade_to_v0.sh @@ -4,11 +4,12 @@ # ~~~~~ # sudo ./upgrade_to_v0.sh +# Setup dingo_ui service to display info on OLED on boot cp dingo_ui_boot.service /etc/systemd/system systemctl enable dingo_ui_boot.service -# Currently, -cp dingo_ui_halt.service /etc/systemd/system -systemctl disable dingo_ui_halt.service +# Turn off OLED display as late as possible to signal its ok to pull power to the Pi +cp oled_power_off.sh /lib/systemd/system-shutdown/ +chmod +x /lib/systemd/system-shutdown/oled_power_off.sh systemctl daemon-reload