Skip to content

Commit dc8ccce

Browse files
authored
Merge pull request #19 from urmahp/test_urcap
Added a test, that verifies that the URCap can be used with a driver
2 parents cf275f3 + 9bbe63f commit dc8ccce

File tree

12 files changed

+543
-15
lines changed

12 files changed

+543
-15
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<Installation directory="/ursim/programs" fileName="default" showSpeedSliderOnRunTab="false">
2+
<IOs>
3+
<DigitalInputNames value=", , , , , , , "/>
4+
<DigitalOutputNames value=", , , , , , , "/>
5+
<ToolDigitalInputNames value=", "/>
6+
<ToolDigitalOutputNames value=", "/>
7+
<ToolAnalogInputNames value=", "/>
8+
<AnalogInputNames value=", "/>
9+
<AnalogOutputNames value=", "/>
10+
<ConfigurableInputNames value=", , , , , , , "/>
11+
<ConfigurableOutputNames value=", , , , , , , "/>
12+
<outputSignalOptions value="0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0"/>
13+
<analogInputRange value="1, 1, 1, 1"/>
14+
<analogOutputDomain value="0, 0"/>
15+
<DigitalInputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
16+
<ToolDigitalInputActions value="NONE, NONE"/>
17+
<ConfigurableInputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
18+
<DigitalOutputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
19+
<ToolDigitalOutputActions value="NONE, NONE"/>
20+
<AnalogOutputActions value="NONE, NONE"/>
21+
<ConfigurableOutputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
22+
<euromapOutputModificationChoice value="1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1"/>
23+
<euromapOutputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
24+
<GeneralPurposeBooleanRegisterInputNames value=", , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , "/>
25+
<GeneralPurposeBooleanRegisterOutputNames value=", , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , "/>
26+
<GeneralPurposeIntRegisterInputNames value=", , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , "/>
27+
<GeneralPurposeIntRegisterOutputNames value=", , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , "/>
28+
<GeneralPurposeFloatRegisterInputNames value=", , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , "/>
29+
<GeneralPurposeFloatRegisterOutputNames value=", , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , "/>
30+
<GeneralPurposeBooleanRegisterInputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
31+
<GeneralPurposeBooleanRegisterOutputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
32+
<GeneralPurposeIntRegisterOutputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
33+
<GeneralPurposeFloatRegisterOutputActions value="NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE"/>
34+
<ModbusUnits/>
35+
</IOs>
36+
<Version projectName="URSoftware" major="5" minor="11" bugfix="1" buildNumber="108318" buildDate="Jun 18 2021, 12:00:00"/>
37+
<LastSavedInVersion reference="../Version"/>
38+
<SmoothTransitionSettings transitionHardness="SOFT"/>
39+
<ResourceControlSettings/>
40+
<ContinuousPulse/>
41+
<PayloadSettings>
42+
<Payload name="Payload" mass="0.0" defaultPayload="true" centerOfGravity="0.0, 0.0, 0.0" inertiaParameters="0.0, 0.0, 0.0, 0.0, 0.0, 0.0" customInertiaEnabled="false"/>
43+
</PayloadSettings>
44+
<SafeHomeSettings enabled="true" position="0.0, -1.5707963267948966, 0.0, -1.5707963267948966, 0.0, 0.0"/>
45+
<TCPSettings activePose="TCP">
46+
<availablePoses>
47+
<tcp id="2e4b410d-3d81-4c59-937c-087544210280" name="TCP" offset="0.0, 0.0, 0.0, 0.0, 0.0, 0.0"/>
48+
</availablePoses>
49+
</TCPSettings>
50+
<DefaultProgramSettings autoLoad="false"/>
51+
<Features>
52+
<CameraView>
53+
<worldTransform class="WorldTransform">
54+
<WorldtoMarshal showAxes="false" isVariable="false" baseAngle="0.0" tiltAngle="0.0"/>
55+
</worldTransform>
56+
</CameraView>
57+
<BaseView>
58+
<BaseView showAxes="true" joggable="true" isVariable="false" id="1b785537-e70a-4c26-a8af-4602e931277e"/>
59+
</BaseView>
60+
<ToolView>
61+
<ToolView showAxes="true" joggable="true" isVariable="false" id="3a9e24b6-0faf-4e81-8344-6af13d2e6405"/>
62+
</ToolView>
63+
</Features>
64+
<ToolCommSettings enabled="false" toolAnalogInput2="VOLTAGE" toolAnalogInput3="VOLTAGE" baudRate="BAUD_115200" parity="NONE" stopBits="ONE" rxIdleChars="1.5" txIdleChars="3.5" toolOutputMode="OUTPUT" toolDigitalOutput0="SINKING" toolDigitalOutput1="SINKING" toolOutputVoltage="ZERO"/>
65+
<SafetySettings>
66+
[SafetyLimits BoundaryPlane0]
67+
name = Safety plane 0
68+
displacement = 0.0
69+
mode = DISABLED
70+
planeNormal = [0.0, 0.0, 0.0]
71+
distanceToOrigin = 0.0
72+
elbowRestricted = true
73+
74+
[SafetyLimits BoundaryPlane1]
75+
name = Safety plane 1
76+
displacement = 0.0
77+
mode = DISABLED
78+
planeNormal = [0.0, 0.0, 0.0]
79+
distanceToOrigin = 0.0
80+
elbowRestricted = true
81+
82+
[SafetyLimits BoundaryPlane2]
83+
name = Safety plane 2
84+
displacement = 0.0
85+
mode = DISABLED
86+
planeNormal = [0.0, 0.0, 0.0]
87+
distanceToOrigin = 0.0
88+
elbowRestricted = true
89+
90+
[SafetyLimits BoundaryPlane3]
91+
name = Safety plane 3
92+
displacement = 0.0
93+
mode = DISABLED
94+
planeNormal = [0.0, 0.0, 0.0]
95+
distanceToOrigin = 0.0
96+
elbowRestricted = true
97+
98+
[SafetyLimits BoundaryPlane4]
99+
name = Safety plane 4
100+
displacement = 0.0
101+
mode = DISABLED
102+
planeNormal = [0.0, 0.0, 0.0]
103+
distanceToOrigin = 0.0
104+
elbowRestricted = true
105+
106+
[SafetyLimits BoundaryPlane5]
107+
name = Safety plane 5
108+
displacement = 0.0
109+
mode = DISABLED
110+
planeNormal = [0.0, 0.0, 0.0]
111+
distanceToOrigin = 0.0
112+
elbowRestricted = true
113+
114+
[SafetyLimits BoundaryPlane6]
115+
name = Safety plane 6
116+
displacement = 0.0
117+
mode = DISABLED
118+
planeNormal = [0.0, 0.0, 0.0]
119+
distanceToOrigin = 0.0
120+
elbowRestricted = true
121+
122+
[SafetyLimits BoundaryPlane7]
123+
name = Safety plane 7
124+
displacement = 0.0
125+
mode = DISABLED
126+
planeNormal = [0.0, 0.0, 0.0]
127+
distanceToOrigin = 0.0
128+
elbowRestricted = true
129+
130+
[SafetyLimits ToolOrientation]
131+
tcpOrientationVector = [0.0, 0.0, 1.0]
132+
maximumTcpOrientationDeviation = 3.159045946109736
133+
mode = DISABLED
134+
135+
[WorkpieceConfiguration]
136+
toolSpheres = [[0.0, 0.0, 0.0, 0.0]]
137+
toolSphereNames = [Tool Flange]
138+
toolSphereIDs = [null]
139+
toolDirectionInclination = 0.0
140+
toolDirectionAzimuth = 0.0
141+
142+
[ThreePositionEnabling]
143+
useTP3PE = 0
144+
allowManualHighSpeed = 1
145+
146+
[MiscConfiguration]
147+
teach_pendant = 1
148+
euromap67 = 0
149+
150+
[SafetyLimits Normal Values]
151+
maxTcpSpeed = 1.5
152+
maxForce = 150.0
153+
maxElbowSpeed = 1.5
154+
maxElbowForce = 150.0
155+
maxStoppingDistance = 0.5
156+
maxStoppingTime = 0.4
157+
maxPower = 300.0
158+
maxMomentum = 25.0
159+
160+
[SafetyLimits Normal Joints]
161+
maxJointSpeed = [3.3415926535897933, 3.3415926535897933, 3.3415926535897933, 3.3415926535897933, 3.3415926535897933, 3.3415926535897933]
162+
minJointPosition = [6.230825429179586, 6.230825429179586, 6.230825429179586, 6.230825429179586, 6.230825429179586, 6.230825429179586]
163+
maxJointPosition = [0.05235987799999986, 0.05235987799999986, 0.05235987799999986, 0.05235987799999986, 0.05235987799999986, 0.05235987799999986]
164+
minJointRevolutions = [-2, -2, -2, -2, -2, -2]
165+
maxJointRevolutions = [1, 1, 1, 1, 1, 1]
166+
167+
[SafetyLimits Reduced Values]
168+
maxTcpSpeed = 0.75
169+
maxForce = 120.0
170+
maxElbowSpeed = 0.75
171+
maxElbowForce = 120.0
172+
maxStoppingDistance = 0.3
173+
maxStoppingTime = 0.3
174+
maxPower = 200.0
175+
maxMomentum = 10.0
176+
177+
[SafetyLimits Reduced Joints]
178+
maxJointSpeed = [3.3415926535897933, 3.3415926535897933, 3.3415926535897933, 3.3415926535897933, 3.3415926535897933, 3.3415926535897933]
179+
minJointPosition = [6.230825429179586, 6.230825429179586, 6.230825429179586, 6.230825429179586, 6.230825429179586, 6.230825429179586]
180+
maxJointPosition = [0.05235987799999986, 0.05235987799999986, 0.05235987799999986, 0.05235987799999986, 0.05235987799999986, 0.05235987799999986]
181+
minJointRevolutions = [-2, -2, -2, -2, -2, -2]
182+
maxJointRevolutions = [1, 1, 1, 1, 1, 1]
183+
184+
[SafetySetup]
185+
withPositionLimits = [true, true, true, true, true, true]
186+
verified = true
187+
188+
[Safety Checksum]
189+
version = 5.5
190+
crc = 1475618907
191+
192+
[SafeHome]
193+
safeHomePosition = [-1.0, -1.0, -1.0, -1.0, -1.0, -1.0]
194+
195+
[SafetyIOConfiguration Inputs]
196+
emergencyStopInputA = 255
197+
emergencyStopInputB = 255
198+
reducedModeInputA = 255
199+
reducedModeInputB = 255
200+
safeguardStopResetInputA = 0
201+
safeguardStopResetInputB = 1
202+
threePositionEnablingInputA = 255
203+
threePositionEnablingInputB = 255
204+
operationalModeInputA = 255
205+
operationalModeInputB = 255
206+
automaticModeSafeguardStopInputA = 255
207+
automaticModeSafeguardStopInputB = 255
208+
automaticModeSafeguardStopResetInputA = 255
209+
automaticModeSafeguardStopResetInputB = 255
210+
211+
[SafetyIOConfiguration Outputs]
212+
systemEmergencyStopOutputA = 255
213+
systemEmergencyStopOutputB = 255
214+
robotMovingOutputA = 255
215+
robotMovingOutputB = 255
216+
robotNotStoppingOutputA = 255
217+
robotNotStoppingOutputB = 255
218+
reducedModeOutputA = 255
219+
reducedModeOutputB = 255
220+
notReducedModeOutputA = 255
221+
notReducedModeOutputB = 255
222+
safeHomeOutputA = 255
223+
safeHomeOutputB = 255
224+
configOut0and1_OSSD = 0
225+
configOut2and3_OSSD = 0
226+
configOut4and5_OSSD = 0
227+
configOut6and7_OSSD = 0
228+
229+
</SafetySettings>
230+
<Contributions>
231+
<URCaps>
232+
<Contributed ownerId="com.ur.urcap.conveyortracking" id="ConveyorTracking" installationContributionTitle="com.ur.urcap.conveyortracking" vendor="Universal Robots" name="Conveyor Tracking"/>
233+
<Contributed ownerId="com.ur.urcap.ethernetip" id="EthernetIP" installationContributionTitle="com.ur.urcap.ethernetip" vendor="Universal Robots" name="Ethernet/IP"/>
234+
<Contributed ownerId="com.ur.urcap.profinet" id="com.ur.urcap.profinet" installationContributionTitle="com.ur.urcap.profinet" vendor="Universal Robots" name="Profinet"/>
235+
<Contributed ownerId="com.fzi.externalcontrol" id="com.fzi.externalcontrol.impl.ExternalControlInstallationNodeService" installationContributionTitle="com.fzi.externalcontrol" vendor="FZI Research Center for Information Technology" name="External Control">
236+
<data key="host_ip" value="127.0.0.1"/>
237+
<data key="name" value="127.0.0.1"/>
238+
</Contributed>
239+
<Contributed ownerId="com.ur.polyscope.screwdriving.polyscope-screwdriving-impl" id="com.ur.polyscope.screwdriving.installation.ScrewdrivingInstallationNodeService" installationContributionTitle="com.ur.polyscope.screwdriving.polyscope-screwdriving-impl" vendor="Universal Robots" name="polyscope-screwdriving-impl"/>
240+
</URCaps>
241+
</Contributions>
242+
</Installation>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#
2+
#Fri Jun 18 10:27:46 BST 2021
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<URProgram name="multiple_urcaps" installation="default" installationRelativePath="default" directory="/ursim/programs" createdIn="5.11.1.108318" lastSavedIn="5.11.1.108318" robotSerialNumber="20195599999" crcValue="1475618907">
2+
<kinematics status="NOT_INITIALIZED" validChecksum="false">
3+
<deltaTheta value="0.0, 0.0, 0.0, 0.0, 0.0, 0.0"/>
4+
<a value="0.0, -0.425, -0.3922, 0.0, 0.0, 0.0"/>
5+
<d value="0.1625, 0.0, 0.0, 0.1333, 0.0997, 0.0996"/>
6+
<alpha value="1.570796327, 0.0, 0.0, 1.570796327, -1.570796327, 0.0"/>
7+
<jointChecksum value="-1, -1, -1, -1, -1, -1"/>
8+
</kinematics>
9+
<children>
10+
<MainProgram runOnlyOnce="true" InitVariablesNode="false">
11+
<children>
12+
<Contributed strategyClass="com.fzi.externalcontrol.ExternalControlNode" strategyProgramNodeType="External Control" strategyURCapDeveloper="FZI Research Center for Information Technology" strategyURCapName="External Control">
13+
<dataModel/>
14+
</Contributed>
15+
<Wait type="Sleep">
16+
<waitTime>1.0</waitTime>
17+
</Wait>
18+
<Contributed strategyClass="com.fzi.externalcontrol.ExternalControlNode" strategyProgramNodeType="External Control" strategyURCapDeveloper="FZI Research Center for Information Technology" strategyURCapName="External Control">
19+
<dataModel/>
20+
</Contributed>
21+
</children>
22+
</MainProgram>
23+
</children>
24+
</URProgram>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<URProgram name="one_urcap" installation="default" installationRelativePath="default" directory="/ursim/programs" createdIn="5.11.1.108318" lastSavedIn="5.11.1.108318" robotSerialNumber="20195599999" crcValue="1475618907">
2+
<kinematics status="NOT_INITIALIZED" validChecksum="false">
3+
<deltaTheta value="0.0, 0.0, 0.0, 0.0, 0.0, 0.0"/>
4+
<a value="0.0, -0.425, -0.3922, 0.0, 0.0, 0.0"/>
5+
<d value="0.1625, 0.0, 0.0, 0.1333, 0.0997, 0.0996"/>
6+
<alpha value="1.570796327, 0.0, 0.0, 1.570796327, -1.570796327, 0.0"/>
7+
<jointChecksum value="-1, -1, -1, -1, -1, -1"/>
8+
</kinematics>
9+
<children>
10+
<MainProgram runOnlyOnce="true" InitVariablesNode="false">
11+
<children>
12+
<Wait type="Sleep">
13+
<waitTime>1.0</waitTime>
14+
</Wait>
15+
<Contributed strategyClass="com.fzi.externalcontrol.ExternalControlNode" strategyProgramNodeType="External Control" strategyURCapDeveloper="FZI Research Center for Information Technology" strategyURCapName="External Control">
16+
<dataModel/>
17+
</Contributed>
18+
</children>
19+
</MainProgram>
20+
</children>
21+
</URProgram>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/ursim/programs.UR5
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
3+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
4+
5+
# Make directory to store the URCap
6+
mkdir ${DIR}/urcaps 2>/dev/null
7+
cp target/externalcontrol-*.jar ${DIR}/urcaps/externalcontrol.jar
8+
9+
docker run --rm --name="mydockerursim" -d \
10+
-e ROBOT_MODEL=UR5 \
11+
--network host \
12+
-v "${DIR}/.vol":/ursim/programs \
13+
-v "${DIR}/urcaps":/urcaps \
14+
--privileged \
15+
--cpus=1 \
16+
universalrobots/ursim_e-series
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
netcat -z 127.0.0.1 29999
4+
while [ $? -eq 1 ]
5+
do
6+
echo "Dashboard server not accepting connections..."
7+
sleep 3
8+
netcat -z 127.0.0.1 29999
9+
done
10+
echo "Dashboard server connections are possible."
11+
sleep 5

.github/workflows/build.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,20 @@ jobs:
1414
- uses: ./.github/actions/build_urcap
1515
env:
1616
SDK_VERSION: ${{ matrix.SDK_VERSION }}
17+
- name: Run URSim
18+
run: |
19+
.github/dockerursim/build_and_run_docker_ursim.sh
20+
- name: Install netcat
21+
run: |
22+
sudo apt-get update && sudo apt-get install -y netcat
23+
- name: Wait for dashboard client
24+
run: |
25+
./.github/helpers/wait_for_dashboard_server.sh
26+
- name: Run test
27+
run: |
28+
python3 tests/test_urcap.py
1729
- name: Upload urcap_artifact
1830
uses: actions/upload-artifact@v1
1931
with:
2032
path: target
2133
name: external_control_sdk-${{ matrix.SDK_VERSION }}.urcap
22-
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1-
popup("Hello world")
1+
2+
def test_func():
3+
textmsg("test function")
4+
end
5+
6+
# NODE_CONTROL_LOOP_BEGINS
7+
8+
test_func()
9+
10+
# NODE_CONTROL_LOOP_ENDS

0 commit comments

Comments
 (0)