diff --git a/Project.toml b/Project.toml index 4bdaf4c..f1d7c6a 100644 --- a/Project.toml +++ b/Project.toml @@ -3,12 +3,14 @@ uuid = "f3e6a059-199c-4ada-8143-fcefb97e6165" keywords = ["navability", "navigation", "slam", "sdk", "robotics", "robots"] desc = "NavAbility SDK: Access NavAbility Cloud factor graph features. Note that this SDK and the related API are still in development. Please let us know if you have any issues at info@navability.io." authors = ["NavAbility "] -version = "0.4.4" +version = "0.4.5" [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Diana = "070d9d8b-17a7-5814-83fa-42438ba5c6e0" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -17,6 +19,8 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] Diana = "0.2" DocStringExtensions = "0.8" +Downloads = "1" +HTTP = "0.9" JSON = "0.21" julia = "1.6" diff --git a/src/NavAbilitySDK.jl b/src/NavAbilitySDK.jl index 71dbac9..e9a3455 100644 --- a/src/NavAbilitySDK.jl +++ b/src/NavAbilitySDK.jl @@ -1,10 +1,15 @@ module NavAbilitySDK +const NVA = NavAbilitySDK +export NVA + # Global imports using DocStringExtensions using LinearAlgebra using JSON using UUIDs +using Downloads +using HTTP # for overloading with visualization helpers import Base: show @@ -18,6 +23,7 @@ export uuid4 include("./navability/graphql/Factor.jl") include("./navability/graphql/Status.jl") include("./navability/graphql/Variable.jl") +include("./navability/graphql/DataBlobs.jl") include("./navability/graphql/QueriesDeprecated.jl") export QUERY_VARIABLE_LABELS @@ -53,6 +59,8 @@ include("./navability/services/Factor.jl") include("./navability/services/Solve.jl") include("./navability/services/Status.jl") include("./navability/services/Utils.jl") +include("./navability/services/StandardAPI.jl") +include("./navability/services/DataBlobs.jl") export getVariable, getVariables, listVariables, ls export addVariable, addPackedVariable export getFactor, getFactors, listFactors, lsf @@ -62,4 +70,5 @@ export getStatusMessages, getStatusLatest, getStatusesLatest export waitForCompletion export GraphVizApp, MapVizApp + end diff --git a/src/navability/entities/Factor.jl b/src/navability/entities/Factor.jl index 1ebe456..757d27e 100644 --- a/src/navability/entities/Factor.jl +++ b/src/navability/entities/Factor.jl @@ -40,8 +40,8 @@ Create a prior factor for a ContinuousScalar (a.k.a. Pose1) with a distribution Default value of Z = `Normal(0.0, 0.1)`. """ -function PriorData(;Z::Distribution = Normal(0.0, 0.1))::FactorData - data = FactorData(fnc = ZInferenceType(Z), certainhypo = [1]) +function PriorData(;Z::Distribution = Normal(0.0, 0.1), kwargs...)::FactorData + data = FactorData(;fnc = ZInferenceType(Z), certainhypo = [1], kwargs...) return data end @@ -52,8 +52,8 @@ Create a prior factor for a Pose2 with a distribution Z representing (x,y,theta) Default value of Z = `FullNormal([0.0, 0.0, 0.0], diagm([0.01, 0.01, 0.01]))`. """ -function PriorPose2Data(;Z::Distribution = FullNormal([0.0, 0.0, 0.0], diagm([0.01, 0.01, 0.01])))::FactorData - data = FactorData(fnc = ZInferenceType(Z), certainhypo = [1]) +function PriorPose2Data(;Z::Distribution = FullNormal([0.0, 0.0, 0.0], diagm([0.01, 0.01, 0.01])), kwargs...)::FactorData + data = FactorData(;fnc = ZInferenceType(Z), certainhypo = [1], kwargs...) return data end @@ -64,8 +64,8 @@ Create a prior factor for a Point2 with a distribution Z representing (x,y) prio Default value of Z = `FullNormal([0.0, 0.0], diagm([0.01, 0.01]))`. """ -function PriorPoint2Data(;Z::Distribution = FullNormal([0.0, 0.0], diagm([0.01, 0.01])))::FactorData - data = FactorData(fnc = ZInferenceType(Z), certainhypo = [1]) +function PriorPoint2Data(;Z::Distribution = FullNormal([0.0, 0.0], diagm([0.01, 0.01])), kwargs...)::FactorData + data = FactorData(;fnc = ZInferenceType(Z), certainhypo = [1], kwargs...) return data end @@ -76,8 +76,8 @@ between the variables, e.g. `Normal(1.0, 0.1)`. Default value of Z = `Normal(1.0, 0.1)`. """ -function LinearRelativeData(;Z::Distribution = Normal(1.0, 0.1))::FactorData - data = FactorData(fnc = ZInferenceType(Z), certainhypo = [1, 2]) +function LinearRelativeData(;Z::Distribution = Normal(1.0, 0.1), kwargs...)::FactorData + data = FactorData(;fnc = ZInferenceType(Z), certainhypo = [1, 2], kwargs...) return data end @@ -88,8 +88,8 @@ between the variables, e.g. `FullNormal([1,0,0.3333*π], diagm([0.01,0.01,0.01]) Default value of Z = `FullNormal([1,0,0.3333*π], diagm([0.01,0.01,0.01]))`. """ -function Pose2Pose2Data(;Z::Distribution = FullNormal([1,0,0.3333*π], diagm([0.01, 0.01, 0.01])))::FactorData - data = FactorData(fnc = ZInferenceType(Z), certainhypo = [1, 2]) +function Pose2Pose2Data(;Z::Distribution = FullNormal([1,0,0.3333*π], diagm([0.01, 0.01, 0.01])), kwargs...)::FactorData + data = FactorData(;fnc = ZInferenceType(Z), certainhypo = [1, 2], kwargs...) return data end @@ -99,8 +99,8 @@ Create a Pose2->Point2 bearing+range factor with 1D distributions: - bearing: The bearing from the pose to the point, default `Normal(0, 1)`. - range: The range from the pose to the point, default `Normal(1, 1)`. """ -function Pose2Point2BearingRangeData(;bearing::Distribution = Normal(0, 1), range::Distribution = Normal(1, 1))::FactorData - data = FactorData(fnc = Pose2Point2BearingRangeInferenceType(bearing, range), certainhypo = [1, 2]) +function Pose2Point2BearingRangeData(;bearing::Distribution = Normal(0, 1), range::Distribution = Normal(1, 1), kwargs...)::FactorData + data = FactorData(;fnc = Pose2Point2BearingRangeInferenceType(bearing, range), certainhypo = [1, 2], kwargs...) return data end @@ -109,8 +109,8 @@ $(SIGNATURES) Create a Point2->Point2 range factor with a 1D distribution: - range: The range from the pose to the point, default `Normal(1, 1)`. """ -function Point2Point2RangeData(;range::Distribution = Normal(1, 1))::FactorData - data = FactorData(fnc = ZInferenceType(range), certainhypo = [1, 2]) +function Point2Point2RangeData(;range::Distribution = Normal(1, 1), kwargs...)::FactorData + data = FactorData(;fnc = ZInferenceType(range), certainhypo = [1, 2], kwargs...) return data end @@ -119,9 +119,9 @@ $(SIGNATURES) Create a AprilTags factor that directly relates a Pose2 to the information from an AprilTag reading. Corners need to be provided, homography and tag length are defaulted and can be overwritten. """ -function Pose2AprilTag4CornersData(id, corners::Vector{Float64}, homography::Vector{Float64}; K::Vector{Float64}=[300.0,0.0,0.0,0.0,300.0,0.0,180.0,120.0,1.0], taglength::Float64=0.25)::FactorData +function Pose2AprilTag4CornersData(id, corners::Vector{Float64}, homography::Vector{Float64}; K::Vector{Float64}=[300.0,0.0,0.0,0.0,300.0,0.0,180.0,120.0,1.0], taglength::Float64=0.25, kwargs...)::FactorData fnc = Pose2AprilTag4CornersInferenceType(;corners,homography,K,taglength,id) - data = FactorData(fnc = fnc, certainhypo = [1, 2]) + data = FactorData(; fnc, certainhypo = [1, 2], kwargs...) return data end @@ -138,12 +138,13 @@ function ScatterAlignPose2Data( bw2::AbstractVector{<:Real}=Float64[]; mkd1 = ManifoldKernelDensity(; varType, pts=cloud1, bw=bw1 ), mkd2 = ManifoldKernelDensity(; varType, pts=cloud2, bw=bw2 ), - kw_sap... + kw_sap::NamedTuple=(;), + kwargs... ) # fnc = ScatterAlignPose2InferenceType(; cloud1=mkd1, cloud2=mkd2, kw_sap...) - data = FactorData(fnc = fnc, certainhypo = [1, 2]) + data = FactorData(; fnc, certainhypo = [1, 2], kwargs...) return data end @@ -170,7 +171,7 @@ function MixtureData( probabilities::Vector{Float64}, dims::Integer # TODO: Confirming we can remove. )::FactorData #where T<:FactorData - data = FactorData( + data = FactorData(; fnc = MixtureInferenceType( N = length(components), # @jim-hill-r this is why I don't like the Data suffix. @@ -183,6 +184,7 @@ function MixtureData( return data end + function Factor( label::String, fncType::String, @@ -190,14 +192,18 @@ function Factor( data::FactorData; tags::Vector{String}=["FACTOR"], timestamp::String = string(now(Dates.UTC))*"Z", - multihypo=Float64[], - nullhypo::Real=0 + multihypo=nothing, + nullhypo=nothing )::Factor # # TODO: Remove independent updates of this and set certainhypo here. data.certainhypo = Vector{Int}(1:size(variableOrderSymbols)[1]) - data.multihypo = multihypo - data.nullhypo = float(nullhypo) + if multihypo !== nothing + data.multihypo = multihypo + end + if nullhypo !== nothing + data.nullhypo = float(nullhypo) + end result = Factor( label, diff --git a/src/navability/entities/InferenceTypes.jl b/src/navability/entities/InferenceTypes.jl index 03bb31e..57c5032 100644 --- a/src/navability/entities/InferenceTypes.jl +++ b/src/navability/entities/InferenceTypes.jl @@ -8,6 +8,23 @@ functions inside of factors. """ abstract type InferenceType end +macro nvaZInferenceType(inferencetype) + return esc(quote + Base.@__doc__ struct $inferencetype <: NVA.InferenceType + Z::Distribution + end + $inferencetype(;Z) = $inferencetype(Z) + end) +end + +@nvaZInferenceType Prior +@nvaZInferenceType LinearRelative +@nvaZInferenceType PriorPose2 +@nvaZInferenceType Pose2Pose2 +@nvaZInferenceType PriorPoint2 +@nvaZInferenceType Point2Point2 +@nvaZInferenceType Point2Point2Range + """ $(TYPEDEF) ZInferenceType is used by many factors as a common inference @@ -25,16 +42,18 @@ $(TYPEDEF) Pose2Point2BearingRangeInferenceType is used to represent a bearing + range measurement. """ -Base.@kwdef struct Pose2Point2BearingRangeInferenceType <: InferenceType +Base.@kwdef struct Pose2Point2BearingRange <: InferenceType bearstr::Distribution rangstr::Distribution end +const Pose2Point2BearingRangeInferenceType = Pose2Point2BearingRange + """ $(TYPEDEF) InferenceType for Pose2AprilTag4CornersData. """ -Base.@kwdef struct Pose2AprilTag4CornersInferenceType <: InferenceType +Base.@kwdef struct Pose2AprilTag4Corners <: InferenceType corners::Vector{Float64} homography::Vector{Float64} K::Vector{Float64} @@ -43,8 +62,9 @@ Base.@kwdef struct Pose2AprilTag4CornersInferenceType <: InferenceType _type::String = "/application/JuliaLang/PackedPose2AprilTag4Corners" end +const Pose2AprilTag4CornersInferenceType = Pose2AprilTag4Corners -Base.@kwdef struct ScatterAlignPose2InferenceType <: InferenceType +Base.@kwdef struct ScatterAlignPose2 <: InferenceType """ This SDK only supports MKD clouds at this time. Note CJL also supports HeatmapGridDensity, TODO """ cloud1::ManifoldKernelDensity cloud2::ManifoldKernelDensity @@ -67,6 +87,8 @@ Base.@kwdef struct ScatterAlignPose2InferenceType <: InferenceType _type::String = "Caesar.PackedScatterAlignPose2" end +const ScatterAlignPose2InferenceType = ScatterAlignPose2 + """ $(TYPEDEF) InferenceType for MixtureData. diff --git a/src/navability/entities/NavAbilityClient.jl b/src/navability/entities/NavAbilityClient.jl index af48caa..5d62baf 100644 --- a/src/navability/entities/NavAbilityClient.jl +++ b/src/navability/entities/NavAbilityClient.jl @@ -17,12 +17,31 @@ struct NavAbilityClient mutate::Function end -function NavAbilityWebsocketClient(apiUrl::String="wss://api.navability.io/graphql")::NavAbilityClient +function NavAbilityWebsocketClient( apiUrl::String="wss://api.navability.io/graphql")::NavAbilityClient throw("Not implemented") end -function NavAbilityHttpsClient(apiUrl::String="https://api.navability.io")::NavAbilityClient +function NavAbilityHttpsClient( + apiUrl::String="https://api.navability.io"; + authorize::Bool=false + )::NavAbilityClient + # dianaClient = GraphQLClient(apiUrl) + + # auth + if authorize + # FIXME, use Base.getpass instead of readline once VSCode supports getpass. + # st = Base.getpass("Copy-paste auth token") + # seekstart(st) + # tok = read(st, String) + # Base.shred!(st) + println(" > VSCode ONLY WORKAROUND, input issue, see https://github.com/julia-vscode/julia-vscode/issues/785") + println(" > Workaround: first press 0 then enter, and then paste the token and hit enter a second time.") + println("Copy-paste auth token: ") + tok = readline(stdin) + dianaClient.serverAuth("Bearer "*tok) + end + function query(options::QueryOptions) # NOTE, the query client library used is synchronous, locally converted to async for package consistency @async dianaClient.Query(options.query, operationName=options.name, vars=options.variables) diff --git a/src/navability/entities/Variable.jl b/src/navability/entities/Variable.jl index 556af03..42739f3 100644 --- a/src/navability/entities/Variable.jl +++ b/src/navability/entities/Variable.jl @@ -7,7 +7,9 @@ _variableTypeConvert = Dict{Symbol, String}( :Point2 => "RoME.Point2", :Pose2 => "RoME.Pose2", :ContinuousScalar => "IncrementalInference.ContinuousScalar", - # TBD - https://github.com/JuliaRobotics/Caesar.jl/discussions/810 + # TBD - https://github.com/JuliaRobotics/Caesar.jl/discussions/807 + :Position1 => "IncrementalInference.ContinuousScalar", + #TODO deprecate :Pose1 => "IncrementalInference.ContinuousScalar" ) diff --git a/src/navability/graphql/DataBlobs.jl b/src/navability/graphql/DataBlobs.jl new file mode 100644 index 0000000..e08a216 --- /dev/null +++ b/src/navability/graphql/DataBlobs.jl @@ -0,0 +1,98 @@ + + +GQL_CREATEDOWNLOAD = """ +mutation sdk_url_createdownload (\$userId: String!, \$fileId: ID!) { + url: createDownload( + userId: \$userId + fileId: \$fileId + ) +} +""" + + +GQL_CREATEUPLOAD = """ +mutation sdk_url_createupload(\$filename: String!, \$filesize: Int!, \$parts: Int!) { + createUpload( + file: { + filename: \$filename, + filesize: \$filesize + }, + parts: \$parts + ) { + uploadId + parts { + partNumber + url + } + file { + id + } + } +} +""" + + +GQL_COMPLETEUPLOAD_SINGLE = """ +mutation completeUpload(\$fileId: ID!, \$uploadId: ID!, \$eTag: String) { + completeUpload ( + fileId: \$fileId, + completedUpload: { + uploadId: \$uploadId, + parts: [ + { + partNumber: 1, + eTag: \$eTag + } + ] + } + ) +} +""" + + +GQL_ADDDATAENTRY = """ +mutation sdk_adddataentry(\$userId: ResourceId!, \$robotId: ResourceId!, \$sessionId: ResourceId!, \$variableLabel: String!, \$dataId: UUID!, \$dataLabel: String!, \$mimeType: String) { + addDataEntry ( + dataEntry: { + client: { + userId: \$userId, + robotId: \$robotId, + sessionId: \$sessionId + }, + blobStoreEntry: { + id: \$dataId, + label: \$dataLabel + mimetype: \$mimeType + }, + nodeLabel: \$variableLabel + } + ) +} +""" + + +GQL_LISTDATAVARIABLE = """ +query sdk_listdatavariable(\$userId: ID!, \$robotId: ID!, \$sessionId: ID!, \$variableLabel: ID!) { + users ( + where: {id: \$userId} + ) { + robots ( + where: {id: \$robotId} + ) { + sessions ( + where: {id: \$sessionId} + ) { + variables ( + where: {label: \$variableLabel} + ) { + data { + id + label + mimeType + } + } + } + } + } +} +""" \ No newline at end of file diff --git a/src/navability/services/DataBlobs.jl b/src/navability/services/DataBlobs.jl new file mode 100644 index 0000000..7eb0ed7 --- /dev/null +++ b/src/navability/services/DataBlobs.jl @@ -0,0 +1,268 @@ + + +""" +$(SIGNATURES) +Request URLs for data blob download. + +Args: + navAbilityClient (NavAbilityClient): The NavAbility client. + userId (String): The userId with access to the data. + fileId (String): The unique file identifier of the data blob. +""" +function createDownloadEvent( + navAbilityClient::NavAbilityClient, + userId::AbstractString, + fileId::AbstractString + ) + # + response = navAbilityClient.mutate(MutationOptions( + "sdk_url_createdownload", + GQL_CREATEDOWNLOAD, + Dict( + "userId" => userId, + "fileId" => fileId + ) + )) |> fetch + rootData = JSON.parse(response.Data) + if haskey(rootData, "errors") + throw("Error: $(rootData["errors"])") + end + data = get(rootData,"data",nothing) + if data === nothing return "Error" end + urlMsg = get(data,"url","Error") + # TODO: What about marshalling? + return urlMsg +end + +createDownload(w...) = @async createDownloadEvent(w...) + +## + + +function getDataEvent( + client::NavAbilityClient, + userId::AbstractString, + fileId::AbstractString + ) + # + url = createDownload(client, userId, fileId) |> fetch + io = PipeBuffer() + Downloads.download(url, io) + io +end + +getData(w...) = @async getDataEvent(w...) + + +## ========================================================================= +## Upload +## ========================================================================= + + +""" +$(SIGNATURES) +Request URLs for data blob upload. + +Args: + navAbilityClient (NavAbilityClient): The NavAbility client. + filename (String): file/blob name. + filesize (Int): total number of bytes to upload. + parts (Int): Split upload into multiple blob parts, FIXME currently only supports parts=1. +""" +function createUploadEvent( + navAbilityClient::NavAbilityClient, + filename::AbstractString, + filesize::Int, + parts::Int=1 + ) + # + response = navAbilityClient.mutate(MutationOptions( + "sdk_url_createupload", + GQL_CREATEUPLOAD, + Dict( + "filename" => filename, + "filesize" => filesize, + "parts" => parts + ) + )) |> fetch + rootData = JSON.parse(response.Data) + if haskey(rootData, "errors") + throw("Error: $(rootData["errors"])") + end + data = get(rootData,"data",nothing) + if data === nothing return "Error" end + uploadResp = get(data,"createUpload","Error") + # TODO: What about marshalling? + return uploadResp +end + +createUpload(w...) = @async createUploadEvent(w...) + + +## Complete the upload + + +function completeUploadSingleEvent( + navAbilityClient::NavAbilityClient, + fileId::AbstractString, + uploadId::AbstractString, + eTag::AbstractString, + ) + response = navAbilityClient.mutate(MutationOptions( + "completeUpload", + GQL_COMPLETEUPLOAD_SINGLE, + Dict( + "fileId" => fileId, + "uploadId" => uploadId, + "eTag" => eTag + ) + )) |> fetch + rootData = JSON.parse(response.Data) + if haskey(rootData, "errors") + throw("Error: $(rootData["errors"])") + end + data = get(rootData,"data",nothing) + if data === nothing return "Error" end + uploadResp = get(data,"completeUpload","Error") + return uploadResp +end + +completeUploadSingle(w...) = @async completeUploadSingleEvent(w...) + + +## + + +function addDataEvent( + client::NavAbilityClient, + blobname::AbstractString, + blob::AbstractVector{UInt8} + ) + # + io = IOBuffer(blob) + + filesize = io.size + np = 1 + # create the upload url destination + d = NVA.createUploadEvent(client, blobname, filesize, np) + + url = d["parts"][1]["url"] + uploadId = d["uploadId"] + fileId = d["file"]["id"] + + # custom header for pushing the file up + headers = [ + "Content-Length" => filesize, + "Accept" => "application/json, text/plain, */*", + "Accept-Encoding" => "gzip, deflate, br", + "Sec-Fetch-Dest" => "empty", + "Sec-Fetch-Mode" => "cors", + "Sec-Fetch-Site" => "cross-site", + "Sec-GPC" => 1, + "Connection" => "keep-alive" + ] + # + + # fid = open(filepath,"r") + resp = HTTP.put(url, headers, io) + # close(fid) + + # Extract eTag + eTag = match(r"[a-zA-Z0-9]+",resp["eTag"]).match + + # close out the upload + res = NVA.completeUploadSingleEvent(client, fileId, uploadId, eTag) + + res == "Accepted" ? nothing : @error("Unable to upload blob, $res") + + fileId +end + + +addData(w...) = @async addDataEvent(w...) + + +## + + +function addDataEntryEvent( + navAbilityClient::NavAbilityClient, + userId::AbstractString, + robotId::AbstractString, + sessionId::AbstractString, + variableLabel::AbstractString, + dataId::AbstractString, + dataLabel::AbstractString, + mimeType::AbstractString="", + ) + response = navAbilityClient.mutate(MutationOptions( + "sdk_adddataentry", + GQL_ADDDATAENTRY, + Dict( + "userId" => userId, + "robotId" => robotId, + "sessionId" => sessionId, + "variableLabel" => variableLabel, + "dataId" => dataId, + "dataLabel" => dataLabel, + "mimeType" => mimeType, + ) + )) |> fetch + rootData = JSON.parse(response.Data) + if haskey(rootData, "errors") + throw("Error: $(rootData["errors"])") + end + data = get(rootData,"data",nothing) + if data === nothing return "Error" end + addentryresp = get(data,"addDataEntry","Error") + return addentryresp +end + +addDataEntry(w...) = @async addDataEntryEvent(w...) + + +## + + +function listDataVariableEvent( + navAbilityClient::NavAbilityClient, + userId::AbstractString, + robotId::AbstractString, + sessionId::AbstractString, + variableLabel::AbstractString + ) + # + response = navAbilityClient.mutate(MutationOptions( + "sdk_listdatavariable", + GQL_LISTDATAVARIABLE, + Dict( + "userId" => userId, + "robotId" => robotId, + "sessionId" => sessionId, + "variableLabel" => variableLabel + ) + )) |> fetch + rootData = JSON.parse(response.Data) + if haskey(rootData, "errors") + throw("Error: $(rootData["errors"])") + end + data = get(rootData,"data",nothing) + if data === nothing return "Error" end + + listdata = data["users"][1]["robots"][1]["sessions"][1]["variables"][1]["data"] + ret = [] + for d in listdata + tupk = Tuple(Symbol.(keys(d))) + nt = NamedTuple{tupk}( values(d) ) + push!(ret, + nt + ) + end + + return ret +end + +listDataVariable(w...) = @async listDataVariableEvent(w...) + + +## \ No newline at end of file diff --git a/src/navability/services/StandardAPI.jl b/src/navability/services/StandardAPI.jl new file mode 100644 index 0000000..ee31d18 --- /dev/null +++ b/src/navability/services/StandardAPI.jl @@ -0,0 +1,103 @@ +## Wrap to standard API spec + +# Leaving as comment for future plans, see https://github.com/NavAbility/nva-sdk/issues/21 +# TODO What to use: +# - NavAbilityPlatform +# - NavAbilityConnection +# - NavAbilityClientContext +# - +# mutable struct NavAbilityPlatform{T} +# client +# context::T +# end + +# function NavAbilityPlatform(; +# apiUrl::AbstractString="https://api.navability.io", +# UserId::AbstractString="guest@navability.io", +# RobotId::AbstractString=ENV["USER"], +# SessionId::AbstractString="Session_" * string(uuid4())[1:8]) +# # +# client = NavAbilityHttpsClient(apiUrl) +# context = Client(UserId, RobotId, SessionId) +# return NavAbilityPlatform(client, context) +# end + +""" + addVariable +Add a variable to the NavAbility Platform service +Example +```julia +addVariable(client, context, "x0", NVA.Pose2) +``` +""" +function addVariable(client, + context, + label::Union{<:AbstractString,Symbol}, + varType::Union{<:AbstractString,Symbol}; + tags::Vector{String}=String[], + timestamp::String = string(now(Dates.UTC))*"Z") + # TODO + # solvable::Int=1 + # nanosecondtime, + # smalldata, + # + union!(tags, ["VARIABLE"]) + v = Variable(string(label), Symbol(varType), tags, timestamp) + return addVariable(client, context, v) +end + + +function assembleFactorName(xisyms::Union{Vector{String},Vector{Symbol}}) + return string(xisyms...,"f_",string(uuid4())[1:4]) +end + +function getFncTypeName(fnc::InferenceType) + return split(string(typeof(fnc)),".")[end] +end + + +#TODO solverParams + +function addFactor(client, + context, + xisyms::Union{Vector{String},Vector{Symbol}}, + fnc::InferenceType; + multihypo::Vector{Float64}=Float64[], + nullhypo::Float64=0.0, + solvable::Int=1, + tags::Vector{String}=String[], + # timestamp::Union{DateTime,ZonedDateTime}=now(localzone()), #TODO why timestamp difference from IIF + timestamp::String = string(now(Dates.UTC))*"Z", + inflation::Real=3.0, + namestring::String = assembleFactorName(xisyms), + nstime::String="0" + ) + # TODO maybe + # graphinit::Bool=true, + # threadmodel=SingleThreaded, + # suppressChecks::Bool=false, + # _blockRecursion::Bool=!getSolverParams(dfg).attemptGradients + # + # create factor data + factordata = FactorData(;fnc, multihypo, nullhypo, inflation) + + fncType = getFncTypeName(fnc) + + union!(tags, ["FACTOR"]) + # create factor + factor = Factor( + namestring, + nstime, + fncType, + string.(xisyms), + factordata, + solvable, + tags, + timestamp, + DFG_VERSION + ) + # add factor + resultId = addFactor(client, context, factor) + + return resultId +end diff --git a/src/navability/services/Status.jl b/src/navability/services/Status.jl index ac1dfd0..224680e 100644 --- a/src/navability/services/Status.jl +++ b/src/navability/services/Status.jl @@ -17,7 +17,7 @@ function getStatusMessagesEvent(navAbilityClient::NavAbilityClient, id::String) )) |> fetch rootData = JSON.parse(response.Data) if haskey(rootData, "errors") - throw("Error: $(data["errors"])") + throw("Error: $(rootData["errors"])") end data = get(rootData,"data",nothing) if data === nothing return "Error" end @@ -47,7 +47,7 @@ function getStatusLatestEvent(navAbilityClient::NavAbilityClient, id::String) )) |> fetch rootData = JSON.parse(response.Data) if haskey(rootData, "errors") - throw("Error: $(data["errors"])") + throw("Error: $(rootData["errors"])") end data = get(rootData,"data",nothing) if data === nothing return "Error" end diff --git a/test/integration/testStandardAPI.jl b/test/integration/testStandardAPI.jl new file mode 100644 index 0000000..d1de61b --- /dev/null +++ b/test/integration/testStandardAPI.jl @@ -0,0 +1,35 @@ +apiUrl = get(ENV,"API_URL","https://api.navability.io") +userId = get(ENV,"USER_ID","guest@navability.io") +robotId = get(ENV,"ROBOT_ID","IntegrationRobot") +sessionId = get(ENV,"SESSION_ID","TestSession_"*string(uuid4())[1:8]) + +@testset "nva-sdk-standard-api-testset" begin + + client = NavAbilityHttpsClient(apiUrl) + context = Client(userId,robotId,sessionId) + + addVariable(client, context, "x0", :Pose2) + addFactor(client, context, ["x0"], NVA.PriorPose2(Z=FullNormal([0.,1.,0], diagm([1.,1,1])))) + + addVariable(client, context, :x1, :Pose2) + addFactor(client, context, [:x0,:x1], NVA.Pose2Pose2(Z=FullNormal([1.,0.,0], diagm([1.,1,1])))) + + addFactor(client, context, [:x1], NVA.PriorPose2(Z=FullNormal([1.,0.,0], diagm([1.,1,1]))); nullhypo=0.1) + + addFactor(client, context, ["x0"], NVA.PriorPose2(Z=FullNormal([0.5,0.5,0], diagm([1.,1,1])))) + + addVariable(client, context, :x2_a, :Pose2) + addVariable(client, context, :x2_b, :Pose2) + addFactor(client, context, ["x1", "x2_a", "x2_b"], NVA.PriorPose2(Z=FullNormal([0.5,0.5,0], diagm([1.,1,1]))); multihypo=[1,0.1,0.9]) + + addVariable(client, context, "y0", :Position1) + addFactor(client, context, ["y0"], NVA.Prior(Z=Normal(0.0, 1.0))) + + + addVariable(client, context, "z0", :Point2) + addFactor(client, context, ["z0"], NVA.PriorPoint2(Z=FullNormal([1.,0.], diagm([1.,1])))) + + addVariable(client, context, "z1", :Point2) + addFactor(client, context, ["z0", "z1"], NVA.Point2Point2(Z=FullNormal([1.,0.], diagm([1.,1])))) + +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index eacfa8b..abaf0fc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,2 +1,5 @@ include("./unit/runtests.jl") -include("./integration/runtests.jl") \ No newline at end of file +include("./integration/runtests.jl") + +#TODO I'm not familiar with the tests yet, so just dumping it here to get us started. +include("./integration/testStandardAPI.jl") \ No newline at end of file