Merge 5a4a5d30d4
into 58c3f6240d
This commit is contained in:
commit
4ba34f08a2
|
@ -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/*
|
||||
|
|
@ -34,3 +34,6 @@ __pycache__
|
|||
|
||||
# JetBrains IDE
|
||||
.idea/
|
||||
|
||||
build
|
||||
dist
|
|
@ -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)
|
|
@ -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>'''
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
from unitree_sdk2py.utils.hz_sample import HZSample
|
||||
|
||||
if __name__ == "__main__":
|
||||
hz=HZSample(2)
|
||||
hz.Start()
|
||||
while True:
|
||||
hz.Sample()
|
||||
|
|
@ -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:
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue