diff --git a/niryo_one_tcp_server/clients/csharp/.gitignore b/niryo_one_tcp_server/clients/csharp/.gitignore new file mode 100644 index 00000000..04e1705d --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml +lcov.info + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Coverage analysis +coverage.json diff --git a/niryo_one_tcp_server/clients/csharp/.vscode/tasks.json b/niryo_one_tcp_server/clients/csharp/.vscode/tasks.json new file mode 100644 index 00000000..2ac6968c --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/.vscode/tasks.json @@ -0,0 +1,78 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/NiryoOneClient.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "pack", + "command": "dotnet", + "type": "process", + "args": [ + "pack", + "${workspaceFolder}/NiryoOneClient.sln", + "--configuration", "Release", + "--include-symbols" + ], + "problemMatcher": "$msCompile", + "group": "build" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/NiryoOneClient.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "test", + "group": { + "kind": "test", + "isDefault": true + }, + "command": "dotnet", + "type": "process", + "args": [ + "test", + "${workspaceFolder}/NiryoOneClient.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "/p:CollectCoverage=true", + "/p:CoverletOutputFormat=lcov", + "/p:CoverletOutput=./lcov.info" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/NiryoOneClient.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/Examples/Examples.csproj b/niryo_one_tcp_server/clients/csharp/Examples/Examples.csproj new file mode 100644 index 00000000..841b5b47 --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/Examples/Examples.csproj @@ -0,0 +1,13 @@ + + + + + + + + Exe + netcoreapp2.0 + false + + + diff --git a/niryo_one_tcp_server/clients/csharp/Examples/Program.cs b/niryo_one_tcp_server/clients/csharp/Examples/Program.cs new file mode 100644 index 00000000..da710b4b --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/Examples/Program.cs @@ -0,0 +1,76 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using NiryoOneClient; + +namespace Examples +{ + class Program + { + public static async Task Main(string[] args) + { + string server = "10.10.10.10"; + + if (args.Length == 1 || args.Length == 7) + { + server = args.FirstOrDefault(); + args = args.Skip(1).ToArray(); + } + + using (var niryoOneClient = new NiryoOneClient.NiryoOneClient(server)) + { + Console.WriteLine($"Connecting to {server}:40001"); + var niryo = await niryoOneClient.Connect(); + Console.WriteLine($"Connected!"); + + PoseObject initialPose = null; + if (args.Length == 6) + initialPose = new PoseObject(args.Select(f => float.Parse(f, CultureInfo.InvariantCulture)).ToArray()); + + Console.WriteLine("Calibrating..."); + await niryo.Calibrate(CalibrateMode.AUTO); + Console.WriteLine("Done!"); + + var pose = await niryo.GetPose(); + await niryo.MoveJoints(new RobotJoints(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + await niryo.ShiftPose(RobotAxis.Y, 0.15f); + + if (initialPose != null) + { + await niryo.MovePose(initialPose); + } + + var digitalIOPins = await niryo.GetDigitalIOState(); + + foreach (var pin in digitalIOPins) + Console.WriteLine($"Pin: {pin.PinId}, name: {pin.Name}, mode: {pin.Mode}, state: {pin.State}"); + + await niryo.SetLearningMode(true); + } + } + } +} diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient.Tests/NiryoOneClient.Tests.csproj b/niryo_one_tcp_server/clients/csharp/NiryoOneClient.Tests/NiryoOneClient.Tests.csproj new file mode 100644 index 00000000..a5e11f83 --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient.Tests/NiryoOneClient.Tests.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp2.0 + + false + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient.Tests/NiryoOneConnectionTest.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient.Tests/NiryoOneConnectionTest.cs new file mode 100644 index 00000000..3efcd23f --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient.Tests/NiryoOneConnectionTest.cs @@ -0,0 +1,339 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; + +namespace NiryoOneClient.Tests +{ + [TestClass] + public class NiryoOneConnectionTest + { + private TextReader _streamReader; + private TextWriter _streamWriter; + private NiryoOneConnection _connection; + + public NiryoOneConnectionTest() + { + _streamReader = Substitute.For(); + _streamWriter = Substitute.For(); + _connection = new NiryoOneConnection(_streamReader, _streamWriter); + } + + [TestMethod] + public async Task Calibrate_SuccessfulAuto_Works() + { + _streamReader.ReadLineAsync().Returns("CALIBRATE:OK"); + await _connection.Calibrate(CalibrateMode.AUTO); + await _streamWriter.Received().WriteLineAsync("CALIBRATE:AUTO"); + } + + [TestMethod] + public async Task Calibrate_SuccessfulManual_Works() + { + _streamReader.ReadLineAsync().Returns("CALIBRATE:OK\n"); + await _connection.Calibrate(CalibrateMode.MANUAL); + await _streamWriter.Received().WriteLineAsync("CALIBRATE:MANUAL"); + } + + [TestMethod] + public async Task Calibrate_Failure_Throws() + { + _streamReader.ReadLineAsync().Returns("CALIBRATE:KO,\"Sucks to be sucky\""); + var e = await Assert.ThrowsExceptionAsync(async () => await _connection.Calibrate(CalibrateMode.MANUAL)); + Assert.AreEqual("Sucks to be sucky", e.Reason); + await _streamWriter.Received().WriteLineAsync("CALIBRATE:MANUAL"); + } + + [TestMethod] + public async Task SetLearningMode_True_Works() + { + _streamReader.ReadLineAsync().Returns("SET_LEARNING_MODE:OK"); + await _connection.SetLearningMode(true); + await _streamWriter.Received().WriteLineAsync("SET_LEARNING_MODE:TRUE"); + } + + [TestMethod] + public async Task SetLearningMode_False_Works() + { + _streamReader.ReadLineAsync().Returns("SET_LEARNING_MODE:OK"); + await _connection.SetLearningMode(false); + await _streamWriter.Received().WriteLineAsync("SET_LEARNING_MODE:FALSE"); + } + + [TestMethod] + public async Task SetLearningMode_WrongResponse_Throws() + { + _streamReader.ReadLineAsync().Returns("COCO:OK"); + var e = await Assert.ThrowsExceptionAsync(async () => await _connection.SetLearningMode(false)); + Assert.AreEqual("Wrong command response received.", e.Reason); + await _streamWriter.Received().WriteLineAsync("SET_LEARNING_MODE:FALSE"); + } + + [TestMethod] + public async Task SetLearningMode_ErrorResponse_Throws() + { + _streamReader.ReadLineAsync().Returns("SET_LEARNING_MODE:KO,\"No good\""); + var e = await Assert.ThrowsExceptionAsync(async () => await _connection.SetLearningMode(false)); + Assert.AreEqual("No good", e.Reason); + await _streamWriter.Received().WriteLineAsync("SET_LEARNING_MODE:FALSE"); + } + + [TestMethod] + public async Task MoveJoints_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("MOVE_JOINTS:OK"); + await _connection.MoveJoints(new RobotJoints(new[] { + 0.03f, 0.0123f, 0.456f, 0.987f, 0.654f, 0.321f + })); + await _streamWriter.Received().WriteLineAsync("MOVE_JOINTS:0.03,0.0123,0.456,0.987,0.654,0.321"); + } + + [TestMethod] + public async Task MovePose_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("MOVE_POSE:OK"); + await _connection.MovePose(new PoseObject(new[] { + 0.03f, 0.0123f, 0.456f, 0.987f, 0.654f, 0.321f + })); + await _streamWriter.Received().WriteLineAsync("MOVE_POSE:0.03,0.0123,0.456,0.987,0.654,0.321"); + } + + [TestMethod] + public async Task ShiftPose_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("SHIFT_POSE:OK"); + await _connection.ShiftPose(RobotAxis.ROLL, 0.03142f); + await _streamWriter.Received().WriteLineAsync("SHIFT_POSE:ROLL,0.03142"); + } + + [TestMethod] + public async Task SetArmMaxVelocity_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("SET_ARM_MAX_VELOCITY:OK"); + await _connection.SetArmMaxVelocity(50); + await _streamWriter.Received().WriteLineAsync("SET_ARM_MAX_VELOCITY:50"); + } + + [TestMethod] + public async Task EnableJoystick_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("ENABLE_JOYSTICK:OK"); + await _connection.EnableJoystick(false); + await _streamWriter.Received().WriteLineAsync("ENABLE_JOYSTICK:FALSE"); + } + + [TestMethod] + public async Task SetPinMode_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("SET_PIN_MODE:OK"); + await _connection.SetPinMode(RobotPin.GPIO_2B, PinMode.OUTPUT); + await _streamWriter.Received().WriteLineAsync("SET_PIN_MODE:GPIO_2B,OUTPUT"); + } + + [TestMethod] + public async Task DigitalWrite_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("DIGITAL_WRITE:OK"); + await _connection.DigitalWrite(RobotPin.GPIO_2A, DigitalState.LOW); + await _streamWriter.Received().WriteLineAsync("DIGITAL_WRITE:GPIO_2A,LOW"); + } + + [TestMethod] + public async Task DigitalRead_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("DIGITAL_READ:OK,HIGH"); + await _connection.DigitalRead(RobotPin.GPIO_1A); + await _streamWriter.Received().WriteLineAsync("DIGITAL_READ:GPIO_1A"); + } + + [TestMethod] + public async Task ChangeTool_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("CHANGE_TOOL:OK"); + await _connection.ChangeTool(RobotTool.GRIPPER_2); + await _streamWriter.Received().WriteLineAsync("CHANGE_TOOL:GRIPPER_2"); + } + + [TestMethod] + public async Task OpenGripper_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("OPEN_GRIPPER:OK"); + await _connection.OpenGripper(RobotTool.GRIPPER_1, 200); + await _streamWriter.Received().WriteLineAsync("OPEN_GRIPPER:GRIPPER_1,200"); + } + + [TestMethod] + public async Task CloseGripper_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("CLOSE_GRIPPER:OK"); + await _connection.CloseGripper(RobotTool.GRIPPER_1, 200); + await _streamWriter.Received().WriteLineAsync("CLOSE_GRIPPER:GRIPPER_1,200"); + } + + [TestMethod] + public async Task PullAirVacuumPump_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("PULL_AIR_VACUUM_PUMP:OK"); + await _connection.PullAirVacuumPump(RobotTool.VACUUM_PUMP_1); + await _streamWriter.Received().WriteLineAsync("PULL_AIR_VACUUM_PUMP:VACUUM_PUMP_1"); + } + + [TestMethod] + public async Task PushAirVacuumPump_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("PUSH_AIR_VACUUM_PUMP:OK"); + await _connection.PushAirVacuumPump(RobotTool.VACUUM_PUMP_1); + await _streamWriter.Received().WriteLineAsync("PUSH_AIR_VACUUM_PUMP:VACUUM_PUMP_1"); + } + + [TestMethod] + public async Task SetupElectromagnet_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("SETUP_ELECTROMAGNET:OK"); + await _connection.SetupElectromagnet(RobotTool.ELECTROMAGNET_1, RobotPin.GPIO_2B); + await _streamWriter.Received().WriteLineAsync("SETUP_ELECTROMAGNET:ELECTROMAGNET_1,GPIO_2B"); + } + + [TestMethod] + public async Task ActivateElectromagnet_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("ACTIVATE_ELECTROMAGNET:OK"); + await _connection.ActivateElectromagnet(RobotTool.ELECTROMAGNET_1, RobotPin.GPIO_2B); + await _streamWriter.Received().WriteLineAsync("ACTIVATE_ELECTROMAGNET:ELECTROMAGNET_1,GPIO_2B"); + } + + [TestMethod] + public async Task DeactivateElectromagnet_Sample_SendsCorrectly() + { + _streamReader.ReadLineAsync().Returns("DEACTIVATE_ELECTROMAGNET:OK"); + await _connection.DeactivateElectromagnet(RobotTool.ELECTROMAGNET_1, RobotPin.GPIO_2C); + await _streamWriter.Received().WriteLineAsync("DEACTIVATE_ELECTROMAGNET:ELECTROMAGNET_1,GPIO_2C"); + } + + [TestMethod] + public async Task GetJoints_Sample_Works() + { + _streamReader.ReadLineAsync().Returns("GET_JOINTS:OK,0.0,0.640187,-1.397485,0.0,0.0,0.0"); + var joints = await _connection.GetJoints(); + CollectionAssert.AreEqual(new[] { 0.0f, 0.640187f, -1.397485f, 0.0f, 0.0f, 0.0f }, joints.ToArray()); + } + + [TestMethod] + public async Task GetPose_Sample_Works() + { + _streamReader.ReadLineAsync().Returns("GET_POSE:OK,0.0695735635306,1.31094787803e-12,0.200777981243,-5.10302119597e-12,0.757298,5.10351727471e-12"); + var pose = await _connection.GetPose(); + CollectionAssert.AreEqual(new[] { 0.0695735635306f, 1.31094787803e-12f, 0.200777981243f, -5.10302119597e-12f, 0.757298f, 5.10351727471e-12f }, + pose.ToArray()); + } + + [TestMethod] + public async Task GetHardwareStatus_Sample_Works() + { + _streamReader.ReadLineAsync().Returns("GET_HARDWARE_STATUS:OK,59,2,True,'',0,False,['Stepper Axis 1', 'Stepper Axis 2', 'Stepper Axis 3', 'Servo Axis 4', 'Servo Axis 5', 'Servo Axis 6'],['Niryo Stepper', 'Niryo Stepper', 'Niryo Stepper', 'DXL XL-430', 'DXL XL-430', 'DXL XL-320'],(34, 34, 37, 43, 45, 37),(0.0, 0.0, 0.0, 11.3, 11.2, 7.9),(0, 0, 0, 0, 0, 0)"); + var status = await _connection.GetHardwareStatus(); + Assert.AreEqual(59, status.RpiTemperature); + Assert.AreEqual(2, status.HardwareVersion); + Assert.AreEqual(true, status.ConnectionUp); + Assert.AreEqual("", status.ErrorMessage); + Assert.AreEqual(0, status.CalibrationNeeded); + Assert.AreEqual(false, status.CalibrationInProgress); + CollectionAssert.AreEqual(new[] { "Stepper Axis 1", "Stepper Axis 2", "Stepper Axis 3", "Servo Axis 4", "Servo Axis 5", "Servo Axis 6" }, status.MotorNames); + CollectionAssert.AreEqual(new[] { "Niryo Stepper", "Niryo Stepper", "Niryo Stepper", "DXL XL-430", "DXL XL-430", "DXL XL-320" }, status.MotorTypes); + CollectionAssert.AreEqual(new[] { 34, 34, 37, 43, 45, 37 }, status.Temperatures); + CollectionAssert.AreEqual(new[] { 0.0m, 0.0m, 0.0m, 11.3m, 11.2m, 7.9m }, status.Voltages); + CollectionAssert.AreEqual(new[] { 0, 0, 0, 0, 0, 0 }, status.HardwareErrors); + } + + [TestMethod] + public async Task GetLearningMode_Sample_Works() + { + _streamReader.ReadLineAsync().Returns("GET_LEARNING_MODE:OK,FALSE"); + var mode = await _connection.GetLearningMode(); + await _streamWriter.Received().WriteLineAsync("GET_LEARNING_MODE"); + Assert.AreEqual(false, mode); + } + + [TestMethod] + public async Task GetDigitalIOState_Sample_Works() + { + _streamReader.ReadLineAsync().Returns("GET_DIGITAL_IO_STATE:OK,[2, '1A', 1, 1],[3, '1B', 1, 1],[16, '1C', 1, 1],[26, '2A', 1, 1],[19, '2B', 1, 1],[6, '2C', 1, 1],[12, 'SW1', 0, 0],[13, 'SW2', 0, 0]"); + var state = await _connection.GetDigitalIOState(); + Assert.AreEqual(2, state[0].PinId); + Assert.AreEqual("1A", state[0].Name); + Assert.AreEqual(PinMode.INPUT, state[0].Mode); + Assert.AreEqual(DigitalState.HIGH, state[0].State); + + Assert.AreEqual(3, state[1].PinId); + Assert.AreEqual("1B", state[1].Name); + Assert.AreEqual(PinMode.INPUT, state[1].Mode); + Assert.AreEqual(DigitalState.HIGH, state[1].State); + + Assert.AreEqual(16, state[2].PinId); + Assert.AreEqual("1C", state[2].Name); + Assert.AreEqual(PinMode.INPUT, state[2].Mode); + Assert.AreEqual(DigitalState.HIGH, state[2].State); + + Assert.AreEqual(26, state[3].PinId); + Assert.AreEqual("2A", state[3].Name); + Assert.AreEqual(PinMode.INPUT, state[3].Mode); + Assert.AreEqual(DigitalState.HIGH, state[3].State); + + Assert.AreEqual(19, state[4].PinId); + Assert.AreEqual("2B", state[4].Name); + Assert.AreEqual(PinMode.INPUT, state[4].Mode); + Assert.AreEqual(DigitalState.HIGH, state[4].State); + + Assert.AreEqual(6, state[5].PinId); + Assert.AreEqual("2C", state[5].Name); + Assert.AreEqual(PinMode.INPUT, state[5].Mode); + Assert.AreEqual(DigitalState.HIGH, state[5].State); + + Assert.AreEqual(12, state[6].PinId); + Assert.AreEqual("SW1", state[6].Name); + Assert.AreEqual(PinMode.OUTPUT, state[6].Mode); + Assert.AreEqual(DigitalState.LOW, state[6].State); + + Assert.AreEqual(13, state[7].PinId); + Assert.AreEqual("SW2", state[7].Name); + Assert.AreEqual(PinMode.OUTPUT, state[7].Mode); + Assert.AreEqual(DigitalState.LOW, state[7].State); + } + + [TestMethod] + public async Task ReceiveAnswerAsync_ArbitraryWhitespaceBetween_Works() + { + _streamReader.ReadLineAsync().Returns("HEJ:OK", "\t BLA:OK \t", "\nMERP:OK\r\n"); + var a1 = await _connection.ReceiveAnswerAsync("HEJ"); + Assert.AreEqual("", a1); + var a2 = await _connection.ReceiveAnswerAsync("BLA"); + Assert.AreEqual("", a2); + var a3 = await _connection.ReceiveAnswerAsync("MERP"); + Assert.AreEqual("", a3); + } + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient.sln b/niryo_one_tcp_server/clients/csharp/NiryoOneClient.sln new file mode 100644 index 00000000..ae46dd4c --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NiryoOneClient", "NiryoOneClient\NiryoOneClient.csproj", "{10575BA8-3DCD-4A5C-8754-39E893F6043B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NiryoOneClient.Tests", "NiryoOneClient.Tests\NiryoOneClient.Tests.csproj", "{41BDE194-283F-47F8-98A4-978A65393FFC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples", "Examples\Examples.csproj", "{7D509E8D-D71B-45FA-B510-526C8ED229D1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Debug|x64.ActiveCfg = Debug|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Debug|x64.Build.0 = Debug|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Debug|x86.ActiveCfg = Debug|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Debug|x86.Build.0 = Debug|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Release|Any CPU.Build.0 = Release|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Release|x64.ActiveCfg = Release|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Release|x64.Build.0 = Release|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Release|x86.ActiveCfg = Release|Any CPU + {10575BA8-3DCD-4A5C-8754-39E893F6043B}.Release|x86.Build.0 = Release|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Debug|x64.ActiveCfg = Debug|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Debug|x64.Build.0 = Debug|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Debug|x86.ActiveCfg = Debug|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Debug|x86.Build.0 = Debug|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Release|Any CPU.Build.0 = Release|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Release|x64.ActiveCfg = Release|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Release|x64.Build.0 = Release|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Release|x86.ActiveCfg = Release|Any CPU + {41BDE194-283F-47F8-98A4-978A65393FFC}.Release|x86.Build.0 = Release|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Debug|x64.ActiveCfg = Debug|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Debug|x64.Build.0 = Debug|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Debug|x86.ActiveCfg = Debug|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Debug|x86.Build.0 = Debug|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Release|Any CPU.Build.0 = Release|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Release|x64.ActiveCfg = Release|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Release|x64.Build.0 = Release|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Release|x86.ActiveCfg = Release|Any CPU + {7D509E8D-D71B-45FA-B510-526C8ED229D1}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/CalibrateMode.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/CalibrateMode.cs new file mode 100644 index 00000000..91c4f3db --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/CalibrateMode.cs @@ -0,0 +1,42 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +namespace NiryoOneClient +{ + /// + /// The type of calibration to be performed + /// + public enum CalibrateMode + { + /// + /// Automatic calibration where the robot performs a set of move to establish calibration + /// + AUTO, + + /// + /// Manual calibration where the robot is manually positioned in the calibration pose and that + /// pose is used to establish calibration + /// + MANUAL + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/HardwareStatus.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/HardwareStatus.cs new file mode 100644 index 00000000..34d23717 --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/HardwareStatus.cs @@ -0,0 +1,90 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; +using System.Globalization; +using System.Linq; + +namespace NiryoOneClient +{ + /// + /// A representation of the status of several system components in the Niryo One. + /// + public class HardwareStatus + { + /// + /// The core temperature of the Raspberry Pi + /// + public int RpiTemperature; + + /// + /// The hardware version + /// + public int HardwareVersion; + + /// + /// Whether a connection to the robot is up + /// + public bool ConnectionUp; + + /// + /// The current error message + /// + public string ErrorMessage; + + /// + /// Whether the robot needs a calibartion to perform moves + /// + public int CalibrationNeeded; + + /// + /// Whether a calibration is in progress + /// + public bool CalibrationInProgress; + + /// + /// The names of the connected motors + /// + public string[] MotorNames; + + /// + /// The model names of the connected motors + /// + public string[] MotorTypes; + + /// + /// The temperatures in degrees celcius of the connected motors + /// + public int[] Temperatures; + + /// + /// The voltages applied to the connected motors + /// + public decimal[] Voltages; + + /// + /// The number of hardware errors on the respective motors + /// + public int[] HardwareErrors; + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/INiryoOneClient.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/INiryoOneClient.cs new file mode 100644 index 00000000..831c8ccf --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/INiryoOneClient.cs @@ -0,0 +1,39 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System.Threading.Tasks; + +namespace NiryoOneClient +{ + /// + /// A client capable of connecting to the tcp server of a Niryo One robotic arm + /// + public interface INiryoOneClient + { + /// + /// Create a connection to the robot + /// + /// A NiryoOneConnection object used for sending commands to the robot + Task Connect(); + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/INiryoOneConnection.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/INiryoOneConnection.cs new file mode 100644 index 00000000..0a4a276b --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/INiryoOneConnection.cs @@ -0,0 +1,167 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System.Threading.Tasks; + +namespace NiryoOneClient +{ + /// + /// A connection object allowing sending commands to the tcp server on a Niryo One robotic arm + /// + public interface INiryoOneConnection + { + /// + /// Request calibration. + /// Whether to request automatic or manual calibration + /// + Task Calibrate(CalibrateMode mode); + + /// + /// Set whether the robot should be in learning mode or not. + /// Activate learning mode or not + /// + Task SetLearningMode(bool mode); + + /// + /// Move joints to specified configuration. + /// The desired destination joint configuration + /// + Task MoveJoints(RobotJoints joints); + + /// + /// Move joints to specified pose. + /// The desired destination pose + /// + Task MovePose(PoseObject pose); + + /// + /// Shift the pose along one axis. + /// Which axis to shift + /// The amount to shift (meters or radians) + /// + Task ShiftPose(RobotAxis axis, float value); + + /// + /// Set the maximum arm velocity.false + /// The maximum velocity in percent of maximum velocity. + /// + Task SetArmMaxVelocity(int velocity); + + /// + /// Enable or disable joystick control.false + /// + Task EnableJoystick(bool mode); + + /// + /// Configure a GPIO pin for input or output. + /// + Task SetPinMode(RobotPin pin, PinMode mode); + + /// + /// Write to a digital pin configured as output. + /// + Task DigitalWrite(RobotPin pin, DigitalState state); + + /// + /// Read from a digital pin configured as input. + /// + Task DigitalRead(RobotPin pin); + + /// + /// Select which tool is connected to the robot. + /// + Task ChangeTool(RobotTool tool); + + /// + /// Open the gripper. + /// Which gripper to open + /// The speed to use. Must be between 0 and 1000, recommended values between 100 and 500. + /// + Task OpenGripper(RobotTool gripper, int speed); + + /// + /// Close the gripper. + /// Which gripper to close + /// The speed to use. Must be between 0 and 1000, recommended values between 100 and 500. + /// + Task CloseGripper(RobotTool gripper, int speed); + + /// + /// Pull air using the vacuum pump. + /// Must be VACUUM_PUMP_1. Only one type available for now. + /// + Task PullAirVacuumPump(RobotTool vacuumPump); + + /// + /// Push air using the vacuum pump. + /// Must be VACUUM_PUMP_1. Only one type available for now. + /// + Task PushAirVacuumPump(RobotTool vacuumPump); + + /// + /// Setup the electromagnet. + /// Must be ELECTROMAGNET_1. Only one type available for now. + /// The pin to which the magnet is connected. + /// + Task SetupElectromagnet(RobotTool tool, RobotPin pin); + + /// + /// Activate the electromagnet. + /// Must be ELECTROMAGNET_1. Only one type available for now. + /// The pin to which the magnet is connected. + /// + Task ActivateElectromagnet(RobotTool tool, RobotPin pin); + + /// + /// Deactivate the electromagnet. + /// Must be ELECTROMAGNET_1. Only one type available for now. + /// The pin to which the magnet is connected. + /// + Task DeactivateElectromagnet(RobotTool tool, RobotPin pin); + + /// + /// Get the current joint configuration. + /// + Task GetJoints(); + + /// + /// Get the current pose. + /// + Task GetPose(); + + /// + /// Get the current hardware status. + /// + Task GetHardwareStatus(); + + /// + /// Get whether the robot is in learning mode. + /// + Task GetLearningMode(); + + /// + /// Get the current state of the digital io pins. + /// + Task GetDigitalIOState(); + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneClient.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneClient.cs new file mode 100644 index 00000000..5f0df59d --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneClient.cs @@ -0,0 +1,90 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace NiryoOneClient +{ + /// + /// A client capable of connecting to the tcp server of a Niryo One robotic arm + /// + public class NiryoOneClient : IDisposable, INiryoOneClient + { + private TcpClient _client; + private readonly int _port; + private readonly string _server; + private NetworkStream _stream; + private NiryoOneConnection _connection; + + /// + /// Construct a client + /// + /// The server address, ip or hostname + /// The port number, defaults to 40001 + public NiryoOneClient(string server, int port = 40001) + { + _server = server; + _port = port; + } + + /// + /// Create a connection to the robot + /// + /// A NiryoOneConnection object used for sending commands to the robot + public async Task Connect() + { + if (_client != null) + { + _client.Close(); + _client = null; + } + + _client = new TcpClient(); + await _client.ConnectAsync(_server, _port); + _stream = _client.GetStream(); + _connection = new NiryoOneConnection(new System.IO.StreamReader(_stream), new System.IO.StreamWriter(_stream)); + return _connection; + } + + /// + /// Dispose the object + /// + public void Dispose() + { + if (_stream != null) + { + _stream.Close(); + _stream.Dispose(); + _stream = null; + } + if (_client != null) + { + _client.Close(); + _client.Dispose(); + _client = null; + } + } + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneClient.csproj b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneClient.csproj new file mode 100644 index 00000000..8b5c2b0f --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneClient.csproj @@ -0,0 +1,25 @@ + + + + netstandard2.0 + + + + NiryoOneClient + 0.1.0 + rickardraysearch + Niryo + MIT + A tcp client in C# that manages the tcp communication with a Niryo One robot + + + + true + + + + + + + + diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneConnection.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneConnection.cs new file mode 100644 index 00000000..4ba39f02 --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneConnection.cs @@ -0,0 +1,350 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NiryoOneClient +{ + /// + /// A connection object allowing sending commands to the tcp server on a Niryo One robotic arm + /// + public class NiryoOneConnection : INiryoOneConnection + { + private readonly TextWriter _textWriter; + private readonly TextReader _textReader; + + /// + /// Construct a connection object + /// + /// A stream reader used for getting responses + /// A stream writer used for sending commands + public NiryoOneConnection(TextReader streamReader, TextWriter streamWriter) + { + _textWriter = streamWriter; + _textReader = streamReader; + } + + /// + /// Send a command to the tcp server + /// + /// A command + internal async Task WriteLineAsync(string s) + { + await _textWriter.WriteLineAsync(s); + await _textWriter.FlushAsync(); + } + + /// + /// Read response data from the tcp server + /// + /// The string read + internal async Task ReadLineAsync() + { + return await _textReader.ReadLineAsync(); + } + + /// + /// Send a command to the tcp server + /// + /// The type of command + /// The arguments + protected async Task SendCommandAsync(string commandType, params string[] args) + { + string cmd; + if (args.Any()) + cmd = $"{commandType}:{string.Join(",", args)}"; + else + cmd = commandType; + await WriteLineAsync(cmd); + } + + /// + /// Receive an answer from the tcp server related to a previously sent command + /// + /// The command for which a response is expected + /// The data portion of the response + internal async Task ReceiveAnswerAsync(string commandType) + { + + var result = await ReadLineAsync(); + + var colonSplit = result.Trim().Split(new[] {':'}, 2); + var cmd = colonSplit[0]; + if (cmd != commandType) + throw new NiryoOneException("Wrong command response received."); + var commaSplit2 = colonSplit[1].Split(new[] {','}, 2); + var status = commaSplit2[0]; + if (status != "OK") + { + var reason = ParserUtils.Strip(commaSplit2[1], "\"", "\""); + throw new NiryoOneException(reason); + } + + if (commaSplit2.Length > 1) + return commaSplit2[1]; + else + return string.Empty; + } + + /// + /// Request calibration. + /// Whether to request automatic or manual calibration + /// + public async Task Calibrate(CalibrateMode mode) + { + await SendCommandAsync("CALIBRATE", mode.ToString()); + await ReceiveAnswerAsync("CALIBRATE"); + } + + /// + /// Set whether the robot should be in learning mode or not. + /// Activate learning mode or not + /// + public async Task SetLearningMode(bool mode) + { + await SendCommandAsync("SET_LEARNING_MODE", mode.ToString().ToUpper()); + await ReceiveAnswerAsync("SET_LEARNING_MODE"); + } + + /// + /// Move joints to specified configuration. + /// The desired destination joint configuration + /// + public async Task MoveJoints(RobotJoints joints) + { + await SendCommandAsync("MOVE_JOINTS", string.Join(",", joints.Select(x => x.ToString(CultureInfo.InvariantCulture)))); + await ReceiveAnswerAsync("MOVE_JOINTS"); + } + + /// + /// Move joints to specified pose. + /// The desired destination pose + /// + public async Task MovePose(PoseObject pose) + { + await SendCommandAsync("MOVE_POSE", string.Join(",", pose.Select(x => x.ToString(CultureInfo.InvariantCulture)))); + await ReceiveAnswerAsync("MOVE_POSE"); + } + + /// + /// Shift the pose along one axis. + /// Which axis to shift + /// The amount to shift (meters or radians) + /// + public async Task ShiftPose(RobotAxis axis, float value) + { + await SendCommandAsync("SHIFT_POSE", axis.ToString(), value.ToString(CultureInfo.InvariantCulture)); + await ReceiveAnswerAsync("SHIFT_POSE"); + } + + /// + /// Set the maximum arm velocity.false + /// The maximum velocity in percent of maximum velocity. + /// + public async Task SetArmMaxVelocity(int velocity) + { + await SendCommandAsync("SET_ARM_MAX_VELOCITY", velocity.ToString()); + await ReceiveAnswerAsync("SET_ARM_MAX_VELOCITY"); + } + + /// + /// Enable or disable joystick control.false + /// + public async Task EnableJoystick(bool mode) + { + await SendCommandAsync("ENABLE_JOYSTICK", mode.ToString().ToUpper()); + await ReceiveAnswerAsync("ENABLE_JOYSTICK"); + } + + /// + /// Configure a GPIO pin for input or output. + /// + public async Task SetPinMode(RobotPin pin, PinMode mode) + { + await SendCommandAsync("SET_PIN_MODE", pin.ToString(), mode.ToString()); + await ReceiveAnswerAsync("SET_PIN_MODE"); + } + + /// + /// Write to a digital pin configured as output. + /// + public async Task DigitalWrite(RobotPin pin, DigitalState state) + { + await SendCommandAsync("DIGITAL_WRITE", pin.ToString(), state.ToString()); + await ReceiveAnswerAsync("DIGITAL_WRITE"); + } + + /// + /// Read from a digital pin configured as input. + /// + public async Task DigitalRead(RobotPin pin) + { + await SendCommandAsync("DIGITAL_READ", pin.ToString()); + var state = await ReceiveAnswerAsync("DIGITAL_READ"); + return (DigitalState)Enum.Parse(typeof(DigitalState), state); + } + + /// + /// Select which tool is connected to the robot. + /// + public async Task ChangeTool(RobotTool tool) + { + await SendCommandAsync("CHANGE_TOOL", tool.ToString()); + await ReceiveAnswerAsync("CHANGE_TOOL"); + } + + /// + /// Open the gripper. + /// Which gripper to open + /// The speed to use. Must be between 0 and 1000, recommended values between 100 and 500. + /// + public async Task OpenGripper(RobotTool gripper, int speed) + { + await SendCommandAsync("OPEN_GRIPPER", gripper.ToString(), speed.ToString()); + await ReceiveAnswerAsync("OPEN_GRIPPER"); + } + + /// + /// Close the gripper. + /// Which gripper to close + /// The speed to use. Must be between 0 and 1000, recommended values between 100 and 500. + /// + public async Task CloseGripper(RobotTool gripper, int speed) + { + await SendCommandAsync("CLOSE_GRIPPER", gripper.ToString(), speed.ToString()); + await ReceiveAnswerAsync("CLOSE_GRIPPER"); + } + + /// + /// Pull air using the vacuum pump. + /// Must be VACUUM_PUMP_1. Only one type available for now. + /// + public async Task PullAirVacuumPump(RobotTool vacuumPump) + { + await SendCommandAsync("PULL_AIR_VACUUM_PUMP", vacuumPump.ToString()); + await ReceiveAnswerAsync("PULL_AIR_VACUUM_PUMP"); + } + + /// + /// Push air using the vacuum pump. + /// Must be VACUUM_PUMP_1. Only one type available for now. + /// + public async Task PushAirVacuumPump(RobotTool vacuumPump) + { + await SendCommandAsync("PUSH_AIR_VACUUM_PUMP", vacuumPump.ToString()); + await ReceiveAnswerAsync("PUSH_AIR_VACUUM_PUMP"); + } + + /// + /// Setup the electromagnet. + /// Must be ELECTROMAGNET_1. Only one type available for now. + /// The pin to which the magnet is connected. + /// + public async Task SetupElectromagnet(RobotTool tool, RobotPin pin) + { + await SendCommandAsync("SETUP_ELECTROMAGNET", tool.ToString(), pin.ToString()); + await ReceiveAnswerAsync("SETUP_ELECTROMAGNET"); + } + + /// + /// Activate the electromagnet. + /// Must be ELECTROMAGNET_1. Only one type available for now. + /// The pin to which the magnet is connected. + /// + public async Task ActivateElectromagnet(RobotTool tool, RobotPin pin) + { + await SendCommandAsync("ACTIVATE_ELECTROMAGNET", tool.ToString(), pin.ToString()); + await ReceiveAnswerAsync("ACTIVATE_ELECTROMAGNET"); + } + + /// + /// Deactivate the electromagnet. + /// Must be ELECTROMAGNET_1. Only one type available for now. + /// The pin to which the magnet is connected. + /// + public async Task DeactivateElectromagnet(RobotTool tool, RobotPin pin) + { + await SendCommandAsync("DEACTIVATE_ELECTROMAGNET", tool.ToString(), pin.ToString()); + await ReceiveAnswerAsync("DEACTIVATE_ELECTROMAGNET"); + } + + /// + /// Get the current joint configuration. + /// + public async Task GetJoints() + { + await SendCommandAsync("GET_JOINTS"); + var joints = await ReceiveAnswerAsync("GET_JOINTS"); + return ParserUtils.ParseRobotJoints(joints); + } + + /// + /// Get the current pose. + /// + public async Task GetPose() + { + await SendCommandAsync("GET_POSE"); + var pose = await ReceiveAnswerAsync("GET_POSE"); + return ParserUtils.ParsePoseObject(pose); + } + + /// + /// Get the current hardware status. + /// + public async Task GetHardwareStatus() + { + await SendCommandAsync("GET_HARDWARE_STATUS"); + var status = await ReceiveAnswerAsync("GET_HARDWARE_STATUS"); + return ParserUtils.ParseHardwareStatus(status); + } + + /// + /// Get whether the robot is in learning mode. + /// + public async Task GetLearningMode() + { + await SendCommandAsync("GET_LEARNING_MODE"); + var mode = await ReceiveAnswerAsync("GET_LEARNING_MODE"); + return bool.Parse(mode); + } + + /// + /// Get the current state of the digital io pins. + /// + public async Task GetDigitalIOState() + { + await SendCommandAsync("GET_DIGITAL_IO_STATE"); + var state = await ReceiveAnswerAsync("GET_DIGITAL_IO_STATE"); + + var regex = new Regex("\\[[0-9]+, '[^']*', [0-9]+, [0-9+]\\]"); + var matches = regex.Matches(state); + + return matches.Cast().Select(m => ParserUtils.ParseDigitalPinObject(m.Value)).ToArray(); + } + } +} diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneException.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneException.cs new file mode 100644 index 00000000..76f6304a --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/NiryoOneException.cs @@ -0,0 +1,47 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; + +namespace NiryoOneClient +{ + /// + /// An exception representing an error from the Niryo One tcp server + /// + public class NiryoOneException : Exception + { + /// + /// Construct an exception with the specified reason + /// + /// A description of the error + public NiryoOneException(string reason) + { + Reason = reason; + } + + /// + /// A description of the error + /// + public string Reason { get; } + } +} diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/ParserUtils.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/ParserUtils.cs new file mode 100644 index 00000000..bfd40459 --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/ParserUtils.cs @@ -0,0 +1,154 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; + +namespace NiryoOneClient +{ + /// + /// A helper class for parsing NiryoOne types in the format used by the tcp server. + /// + public static class ParserUtils + { + /// + /// Parse a string representation of the hardware status in the format of the tcp server + /// + /// The string representation + /// A parsed object + public static HardwareStatus ParseHardwareStatus(string data) + { + var regex = new Regex(@"((?:\[[^[\]]+\])|(?:\([^\)]+\))|True|False|\d+|'\w*')"); + var matches = regex.Matches(data); + + if (matches.Count != 11) + { + throw new NiryoOneException("Incorrect answer received, cannot understand received format."); + } + + var rpiTemperature = int.Parse(matches[0].Value); + var hardwareVersion = int.Parse(matches[1].Value); + var connectionUp = bool.Parse(matches[2].Value); + var errorMessage = Strip(matches[3].Value, "'", "'"); + var calibrationNeeded = int.Parse(matches[4].Value); + var calibrationInProgress = bool.Parse(matches[5].Value); + + var motorNames = ParseStrings_(matches[6].Value); + var motorTypes = ParseStrings_(matches[7].Value); + + var temperatures = ParseNumbers_(matches[8].Value, int.Parse); + var voltages = ParseNumbers_(matches[9].Value, x => decimal.Parse(x, CultureInfo.InvariantCulture)); + var hardwareErrors = ParseNumbers_(matches[10].Value, int.Parse); + + var hardwareStatus = new HardwareStatus() + { + RpiTemperature = rpiTemperature, + HardwareVersion = hardwareVersion, + ConnectionUp = connectionUp, + ErrorMessage = errorMessage, + CalibrationNeeded = calibrationNeeded, + CalibrationInProgress = calibrationInProgress, + MotorNames = motorNames, + MotorTypes = motorTypes, + Temperatures = temperatures, + Voltages = voltages, + HardwareErrors = hardwareErrors + }; + return hardwareStatus; + } + + + /// + /// Parse a string representation of a pose in the format of the tcp server + /// + /// The string representation + /// A parsed object + public static PoseObject ParsePoseObject(string s) + { + return new PoseObject(s.Split(',').Select(x => float.Parse(x, CultureInfo.InvariantCulture)).ToArray()); + } + + + /// + /// Parse a string representation of a joint configuration in the format of the tcp server + /// + /// The string representation + /// A parsed object + public static RobotJoints ParseRobotJoints(string s) + { + return new RobotJoints(s.Split(',').Select(x => float.Parse(x, CultureInfo.InvariantCulture)).ToArray()); + } + + + /// + /// Parse a string representation of a digital pin state in the format of the tcp server + /// + /// The string representation + /// A parsed object + public static DigitalPinObject ParseDigitalPinObject(string s) + { + if (!s.StartsWith("[") || !s.EndsWith("]")) + throw new ArgumentException(); + + var ss = s.Substring(1, s.Length - 2).Split(',').Select(x => x.Trim()).ToArray(); + + return new DigitalPinObject + { + PinId = int.Parse(ss[0]), + Name = ss[1].Trim().Substring(1, ss[1].Length - 2), + Mode = (PinMode)int.Parse(ss[2]), + State = (DigitalState)int.Parse(ss[3]) + }; + } + + /// + /// Require s to start with the supplied prefix and end with the supplied suffix. Return s without the prefix and suffix. + /// + /// The initial string + /// The prefix to remove + /// The suffix to remove + /// The supplied string s without prefix and suffix + public static string Strip(string s, string prefix, string suffix) + { + if (!s.StartsWith(prefix)) + throw new ArgumentException(); + if (!s.EndsWith(suffix)) + throw new ArgumentException(); + return s.Substring(1, s.Length - 2); + } + + private static string[] ParseStrings_(string s) + { + var regex = new Regex(@"'[^']*'"); + return regex.Matches(s).Cast().Select(m => Strip(m.Value, "'", "'")).ToArray(); + } + + private static T[] ParseNumbers_(string s, Func parser) + { + var regex = new Regex(@"[0-9]+(\.[0-9]*)?"); + return regex.Matches(s).Cast().Select(m => parser(m.Value)).ToArray(); + } + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/PoseObject.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/PoseObject.cs new file mode 100644 index 00000000..7f39fcec --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/PoseObject.cs @@ -0,0 +1,134 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace NiryoOneClient +{ + /// + /// A representation of a cartesian position of the robotic arm in 6 dimensions. + /// For a description of the coordinate system, see REP-0103. + /// + public class PoseObject : IEnumerable + { + private float[] _j = new float[6]; + + /// + /// Construct a PoseObject from explicit coordinates. + /// For a description of the coordinate system, see REP-0103. + /// + /// X position in meters + /// Y position in meters + /// Z position in meters + /// Rotation about the fixed X axis in radians + /// Rotation about the fixed Y axis in radians + /// Rotation about the fixed Z axis in radians + public PoseObject(float x, float y, float z, float roll, float pitch, float yaw) + { + _j = new[] { x, y, z, roll, pitch, yaw }; + } + + /// + /// Construct a PoseObject from a coordinate array. + /// For a description of the coordinate system, see REP-0103. + /// + /// An array of the coordinates, in order X, Y, Z, roll, pitch, yaw + public PoseObject(float[] j) + { + if (j.Length != 6) + throw new ArgumentException("Joints must be constructed from 6 values.", nameof(j)); + + _j = j; + } + + /// + /// The X position in meters. + /// + public float X { get => _j[0]; set => _j[0] = value; } + + /// + /// The Y position in meters. + /// + public float Y { get => _j[1]; set => _j[1] = value; } + + /// + /// The Z position in meters. + /// + public float Z { get => _j[2]; set => _j[2] = value; } + + /// + /// The roll, i.e. the rotation around the fixed X axis in radians. + /// + public float Roll { get => _j[3]; set => _j[3] = value; } + + /// + /// The pitch, i.e. the rotation around the fixed Y axis in radians. + /// + public float Pitch { get => _j[4]; set => _j[4] = value; } + + /// + /// The yaw, i.e. the rotation around the fixed Z axis in radians. + /// + public float Yaw { get => _j[5]; set => _j[5] = value; } + + /// + /// Returns an enumerator which iterates through the collection + /// + public IEnumerator GetEnumerator() + { + return ((IEnumerable)_j).GetEnumerator(); + } + + /// + /// Returns an enumerator which iterates through the collection + /// + IEnumerator IEnumerable.GetEnumerator() + { + return _j.GetEnumerator(); + } + } + + /// + /// One of the 6 axes. + /// For a description of the coordinate system, see REP-0103. + /// + public enum RobotAxis + { + /// The X axis + X, + /// The Y axis + Y, + /// The Z axis + Z, + /// The roll, i.e. the rotation around the fixed X axis in radians. + ROLL, + /// The pitch, i.e. the rotation around the fixed Y axis in radians. + PITCH, + /// The yaw, i.e. the rotation around the fixed Z axis in radians. + YAW + } +} diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/Properties/AssemblyInfo.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..98682e85 --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleToAttribute("NiryoOneClient.Tests")] diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotJoints.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotJoints.cs new file mode 100644 index 00000000..e0d6b99b --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotJoints.cs @@ -0,0 +1,94 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace NiryoOneClient +{ + /// + /// A representation of the position of the joints in a Niryo One robotic arm + /// + public class RobotJoints : IEnumerable + { + private float[] _j = new float[6]; + + /// + /// Construct an object from the 6 joint values + /// + /// The first joint rotation in radians + /// The second joint rotation in radians + /// The third joint rotation in radians + /// The fourth joint rotation in radians + /// The fifth joint rotation in radians + /// The sixth joint rotation in radians + public RobotJoints(float j1, float j2, float j3, float j4, float j5, float j6) + { + _j = new[] { j1, j2, j3, j4, j5, j6 }; + } + + /// + /// Construct an object from 6 joint values + /// + /// An array of the 6 joint rotations in radians + public RobotJoints(float[] j) + { + if (j.Length != 6) + throw new ArgumentException("Joints must be constructed from 6 values.", nameof(j)); + + _j = j; + } + + /// The value of the first joint, in radians + public float J1 { get => _j[0]; set => _j[0] = value; } + /// The value of the second joint, in radians + public float J2 { get => _j[1]; set => _j[1] = value; } + /// The value of the third joint, in radians + public float J3 { get => _j[2]; set => _j[2] = value; } + /// The value of the fourth joint, in radians + public float J4 { get => _j[3]; set => _j[3] = value; } + /// The value of the fifth joint, in radians + public float J5 { get => _j[4]; set => _j[4] = value; } + /// The value of the sixth joint, in radians + public float J6 { get => _j[5]; set => _j[5] = value; } + + /// + /// Returns an enumerator that iterates through the colleection + /// + public IEnumerator GetEnumerator() + { + return ((IEnumerable)_j).GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the colleection + /// + IEnumerator IEnumerable.GetEnumerator() + { + return _j.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotPin.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotPin.cs new file mode 100644 index 00000000..bcfac1a2 --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotPin.cs @@ -0,0 +1,99 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +using System; + +namespace NiryoOneClient +{ + /// + /// An enumeration of the digital GPIO pins on the Niryo One robotic arm + /// + public enum RobotPin + { + /// The pin labeled 1A + GPIO_1A, + /// The pin labeled 1B + GPIO_1B, + /// The pin labeled 1C + GPIO_1C, + /// The pin labeled 2A + GPIO_2A, + /// The pin labeled 2B + GPIO_2B, + /// The pin labeled 2C + GPIO_2C + } + + /// + /// The configuration mode of a GPIO pin - input or output + /// + public enum PinMode + { + /// + /// The pin is configured as an output + /// + OUTPUT = 0, + /// + /// The pin is configured as an input + /// + INPUT = 1 + } + + /// + /// The state of a pin - high or low + /// + public enum DigitalState + { + /// + /// The pin is low + /// + LOW = 0, + /// + /// The pin is high + /// + HIGH = 1 + } + + /// + /// A representation of the state of a pin + /// + public class DigitalPinObject + { + /// + /// The internal pin id + /// + public int PinId; + /// + /// The user-readable name of a pin + /// + public string Name; + /// + /// Whether the pin is configured for output or input + /// + public PinMode Mode; + /// + /// Whether the pin is low or high + /// + public DigitalState State; + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotTool.cs b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotTool.cs new file mode 100644 index 00000000..2826983a --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/NiryoOneClient/RobotTool.cs @@ -0,0 +1,42 @@ +/* MIT License + + Copyright (c) 2019 Niryo + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +namespace NiryoOneClient +{ + /// + /// An enumeration of the known types of tools that can be connected to the Niryo One robotic arm + /// + public enum RobotTool + { + /// The first type of gripper + GRIPPER_1, + /// The second type of gripper + GRIPPER_2, + /// The third type of gripper + GRIPPER_3, + /// A vacuum pump + VACUUM_PUMP_1, + /// An electromagnet + ELECTROMAGNET_1 + } +} \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/csharp/README.md b/niryo_one_tcp_server/clients/csharp/README.md new file mode 100644 index 00000000..053559fa --- /dev/null +++ b/niryo_one_tcp_server/clients/csharp/README.md @@ -0,0 +1,23 @@ +# niryo_one_csharp_tcp_client +Tcp client that communicates with the [tcp server](../..) of the Niryo One +
**Notes:** The TCP server use the underlying [python_api](../../../niryo_one_python_api) package. This package is modeled after the python api client, so the functions available will be similar with this package but may differ in some minor terms, please refer to the current documentation. + +## Connection + +Port of the server: 40001 + +## Building + +* The target framework for the API is .NET Core 2.0. With the SDK for that installed, just run the command `dotnet pack --configuration Release --include-symbols` in the csharp directory, and a NuGet package will be produced in the directory `NiryoOneClient/bin/Release` along with a separate debug symbols package. Alternatively, in Visual Studio Code, just run the task "pack", which will do the same thing. + +## Examples + +See the [Examples](Examples) folder for existing scripts. + +## Functions available + +After running `dotnet publish`, xml documentation will be generated at [NiryoOneClient](NiryoOneClient/bin/Debug/netcoreapp3.0/publish/NiryoOneClient.xml). + +## Tests + +Running the command `dotnet test` will compile and run the unit tests available. Running the Visual Studio Code task "test" will also produce a test coverage report. \ No newline at end of file diff --git a/niryo_one_tcp_server/clients/python/niryo_one_tcp_client/tcp_client.py b/niryo_one_tcp_server/clients/python/niryo_one_tcp_client/tcp_client.py index df47bdf3..0c799f3a 100644 --- a/niryo_one_tcp_server/clients/python/niryo_one_tcp_client/tcp_client.py +++ b/niryo_one_tcp_server/clients/python/niryo_one_tcp_client/tcp_client.py @@ -49,6 +49,7 @@ def __init__(self, timeout=5): self.__is_connected = False self.__timeout = timeout self.__client_socket = None + self.__client_sockfile = None self.__packet_builder = PacketBuilder() def __del__(self): @@ -58,12 +59,14 @@ def quit(self): self.__is_running = False self.__shutdown_connection() self.__client_socket = None + self.__client_sockfile = None def __shutdown_connection(self): if self.__client_socket is not None and self.__is_connected is True: try: self.__client_socket.shutdown(socket.SHUT_RDWR) self.__client_socket.close() + self.__client_sockfile.close() except socket.error as e: pass self.__is_connected = False @@ -85,6 +88,7 @@ def connect(self, ip_address): print("Connected to server ({}) on port: {}".format(ip_address, self.__port)) self.__is_connected = True self.__client_socket.settimeout(None) + self.__client_sockfile = self.__client_socket.makefile() return self.__is_connected @@ -244,19 +248,18 @@ def send_command(self, command_type, parameter_list=None): if self.__is_connected is False: raise self.ClientNotConnectedException() send_success = False - if self.__client_socket is not None: + if self.__client_sockfile is not None: try: packet = self.__packet_builder.build_command_packet(command_type, parameter_list) - self.__client_socket.send(packet) + self.__client_sockfile.write(packet + "\n") except socket.error as e: print(e) raise self.HostNotReachableException() return send_success def receive_answer(self): - READ_SIZE = 512 try: - received = self.__client_socket.recv(READ_SIZE) + received = self.__client_sockfile.readline().rstrip() except socket.error as e: print(e) raise self.HostNotReachableException() diff --git a/niryo_one_tcp_server/src/niryo_one_tcp_server/command_interpreter.py b/niryo_one_tcp_server/src/niryo_one_tcp_server/command_interpreter.py index 8f1b192d..6ca2decc 100644 --- a/niryo_one_tcp_server/src/niryo_one_tcp_server/command_interpreter.py +++ b/niryo_one_tcp_server/src/niryo_one_tcp_server/command_interpreter.py @@ -106,7 +106,7 @@ def interpret_command(self, command_received): split_list = command_received.split(":") if len(split_list) > 2: rospy.logwarn("Incorrect command format: " + command_received) - return command_received + ":KO,Incorrect command format." + return command_received + ":KO,\"Incorrect command format.\"" command_string_part = split_list[0].rstrip('\r\n') ret_string = command_string_part + ":" if command_string_part in self.__commands_dict: @@ -121,7 +121,7 @@ def interpret_command(self, command_received): return ret_string + ret except TypeError as e: rospy.logwarn("Incorrect number of parameter(s) given. " + str(e)) - return ret_string + "KO,\"" + "Incorrect number of parameter(s) given.\"" + return ret_string + "KO,\"Incorrect number of parameter(s) given.\"" except python_api.NiryoOneException as e: rospy.logwarn(e) return ret_string + "KO,\"" + str(e) + "\"" @@ -129,7 +129,7 @@ def interpret_command(self, command_received): rospy.logwarn("Incorrect parameter(s) given to : " + command_string_part + " function. " + str(e)) return ret_string + "KO,\"" + str(e) + "\"" else: - return command_string_part + ": " + "KO,\"" + "Unknown command\"" + return command_string_part + ": " + "KO,\"Unknown command\"" def __successfull_answer(self, param_list=None): string_answer = "OK" diff --git a/niryo_one_tcp_server/src/niryo_one_tcp_server/tcp_server.py b/niryo_one_tcp_server/src/niryo_one_tcp_server/tcp_server.py index ebfb456b..8cc0d8dd 100644 --- a/niryo_one_tcp_server/src/niryo_one_tcp_server/tcp_server.py +++ b/niryo_one_tcp_server/src/niryo_one_tcp_server/tcp_server.py @@ -38,6 +38,7 @@ def __init__(self): self.__is_running = True self.__is_busy = False self.__client = None + self.__sockfile = None self.__interpreter = CommandInterpreter() self.__queue = Queue.Queue(1) @@ -78,7 +79,7 @@ def __answer_client_robot_busy(self, command_received): command_name = command_received else: command_name = command_received_split[0] - self.__send(command_name + ":KO,Robot is busy right now, command ignored.") + self.__send(command_name + ":KO,\"Robot is busy right now, command ignored.\"") def __client_socket_event(self, inputs): command_received = self.__read_command() @@ -112,16 +113,17 @@ def __shutdown_client(self): except socket.error as e: pass self.__client.close() + self.__sockfile.close() def __accept_client(self): self.__client, address = self.__server.accept() + self.__sockfile = self.__client.makefile() self.__is_client_connected = True rospy.loginfo("Client connected from ip address: " + str(address)) def __read_command(self): - READ_SIZE = 512 try: - received = self.__client.recv(READ_SIZE) + received = self.__sockfile.readline() except socket.error as e: rospy.logwarn("Error while receiving answer: " + str(e)) return None @@ -130,8 +132,9 @@ def __read_command(self): return received def __send(self, content): - if self.__client is not None: + if self.__sockfile is not None: try: - self.__client.send(content) + self.__sockfile.write(content + "\n") + self.__sockfile.flush() except socket.error as e: rospy.logwarn("Error while sending answer to client: " + str(e))