因為上篇文章沒有對shell腳本做完全的解析,比較遺憾,這篇補(bǔ)上。 我們繼續(xù)來研究這個 它的功能是為某一個文件或目錄在另外一個位置建立一個同步的鏈接, 類似Windows下的超級鏈接。 創(chuàng)建的鏈接有兩種,一種被稱為硬鏈接(Hard Link),另一種被稱為符號鏈接(Symbolic Link)。建立硬鏈接時,鏈接文件和被鏈接文件必須位于同一個文件系統(tǒng)中,并且不能建立指向目錄的硬鏈接,就是要指向一個單一的實體。而對符號鏈接,則不存在這個問題。默認(rèn)情況下,ln產(chǎn)生硬鏈接。
https://blog.csdn.net/will5451/article/details/51323999 下面是我們文中使用的例子 sudo ln -s 源文件 目標(biāo)文件 sudo ln -s $(realpath .)/robot.service /etc/systemd/system/ $(realpath .) realpath 用于獲取指定目錄或文件的絕對路徑。 -e, --canonicalize-existing 文件 FILE 的所有組成部件必須都存在 -m, --canonicalize-missing 文件 FILE 的組成部件可以不存在 -L, --logical 在軟鏈接之前解析父目錄 .. -P, --physical 解析軟鏈接,默認(rèn)動作 -q, --quiet 靜默模式輸出,禁止顯示大多數(shù)錯誤消息 --relative-to=DIR 相對于目錄 DIR 的路徑 --relative-base=DIR 如果文件在基目錄 DIR下,打印結(jié)果會省去基目錄,否則打印絕對路徑 -s, --strip, --no-symlinks 不擴(kuò)展軟鏈接 -z, --zero 不分隔輸出,即所有的輸出均在一行而不是單獨(dú)每行 --help 顯示幫助信息 --version 顯示版本信息 用法參數(shù) https://man7.org/linux/man-pages/man1/realpath.1.html 參考資料 在我的WSL上面運(yùn)行的結(jié)果 可以自己去試一下,這里是集成在shell腳本里面了, sudo systemctl daemon-reload sudo systemctl enable robot sudo systemctl start robot 接下來看systemctl這個工具 它是什么? systemd是Linux系統(tǒng)最新的初始化系統(tǒng)(init),作用是提高系統(tǒng)的啟動速度,盡可能啟動較少的進(jìn)程,盡可能更多進(jìn)程并發(fā)啟動。 根據(jù) Linux 慣例,字母 使用了 Systemd,就不需要再用 systemd 245 (245.4-4ubuntu3.4) +PAM +AUDIT +SELINUX +IMA +APPARMOR SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=hybrid 這個是我機(jī)器上面的版本 暫且放一個構(gòu)架圖 # 重啟系統(tǒng) $ sudo systemctl reboot
# 關(guān)閉系統(tǒng),切斷電源 $ sudo systemctl poweroff
# CPU停止工作 $ sudo systemctl halt
# 暫停系統(tǒng) $ sudo systemctl suspend
# 讓系統(tǒng)進(jìn)入冬眠狀態(tài) $ sudo systemctl hibernate
# 讓系統(tǒng)進(jìn)入交互式休眠狀態(tài) $ sudo systemctl hybrid-sleep
# 啟動進(jìn)入救援狀態(tài)(單用戶狀態(tài)) $ sudo systemctl rescue # 查看啟動耗時 $ systemd-analyze
# 查看每個服務(wù)的啟動耗時 $ systemd-analyze blame
# 顯示瀑布狀的啟動過程流 $ systemd-analyze critical-chain
# 顯示指定服務(wù)的啟動流 $ systemd-analyze critical-chain atd.service # 顯示當(dāng)前主機(jī)的信息 $ hostnamectl
# 設(shè)置主機(jī)名。 $ sudo hostnamectl set-hostname rhel7 上面的三組命令是我常用的 還有我們之處腳本出現(xiàn)的 systemctl 提供了一組子命令來管理單個的 unit,其命令格式為:
systemctl [command] [unit] 具體使用的命令參數(shù) command 主要有:
start:立刻啟動后面接的 unit。
stop:立刻關(guān)閉后面接的 unit。
restart:立刻關(guān)閉后啟動后面接的 unit,亦即執(zhí)行 stop 再 start 的意思。
reload:不關(guān)閉 unit 的情況下,重新載入配置文件,讓設(shè)置生效。
enable:設(shè)置下次開機(jī)時,后面接的 unit 會被啟動。
disable:設(shè)置下次開機(jī)時,后面接的 unit 不會被啟動。
status:目前后面接的這個 unit 的狀態(tài),會列出有沒有正在執(zhí)行、開機(jī)時是否啟動等信息。
is-active:目前有沒有正在運(yùn)行中。
is-enable:開機(jī)時有沒有默認(rèn)要啟用這個 unit。
kill :不要被 kill 這個名字嚇著了,它其實是向運(yùn)行 unit 的進(jìn)程發(fā)送信號。
show:列出 unit 的配置。
mask:注銷 unit,注銷后你就無法啟動這個 unit 了。
unmask:取消對 unit 的注銷。 start:立刻啟動后面接的 unit。 enable:設(shè)置下次開機(jī)時,后面接的 unit 會被啟動。 sudo systemctl daemon-reload 這個daemon-reload有點(diǎn)不一樣 新添加 unit 配置文件時需要執(zhí)行 daemon-reload 子命令,有 unit 的配置文件發(fā)生變化時也需要執(zhí)行 daemon-reload 子命令。daemon-reload 命令會做很多的事情,其中之一是重新生成依賴樹(也就是 unit 之間的依賴關(guān)系),所以當(dāng)你修改了 unit 配置文件中的依賴關(guān)系后如果不執(zhí)行 daemon-reload 命令是不會生效的。 我們來總結(jié)一下 首先cd到文件夾下面 建立一個同步變化的超鏈接,具體同步的是一個service。相當(dāng)于注冊服務(wù) 然后設(shè)置為下次開機(jī),自己啟動。啟動后,后面的單元跟著啟動 下面就是上面看見的這個service [Unit] 部分 Description : 服務(wù)的簡單描述 Documentation :服務(wù)文檔 Before、After:定義啟動順序。Before=xxx.service,代表本服務(wù)在xxx.service啟動之前啟動。After=xxx.service,代表本服務(wù)在xxx.service之后啟動。 Requires:這個單元啟動了,它需要的單元也會被啟動;它需要的單元被停止了,這個單元也停止了。 Wants:推薦使用。這個單元啟動了,它需要的單元也會被啟動;它需要的單元被停止了,對本單元沒有影響。 [Service] 部分 Type=simple(默認(rèn)值):systemd認(rèn)為該服務(wù)將立即啟動。服務(wù)進(jìn)程不會fork。如果該服務(wù)要啟動其他服務(wù),不要使用此類型啟動,除非該服務(wù)是socket激活型。 Type=forking:systemd認(rèn)為當(dāng)該服務(wù)進(jìn)程fork,且父進(jìn)程退出后服務(wù)啟動成功。對于常規(guī)的守護(hù)進(jìn)程(daemon),除非你確定此啟動方式無法滿足需求,使用此類型啟動即可。使用此啟動類型應(yīng)同時指定 PIDFile=,以便systemd能夠跟蹤服務(wù)的主進(jìn)程。 Type=oneshot:這一選項適用于只執(zhí)行一項任務(wù)、隨后立即退出的服務(wù)。可能需要同時設(shè)置 RemainAfterExit=yes 使得 systemd 在服務(wù)進(jìn)程退出之后仍然認(rèn)為服務(wù)處于激活狀態(tài)。 Type=notify:與 Type=simple 相同,但約定服務(wù)會在就緒后向 systemd 發(fā)送一個信號。這一通知的實現(xiàn)由 libsystemd-daemon.so 提供。 Type=dbus:若以此方式啟動,當(dāng)指定的 BusName 出現(xiàn)在DBus系統(tǒng)總線上時,systemd認(rèn)為服務(wù)就緒。 Type=idle: systemd會等待所有任務(wù)(Jobs)處理完成后,才開始執(zhí)行idle類型的單元。除此之外,其他行為和Type=simple 類似。 PIDFile:pid文件路徑 ExecStart:指定啟動單元的命令或者腳本,ExecStartPre和ExecStartPost節(jié)指定在ExecStart之前或者之后用戶自定義執(zhí)行的腳本。Type=oneshot允許指定多個希望順序執(zhí)行的用戶自定義命令。 ExecReload:指定單元停止時執(zhí)行的命令或者腳本。 ExecStop:指定單元停止時執(zhí)行的命令或者腳本。 PrivateTmp:True表示給服務(wù)分配獨(dú)立的臨時空間 Restart:這個選項如果被允許,服務(wù)重啟的時候進(jìn)程會退出,會通過systemctl命令執(zhí)行清除并重啟的操作。 RemainAfterExit:如果設(shè)置這個選擇為真,服務(wù)會被認(rèn)為是在激活狀態(tài),即使所以的進(jìn)程已經(jīng)退出,默認(rèn)的值為假,這個選項只有在Type=oneshot時需要被配置。 [Install] 部分 Alias:為單元提供一個空間分離的附加名字。 RequiredBy:單元被允許運(yùn)行需要的一系列依賴單元,RequiredBy列表從Require獲得依賴信息。 WantBy:單元被允許運(yùn)行需要的弱依賴性單元,Wantby從Want列表獲得依賴信息。 Also:指出和單元一起安裝或者被協(xié)助的單元。 DefaultInstance:實例單元的限制,這個選項指定如果單元被允許運(yùn)行默認(rèn)的實例。 對應(yīng)我們的腳本是這樣的 在上面的service里面會打開這個手柄的服務(wù),那這里我把服務(wù)也寫過來 上面的手柄服務(wù)是調(diào)用了這個py文件 最外面的層級是調(diào)用這個py文件 接下來就是我們正式讀實現(xiàn)的階段啦! 整個過程都是向圖中所示 PS4操縱桿接口負(fù)責(zé)從 UDP 套接字讀取操縱桿輸入并將它們轉(zhuǎn)換為通用機(jī)器人command類型。 實現(xiàn)里面有一個單獨(dú)的程序 ,joystick.py發(fā)布這些 UDP 消息,并負(fù)責(zé)通過藍(lán)牙從 PS4 控制器讀取輸入。 控制器完成大部分工作,在狀態(tài)(小跑、行走、休息等)之間切換并生成伺服位置目標(biāo)。 控制器的詳細(xì)模型如下所示。 這里就是單獨(dú)的joystick的文件 控制器模型 這是代碼的第三個組件,硬件接口,將來自控制器的位置目標(biāo)轉(zhuǎn)換為 PWM 占空比,然后傳遞給 Python 綁定到pigpiod,然后在軟件中生成 PWM 信號并將這些信號發(fā)送到連接到控制器的電機(jī)樹莓派。 步態(tài)調(diào)度器負(fù)責(zé)計劃在任何給定時間哪些腳應(yīng)該放在地面上(站姿),哪些腳應(yīng)該向前移動到下一步(擺動)。例如,在小跑中,對角線對的腿同步移動并在站立和擺動之間輪流移動。步態(tài)調(diào)度器可以被認(rèn)為是每條腿的導(dǎo)體,隨著時間的推移在站立和擺動之間切換。 姿態(tài)控制器控制著地面的腳,其實很簡單。它查看所需的機(jī)器人速度,然后為這些站立腳生成與所需速度相反方向的與身體相關(guān)的目標(biāo)速度。它還包含轉(zhuǎn)動,在這種情況下,它會在與所需的身體旋轉(zhuǎn)相反的方向上相對于身體旋轉(zhuǎn)腳。 揮桿控制器拿起剛完成站立階段的腳,并將它們帶到下一個觸地位置。選擇觸地位置,使腳在擺動時向前移動與在站立時向后移動相同的距離。例如,如果在站立階段,腳以 -0.4m/s 的速度向后移動(以達(dá)到 +0.4m/s 的身體速度)并且站立階段為 0.5 秒長,那么我們知道腳將向后移動 -0.20米。然后擺動控制器將腳向前移動 0.20m,將腳放回起始位置??梢韵胂螅绻麚u擺控制器只將腿向前移動0.15m,那么每走一步,腳就會越來越落后于身體-0.05m。 站姿控制器和擺動控制器都以相對于身體重心的笛卡爾坐標(biāo)生成腳的目標(biāo)位置。使用笛卡爾坐標(biāo)進(jìn)行站姿和揮桿規(guī)劃很方便,但我們現(xiàn)在需要將它們轉(zhuǎn)換為運(yùn)動角度。這是通過使用逆運(yùn)動學(xué)模型完成的,該模型在笛卡爾身體坐標(biāo)和運(yùn)動角度之間進(jìn)行映射。然后將這些電機(jī)角度(也稱為關(guān)節(jié)角度)填充到state變量中并由模型返回。 這一部分的代碼是校準(zhǔn)使用 實現(xiàn)具體的運(yùn)動過程的代碼 import numpy as np import time from src.IMU import IMU from src.Controller import Controller from src.JoystickInterface import JoystickInterface from src.State import State from pupper.HardwareInterface import HardwareInterface from pupper.Config import Configuration from pupper.Kinematics import four_legs_inverse_kinematics def main(use_imu=False): """ 主程序 """ # 創(chuàng)建配置 config = Configuration() hardware_interface = HardwareInterface() # 硬件接口
# 創(chuàng)建IMU處理 if use_imu: imu = IMU(port="/dev/ttyACM0") # 是一個串口呀 imu.flush_buffer()
# 創(chuàng)建控制器和用戶輸入處理 controller = Controller( config, four_legs_inverse_kinematics, ) state = State() print("創(chuàng)建joystick監(jiān)聽...") joystick_interface = JoystickInterface(config) print("完成.")
last_loop = time.time()
print("步態(tài)參數(shù)匯總:") print("重疊 time: ", config.overlap_time) print("搖擺 time: ", config.swing_time) print("z 間隙: ", config.z_clearance) print("x 轉(zhuǎn)換: ", config.x_shift)
# 等待激活按鈕被摁下 while True: print("等待L1激活機(jī)器人.") while True: command = joystick_interface.get_command(state) joystick_interface.set_color(config.ps4_deactivated_color) if command.activate_event == 1: break time.sleep(0.1) print("機(jī)器人激活.") joystick_interface.set_color(config.ps4_color)
while True: now = time.time() if now - last_loop < config.dt: continue last_loop = time.time()
# 解析UDP操縱桿命令,然后更新機(jī)器人控制器參數(shù) command = joystick_interface.get_command(state) if command.activate_event == 1: print("停止機(jī)器人") break
# 讀取IMU數(shù)據(jù),如果沒有可以使用的數(shù)據(jù),則方向為無 quat_orientation = ( imu.read_orientation() if use_imu else np.array([1, 0, 0, 0]) ) state.quat_orientation = quat_orientation
# 通過dt使控制器前進(jìn) controller.run(state, command)
# 更新加入伺服系統(tǒng)的pwm寬度 hardware_interface.set_actuator_postions(state.joint_angles) main() 這里先放一點(diǎn)代碼,接下來慢慢解剖 上面出現(xiàn)的這個Teensy,是NXP的一個開發(fā)板,性能比較好 這是它的一些介紹 通用的py庫 接下來是源碼庫和狗的庫,我一直以為是小學(xué)生的意思 假如我們就以庫引入的順序作為其功能對最終的機(jī)器人的貢獻(xiàn),那對機(jī)器人最重要的就是姿態(tài)的控制了。 姿態(tài)測量單元 只有三個函數(shù)而已 首先是使用用串口的接口,做了一些相應(yīng)的配置 最后的函數(shù)是最重要的,讀取傳感器的4元數(shù)信息 self.last_quat = np.array([1, 0, 0, 0]) 一個空的數(shù)據(jù)結(jié)構(gòu) 每次從串口處讀取一行,編碼為utf。下面是一些異常處理 這是Woofer,12自由度的機(jī)器人 https://stanfordstudentrobotics.org/woofer 源碼,將兩個庫放在了一起 pupper,廉價的4足機(jī)器人 我們只要讀pupper 我還是把完整的流程寫完再分析源碼,再全部安裝過后。在開機(jī)后需要校準(zhǔn)機(jī)器人。校準(zhǔn)是運(yùn)行機(jī)器人之前的必要步驟,因為還沒有精確測量伺服臂如何相對于伺服輸出軸固定。運(yùn)行校準(zhǔn)腳本將提示你將 12 個自由度中的每一個與已知角度(例如水平或垂直)對齊,從而幫助你確定此旋轉(zhuǎn)偏移。 先SSH: rw sudo systemctl stop robot 然后運(yùn)行: cd StanfordQudruped sudo pigpiod python3 calibrate_servos.py 校準(zhǔn)時的三種擺放方式 sudo systemctl start robot 完成后,重啟機(jī)器 這些是校準(zhǔn)的函數(shù),以后讀 https://github.com/stanfordroboticsclub/StanfordQuadruped/blob/master/calibrate_servos.py 這個文件是控制機(jī)器人動起來時的參數(shù) 里面時4個大類 class Configuration: def __init__(self): ################# 控制器集成顏色 ############## self.ps4_color = PS4_COLOR self.ps4_deactivated_color = PS4_DEACTIVATED_COLOR
#################### 命令 #################### self.max_x_velocity = 0.4 self.max_y_velocity = 0.3 self.max_yaw_rate = 2.0 self.max_pitch = 30.0 * np.pi / 180.0
#################### 運(yùn)動參數(shù) #################### self.z_time_constant = 0.02 self.z_speed = 0.03 # maximum speed [m/s] self.pitch_deadband = 0.02 self.pitch_time_constant = 0.25 self.max_pitch_rate = 0.15 self.roll_speed = 0.16 # maximum roll rate [rad/s] self.yaw_time_constant = 0.3 self.max_stance_yaw = 1.2 self.max_stance_yaw_rate = 2.0
#################### 姿態(tài) #################### self.delta_x = 0.1 self.delta_y = 0.09 self.x_shift = 0.0 self.default_z_ref = -0.16
#################### 搖擺 ###################### self.z_coeffs = None self.z_clearance = 0.07 self.alpha = ( 0.5 # 觸地距離與總水平站姿運(yùn)動的比率 ) self.beta = ( 0.5 # 觸地距離與總水平站姿運(yùn)動的比率 )
#################### 步態(tài) ####################### self.dt = 0.01 self.num_phases = 4 self.contact_phases = np.array( [[1, 1, 1, 0], [1, 0, 1, 1], [1, 0, 1, 1], [1, 1, 1, 0]] ) self.overlap_time = ( 0.10 # 四足著地階段的持續(xù)時間 ) self.swing_time = ( 0.15 # 兩腳落地時的階段持續(xù)時間 )
######################## 幾何學(xué) ###################### self.LEG_FB = 0.10 # 兩腳落地時的階段持續(xù)時間 self.LEG_LR = 0.04 # 中心線到腿部平面的左右距離 self.LEG_L2 = 0.115 self.LEG_L1 = 0.1235 self.ABDUCTION_OFFSET = 0.03 # 外展軸到腿的距離 self.FOOT_RADIUS = 0.01
self.HIP_L = 0.0394 self.HIP_W = 0.0744 self.HIP_T = 0.0214 self.HIP_OFFSET = 0.0132
self.L = 0.276 self.W = 0.100 self.T = 0.050
self.LEG_ORIGINS = np.array( [ [self.LEG_FB, self.LEG_FB, -self.LEG_FB, -self.LEG_FB], [-self.LEG_LR, self.LEG_LR, -self.LEG_LR, self.LEG_LR], [0, 0, 0, 0], ] )
self.ABDUCTION_OFFSETS = np.array( [ -self.ABDUCTION_OFFSET, self.ABDUCTION_OFFSET, -self.ABDUCTION_OFFSET, self.ABDUCTION_OFFSET, ] )
################### 慣性 #################### self.FRAME_MASS = 0.560 # kg self.MODULE_MASS = 0.080 # kg self.LEG_MASS = 0.030 # kg self.MASS = self.FRAME_MASS + (self.MODULE_MASS + self.LEG_MASS) * 4
# Compensation factor of 3 because the inertia measurement was just # of the carbon fiber and plastic parts of the frame and did not # include the hip servos and electronics self.FRAME_INERTIA = tuple( map(lambda x: 3.0 * x, (1.844e-4, 1.254e-3, 1.337e-3)) ) self.MODULE_INERTIA = (3.698e-5, 7.127e-6, 4.075e-5)
leg_z = 1e-6 leg_mass = 0.010 leg_x = 1 / 12 * self.LEG_L1 ** 2 * leg_mass leg_y = leg_x self.LEG_INERTIA = (leg_x, leg_y, leg_z)
@property def default_stance(self): return np.array( [ [ self.delta_x + self.x_shift, self.delta_x + self.x_shift, -self.delta_x + self.x_shift, -self.delta_x + self.x_shift, ], [-self.delta_y, self.delta_y, -self.delta_y, self.delta_y], [0, 0, 0, 0], ] ) 我將具體配置的參數(shù)附上 其實用戶需要改的地方在這里,別的地方是牽一而全身動 如果你做了自己的尺寸,更改的參數(shù)在這里 這些參數(shù)和車體的構(gòu)成材料和質(zhì)量相關(guān) 這些參數(shù)是控制步態(tài),就是走路的樣子 此段配置是具體到機(jī)器人的每一個部件和算法之間的約束關(guān)系 下篇文章對源碼進(jìn)行細(xì)致分析,敬請期待~ |
|