tl/dr: Is there a simple way to get a command's output during a telnet session with Python?
I am writing a Python script (using 3.4) to automate actions during a telnet session. I want to be able to telnet in, write to the command line to perform an action, and then return the output from that command. I have a method, but it seems like it could be improved upon, and it is not very dynamic.
My current method:
def telnet_in(ip):
"""Telnets into the device"""
tn = telnetlib.Telnet(ip)
tn.read_until(b"login: ")
tn.write(TEL_PASS)
tn.read_until(PROMPT) # Clear buffer of output
return tn
This will telnet into my device just fine for my setup.
The next part I try to cat some information from the device:
def get_model(ip):
"""Returns the device's model"""
session = telnet_in(ip)
session.write(b"cat " + MODEL_DIR + b"model\r\n")
time.sleep(1) # Give it time to enter buffer
model = session.read_very_eager()
model = model[26:-4] # Clean up output to only return model
model = decode_byte_str(model) # Decode to str
telnet_exit(session)
return model
A few things I have noticed/tried:
I have to use the byte str 'b' for when I use tn.write()
I have to use \r as well.
Without those, I do not get a return or I get an error.
Additionally:
The output I get from model = session.read_very_eager() will return the string that I used for my command as well as the prompt after the command's returned output. I have to trim the string down to size to just get the model str.
Given that I have to use 'b' for my strings, the string returned is also a byte string and needs to be decoded. I handle that with my decode_byte_str() just fine:
def decode_byte_str(byte_str):
"""Decodes a byte string using utf-8"""
return byte_str.decode("utf-8")
I am able to get the string in the end that I am looking for, but it seems to be such a convoluted way to just get a returned string. Also, this method is not dynamic, and I have to write individual functions for each info that I want to obtain, given that I have to trim the strings to a certain size. Certainly, I could just "pass" the size that needs to be trimmed, but that is additional work that really shouldn't be needed. Further, if I want to write a function that takes a variable that the user inputs, then I wouldn't know the exact amount to trim before the output. I suppose I could get the length of the str, and then pass that length in, but again that further complicates what should be a simple task.
So, my question is: Is there a simple way to get the command's output during a telnet session with Python? Also, I am not married to using telnetlib. If there is a better lib or method then I am open to those as well.
Thanks in advance!
EDIT:
Here is my updated version, if anyone wants to use it until we can find an easier/better way:
def telnet_in(ip):
"""Telnets into a device"""
tn = telnetlib.Telnet(ip)
tn.read_until(b"login: ")
tn.write(TEL_PASS)
tn.read_until(PROMPT) # Clear buffer of output
return tn
def telnet_exit(session):
"""Exits a telnet session"""
session.write(b"exit\r\n")
def _encode_str(in_str):
"""Encodes a string to a byte string"""
return bytes(in_str, encoding="utf-8")
def _decode_byte_str(byte_str):
"""Decodes a byte string using utf-8"""
return byte_str.decode("utf-8")
def _get_cmd_output(session, cmd_str):
"""Returns output from a command"""
cmd_str_len = len(cmd_str)
bcmd_str = _encode_str(cmd_str) # Encode str to byte str
session.write(bcmd_str)
time.sleep(0.1) # Give it time to enter buffer
cmd_out = session.read_very_eager()
cmd_out = cmd_out[cmd_str_len:PROMPT_LEN] # Trim output to only return command's returned output
cmd_out = _decode_byte_str(cmd_out) # Decode to a str
return cmd_out
def get_model(ip):
"""Returns the device's model"""
session = telnet_in(ip)
cat_model = "cat " + INFO_DIR + "model\r\n"
model = _get_cmd_output(session, cat_model)
telnet_exit(session)
return model
def get_device_id(ip):
"""Returns the device id"""
session = telnet_in(ip)
cat_device_id = "cat " + INFO_DIR + "device_id\r\n"
device_id = _get_cmd_output(session, cat_device_id)
telnet_exit(session)
return device_id
Works for my purposes. I still wonder if there is already some built-in lib that can already handle this stuff.
Related
i need to repeat a cmd which always generated a new random string, after that i need to make sure that this specific string has not been generated before. I never did while loops before and im not able to figure out how to repate the cmd until a result has been found which is not already part of the database. I can't be to specific as this source is closed
all this is packed into a celery task
tasks.py
#app.task
def allocate_new_string(user_pk):
user = User.objects.get(pk=user_pk)
new_string = subprocess.Popen("$get_new_string_cmd", shell=True, stdout=subprocess.PIPE).communicate()[
0].decode('utf-8').strip()
try:
while True:
if Used_String.objects.filter(string=new_string, atom=0).exists():
new_string << how to repeat the command from above here until a new random string has been found?
else:
used_string = Used_String.objects.create(user=user, string=new_string, atom=0)
used_string.save()
logger.info(str("New String has been set)
except:
logger.info(str("Something went wrong while processing the task"))
The function i want to have here is: Search for a none existing sting until one has found that has never been generated before or is at least not part of the database.
the cmd im using isn't openSSL or something like that and it's quite likly that i hit two times the same random generated string.
Thanks in advance
Slight change.
#app.task
def allocate_new_string(user_pk):
user = User.objects.get(pk=user_pk)
try:
Found = True
while Found:
new_string = subprocess.Popen("$get_new_string_cmd", shell=True, stdout=subprocess.PIPE).communicate()[
0].decode('utf-8').strip()
if Used_String.objects.filter(string=new_string, atom=0).exists():
Found = True
else:
used_string = Used_String.objects.create(user=user, string=new_string, atom=0)
used_string.save()
logger.info(str("New String has been set"))
Found = False
except:
logger.info(str("Something went wrong while processing the task"))
I have not tested it but should work. Please try it out and let me know.
Trying to build a URL using the variables I've already setup and then a known part of the URL on the back end. The code will throw the following error:
TypeError: cannot concatenate 'str' and 'NoneType' objects
block_explorer_url = "https://blockexplorer.com/api/addrs/"
#?from=0&to=50
parser = argparse.ArgumentParser(description='Collect and visualize Bitcoin transactions and any related hidden services.')
parser.add_argument("--graph",help="Output filename of the graph file. Example: bitcoin.gexf",default="bitcoingraph.gexf")
parser.add_argument("--address", help="A bitcoin address to begin the search on.",)
args = parser.parse_args()
bitcoin_address = args.address
graph_file = args.graph
#
# Retrieve all bitcoin transactions for a Bitcoin address
#
def get_all_transactions(bitcoin_address):
transactions = []
from_number = 0
to_number = 50
block_explorer_url_full = block_explorer_url + bitcoin_address + "/txs?from=%d&to=%d" % (from_number,to_number)
Logically, having the variables there and then adding on the rest of the URL as a string makes since to me. Where am I going astray?
The problem is that when bitcoin_address is None (not provided by user), your program still tries to concatenate it to a str, which definitely won't work.
To solve that, you could add some code that checks the result of parse_args and raises an error when that happens, such as this:
if args.address is None:
raise ValueError('A bitcoin address must be provided.')
Separately, while your approach to string formatting is generally correct, you should consider moving away from C-style formatting and towards the format method, for example:
At the start of your script:
base_url = 'https://blockexplorer.com/api/addrs/{address}/txs?from={from}&to={to}'
And later in the function:
full_url = base_url.format(address=bitcoin_address,
from=from_number,
to=to_number)
I am developing a new payment_acquirer module for Odoo, and since last week, I am always getting an error when I try to decrypt data that I received through the server.
When I copy the data in an another python file to test, it seems to be working perfectly with the same data, but when I do it in my controller, it returns an error.
This is the code inside my controller :
#http.route('/payment/ariarynet/result', type='http', auth="none", methods=['POST', 'GET'], csrf=False)
def ariarynet_result(self, **post):
""" Handle Ariary.net response and redirect to form_validate"""
_logger.info('Beginning Ariary.net form_feedback with post data %s', pprint.pformat(post)) # debug
key = bytes("477c3551da64136491eff1cb6ab27be35093b2512eb78f2c8d"[:24])
params = dict(post)
raw = b"%s"%post.get('idpanier')
decode = raw.encode('utf8')
idpanier = main.Utils().decrypt(key,decode) #it return an error
When executed, I have the following error:
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
ValueError: Invalid data length, data must be a multiple of 8 bytes
I am using pyDes module to crypt and decrypt data.
This is the test that is working :
def test_bytes(self):
key = bytes("477c3551da64136491eff1cb6ab27be35093b2512eb78f2c8d"[:24])
expect = "12177"
raw = "%8E%16%B8n%A6%1F%2Fj" #this is the data that I copied from the url
text = urllib.unquote(raw)
byteArray = bytes(text)
print Utils().decrypt(key, text)
self.assertEqual(expect,Utils().decrypt(key, text), "%s est diférent de %s" % (expect, Utils().decrypt(key, text)) )
I really need your help to figure out what am I doing wrong.
Update:
I think that the probleme have to do with the character encoding, because when I am trying to compare the data I get with the excpected one, I don't get the same thing:
param = post.get('idpanier')
text = (param.encode('utf8'))
print "utf8 encode %s, hex encoded text %s" % (text, text.encode('hex'))
print "utf8 encode %s, hex encoded text %s" % ("b4227475d651420b".decode('hex'), "b4227475d651420b") #excpected behavior
Here is the output:
utf8 encode �"tu�QB
, hex encoded text efbfbd227475efbfbd51420b
utf8 encode �"tu�QB
, hex encoded text b4227475d651420b
The solution I found : instead of retriving parameters with post.get(), I have manage to get the real parameters data through the incoming url directly, where parameters encoding is not changed yet.
query = parse_qs("http://url?%s"%request.httprequest.query_string) #append the query string to a dummy url to get a well formed url
param = query.get('idpanier')
After that, everything worked fine.
I am writing a program using bluepy that listen for a characteristic sent by a bluetooth device. I can also use any library or language, the only constraint is to run on Linux and not in mobile environment (it seems is widely used only in mobile devices, no one use BLE with desktop).
Using bluepy I register the delegate and after trying to register for notification calling write('\x01\x00') as described in the bluetooth rfc.
But it doesn't work, any notification for the characteristic is received.
Maybe I am wrong in writing the message for subscribing.
Is there an error in the small snippet I wrote? Thank you so much.
class MyDelegate(btle.DefaultDelegate):
def __init__(self, hndl):
btle.DefaultDelegate.__init__(self)
self.hndl=hndl;
def handleNotification(self, cHandle, data):
if (cHandle==self.hndl):
val = binascii.b2a_hex(data)
val = binascii.unhexlify(val)
val = struct.unpack('f', val)[0]
print str(val) + " deg C"
p = btle.Peripheral("xx:xx:xx:xx", "random")
try:
srvs = (p.getServices());
chs=srvs[2].getCharacteristics();
ch=chs[1];
print(str(ch)+str(ch.propertiesToString()));
p.setDelegate(MyDelegate(ch.getHandle()));
# Setup to turn notifications on, e.g.
ch.write("\x01\x00");
# Main loop --------
while True:
if p.waitForNotifications(1.0):
continue
print "Waiting..."
finally:
p.disconnect();
I was struggling with this myself, and jgrant's comment really helped. I'd like to share my solution, if it could help anyone.
Note that I needed indication, hence the x02 rather than x01.
If it were possible to read the descriptors using bluepy, I would do that, but it doesn't seem to work (bluepy v 1.0.5). The method in the service class appears to be missing, and the method in the peripheral class gets stuck when I try to use it.
from bluepy import btle
class MyDelegate(btle.DefaultDelegate):
def __init__(self):
btle.DefaultDelegate.__init__(self)
def handleNotification(self, cHandle, data):
print("A notification was received: %s" %data)
p = btle.Peripheral(<MAC ADDRESS>, btle.ADDR_TYPE_RANDOM)
p.setDelegate( MyDelegate() )
# Setup to turn notifications on, e.g.
svc = p.getServiceByUUID( <UUID> )
ch = svc.getCharacteristics()[0]
print(ch.valHandle)
p.writeCharacteristic(ch.valHandle+1, "\x02\x00")
while True:
if p.waitForNotifications(1.0):
# handleNotification() was called
continue
print("Waiting...")
# Perhaps do something else here
It looks like the problem is that you're trying to write \x01\x00 to the characteristic itself. You need to write it to the Client Characteristic Configuration descriptor that proceeds it (0x2902). The handle is likely 1 greater than the characteristic (but you may want to confirm by reading the descriptors).
ch=chs[1]
cccd = ch.valHandle + 1
cccd.write("\x01\x00")
What was confusing for me was that in https://ianharvey.github.io/bluepy-doc/notifications.html
the part that enabled the notifications was in comments, so it didn't look obligatory to me.
the bare minimum (given you know the MAC-adress already an you included everything and declared the Delegateclass) for me is
p1 = Peripheral(<MAC>)
ch1 = p1.getCharacteristics()[3]
p1.setDelegate(MyDelegate())
p1.writeCharacteristic(ch1.valHandle + 1, b"\x01\x00")
Note that I already knew I wanted to get notifications from characteristic#3.
Also, without the 'b'-bytesprefix infront of "\x0\x00", it wouldn't work for me.
bluepy classes docs and samples are crazy, and not complete. To get more details, just checkout bluepy source (it is not big and easy to read)
But, as starting point you can use this notifications code sample, working with from Heart Rate Service (tested on bluepy 1.3.0)
Don't forget to replace device MAC to your own in Peripheral!
from bluepy import btle
from bluepy.btle import AssignedNumbers
import binascii
class MyDelegate(btle.DefaultDelegate):
def __init__(self, handle):
btle.DefaultDelegate.__init__(self)
self.handle = handle
print "Created delegate for handle", self.handle
# ... more initialise here
def handleNotification(self, cHandle, data):
if(cHandle == self.handle):
print "handleNotification for handle: ", cHandle, "; Raw data: ", binascii.b2a_hex(data)
#Found somewhere. Not tested is this working, but leave here as decode example
#val = binascii.b2a_hex(data)
#val = binascii.unhexlify(val)
#val = struct.unpack('f', val)[0]
#print str(val) + " deg C"
print "Connecting..."
dev = btle.Peripheral("c8:2b:96:a3:d4:76")
try:
print "Device services list:"
for svc in dev.services:
print str(svc)
HRService = dev.getServiceByUUID(AssignedNumbers.heartRate)
print "HRService", HRService
print "HRService characteristics list: "
for char in HRService.getCharacteristics():
print "HRService char[", char.getHandle(), "]: ", char
HRMeasurementChar = HRService.getCharacteristics(AssignedNumbers.heart_rate_measurement)[0] #Notice! Check is characteristic found before usage in production code!
print "HRMeasurementChar", HRMeasurementChar, HRMeasurementChar.propertiesToString();
# Assign delegate to target characteristic
dev.setDelegate(MyDelegate(HRMeasurementChar.getHandle()));
# We need to write into org.bluetooth.descriptor.gatt.client_characteristic_configuration descriptor to enabe notifications
# to do so, we must get this descriptor from characteristic first
# more details you can find in bluepy source (def getDescriptors(self, forUUID=None, hndEnd=0xFFFF))
desc = HRMeasurementChar.getDescriptors(AssignedNumbers.client_characteristic_configuration);
print "desc", desc
print "Writing \"notification\" flag to descriptor with handle: ", desc[0].handle
dev.writeCharacteristic(desc[0].handle, b"\x01\x00")# Notice! Do not use [0] in production. Check is descriptor found first!
print "Waiting for notifications..."
while True:
if dev.waitForNotifications(1.0):
# handleNotification() was called
continue
finally:
dev.disconnect();
I am having problems using the pyUSB library to read data from an ELM327 OBDII to USB device. I know that I need to write a command to the device on the write endpoint and read the received data back on the read endpoint. It doesn't seem to want to work for me though.
I wrote my own class obdusb for this:
import usb.core
class obdusb:
def __init__(self,_vend,_prod):
'''Handle to USB device'''
self.idVendor = _vend
self.idProduct = _prod
self._dev = usb.core.find(idVendor=_vend, idProduct=_prod)
return None
def GetDevice(self):
'''Must be called after constructor'''
return self._dev
def SetupEndpoint(self):
'''Must be called after constructor'''
try:
self._dev.set_configuration()
except usb.core.USBError as e:
sys.exit("Could not set configuration")
self._endpointWrite = self._dev[0][(0,0)][1]
self._endpointRead = self._dev[0][(0,0)][0]
#Resetting device and setting vehicle protocol (Auto)
#20ms is required as a delay between each written command
#ATZ resets device
self._dev.write(self._endpointWrite.bEndpointAddress,'ATZ',0)
sleep(0.002)
#ATSP 0 should set vehicle protocol automatically
self._dev.write(self._endpointWrite.bEndpointAddress,'ATSP 0',0)
sleep(0.02)
return self._endpointRead
def GetData(self,strCommand):
data = []
self._dev.write(self._endpintWrite.bEndpointAddress,strCommand,0)
sleep(0.002)
data = self._dev.read(self._endpointRead.bEndpointAddress, self._endpointRead.wMaxPacketSize)
return data
So I then use this class and call the GetData method using this code:
import obdusb
#Setting up library,device and endpoint
lib = obdusb.obdusb(0x0403,0x6001)
myDev = lib.GetDevice()
endp = lib.SetupEndpoint()
#Testing GetData function with random OBD command
#0902 is VIN number of vehicle being requested
dataArr = lib.GetData('0902')
PrintResults(dataArr)
raw_input("Press any key")
def PrintResults(arr):
size = len(arr)
print "Data currently in buffer:"
for i in range(0,size):
print "[" + str(i) + "]: " + str(make[i])
This only ever prints the numbers 1 and 60 from [0] and [1] element in the array. No other data has been return from the command. This is the case whether the device is connected to a car or not. I don't know what these 2 pieces of information are. I am expecting it to return a string of hexadecimal numbers. Does anyone know what I am doing wrong here?
If you don't use ATST or ATAT, you have to expect a timeout of 200ms at start, between every write/read combination.
Are you sending a '\r' after each command? It looks like you don't, so it's forever waiting for a Carriage Return.
And a hint: test with 010D or 010C or something. 09xx might be difficult what to expect.
UPDATE:
You can do that both ways. As long as you 'seperate' each command with a carriage return.
http://elmelectronics.com/ELM327/AT_Commands.pdf
http://elmelectronics.com/DSheets/ELM327DS.pdf (Expanded list).
That command list was quite usefull to me.
ATAT can be used to the adjust the timeout.
When you send 010D, the ELM chip will wait normally 200 ms, to get all possible reactions. Sometimes you can get more returns, so it waits the 200 ms.
What you also can do, and it's a mystery as only scantools tend to implement this:
'010D1/r'
The 1 after the command, specifies the ELM should report back, when it has 1 reply from the bus. So it reduces the delay quite efficiently, at the cost of not able to get more values from the address '010D'. (Which is speed!)
Sorry for my english, I hope send you in the right direction.