#!/usr/bin/env bash help_text=' This script configures bluetooth and wifi mac addresses It is using the eth0 mac address as reference and then applying a logical operation based on the mac address range. Since the handling of wifi and bluetooth devices may be different on different devices, this script is doing several attempts with different approaches to be compatible with all devices. Currently supported devices: HW21 and HW23 It currently supports two schemes: * VCU1 scheme, starts with 7C:97:63: and ends with: eth0: :50:XX:YY wlan0: :70:XX:YY bt0: :80:XX:YY * VCU2 scheme, starts with 00:11:2B: and ends with: eth0: :XX:YY:ZZ wlan0: :XX:YY:ZZ+3 bt0: :XX:YY:ZZ+4 This script is NOT setting eth0 mac address, this must be done before (usually bootloader). ' level_error=0 level_warning=1 level_info=2 level_test=3 log_level="$level_info" eth_mac=0 set_wlan=0 set_bt=0 invert_bt_mac=0 log () { msg_level="$1" level_string="$2" msg="$3" if [ "$log_level" = "$level_test" ]; then echo "$msg" elif [ "$log_level" -ge "$msg_level" ]; then echo "$0: $level_string: $msg" fi } log_test() { msg="$1" log "$level_test" "test" "$msg" } log_info() { msg="$1" log "$level_info" "info" "$msg" } log_warning() { msg="$1" log "$level_warning" "warning" "$msg" } log_error() { msg="$1" log "$level_error" "error" "$msg" } wait_on() { cmd=$1 device=$2 if $cmd "$device" > /dev/null 2>/dev/null; then return fi log_info "$device not ready" log_info "Waiting...." wait_timeout=0 while ! $cmd "$device" > /dev/null 2>/dev/null; do if [ $wait_timeout -gt 10 ]; then log_warning "Timeout after 10 seconds, exiting now.." break else sleep 1 fi wait_timeout=$(($wait_timeout+1)) done log_info "$device ready" } wait_on_wlan0() { wait_on ifconfig wlan0 } wait_on_hci0() { wait_on hciconfig hci0 } is_wlan0_up() { if ifconfig wlan0 | grep UP > /dev/null; then return "1" else return "0" fi } set_wlan_mac_address() { address=$1 log_info "Setting wlan0 mac address to $address" ip link set wlan0 address "$address" ret=$? if [ $ret -ne 0 ]; then log_warning "Setting wlan address failed $ret" fi return $ret } set_and_check_wlan_mac_address() { address=$1 set_wlan_mac_address "$address" ret=$? if [ $ret -ne 0 ]; then log_warning "Setting wlan address failed: $ret" return $ret fi log_info "Checking success" # Setting the mac may fail without error # Checking MAC to be sure it worked if ifconfig wlan0 | grep -i "$address" > /dev/null; then log_info "wlan mac address successfully set" return 0 else log_warning "Setting wlan address failed without throwing an error" return 1 fi } prepare_wlan() { is_wlan0_up wlan_iface_was_up=$? if [[ "$wlan_iface_was_up" -eq "1" ]]; then log_info "Taking wlan0 down" ifconfig wlan0 down fi return "$wlan_iface_was_up" } unprepare_wlan() { wlan_iface_was_up=$1 if [[ "$wlan_iface_was_up" -eq "1" ]]; then log_info "Taking wlan0 back up" ifconfig wlan0 up else log_info "Making sure wlan0 is down" ifconfig wlan0 down fi } prepare_and_set_wlan_mac_address() { address="$1" wait_on_wlan0 prepare_wlan wlan_iface_was_up=$? set_and_check_wlan_mac_address "$address" ret=$? if [[ "$ret" -ne 0 ]]; then # On some devices the interface must be up to set the address log_warning "Setting wlan address failed, trying with interface up" ifconfig wlan0 up set_and_check_wlan_mac_address "$address" ret=$? fi unprepare_wlan "$wlan_iface_was_up" return $ret } # Returns 1 in case reset failed # Returns 2 when the address is not correct after reset set_and_check_bt_mac_address() { address="$1" addr_to_set="$address" # Because of some bug the address to set may be inverted if [[ "$invert_bt_mac" -eq "1" ]]; then addr_to_set=$(echo "$address" | sed -r "s/^(.{2}):(.{2}):(.{2}):(.{2}):(.{2}):(.{2})/\\6:\\5:\\4:\\3:\\2:\\1/") fi bdaddr "$addr_to_set" > /dev/null ret=$? if [ $ret -ne 0 ]; then log_warning "Setting bluetooth address failed: $ret" fi sleep 2 log_info "Restarting bluetooth service" if ! hciconfig hci0 reset; then return 1 fi systemctl restart bluetooth sleep 1 wait_on_hci0 hciconfig hci0 up log_info "Checking success" if hciconfig hci0 | grep -i "$address" > /dev/null; then log_info "Bluetooth address successfully set" ret=0 else log_warning "Setting bluetooth address failed without error" ret=2 fi return $ret } prepare_and_set_bt_mac_address() { address=$1 wait_on_hci0 hciconfig hci0 up log_info "Setting bluetooth mac address to $address" set_and_check_bt_mac_address "$address" ret=$? return $ret } do_bt_hard_reset() { if systemctl is-active --quiet jody-w1-bt-init; then systemctl restart jody-w1-bt-init elif systemctl is-active --quiet tibluetooth; then systemctl restart tibluetooth else log_error "No hard reset possible" return 1 fi } # VCU1 scheme, starts with 7C:97:63: # eth0: 50:XX:YY # wlan0: 70:XX:YY # bt0: 80:XX:YY is_vcu1_mac() { [[ ${1^^} == "7C:97:63:50:"* ]] } vcu1_eth_to_wlan() { echo "${1^^}" | sed -r "s/^(.{9})50(.{6})/\\170\\2/" } vcu1_eth_to_bt() { echo "${1^^}" | sed -r "s/^(.{9})50(.{6})/\\180\\2/" } # VCU2 scheme, starts with 00:11:2B: # eth0: XX:YY:ZZ # wlan0: XX:YY:ZZ+3 # bt0: XX:YY:ZZ+4 is_vcu2_mac() { [[ $1 == "00:11:2B:"* ]] } vcu2_eth_to_wlan_and_bt() { iface="$1" IFS=':' read -r -a digits <<< "$2" # convert digits from hex to decimal for ((x=0 ; x<6 ; x++)); do digits[x]=0x${digits[x]}; done if [ $iface = "wlan" ]; then digits[5]=$((digits[5] + 3)) elif [ $iface = "bt" ]; then digits[5]=$((digits[5] + 4)) fi mac=$(printf ":%02X" "${digits[@]}") mac=${mac:1} # Remove first ":" echo "$mac" } main() { ret=0 if [ "$1" = "-h" ]; then echo "$help_text" echo "Usage:" echo " $0" echo " $0 -l # loglevel being between 0 (errors) and 2 (info)" echo " $0 -w # set wifi address" echo " $0 -b # set bluetooth address" echo " $0 -i # invert bluetooth address" echo " $0 -e # mac address input, e.g. 00:11:22:33:44:10" exit 0 fi while getopts l:wbie: option do case "${option}" in l)log_level=${OPTARG};; w)set_wlan=1;; b)set_bt=1;; i)invert_bt_mac=1;; e)eth_mac=${OPTARG};; esac done if [ $eth_mac = 0 ]; then eth_mac=$(ip addr show eth0 | awk '$1 == "link/ether" {gsub(/\/.*$/, "", $2); print $2}') fi eth_mac=${eth_mac^^} # UPPER case if is_vcu1_mac "$eth_mac"; then log_info "VCU1 mac address scheme" wlan_mac=$(vcu1_eth_to_wlan "$eth_mac") bt_addr=$(vcu1_eth_to_bt "$eth_mac") elif is_vcu2_mac "$eth_mac"; then log_info "VCU2 mac address scheme" wlan_mac=$(vcu2_eth_to_wlan_and_bt "wlan" "$eth_mac") bt_addr=$(vcu2_eth_to_wlan_and_bt "bt" "$eth_mac") else log_error "Unknown mac address scheme for: $eth_mac" exit 1 fi log_test "wlan: $wlan_mac" log_test "bt: $bt_addr" if [ $set_wlan = 1 ]; then prepare_and_set_wlan_mac_address "$wlan_mac" ret_wlan=$? if [ $ret_wlan -ne 0 ]; then log_error "Failed to set wlan mac address" ret="$ret_wlan" fi fi if [ $set_bt = 1 ]; then prepare_and_set_bt_mac_address "$bt_addr" ret_bt=$? while [ $ret_bt -eq 1 ]; do log_warning "Soft reset failed, doing hard reset and retrying" do_bt_hard_reset prepare_and_set_bt_mac_address "$bt_addr" ret_bt=$? done if [ $ret_bt -ne 0 ]; then log_error "Failed to set bluetooth mac address" ret="$ret_bt" fi fi return "$ret" } if [ "$1" = "testify" ]; then return 0 fi main $@ exit $?