Catching stdout and camera frames in realtime from subprocess - python

I have a wireless sensor node that transmits wirelessly to a receiver connected to my computer. I use this command to see the data in real time
sudo stdbuf -o0 ./pip_sense.v2 l l | stdbuf -o0 grep -P "TX:03(376|004)
I am new to Python, but I was able to develop a piece of code as show below to capture the stdout and takes frames from a camera connected to the same computer. That is when a packet (data) is received, at that timestamp, a frame will be captured.
It is working fine, except the fact that its running slow. I am running my sensor at a rate of 10 Hz (transmitting every 0.1 sec.) but the code does what is required at a slower rate. I know that is because I am using cv2.imwrite() (I commented out this part and that helped in reducing the delay). But also, as discussed in this post and its said (as I understand) that using shell=True can invoke extra shell process and thus increase the delay.
Also, looking at this, Popen() seems to be causing delays as well. A solution is proposed in the second link, but I do not really understand how to modify it to work for me.
#!/usr/bin/env python
from subprocess import Popen, PIPE
import time
import sys
import cv2
import os
import csv
import argparse
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from collections import OrderedDict
from datetime import datetime
PIP_CMD = 'sudo stdbuf -o0 ./pip_sense.v2 l l | stdbuf -o0 grep -P "TX:03(376|004)"'
def run(command):
process = Popen(command, stdout=PIPE, shell=True)
while True:
line = process.stdout.readline().rstrip()
if not line:
break
yield line
print ('Starting...')
def createFolder(directory):
try:
if not os.path.exists(directory):
os.makedirs(directory)
except OSError:
print ('Error: Creating directory. ' + directory)
createFolder('/home/piptag/pip-test/capture_webcam/EXPERIMENTS')
file1 = open('/home/piptag/pip-test/capture_webcam/EXPERIMENTS/3376.txt','w')
file1.write("Time Stamp \t TX ID \t RSSI \tSEQ NO \tCAP \tX-Axis \tY-Axis \tZ-Axis \t X \t Y \t Z \n") # Header line
file1.write("-------------------------------------------------------------------------------------------------------------------------------------\n")
file2 = open('/home/piptag/pip-test/capture_webcam/EXPERIMENTS/3004.txt','w')
file2.write("Time Stamp \t TX ID \t RSSI \tSEQ NO \tX-Axis \tY-Axis \tZ-Axis \t X \t Y \t Z \n") # Header line
file2.write("-------------------------------------------------------------------------------------------------------------------------------------\n")
dirname = "/home/piptag/pip-test/capture_webcam/EXPERIMENTS/"
def save_webcam(dirname):
cam = cv2.VideoCapture(-1) # was getting error (V4L: can't open camera by index 0), thus changes it to -1
jpg_quality = 75
frame_number = 0
if cam.isOpened():
ret_val, img = cam.read()
else:
ret_val = False
timestamp = int(TS)
path = dirname + str(tx) + str("-") + str(timestamp) + ".jpg"
cv2.imwrite(path, img, [int(cv2.IMWRITE_JPEG_QUALITY), jpg_quality])
frame_number = frame_number + 1
##this is the end of the camera program
def twos_comp(val, bits):
"""compute the 2's compliment of int value val"""
if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
val = val - (1 << bits) # compute negative value
return val # return positive value as is
#########################################MAIN###################################
if __name__ == "__main__":
for line in run(PIP_CMD):
raw_data = line.split('\t')
if len(raw_data) > 1:
TS = raw_data[0][3:]
tx = raw_data[3].split(':')[-1]
rssi = float(raw_data[4][5:])
crc_ok = True if (raw_data[5] != b'BAD CRC') else False
no_drop = True if (raw_data[1] == b'Drop:0') else False
# If CRC check is ok and no drop, process the packet
if crc_ok and no_drop:
data = raw_data[-1].split(':')[-1].split()
cat = ""
for i in data:
cat += str(i)
if tx == '03376':
save_webcam(dirname)
print data
CapStatus=data[1]
if CapStatus == '50':
Cap='0'
elif CapStatus == '51':
Cap='1'
SEQNO1=str(int((data[2]),16))
x_axis1=data[3]+data[4]
y_axis1=data[5]+data[6]
z_axis1=data[7]+data[8]
TX1=tx
x1 = twos_comp(int(x_axis1,16), 16) * 0.0039 #* 9.80665 #the value is multiplied by 0.004 as the ADXL345 reports data as 4mg per 1lsb
y1 = twos_comp(int(y_axis1,16), 16) * 0.0039 #* 9.80665 #the value is multiplied by 0.004 as the ADXL345 reports data as 4mg per 1lsb
z1 = twos_comp(int(z_axis1,16), 16) * 0.0039 #* 9.80665 #the value is multiplied by 0.004 as the ADXL345 reports data as 4mg per 1lsb
st1 = str(TS) + "\t "+ "{:<5}".format(str (TX1)) + "\t" + "{:<10}".format(str (rssi)) + "{:<5}".format(SEQNO1) + "\t" + "{:<5}".format(str (Cap)) + "\t" + "{:<5}".format(str (x_axis1)) + "\t" + "{:<5}".format(str (y_axis1)) + "\t"+ "{:<5}".format(str(z_axis1)) + "\t" + "{:<5}".format(str(x1)) + "\t" + "{:<5}".format(str(y1)) + "\t" + "{:<5}".format(str(z1)) +"\n"
file1.write(st1)
elif tx == '03004':
save_webcam(dirname)
print data
SEQNO2=str(int((data[1]),16))
x_axis2=data[2]+data[3]
y_axis2=data[4]+data[5]
z_axis2=data[6]+data[7]
TX2=tx
x2 = twos_comp(int(x_axis2,16), 16) * 0.0039 #* 9.80665 #the value is multiplied by 0.004 as the ADXL345 reports dataas 4mg per 1lsb
y2 = twos_comp(int(y_axis2,16), 16) * 0.0039 #* 9.80665 #the value is multiplied by 0.004 as the ADXL345 reports data as 4mg per 1lsb
z2 = twos_comp(int(z_axis2,16), 16) * 0.0039 #* 9.80665 #the value is multiplied by 0.004 as the ADXL345 reports data as 4mg per 1lsb
st2 = str(TS) + "\t "+ "{:<5}".format(str (TX2)) +"\t "+ "{:<10}".format(str (rssi)) + "{:<5}".format(SEQNO2) + "\t" + "{:<5}".format(str (x_axis2)) + "\t"+ "{:<5}".format(str (y_axis2)) + "\t"+ "{:<5}".format(str(z_axis2))+ "\t"+"{:<5}".format(str(x2)) + "\t" + "{:<5}".format(str(y2))+ "\t"+ "{:<5}".format(str(z2)) +"\n"
file2.write(st2)
file1.close()
file2.close()
I appreciate any help
Thank you

Update:
I got this advice from a friend and it worked.
Calling the cam = cv2.VideoCapture(-1) outside the save_webcam function (as a global variable) reduced the delays a lot.
In this case, the camera will be turned on one time and then capturing images will be done each time save_webcam is called...hence, turning on and off the camera was slowing the process.
However, I would like to enhance the program more, as it get stuck sometimes. So, anyone understands what is in this post and how my stdout part should be modified, then please comment here. I appreciate this.

Related

Lidar and Servo on Raspberry pi 4

I've two separate code bases for Tf-mini(Lidar) and servo motor. I want the both code to run at the same time. I tried to combine them in to a single code, But only one code runs at a time. Please help to rewrite or edit the code.
Code for servo motor here:
import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(7, GPIO.OUT)
p = GPIO.PWM(7, 100)
t = 0.007
r = 20
p.start(r)
while True:
for i in range(5,r):
p.ChangeDutyCycle(i)
print(i)
time.sleep(t)
for i in range(r,5,-1):
p.ChangeDutyCycle(i)
print(i)
time.sleep(t)`
Code for lidar here:
import time
import serial
ser = serial.Serial("/dev/ttyAMA0", 115200)
def read_data():
while True:
counter = ser.in_waiting
if counter > 8:
bytes_serial = ser.read(9)
ser.reset_input_buffer()
if bytes_serial[0] == 0x59 and bytes_serial[1] == 0x59: # this portion is for python3
print("Printing python3 portion")
distance = bytes_serial[2] + bytes_serial[3]*256 # multiplied by 256, because the binary
data is shifted by 8 to the left (equivalent to "<< 8").
# Dist_L, could simply be added resulting in 16-bit data of Dist_Total.
strength = bytes_serial[4] + bytes_serial[5]*256
temperature = bytes_serial[6] + bytes_serial[7]*256
temperature = (temperature/8) - 256
print("Distance:"+ str(distance))
print("Strength:" + str(strength))
if temperature != 0:
print("Temperature:" + str(temperature))
ser.reset_input_buffer()
if bytes_serial[0] == "Y" and bytes_serial[1] == "Y":
distL = int(bytes_serial[2].encode("hex"), 16)
distH = int(bytes_serial[3].encode("hex"), 16)
stL = int(bytes_serial[4].encode("hex"), 16)
stH = int(bytes_serial[5].encode("hex"), 16)
distance = distL + distH*256
strength = stL + stH*256
tempL = int(bytes_serial[6].encode("hex"), 16)
tempH = int(bytes_serial[7].encode("hex"), 16)
temperature = tempL + tempH*256
temperature = (temperature/8) - 256
print("Printing python2 portion")
print("Distance:"+ str(distance) + "\n")
print("Strength:" + str(strength) + "\n")
print("Temperature:" + str(temperature) + "\n")
ser.reset_input_buffer()
if __name__ == "__main__":
try:
if ser.isOpen() == False:
ser.open()
read_data()
except KeyboardInterrupt(): # ctrl + c in terminal.
if ser != None:
ser.close()
print("program interrupted by the user")
Please help me to combine these two code to run at the same time.
I think I see your problem. Both segments of code are sequentially driven and hence it will create problems. Here is what you need to do (pseudo code):
loop forever:
t1=start_time
change duty cycle #remove the wait code
poll the serial port #the ladar code
t2=end_time
if t2 - t1 < t:
wait (t - (t2-t1))
Being an old Arduino fan myself, I see the struggles of handling the servo motor. I have a simple library to do most of the dirty work for me. You are most welcome to use my library via any of the two links:
https://github.com/vikramdayal/RaspberryMotors or https://pypi.org/project/RaspberryMotors/#description

Python while loop not terminating after defined amount of time

For this project, I have been making a vibration measurement device that plots high speed vibrations that are controlled by a motor. Here is the code below:
# Import the modules
from __future__ import division
import spidev, datetime, time
from sys import exit
import RPi.GPIO as GPIO
# Setup SPI
spi = spidev.SpiDev()
spi.open(0, 0)
spi.mode = 3
# Constants
accres, accrate = 1, 15
# Set GPIO chip select pins
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
cs1, cs2, motor = 23, 24, 18
GPIO.setup(cs1, GPIO.OUT)
GPIO.setup(cs2, GPIO.OUT)
GPIO.setup(motor, GPIO.OUT)
# Note: the duty cycle goes from 0.0 to 100.0, with 100.0 being no motor movement,
# and 0.0 being the maximum motor speed.
motor_output = GPIO.PWM(motor, 60)
# Initialize the ADXL345
def initadxl345():
# Set data rate (accrate=15 -> 3200 Hz, 14=1600 Hz, 13=800 Hz, 12=400 Hz, 11=200 Hz, 10=100 Hz etc.)
spi.xfer2([44, accrate])
# Enable full range (10 bits resolution) and +/- 16g 4 LSB
spi.xfer2([49, accres])
# Read the first ADXL x-y-z axes
def readadxl345_1():
# Chip select pin ensures that the first sensor is being read by grounding its pin
GPIO.output(cs1, 0)
GPIO.output(cs2 ,1)
rx = spi.xfer2([242, 0, 0, 0, 0, 0, 0])
out = [rx[1] | (rx[2] << 8), rx[3] | (rx[4] << 8), rx[5] | (rx[6] << 8)]
# Format x-axis
if (out[0] & (1 << 16 - 1 )):
out[0] = out[0] - (1 << 16)
# Format y-axis
if (out[1] & (1 << 16 - 1 )):
out[1] = out[1] - (1<<16)
# Format z-axis
if (out[2] & (1 << 16 - 1 )):
out[2] = out[2] - (1 << 16)
# Return human readable values
return out
# Read the second ADXL x-y-z axes
def readadxl345_2():
# Chip select pin ensures that the first sensor is being read by grounding its pin
GPIO.output(cs1, 1)
GPIO.output(cs2 ,0)
rx = spi.xfer2([242, 0, 0, 0, 0, 0, 0])
out = [rx[1] | (rx[2] << 8), rx[3] | (rx[4] << 8), rx[5] | (rx[6] << 8)]
# Format x-axis
if (out[0] & (1 << 16 - 1 )):
out[0] = out[0] - (1 << 16)
# Format y-axis
if (out[1] & (1 << 16 - 1 )):
out[1] = out[1] - (1<<16)
# Format z-axis
if (out[2] & (1 << 16 - 1 )):
out[2] = out[2] - (1 << 16)
# Return human readable values
return out
print("Vibration Reader Initializing...")
time.sleep(1)
print(GPIO.RPI_INFO)
time.sleep(1)
response = input("Proceed measurements? [Y, n] ")
if response == "Y" or "y":
filename = input("Filename: ")
pwm_speed = float(input("Motor PWM value: "))
# Initialize the ADXL345 accelerometer
print("Initializing ADXL345s...")
initadxl345()
motor_output.start(pwm_speed)
print("Motor is up and running at {}".format(pwm_speed))
time.sleep(1)
timeout = 0.0003125 / 2 # timeout=1/samplerate=>not sufficient measurements. Half the time is sufficient (don't know why!)
timetosend = 1
while True:
with open('/proc/uptime', 'r') as f: # get uptime
uptime_start = float(f.readline().split()[0])
uptime_last = uptime_start
active_file_first = filename + '.csv'
file = open('/var/log/sensor/' + active_file_first, 'w')
while uptime_last < uptime_start + timetosend:
time1 = str(datetime.datetime.now().strftime('%S.%f'))
sensor1 = readadxl345_1()
sensor2 = readadxl345_2()
file.write(str(sensor1[0]) + ',' + str(sensor1[1]) + ',' + str(sensor1[2]) + ',' + str(sensor2[0]) + ',' + str(sensor2[1]) + ',' + str(sensor2[2]) + ',' + time1 + '\n')
# Print data every "timeout" second
elapsed = time.process_time()
current = 0
while(current < timeout):
current = time.process_time() - elapsed
motor_output.stop
print("Motor shutting off and cleaning up GPIO.")
GPIO.cleanup()
elif response == "N" or "n":
print("Quitting...")
time.sleep(1)
quit()
From this I get into trouble. The issue is that once I do choose to proceed, the print statement Motor is up and running at 100.0 displays and nothing happens until I press a random key on the keyboard in which is says
Traceback (most recent call last):
File "accelerometer.py", line 116, in <module>
current = time.process_time() - elapsed
KeyboardInterrupt
Its as if the code following that print statement didn't even exist, or the interpreter just ignored it completely. Does anyone have a coherent explanation of these problems?
Let's take a step back and think about what this block of code is doing:
while True:
### Block 1 ###
with open('/proc/uptime', 'r') as f: # get uptime
uptime_start = float(f.readline().split()[0])
uptime_last = uptime_start
active_file_first = filename + '.csv'
file = open('/var/log/sensor/' + active_file_first, 'w')
### Block 2 ###
while uptime_last < uptime_start + timetosend:
time1 = str(datetime.datetime.now().strftime('%S.%f'))
sensor1 = readadxl345_1()
sensor2 = readadxl345_2()
file.write(str(sensor1[0]) + ',' + str(sensor1[1]) + ',' + str(sensor1[2]) + ',' + str(sensor2[0]) + ',' + str(sensor2[1]) + ',' + str(sensor2[2]) + ',' + time1 + '\n')
# Print data every "timeout" second
elapsed = time.process_time()
current = 0
### Block 3 ###
while(current < timeout):
current = time.process_time() - elapsed
Block 1 checks an uptime file, reads the time and then opens a file. However, if this uptime_start variable never changes, then we can just set it once outside of (and instead of) the top while loop. Block 2 then checks uptime_last < uptime_start + x, but in the previous few lines, you've defined uptime_last = uptime_start. This means that for as long as the timetosend is not negative, this loop will execute infinitely unless an internal break condition is met. Block 2 writes sensor data to file. Block 3 does a time calculation until a condition is met, but doesn't really do anything with this information.
My guess as to what could be a good answer is this:
### Block 1 ###
with open('/proc/uptime', 'r') as f: # get uptime
uptime_start = float(f.readline().split()[0])
file = open('/var/log/sensor/' + filename + '.csv', 'w')
time_elapsed = 0
time_start = time.process_time()
### Block 2 ###
while time_elapsed < uptime_start:
time1 = str(datetime.datetime.now().strftime('%S.%f'))
sensor1 = readadxl345_1()
sensor2 = readadxl345_2()
file.write(str(sensor1[0]) + ',' + str(sensor1[1]) + ',' + str(sensor1[2]) + ',' + str(sensor2[0]) + ',' + str(sensor2[1]) + ',' + str(sensor2[2]) + ',' + time1 + '\n')
time_elapsed = time.process_time() - time_start

Print does not fit the data interval settings

I am using this answer here and I have this code which get the gauge strain signal (2 channels) and then I apply a calibration step, then I print the 2 channels and the current time. But it looks like the print does not match the frequency I use in my code.
from time import sleep
from Phidget22.Phidget import *
from Phidget22.Devices.VoltageRatioInput import *
import time
import datetime
TIME_OUT = 5000 #5s beofre it throws a timeout exception
DATA_INTERVAL = 50 #50ms sample frequency
A0 = -6.128983223994E-06
B0 = -0.000059639277340
A1 = -6.101017778744E-06
B1 = -0.000286467338645
def onVoltageRatioChange0(self, voltageRatio):
Masse = (voltageRatio - (B0) ) / (A0)
self.masse = Masse
def onVoltageRatioChange1(self, voltageRatio):
Masse = (voltageRatio - (B1) ) / (A1)
self.masse = Masse
def results():
voltageRatioInput0 = VoltageRatioInput()
voltageRatioInput0.masse = 0
voltageRatioInput0.setChannel(0)
voltageRatioInput0.setOnVoltageRatioChangeHandler(onVoltageRatioChange0)
voltageRatioInput0.openWaitForAttachment(TIME_OUT)
voltageRatioInput0.setBridgeGain(BridgeGain.BRIDGE_GAIN_128)
voltageRatioInput0.setDataInterval(DATA_INTERVAL)
voltageRatioInput1 = VoltageRatioInput()
voltageRatioInput1.masse = 0
voltageRatioInput1.setChannel(1)
voltageRatioInput1.setOnVoltageRatioChangeHandler(onVoltageRatioChange1)
voltageRatioInput1.openWaitForAttachment(TIME_OUT)
voltageRatioInput1.setBridgeGain(BridgeGain.BRIDGE_GAIN_128)
voltageRatioInput1.setDataInterval(DATA_INTERVAL)
print(str(datetime.datetime.now()) + " / " + str(voltageRatioInput0.masse) + " / " + str(voltageRatioInput1.masse))
voltageRatioInput0.close()
voltageRatioInput1.close()
if __name__ == '__main__':
try:
while True:
results()
except KeyboardInterrupt:
print("Goodbye")
pass
So normally using the voltageRatioInput0.setDataInterval(DATA_INTERVAL) it should print the value with a frequency of 20 hz. But it prints me this :
2019-11-22 10:24:59.460503 / -0.03847266852956798 / 0.2918630004986786
2019-11-22 10:25:00.099831 / -0.03695316689942101 / -0.02070779820379342
2019-11-22 10:25:00.772398 / -0.04029613574942367 / 0.28775155534154484
2019-11-22 10:25:01.420283 / -0.043487203384171676 / 0.25676361089420047
So clearly here I do not have 20 Hz...
A lot of time is probably spent in your result() function. I cannot reproduce here, but a simple
python -m cProfile -s tottime your_program.py
should help you to see where most of the time is spent each loop iteration.
I guess that instanciation of a VoltageRatioInput() and VoltageRatioInput.close() are a good way to start. Could you try taking them outside the loop as in:
from Phidget22.Phidget import *
from Phidget22.Devices.VoltageRatioInput import *
import datetime
TIME_OUT = 5000 #5s beofre it throws a timeout exception
DATA_INTERVAL = 50 #50ms sample frequency
A0 = -6.128983223994E-06
B0 = -0.000059639277340
A1 = -6.101017778744E-06
B1 = -0.000286467338645
def onVoltageRatioChange0(self, voltageRatio):
Masse = (voltageRatio - (B0) ) / (A0)
self.masse = Masse
def onVoltageRatioChange1(self, voltageRatio):
Masse = (voltageRatio - (B1) ) / (A1)
self.masse = Masse
def results(voltageRatioInput0, voltageRatioInput1):
voltageRatioInput0.masse = 0
voltageRatioInput0.setChannel(0)
voltageRatioInput0.setOnVoltageRatioChangeHandler(onVoltageRatioChange0)
voltageRatioInput0.openWaitForAttachment(TIME_OUT)
voltageRatioInput0.setBridgeGain(BridgeGain.BRIDGE_GAIN_128)
voltageRatioInput0.setDataInterval(DATA_INTERVAL)
voltageRatioInput1.masse = 0
voltageRatioInput1.setChannel(1)
voltageRatioInput1.setOnVoltageRatioChangeHandler(onVoltageRatioChange1)
voltageRatioInput1.openWaitForAttachment(TIME_OUT)
voltageRatioInput1.setBridgeGain(BridgeGain.BRIDGE_GAIN_128)
voltageRatioInput1.setDataInterval(DATA_INTERVAL)
print(str(datetime.datetime.now()) + " / " + str(voltageRatioInput0.masse) + " / " + str(voltageRatioInput1.masse))
if __name__ == '__main__':
try:
voltageRatioInput0 = VoltageRatioInput()
voltageRatioInput1 = VoltageRatioInput()
while True:
results(voltageRatioInput0, voltageRatioInput1)
voltageRatioInput0.close()
voltageRatioInput1.close()
except KeyboardInterrupt:
print("Goodbye")

Print a fancy string and update it after it's printed to simulate a GUI

I don't know if this can be done but I'm sure I saw this in some scripts, that's why I'm asking.
I need to print a string like this one:
++++++++++++++++++++++++++++++ +++++++++++++++
+ A 1 ++ A 2 + + A n +
+-------------++-------------+ +-------------+
+ B 1 ++ B 2 + ... + B n +
+-------------++-------------+ +-------------+
+ C 1 ++ C 2 + + C n +
++++++++++++++++++++++++++++++ +++++++++++++++
where n is the number of columns and it depends on user's input.
The A row is fixed, while B and C has to change while the program goes.
So, first of all I need a way to print this kind of string, knowing that A and B are strings with length of 8 but C goes from 8 to 1 char.
I took a look at the various "formatter" solutions and to ppretty but they seem way too far from what I need (and I didn't found a lot of examples!).
(I've just tried ppretty because the other solutions required something like a data source while I'm getting my data with class.getData() since I'm coming from Java!)
Now, after printing this string, I want it to be updated as B and C changes, without printing it again to avoid a lot of printed stuff and to make everything tidier and easier to be read.
Is there any way to do that?
edit:
This is what I've tried (with no success)
def printCrackingState(threads):
info_string = '''
++++++++++++++++++++++++++++++++++
+ Starting password = s.%08d +
+--------------------------------+
+ Current pin = s.%08d +
++++++++++++++++++++++++++++++++++
+ Missing pins = %08d +
++++++++++++++++++++++++++++++++++
'''
while 1:
for t in threads:
printed_string = info_string % (t.starting_pin, t.testing_pin, t.getMissingPinsCount())
sys.stdout.write(printed_string)
time.sleep(3500)
and this is the result:
++++++++++++++++++++++++++++++++++
+ Starting password = s.00000000 +
+--------------------------------+
+ Current pin = 00000523 +
++++++++++++++++++++++++++++++++++
+ Missing pins = 01249477 +
++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++
+ Starting password = s.01250000 +
+--------------------------------+
+ Current pin = 01250491 +
++++++++++++++++++++++++++++++++++
+ Missing pins = 01249509 +
++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++
+ Starting password = s.02500000 +
+--------------------------------+
+ Current pin = 02500465 +
++++++++++++++++++++++++++++++++++
+ Missing pins = 01249535 +
++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++
+ Starting password = s.03750000 +
+--------------------------------+
+ Current pin = 03750564 +
++++++++++++++++++++++++++++++++++
+ Missing pins = 01249436 +
++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++
+ Starting password = s.05000000 +
+--------------------------------+
+ Current pin = 05000592 +
++++++++++++++++++++++++++++++++++
+ Missing pins = 01249408 +
++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++
+ Starting password = s.06250000 +
+--------------------------------+
+ Current pin = 06250579 +
++++++++++++++++++++++++++++++++++
+ Missing pins = 01249421 +
++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++
+ Starting password = s.07500000 +
+--------------------------------+
+ Current pin = 07500577 +
++++++++++++++++++++++++++++++++++
+ Missing pins = 01249423 +
++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++
+ Starting password = s.08750000 +
+--------------------------------+
+ Current pin = 08750555 +
++++++++++++++++++++++++++++++++++
+ Missing pins = 01249445 +
++++++++++++++++++++++++++++++++++
I've used sys.stdout.write() to have them on the same line but it doesn't work!
edit 2:
My second attemp is with curses, as suggested in the replies.
I can write things on the same line but they don't get updated.
Here's my code:
import curses
import time
import threading
class CursesPrinter(threading.Thread):
windows = []
screen = None
processes = []
info_string = '''
+++++++++++++++++++++++++
+ Starting = s.%08d +
+-----------------------+
+ Current = s.%08d +
+-----------------------+
+ Missing = %08d +
+++++++++++++++++++++++++
'''
def _makeWindows(self, numWindows):
x = 0
y = 0
height = 15
width = 30
for i in range(numWindows):
win = curses.newwin(height, width, x, y)
#win.border(1)
y+=width
if y>self.screen.getmaxyx():
#This should make a new line if we reach the end of the terminal
y = 0
x+=height
self.windows.append(win)
def run(self):
while 1:
for i in range(len(self.processes)-1):
print_string = self.info_string % (self.processes[i].starting_pin, self.processes[i].testing_pin, self.processes[i].getMissingPinsCount())
self.windows[i].addstr(0,0, print_string)
self.windows[i].refresh()
#time.sleep(60)
def __init__(self, threads, processes):
super(CursesPrinter, self).__init__()
self.screen = curses.initscr()
curses.curs_set(0)
self.processes = processes
self._makeWindows(threads)
#curses.endwin()
A quick-and-dirty method that relies on your terminal handling VT100 escape sequences is to clear the screen and then move the cursor to the home position before printing the output.
A quick demo:
import sys
import time
import random
ESC = "\x1b"
def home():
sys.stdout.write(ESC + '[0;0H')
sys.stdout.flush()
def cls():
sys.stdout.write(ESC + '[2J')
sys.stdout.flush()
def print_status():
w = 8
print('+' + '-'*w + '+')
for row in range(3):
fmt = '|%%%dd |' % (w - 1)
print(fmt % random.randint(0, 1000))
print('+' + '-'*w + '+')
sys.stdout.flush()
if __name__ == "__main__":
cls()
for k in range(16, 0, -1):
home() # or use cls()
print_status()
time.sleep(0.5)
Instead of using the "home" sequence, you could keep track of how many lines you printed, and print that many "cursor up" escape sequences before printing the next update.
If you want to get fancier, check out the python curses module, blessings, or urwid.
I don't think any of those work in a regular Windows terminal; that's why I asked about the OS you were targeting.
You could just print out a lot of new line characters to make it look like the screen has been cleared. Also its easy to implement and helpful for debugging.
The code would be some thing like:
print "\n"*100

Creating a progress bar in a CLI application

When using tools like bzr, doxygen and scp or wget, I see that all of them have a nice progress bar that looks like this:
|=============>---------| 55% ETA 3:30
I tried writing something like that in C++ using the \b character as many times as I had written something out before. The output was flickering pretty badly and did not look nearly as smooth as the mentioned tools do.
How can I create such a progress bar (or at least change the displayed ETA) with Python smoothly?
Use "\r" to send the cursor to the beginning of the line. Reprint no more than 2-3 times per second to avoid flickering.
this might be useful example:
http://code.google.com/p/corey-projects/source/browse/trunk/python2/progress_bar.py
import sys
import time
class ProgressBar:
def __init__(self, duration):
self.duration = duration
self.prog_bar = '[]'
self.fill_char = '#'
self.width = 40
self.__update_amount(0)
def animate(self):
for i in range(self.duration):
if sys.platform.lower().startswith('win'):
print self, '\r',
else:
print self, chr(27) + '[A'
self.update_time(i + 1)
time.sleep(1)
print self
def update_time(self, elapsed_secs):
self.__update_amount((elapsed_secs / float(self.duration)) * 100.0)
self.prog_bar += ' %ds/%ss' % (elapsed_secs, self.duration)
def __update_amount(self, new_amount):
percent_done = int(round((new_amount / 100.0) * 100.0))
all_full = self.width - 2
num_hashes = int(round((percent_done / 100.0) * all_full))
self.prog_bar = '[' + self.fill_char * num_hashes + ' ' * (all_full - num_hashes) + ']'
pct_place = (len(self.prog_bar) / 2) - len(str(percent_done))
pct_string = '%d%%' % percent_done
self.prog_bar = self.prog_bar[0:pct_place] + \
(pct_string + self.prog_bar[pct_place + len(pct_string):])
def __str__(self):
return str(self.prog_bar)
if __name__ == '__main__':
""" example usage """
# print a static progress bar
# [########## 25% ] 15s/60s
p = ProgressBar(60)
p.update_time(15)
print 'static progress bar:'
print p
# print a static progress bar
# [=================83%============ ] 25s/30s
p = ProgressBar(30)
p.fill_char = '='
p.update_time(25)
print 'static progress bar:'
print p
# print a dynamic updating progress bar on one line:
#
# [################100%##################] 10s/10s
# done
secs = 10
p = ProgressBar(secs)
print 'dynamic updating progress bar:'
print 'please wait %d seconds...' % secs
# spawn asych (threads/processes/etc) code here that runs for secs.
# the call to .animate() blocks the main thread.
p.animate()
print 'done'

Categories

Resources