This commit is contained in:
久伴 2024-10-04 18:23:50 -07:00 committed by GitHub
commit 4ba34f08a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 279 additions and 44 deletions

View File

@ -0,0 +1,126 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Python package
run-name: ${{ github.actor }} package
on:
workflow_dispatch:
inputs:
c_cyclonedds_version:
description: 'clang cyclonedds version'
required: false
default: 'releases/0.10.x'
cyclonedds_python_version:
description: cyclonedds_python的版本
required: false
default: 'releases/0.10.x'
jobs:
build:
# 输入参数定义部分
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: ["3.11"]
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
# 初始化
- uses: actions/checkout@v4
# 配置python
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
# 初始化制品收集目录
- name: init dist
run: |
mkdir dist
# 准备构建python wheel包
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
# 由于cyclonedds-python暂时没有提供python3.11 顺带也编译了 issue:https://github.com/eclipse-cyclonedds/cyclonedds-python/issues/221
- name: build cyclonedds-python in windows
if: startsWith(runner.os, 'Windows')
run: |
git clone https://github.com/eclipse-cyclonedds/cyclonedds-python -b ${{ github.event.inputs.cyclonedds_python_version }} --depth=1
cd cyclonedds-python
(Get-Content pyproject.toml) -replace 'https://github.com/eclipse-cyclonedds/cyclonedds.git','https://github.com/eclipse-cyclonedds/cyclonedds -b ${{ github.event.inputs.c_cyclonedds_version }} --depth=1' | Set-Content pyproject.toml
(Get-Content pyproject.toml) -replace '(?<=^skip = ")','cp3{6..10}-* ' | Set-Content pyproject.toml
(Get-Content pyproject.toml) -replace 'delvewheel==0.0.18','delvewheel==1.6.0' | Set-Content pyproject.toml
(Get-Content setup.py) -replace '(?<="Programming Language :: Python :: 3.10",.*?)', "`n `"Programming Language :: Python :: 3.11`",`n `"Programming Language :: Python :: 3.12`",`n `"Programming Language :: Python :: 3.13`"," | Set-Content setup.py
pip install --user cibuildwheel==2.18.*
python -m cibuildwheel --output-dir wheelhouse .
tar zcvf cyclonedds.tar.gz -C cyclonedds-build .
shell: pwsh
# 兼容sed see:https://gist.github.com/andre3k1/e3a1a7133fded5de5a9ee99c87c6fa0d
- name: install GNU sed in mac
if: startsWith(runner.os, 'macOS')
run: |
brew install gnu-sed
- name: build cyclonedds-python in unix
if: startsWith(runner.os, 'Linux') || startsWith(runner.os, 'macOS')
run: |
if [ "${{ startsWith(runner.os, 'macOS') }}" ]; then
PATH="/opt/homebrew/opt/gnu-sed/libexec/gnubin:$PATH"
fi
git clone https://github.com/eclipse-cyclonedds/cyclonedds-python -b ${{ github.event.inputs.c_cyclonedds_version }} --depth=1
cd cyclonedds-python
sed -i "s~https://github.com/eclipse-cyclonedds/cyclonedds.git~https://github.com/eclipse-cyclonedds/cyclonedds -b ${{ github.event.inputs.c_cyclonedds_version }} --depth=1~g" pyproject.toml
echo "debugprintpoint1"
sed -i 's/\(^skip = "\)/\1cp3{6..10}-* /g' pyproject.toml
echo "printpoint2"
sed -i 's/delvewheel==0\.0\.18/delvewheel==1\.6\.0/g' pyproject.toml
echo "printpoint3"
sed -i 's/(?<="Programming Language :: Python :: 3.10",.*?)/\n "Programming Language :: Python :: 3.11",\n "Programming Language :: Python :: 3.12",\n "Programming Language :: Python :: 3.13",/g' setup.py
pip install --user cibuildwheel==2.18.*
python -m cibuildwheel --output-dir wheelhouse .
shell: bash
# 构建宇树python sdk本身
- name: build package unix
if: startsWith(runner.os, 'Linux') || startsWith(runner.os, 'macOS')
run: |
export CYCLONEDDS_HOME=${{ github.workspace }}/cyclonedds/install
python -m build --wheel
shell: bash
- name: build package windows
if: startsWith(runner.os, 'Windows')
run: |
set CYCLONEDDS_HOME=${{ github.workspace }}/cyclonedds/install
python -m build --wheel
shell: cmd
# 收集构建产物
- name: collect binary
if: startsWith(runner.os, 'Windows')
run: |
mv ${{ github.workspace }}/cyclonedds-python/wheelhouse/* dist/
mv ${{ github.workspace }}/dist/* dist/
- name: collect binary
if: startsWith(runner.os, 'Linux') || startsWith(runner.os, 'macOS')
run: |
# 对复杂的不同平台目录机制信息收集
find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g' > dist/${{runner.os}}_${{runner.arch}}.txt
mv -f ${{ github.workspace }}/cyclonedds-python/wheelhouse/* dist/
# mv -f ${{ github.workspace }}/dist/* dist/
# 上传到工作流制品库
- name: upload package and dependency
uses: actions/upload-artifact@v4
with:
name: unitree_sdk2_python${{ matrix.python-version }}_${{runner.os}}_${{runner.arch}}
path: |
dist/*

3
.gitignore vendored
View File

@ -34,3 +34,6 @@ __pycache__
# JetBrains IDE
.idea/
build
dist

21
tests/check_init.py Normal file
View File

@ -0,0 +1,21 @@
import os,sys
def check_init_files(directory):
"""递归确认包存在__init__.py防止缺胳膊少腿"""
all_dirs_have_init = True
for root, dirs, files in os.walk(directory):
if "__pycache__" in dirs:
dirs.remove("__pycache__")
if "test" in dirs:
dirs.remove("test")
if "__init__.py" not in files:
print(f"Directory '{root}' is missing '__init__.py'")
all_dirs_have_init = False
return all_dirs_have_init
directory_to_check = os.path.join(os.path.dirname(os.path.realpath(__file__)),"..","unitree_sdk2py")
if check_init_files(directory_to_check):
print("所有文件夹均含 '__init__.py'.")
else:
print("校验未通过", file=sys.stderr)

View File

@ -1,4 +1,13 @@
ChannelConfigHasInterface = '''<?xml version="1.0" encoding="UTF-8" ?>
import platform
# build platform compatible
if platform.system() == "Windows":
import os
tmp = os.environ['TEMP']
else:
tmp = "/tmp"
ChannelConfigHasInterface = f'''<?xml version="1.0" encoding="UTF-8" ?>
<CycloneDDS>
<Domain Id="any">
<General>
@ -8,7 +17,7 @@ ChannelConfigHasInterface = '''<?xml version="1.0" encoding="UTF-8" ?>
</General>
<Tracing>
<Verbosity>config</Verbosity>
<OutputFile>/tmp/cdds.LOG</OutputFile>
<OutputFile>{tmp}/cdds.LOG</OutputFile>
</Tracing>
</Domain>
</CycloneDDS>'''

View File

@ -0,0 +1,8 @@
from unitree_sdk2py.utils.hz_sample import HZSample
if __name__ == "__main__":
hz=HZSample(2)
hz.Start()
while True:
hz.Sample()

View File

@ -52,10 +52,7 @@ class RecurrentThread(Thread):
super().Wait(timeout)
def __LoopFunc(self):
# clock type CLOCK_MONOTONIC = 1
tfd = timerfd_create(1, 0)
spec = itimerspec.from_seconds(self.__inter, self.__inter)
timerfd_settime(tfd, 0, ctypes.byref(spec), None)
timer = Timer(self.__inter, self.__inter)
while not self.__quit:
try:
@ -65,13 +62,13 @@ class RecurrentThread(Thread):
print(f"[RecurrentThread] target func raise exception: name={info[0].__name__}, args={str(info[1].args)}")
try:
buf = os.read(tfd, 8)
timer.blockWait()
# print(struct.unpack("Q", buf)[0])
except OSError as e:
if e.errno != errno.EAGAIN:
raise e
os.close(tfd)
timer.close()
def __LoopFunc_0(self):
while not self.__quit:

View File

@ -1,45 +1,116 @@
import math
import ctypes
from .clib_lookup import CLIBLookup
import platform
class timespec(ctypes.Structure):
_fields_ = [("sec", ctypes.c_long), ("nsec", ctypes.c_long)]
__slots__ = [name for name,type in _fields_]
@classmethod
def from_seconds(cls, secs):
c = cls()
c.seconds = secs
return c
"""声明计时通用接口主要为兼容windows平台和linux的文件描述符,在python3.13 os会支持但现在还没release.."""
class Timer:
@property
def seconds(self):
return self.sec + self.nsec / 1000000000
@seconds.setter
def seconds(self, secs):
x, y = math.modf(secs)
self.sec = int(y)
self.nsec = int(x * 1000000000)
class itimerspec(ctypes.Structure):
_fields_ = [("interval", timespec),("value", timespec)]
__slots__ = [name for name,type in _fields_]
"""
参数
time: 首次运行等待时间.First Wait Time, second.
period: 周期等待时间, .Period Wait Time, second.
"""
def __init__(self, time :float, period :float):
pass
@classmethod
def from_seconds(cls, interval, value):
spec = cls()
spec.interval.seconds = interval
spec.value.seconds = value
return spec
"""
阻塞等待,会在下方根据平台重写
"""
def blockWait(self):
pass
"""
关闭句柄
"""
def close(self):
pass
# function timerfd_create
timerfd_create = CLIBLookup("timerfd_create", ctypes.c_int, (ctypes.c_long, ctypes.c_int))
# build platform compatible
if platform.system() == "Windows":
from ctypes import wintypes
kernel32 = ctypes.windll.kernel32
INFINITE=wintypes.DWORD(-1)
PTIMERAPCROUTINE = ctypes.WINFUNCTYPE(None, wintypes.LPVOID, wintypes.DWORD, wintypes.DWORD)
@PTIMERAPCROUTINE
def timer_callback(arg, timer_low, timer_high):
pass
# 重写Timer方法
def Timer_init(self, time :float, period :float):
self.handle = kernel32.CreateWaitableTimerW(None, True, None)
due_time = wintypes.LARGE_INTEGER(-int(time * 10000000)) # 秒转100纳秒
period = int(period*1000) # 秒转毫秒
# https://learn.microsoft.com/zh-cn/windows/win32/api/synchapi/nf-synchapi-setwaitabletimer
if not kernel32.SetWaitableTimer(self.handle, ctypes.byref(due_time), period, timer_callback, 0, True):
raise OSError("Failed to set waitable timer.")
def Timer_blockWait(self):
kernel32.WaitForSingleObject(self.handle, INFINITE)
def Timer_close(self):
kernel32.CancelWaitableTimer(self.handle)
kernel32.CloseHandle(self.handle)
Timer.__init__ = Timer_init
Timer.blockWait = Timer_blockWait
Timer.close = Timer_close
elif platform.system() == "Linux":
from .clib_lookup import CLIBLookup
# function timerfd_settime
timerfd_settime = CLIBLookup("timerfd_settime", ctypes.c_int, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(itimerspec), ctypes.POINTER(itimerspec)))
class timespec(ctypes.Structure):
_fields_ = [("sec", ctypes.c_long), ("nsec", ctypes.c_long)]
__slots__ = [name for name,type in _fields_]
# function timerfd_gettime
timerfd_gettime = CLIBLookup("timerfd_gettime", ctypes.c_int, (ctypes.c_int, ctypes.POINTER(itimerspec)))
@classmethod
def from_seconds(cls, secs):
c = cls()
c.seconds = secs
return c
@property
def seconds(self):
return self.sec + self.nsec / 1000000000
@seconds.setter
def seconds(self, secs):
x, y = math.modf(secs)
self.sec = int(y)
self.nsec = int(x * 1000000000)
class itimerspec(ctypes.Structure):
_fields_ = [("interval", timespec),("value", timespec)]
__slots__ = [name for name,type in _fields_]
@classmethod
def from_seconds(cls, interval, value):
spec = cls()
spec.interval.seconds = interval
spec.value.seconds = value
return spec
# function timerfd_create
timerfd_create = CLIBLookup("timerfd_create", ctypes.c_int, (ctypes.c_long, ctypes.c_int))
# function timerfd_settime
timerfd_settime_linux = CLIBLookup("timerfd_settime", ctypes.c_int, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(itimerspec), ctypes.POINTER(itimerspec)))
def timerfd_settime(handle,interval,value):
spec = itimerspec.from_seconds(interval, value)
timerfd_settime_linux(handle, 0, spec, None)
# function timerfd_gettime
timerfd_gettime = CLIBLookup("timerfd_gettime", ctypes.c_int, (ctypes.c_int, ctypes.POINTER(itimerspec)))
# 重写Timer方法
import os
def Timer_init(self, time :float, period :float):
self.handle = timerfd_create(1, 0)
spec = itimerspec.from_seconds(period, time)
timerfd_settime_linux(self.handle, 0, spec, None)
def Timer_blockWait(self):
os.read(self.handle,8)
def Timer_close(self):
os.close(self.handle)
Timer.__init__ = Timer_init
Timer.blockWait = Timer_blockWait
Timer.close = Timer_close