bluetoothctl to hcitool equivalent commands - linux

In Linux I used to use "hidd --connect mmac" to connect with BT devices but that is now gone since Bluez5.
I can use bluetoothctl to make the connection manually but I need to use these commands from my app and using bluetoothctl would be difficult.
What are the hcitool equivalent commands to do what bluetoothctl does?
For example, I would type in bluetoothctl:
select <cmac>
scan on
trust <mmac>
pairable on
pair <mmac>
connect <mmac>
I can use "hcitool scan" for the scanning but I haven't figured out connecting.
I've tried using "hcitool cc mmac" followed by "hcitool auth mmac" but nothing works.
Or can hcitool do what bluetoothctl does?

I am using bluetoothctl from scripts like this:
#!/bin/bash
bluetoothctl << EOF
power on
EOF
And it is possible to specify multiple commands as one command per line.
Strangely enough, it does not work like this for me:
echo "power on" | bluetoothctl
(I am using bluez-5.21-r1 - not sure whether this is version dependent)

You can pass commands to bluetoothctl like this:
echo -e 'power on\nquit' | bluetoothctl
You can even use tab to autocomplete:
echo -e 'power on\nconnect \t \nquit' | bluetoothctl
I am not adding this as a comment on Jiri's answer so it is more visible.

Another solution (the best in my opinion) would be to use expect TCL scripting with bluetoothctl.
I use it to automatically connect to bluetooth devices using bluetoothctl without having to interact with it.
For example to connect to a device identified by its MAC address
#!/usr/bin/expect -f
set address [lindex $argv 0]
set prompt "#"
log_user 0
spawn bluetoothctl
expect $prompt
send -- "remove $address\r"
expect $prompt
send -- "scan on\r"
expect "Discovery started"
sleep 10
send -- "scan off\r"
expect "Discovery stopped"
expect $prompt
send -- "trust $address\r"
expect "trust succeeded"
expect $prompt
send -- "pair $address\r"
expect "Pairing successful"
expect "Device $address Connected: no"
expect $prompt
send -- "connect $address\r"
expect "Connection successful"
expect $prompt
send "quit\r"
expect "eof"
You can launch this script as it ./myExpectScript <MAC_addr>
If you want to see the output just set the log_user value to 1

I solved this using tmux, i.e.:
Install tmux:
apt install tmux
Create Session:
tmux new-session -d -s ServerFault 'sudo bluetoothctl -a |& tee /run/shm/BLUETOOTH_OUTPUT'
Then you can issue commands like:
tmux send-keys -t ServerFault "pair AC:22:0B:9F:0C:D6" Enter

I wrote a python3 script to auto-connect my gamepads on my game cabinet. You have to run it for each device you want to connect, but no user interaction is needed. It uses the expect python module, similar to the above answers, to communicate with bluetoothctl. I found it a little easier to use than the expect/tcl scripts. If python can't find pexpect, you would need to install python3-pexpect.
sudo apt install python3-pexpect
You'll want to change the mylist list variable to search for the MACs that match the first 3 bytes (the vendor part) of your bluetooth devices. So, for example, if the first 3 bytes of the MACs on your devices start with AA:BB:CC:, then change the EF\:17\:D8\: part to AA\:BB\:CC\:
You can add as many devices you want to scan for in the mylist variable. My example searches for two different vendors, one starting with EF\:17\:D8\:, and one starting with 16\:04\:18\: The script will reject all other bluetooth devices that may be transmitting, and only connect the gamepad MACs you've configured in the mylist variable.
mylist = ['E4\:17\:D8\:[0-9A-F].[:][0-9A-F].[:][0-9A-F].', '16\:04\:18\:[0-9A-F].[:][0-9A-F].[:][0-9A-F].',pexpect.EOF]
Here is the python3 script:
#!/usr/bin/python3
import os,sys,time,pexpect
def findaddress():
address=''
p = pexpect.spawn('hcitool scan', encoding='utf-8')
p.logfile_read = sys.stdout
mylist = ['E4\:17\:D8\:[0-9A-F].[:][0-9A-F].[:][0-9A-F].', '16\:04\:18\:[0-9A-F].[:][0-9A-F].[:][0-9A-F].',pexpect.EOF]
p.expect(mylist)
address=p.after
if address==pexpect.EOF:
return ''
else:
return address
def setbt(address):
response=''
p = pexpect.spawn('bluetoothctl', encoding='utf-8')
p.logfile_read = sys.stdout
p.expect('#')
p.sendline("remove "+address)
p.expect("#")
p.sendline("scan on")
mylist = ["Discovery started","Failed to start discovery","Device "+address+" not available","Failed to connect","Connection successful"]
while response != "Connection successful":
p.expect(mylist)
response=p.after
p.sendline("connect "+address)
time.sleep(1)
p.sendline("quit")
p.close()
#time.sleep(1)
return
address=''
while address=='':
address=findaddress()
time.sleep(1)
print (address," found")
setbt(address)
I wrote another python3 script that wraps the entire process in a Vte and shows the process as it is happening, and lets you to exit it, if needed. If you want to see that, just let me know.

You can give commands as arguments directly to bluetoothctl from the shell, without needing expect scripts.
I use this in a Bash script in Ubuntu 20.04 :
mac="90:03:B7:17:00:08"
# turn on bluetooth in case it's off
rfkill unblock bluetooth
bluetoothctl power on
bluetoothctl connect $mac
To disconnect, use
bluetoothctl disconnect
This assumes the destination $mac is already paired of course. If it isn't, you can first do
bluetoothctl pair $mac
To list all available commands:
bluetoothctl help

Related

Bash script to respond to console output?

Currently trying to run a bash script on startup to automatically install squid, however the command I'm running requires input.
Currently the script i have is:
#!/bin/sh
PROXY_USER=user1
PROXY_PASS=password1
wget https://raw.githubusercontent.com/hidden-refuge/spi/master/spi && bash spi -rhel7 && rm spi
#After i run this command it asks "Enter username"
#followed by "Enter password" and "Renter password"
echo $PROXY_USER
echo $PROXY_PASS
echo $PROXY_PASS
echo yes
However i am unable to get the input working, and the script fails to create a username and password. I'm running centos 7.
Look you are calling some tools which act in interactive mode, so as dani-gehtdichnixan mentioned at (passing arguments to an interactive program non interactively) you can use expect utilities.
Install expect at debian:
apt-get install expect
Create a script call spi-install.exp which could look like this:
#!/usr/bin/env expect
set user username
set pass your-pass
spawn spi -rhel7
expect "Enter username"
send "$user\r"
expect "Renter password"
send "$pass\r"
Then call it at your main bash script:
#!/bin/bash
wget https://raw.githubusercontent.com/hidden-refuge/spi/master/spi && ./spi-install.exp && rm spi
Expect is used to automate control of interactive applications such as Telnet, FTP, passwd, fsck, rlogin, tip, SSH, and others. Expect uses pseudo terminals (Unix) or emulates a console (Windows), starts the target program, and then communicates with it, just as a human would, via the terminal or console interface. Tk, another Tcl extension, can be used to provide a GUI.
https://en.wikipedia.org/wiki/Expect
Reference :
[1] passing arguments to an interactive program non interactively
[2] https://askubuntu.com/questions/307067/how-to-execute-sudo-commands-with-expect-send-commands-in-bash-script
[3] https://superuser.com/questions/488713/what-is-the-meaning-of-spawn-linux-shell-commands-centos6
Try just passing the values to bash's stdin
#!/bin/sh
PROXY_USER=user1
PROXY_PASS=password1
if wget https://raw.githubusercontent.com/hidden-refuge/spi/master/spi; then
printf "%s\n" "$PROXY_USER" "$PROXY_PASS" "$PROXY_PASS" yes | bash spi -rhel7
rm spi
fi

Running an RFCOMM server in the background using subprocess

I have been trying various ways of getting an RFCOMM server up and running using python's subprocess package for some time now but have hit a wall. What I want to do is start a process in the background with this RFCOMM server running and getting the usual return from the command which is something like "Connected /dev/rfcomm0 to xx:xx:xx:xx:xx:xx on channel n" and "Press CTRL-C for hangup". The thing is, the process starts with two different ways I have tried it but getting this return and letting it sit in the background is an issue for me.
Alternative 1:
ref = Popen("sudo rfcomm connect 0 xx:xx:xx:xx:xx:xx 1 -i hci0 &", stdout=PIPE, stderr=PIPE, shell=True)
The above starts the RFCOMM server but either if I use communicate() or try to read from ref.stdout/stderr the program freezes.
Alternative 2:
res = run("sudo rfcomm connect 0 xx:xx:xx:xx:xx:xx 1 -i hci0 &", stdout=PIPE, stderr=PIPE, shell=True)
Same thing here, if I try to access the CompletedProcess objects stdout/stderr fields, the program freezes and refuses to continue. The RFCOMM server starts but the script does not terminate and I cannot read the stdout/stderr fields.
Alternative 3:
res = run(["sudo", "rfcomm", "connect", "0", "xx:xx:xx:xx:xx:xx", "1", "-i", "hci0"], stdout=PIPE, stderr=PIPE)
Same thing here, even though shell is left at its default and the command is not to be run in the background, the run function does not return and the script does not terminate. The RFCOMM server starts but the script does not terminate and I cannot read the stdout/stderr fields.
So, what I want is to start this RFCOMM server in the background, read the two lines it should show, and continue with my program so that I can poll its availability through other commands.
Quite strange answer in my opinion for this one, but the answer lied in that I assigned stdout and stderr to the PIPE constant. This for some reason "binds" the script to the execution and will not let it finish. When removing these two assignments the script finishes happily and I have to find another way to get the output that I in my question listed I wanted, the "Connected ..." message that is. Seems like there is no way for me to retrieve it for now.
Script that works and starts the server in the background:
mac_addr = "xx:xx:xx:xx:xx:xx"
run("sudo rfcomm connect 0 " + mac_addr + " 1 -i hci0 &", shell=True)
So, as you can see, I just removed stdout and stderr... If someone in turn can answer why that would essentially "lock" the script I would be grateful.

How to interact with telnet using empty

I need to replace a very simple expect script that looks like this:
#!/usr/bin/expect
spawn telnet 192.168.1.175
expect {
"assword" {send "lamepassword\r"}
}
interact
With the equivalent bash script using empty, like this:
#!/bin/bash
empty -f -i in -o out telnet 192.168.1.175
empty -w -i out -o in "assword" "lamepassword\n"
After which I need the user to interact with telnet, which I do not know how to do. The closest thing that comes to my mind is binding stdin and stdout with named pipes using something like socat - in. Any suggestions are more than welcome!
I tried cat out & cat /dev/stdin >in, it works, but it has an extra
newline, tab completion does not work and ctr+c terminates cat and
not the running host process. I am trying to persuade socat to act
according to those needs.
Using socat for transmitting keyboard input to the telnet process is a good idea. Example:
cat out & socat -u -,raw,echo=0 ./in
For allowing Ctrl-C to terminate socat, add escape=3:
cat out & socat -u -,raw,echo=0,escape=3 ./in
But note that this will not terminate the telnet session, since it did start in daemon mode, so you can reconnect to telnet by executing socat again. To end telnet, you could just logout.

How to get the output of a telnet command from bash?

I'm trying to get the list of processes running on my Windows machine from Linux, but I don't get any output when I do it in a script. If I use telnet manually and use the command pslist I get the complete list of processes, but not in my script.
Here is the bash script (minus the variables):
( echo open ${host}
sleep 1
echo ${user}
sleep 3
echo ${pass}
sleep 1
echo pslist
sleep 2
) | telnet
and I simply call it with bash pslist.sh and the output is something like that:
telnet> Trying ip_address...
Connected to ip_address.
Escape character is '^]'.
Welcome to Microsoft Telnet Service
login: my_loginmy_passwordpslistConnection closed by foreign host.
What am I doing wrong ?
telnet is notoriously tricky to script. You may be able to succeed more often if you add a longer still sleep between the commands.
A better approach is to switch to a properly scriptable client, viz. netcat (aka nc). Better still would be to install an SSH server on your Windows box (perhaps for security only make it accessible from inside your network) and set it up with passwordless authentication. Then you can simply ssh user#ipaddress pslist
Terminate each echo with \r character, like this: echo -e "${user}\r"

Bash and Expect: Is there a way to ignore or remove ANSI control sequences from Expect buffer?

I'm using Expect to connect to my server over a (virtual) serial port. (HP iLo, to be specific)
When booting from a Linux OS ISO image, you eventually get to the 'boot:' prompt. When my server reaches that prompt, I would like to enter my own custom boot options and press enter. Easy, right?
This is how the boot prompt looks when you're watching my Expect script execute (looks normal):
boot:
However, I have not been able to match 'boot:'. Looking at the Expect Buffer in my logfile, this is what is being captured for that line:
ESC\[25;01HbbESC\[25;01HESC\[25;02HooESC\[25;02HESC\[25;03HooESC\[25;03HESC\[25;04HttESC\[25;04HESC\[25;05H::ESC\[25;05HESC\[25;06H ESC\[25;06HESC\[25;07H"
I think all those control sequences are screwing up my match. If you look closely 'boot:' is actually in there, but it's surrounded by what I believe are ANSI control sequences.
In fact, the logfile is absolutely full of ANSI control characters.
Relevant pieces of the Expect script I've been playing around with:
bash #] expect -d -c '
.....
# SSH to the Virtual Serial Port Management server
spawn ssh user#1.2.3.4
.....
# Access the Virtual Serial Port for the server being booted
send "vsp\r"
.....
# After rebooting the server, when the boot: prompt appears, enter boot options
expect {
"boot:" {send $bootOptions \r\n"}
timeout {send_user "Never found boot prompt\n"; send_user "$expect_out(buffer)"; exit 1}
}
.....
exit'
Any ideas about what the best way to handle those control characters would be? I've tried exporting TERM=dumb and TERM=vt1000 on the machine I'm running the script on. Didn't make much of a difference.
Not sure if this will help, but you could create a wrapper for ssh and exec that instead of ssh and then have
ssh <host> | perl -pe 's/\e([^\[\]]|\[.*?[a-zA-Z]|\].*?\a)//g' | col -b
perhaps take out the col -b which filters newlines if you don't need that.

Resources